Skip to content

Commit

Permalink
Remove use of electron.remote (#23)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
Slapbox and sindresorhus authored Feb 13, 2022
1 parent 10680d8 commit 3220f09
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 22 deletions.
7 changes: 7 additions & 0 deletions example-preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const {logError} = require('.');

window.logError = logError;

setTimeout(() => {
logError(Symbol('Tests handling of non-serializable values'));
}, 2000); // We use setTimeout so the error won't occur before devtools can open
10 changes: 7 additions & 3 deletions example.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const {app, BrowserWindow} = require('electron');
const path = require('path');
const {openNewGitHubIssue, debugInfo} = require('electron-util');
const unhandled = require('.');

Expand All @@ -19,8 +20,11 @@ let mainWindow;
(async () => {
await app.whenReady();

mainWindow = new BrowserWindow();
mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'example-preload.js')
}
});
mainWindow.openDevTools();
await mainWindow.loadURL('https://google.com');

unhandled.logError(new Error('Test'), {title: 'Custom Title'});
})();
73 changes: 58 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,65 @@
'use strict';
const electron = require('electron');
const {app, dialog, clipboard} = require('electron');
const {serialize} = require('v8');
const cleanStack = require('clean-stack');
const ensureError = require('ensure-error');
const debounce = require('lodash.debounce');
const isDev = require('electron-is-dev');

const app = electron.app || electron.remote.app;
const dialog = electron.dialog || electron.remote.dialog;
const clipboard = electron.clipboard || electron.remote.clipboard;
const appName = 'name' in app ? app.name : app.getName();
let appName;

// The dialog.showMessageBox method has been split into a sync and an async variant in Electron 6.0.0
const showMessageBox = dialog.showMessageBoxSync || dialog.showMessageBox;
let invokeErrorHandler;

const ERROR_HANDLER_CHANNEL = 'electron-unhandled.ERROR';

const tryMakeSerialized = arg => {
try {
const serialized = serialize(arg);
if (serialized) {
return serialized;
}
} catch {}
};

if (process.type === 'renderer') {
const {ipcRenderer} = require('electron');
// Default to 'App' because I don't think we can populate `appName` reliably here without remote or adding more IPC logic
invokeErrorHandler = async (title = 'App encountered an error', error) => {
try {
await ipcRenderer.invoke(ERROR_HANDLER_CHANNEL, title, error);
return;
} catch (invokeError) {
if (invokeError.message === 'An object could not be cloned.') {
// 1. If serialization failed, force the passed arg to an error format
error = ensureError(error);

// 2. Then attempt serialization on each property, defaulting to undefined otherwise
const serialized = {
name: tryMakeSerialized(error.name),
message: tryMakeSerialized(error.message),
stack: tryMakeSerialized(error.stack)
};
// 3. Invoke the error handler again with only the serialized error properties
ipcRenderer.invoke(ERROR_HANDLER_CHANNEL, title, serialized);
}
}
};
} else {
appName = 'name' in app ? app.name : app.getName();
const {ipcMain} = require('electron');
ipcMain.handle(ERROR_HANDLER_CHANNEL, async (evt, title, error) => {
handleError(title, error);
});
}

let installed = false;

let options = {
logger: console.error,
showDialog: !isDev
showDialog: process.type !== 'renderer' && !require('electron-is-dev')
};

const handleError = (title, error) => {
// NOTE: The ES6 default for title will only be used if the error is invoked from the main process directly. When invoked via the renderer, it will use the ES6 default from invokeErrorHandler
const handleError = (title = `${appName} encountered an error`, error) => {
error = ensureError(error);

try {
Expand All @@ -44,7 +83,7 @@ const handleError = (title, error) => {
}

// Intentionally not using the `title` option as it's not shown on macOS
const buttonIndex = showMessageBox({
const buttonIndex = dialog.showMessageBoxSync({
type: 'error',
buttons,
defaultId: 0,
Expand Down Expand Up @@ -79,16 +118,17 @@ module.exports = inputOptions => {
};

if (process.type === 'renderer') {
// Debounced because some packages, for example React, because of their error boundry feature, throws many identical uncaught errors
const errorHandler = debounce(error => {
handleError('Unhandled Error', error);
invokeErrorHandler('Unhandled Error', error);
}, 200);
window.addEventListener('error', event => {
event.preventDefault();
errorHandler(event.error || event);
});

const rejectionHandler = debounce(reason => {
handleError('Unhandled Promise Rejection', reason);
invokeErrorHandler('Unhandled Promise Rejection', reason);
}, 200);
window.addEventListener('unhandledrejection', event => {
event.preventDefault();
Expand All @@ -107,9 +147,12 @@ module.exports = inputOptions => {

module.exports.logError = (error, options) => {
options = {
title: `${appName} encountered an error`,
...options
};

handleError(options.title, error);
if (typeof invokeErrorHandler === 'function') {
invokeErrorHandler(options.title, error);
} else {
handleError(options.title, error);
}
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
],
"dependencies": {
"clean-stack": "^2.1.0",
"electron-is-dev": "^1.0.1",
"electron-is-dev": "^2.0.0",
"ensure-error": "^2.0.0",
"lodash.debounce": "^4.0.8"
},
"devDependencies": {
"ava": "^2.2.0",
"electron": "^6.0.1",
"electron": "^7.0.0",
"electron-util": "^0.12.1",
"execa": "^2.0.3",
"tsd": "^0.7.3",
Expand Down
8 changes: 6 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ You can use this module directly in both the main and renderer process.
$ npm install electron-unhandled
```

*Requires Electron 5 or later.*
*Requires Electron 7 or later.*


## Usage
Expand All @@ -27,12 +27,16 @@ unhandled();

### unhandled(options?)

You probably want to call this both in the main process and any renderer processes to catch all possible errors.
You probably want to call this both in the `main` process and any `renderer` processes to catch all possible errors.

Note: At minimum, this function must be called in the `main` process

### options

Type: `object`

Note: Options can only be specified in the `main` process

#### logger

Type: `Function`<br>
Expand Down

0 comments on commit 3220f09

Please sign in to comment.