From 2b6f6a94d1a4c5c2b96021ae5722099adb6bb7d8 Mon Sep 17 00:00:00 2001 From: Ben Gourley Date: Thu, 15 Apr 2021 10:45:18 +0100 Subject: [PATCH 1/3] refactor: Rename electron plugins and add unit tests --- packages/electron/package-lock.json | 21 +- packages/electron/package.json | 13 +- packages/electron/src/client/main.js | 7 +- packages/electron/src/client/renderer.js | 4 +- .../README.md | 14 ++ .../client-state-manager.js} | 18 +- .../package-lock.json | 0 .../package.json | 6 +- .../test/client-state-manager.test.ts | 109 ++++++++++ .../binding.gyp | 4 +- .../clib.json | 0 .../client-state-persistence.js} | 10 +- .../package-lock.json | 0 .../package.json | 12 +- .../src/api.c | 48 ++--- ...gsnag_electron_client_state_persistence.c} | 108 +++++----- ...gsnag_electron_client_state_persistence.h} | 38 ++-- .../src/deps/parson/package.json | 0 .../src/deps/parson/parson.c | 0 .../src/deps/parson/parson.h | 0 .../src/deps/tinycthread/README.txt | 0 .../src/deps/tinycthread/package.json | 0 .../src/deps/tinycthread/tinycthread.c | 0 .../src/deps/tinycthread/tinycthread.h | 0 .../src/signal_handler.c | 6 +- .../src/signal_handler.h | 7 + .../test/client-sync.test.ts | 14 +- .../test/error-handling.test.ts | 2 +- .../test/persistence.test.ts | 2 +- .../main-event-sync.js | 84 -------- .../main-internal-plugin-marker.js | 19 -- .../plugin-electron-ipc/bugsnag-ipc-main.js | 106 ++++++++-- .../test/bugsnag-ipc-main.test.ts | 191 +++++++++++++++--- .../src/signal_handler.h | 7 - .../README.md | 0 .../client-state-updates.js} | 0 .../package-lock.json | 0 .../package.json | 6 +- .../test/client-state-updates.test.ts} | 32 +-- .../package-lock.json | 0 .../package.json | 10 +- .../renderer-event-data.js} | 0 packages/plugin-electron-state-sync/README.md | 75 ------- .../test/state-sync.test.ts | 46 ----- .../internal-callback-marker.js} | 4 +- .../package-lock.json | 131 ++++++++++++ .../package.json | 29 +++ .../test/internal-callback-marker.test.ts | 46 +++++ 48 files changed, 770 insertions(+), 459 deletions(-) create mode 100644 packages/plugin-electron-client-state-manager/README.md rename packages/{plugin-electron-state-sync/state-sync.js => plugin-electron-client-state-manager/client-state-manager.js} (76%) rename packages/{plugin-electron-state-sync => plugin-electron-client-state-manager}/package-lock.json (100%) rename packages/{plugin-electron-state-sync => plugin-electron-client-state-manager}/package.json (81%) create mode 100644 packages/plugin-electron-client-state-manager/test/client-state-manager.test.ts rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/binding.gyp (58%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/clib.json (100%) rename packages/{plugin-electron-native-client-sync/client-sync.js => plugin-electron-client-state-persistence/client-state-persistence.js} (69%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/package-lock.json (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/package.json (79%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/api.c (90%) rename packages/{plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.c => plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.c} (78%) rename packages/{plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.h => plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.h} (69%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/parson/package.json (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/parson/parson.c (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/parson/parson.h (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/tinycthread/README.txt (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/tinycthread/package.json (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/tinycthread/tinycthread.c (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/deps/tinycthread/tinycthread.h (100%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/src/signal_handler.c (90%) create mode 100644 packages/plugin-electron-client-state-persistence/src/signal_handler.h rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/test/client-sync.test.ts (95%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/test/error-handling.test.ts (98%) rename packages/{plugin-electron-native-client-sync => plugin-electron-client-state-persistence}/test/persistence.test.ts (99%) delete mode 100644 packages/plugin-electron-event-sync/main-event-sync.js delete mode 100644 packages/plugin-electron-event-sync/main-internal-plugin-marker.js delete mode 100644 packages/plugin-electron-native-client-sync/src/signal_handler.h rename packages/{plugin-electron-renderer-client-sync => plugin-electron-renderer-client-state-updates}/README.md (100%) rename packages/{plugin-electron-renderer-client-sync/client-sync.js => plugin-electron-renderer-client-state-updates/client-state-updates.js} (100%) rename packages/{plugin-electron-renderer-client-sync => plugin-electron-renderer-client-state-updates}/package-lock.json (100%) rename packages/{plugin-electron-renderer-client-sync => plugin-electron-renderer-client-state-updates}/package.json (80%) rename packages/{plugin-electron-renderer-client-sync/test/client-sync.test.ts => plugin-electron-renderer-client-state-updates/test/client-state-updates.test.ts} (78%) rename packages/{plugin-electron-event-sync => plugin-electron-renderer-event-data}/package-lock.json (100%) rename packages/{plugin-electron-event-sync => plugin-electron-renderer-event-data}/package.json (62%) rename packages/{plugin-electron-event-sync/renderer-event-sync.js => plugin-electron-renderer-event-data/renderer-event-data.js} (100%) delete mode 100644 packages/plugin-electron-state-sync/README.md delete mode 100644 packages/plugin-electron-state-sync/test/state-sync.test.ts rename packages/{plugin-electron-event-sync/internal-plugin-marker.js => plugin-internal-callback-marker/internal-callback-marker.js} (87%) create mode 100644 packages/plugin-internal-callback-marker/package-lock.json create mode 100644 packages/plugin-internal-callback-marker/package.json create mode 100644 packages/plugin-internal-callback-marker/test/internal-callback-marker.test.ts diff --git a/packages/electron/package-lock.json b/packages/electron/package-lock.json index 96588cfb44..5683c3952a 100644 --- a/packages/electron/package-lock.json +++ b/packages/electron/package-lock.json @@ -145,37 +145,44 @@ "@bugsnag/plugin-console-breadcrumbs": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-console-breadcrumbs/-/plugin-console-breadcrumbs-7.9.0.tgz", - "integrity": "sha512-VXa94RcwpBLTLHNpyp5pD6nnZso6eVV9AySskkCtq36X1Msghwkx29TRBjGf+KNwaqMuDJZD7tLgRhmeBFThwA==" + "integrity": "sha512-VXa94RcwpBLTLHNpyp5pD6nnZso6eVV9AySskkCtq36X1Msghwkx29TRBjGf+KNwaqMuDJZD7tLgRhmeBFThwA==", + "requires": {} }, "@bugsnag/plugin-interaction-breadcrumbs": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-interaction-breadcrumbs/-/plugin-interaction-breadcrumbs-7.9.0.tgz", - "integrity": "sha512-9syRo6QFLpFzCtD7aIe40jexn+qDjl/L2Fpts4XhONCmSadxVslBLdUhgjieWQMGxwvGbbVker4BP29ddeq3pg==" + "integrity": "sha512-9syRo6QFLpFzCtD7aIe40jexn+qDjl/L2Fpts4XhONCmSadxVslBLdUhgjieWQMGxwvGbbVker4BP29ddeq3pg==", + "requires": {} }, "@bugsnag/plugin-network-breadcrumbs": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-network-breadcrumbs/-/plugin-network-breadcrumbs-7.9.0.tgz", - "integrity": "sha512-j/Ud41sJicWcAx9agWr3QICEkDJYad9sVij7RsU7YdVed1uS/W/PcWa7KbMqy+8YLH3QJUJXVRa/Yl1RyWyeKQ==" + "integrity": "sha512-j/Ud41sJicWcAx9agWr3QICEkDJYad9sVij7RsU7YdVed1uS/W/PcWa7KbMqy+8YLH3QJUJXVRa/Yl1RyWyeKQ==", + "requires": {} }, "@bugsnag/plugin-node-uncaught-exception": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-node-uncaught-exception/-/plugin-node-uncaught-exception-7.9.0.tgz", - "integrity": "sha512-vATQEMknUO5Z0xyIu3o6yzj7Cf0cpZkabGoG3NxZuhxVAy8Z2Q4eS9HPG7kHckhNRvGHs+aHrfEK1OMvyR6p3g==" + "integrity": "sha512-vATQEMknUO5Z0xyIu3o6yzj7Cf0cpZkabGoG3NxZuhxVAy8Z2Q4eS9HPG7kHckhNRvGHs+aHrfEK1OMvyR6p3g==", + "requires": {} }, "@bugsnag/plugin-node-unhandled-rejection": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-node-unhandled-rejection/-/plugin-node-unhandled-rejection-7.9.0.tgz", - "integrity": "sha512-cGTRJTgFBG7YdGuZiXKWboL9mzwMMu/knF10NiGg9oTJMDWE9V4ZIYGCgcoSirgO5fA/xkdUprau2S433sKLiw==" + "integrity": "sha512-cGTRJTgFBG7YdGuZiXKWboL9mzwMMu/knF10NiGg9oTJMDWE9V4ZIYGCgcoSirgO5fA/xkdUprau2S433sKLiw==", + "requires": {} }, "@bugsnag/plugin-window-onerror": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-window-onerror/-/plugin-window-onerror-7.9.0.tgz", - "integrity": "sha512-dbv6HB2HapdwHS7uQG2XWqNtVf6oW9CLgMA+aTg7383asRte3DL7q+LZEU27mlA4JrrLX5JwfizdxV6pKd4BCA==" + "integrity": "sha512-dbv6HB2HapdwHS7uQG2XWqNtVf6oW9CLgMA+aTg7383asRte3DL7q+LZEU27mlA4JrrLX5JwfizdxV6pKd4BCA==", + "requires": {} }, "@bugsnag/plugin-window-unhandled-rejection": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@bugsnag/plugin-window-unhandled-rejection/-/plugin-window-unhandled-rejection-7.9.0.tgz", - "integrity": "sha512-2TkC2dBB9/szqfIh/iHmzbZRJdfu3XPHSfl5hux4+zWz/bnyOmvZggefGDUisAVYisJO3bCAANmSmlehmjwZSw==" + "integrity": "sha512-2TkC2dBB9/szqfIh/iHmzbZRJdfu3XPHSfl5hux4+zWz/bnyOmvZggefGDUisAVYisJO3bCAANmSmlehmjwZSw==", + "requires": {} }, "@bugsnag/safe-json-stringify": { "version": "6.0.0", diff --git a/packages/electron/package.json b/packages/electron/package.json index 1c2a6f6e1b..dfe311ebf5 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -22,18 +22,19 @@ "@bugsnag/plugin-console-breadcrumbs": "^7.9.0", "@bugsnag/plugin-electron-app": "1.0.0", "@bugsnag/plugin-electron-app-breadcrumbs": "1.0.0", + "@bugsnag/plugin-electron-client-state-manager": "1.0.0", "@bugsnag/plugin-electron-device": "1.0.0", "@bugsnag/plugin-electron-ipc": "1.0.0", "@bugsnag/plugin-electron-network-status": "1.0.0", - "@bugsnag/plugin-electron-renderer-client-sync": "1.0.0", - "@bugsnag/plugin-electron-event-sync": "1.0.0", + "@bugsnag/plugin-electron-renderer-client-state-updates": "1.0.0", + "@bugsnag/plugin-electron-renderer-event-data": "1.0.0", "@bugsnag/plugin-electron-session": "^1.0.0", - "@bugsnag/plugin-electron-state-sync": "1.0.0", - "@bugsnag/plugin-window-onerror": "^7.9.0", - "@bugsnag/plugin-window-unhandled-rejection": "^7.9.0", + "@bugsnag/plugin-internal-callback-marker": "^1.0.0", "@bugsnag/plugin-interaction-breadcrumbs": "^7.9.0", "@bugsnag/plugin-network-breadcrumbs": "^7.9.0", "@bugsnag/plugin-node-uncaught-exception": "^7.7.0", - "@bugsnag/plugin-node-unhandled-rejection": "^7.7.0" + "@bugsnag/plugin-node-unhandled-rejection": "^7.7.0", + "@bugsnag/plugin-window-onerror": "^7.9.0", + "@bugsnag/plugin-window-unhandled-rejection": "^7.9.0" } } diff --git a/packages/electron/src/client/main.js b/packages/electron/src/client/main.js index ac4131c779..fa1aecc1b4 100644 --- a/packages/electron/src/client/main.js +++ b/packages/electron/src/client/main.js @@ -27,9 +27,8 @@ module.exports = (opts) => { // main internal plugins go here const internalPlugins = [ // THIS PLUGIN MUST BE FIRST! - require('@bugsnag/plugin-electron-event-sync/internal-plugin-marker').firstPlugin, - require('@bugsnag/plugin-electron-event-sync/main-event-sync.js'), - require('@bugsnag/plugin-electron-state-sync'), + require('@bugsnag/plugin-internal-callback-marker').FirstPlugin, + require('@bugsnag/plugin-electron-client-state-manager'), require('@bugsnag/plugin-electron-ipc'), require('@bugsnag/plugin-node-uncaught-exception'), require('@bugsnag/plugin-node-unhandled-rejection'), @@ -39,7 +38,7 @@ module.exports = (opts) => { require('@bugsnag/plugin-electron-session')(electron.app, electron.BrowserWindow), require('@bugsnag/plugin-console-breadcrumbs'), // THIS PLUGIN MUST BE LAST! - require('@bugsnag/plugin-electron-event-sync/internal-plugin-marker').lastPlugin + require('@bugsnag/plugin-internal-callback-marker').LastPlugin ] const bugsnag = new Client(opts, schema, internalPlugins, require('../id')) diff --git a/packages/electron/src/client/renderer.js b/packages/electron/src/client/renderer.js index 1978505a00..a0e42d06af 100644 --- a/packages/electron/src/client/renderer.js +++ b/packages/electron/src/client/renderer.js @@ -16,14 +16,14 @@ module.exports = (rendererOpts) => { opts.enabledBreadcrumbTypes = opts.enabledBreadcrumbTypes.filter(type => type !== 'error') const internalPlugins = [ - require('@bugsnag/plugin-electron-renderer-client-sync')(window.__bugsnag_ipc__), + require('@bugsnag/plugin-electron-renderer-client-state-updates')(window.__bugsnag_ipc__), require('@bugsnag/plugin-electron-network-status')(), require('@bugsnag/plugin-window-onerror')(), require('@bugsnag/plugin-window-unhandled-rejection')(), require('@bugsnag/plugin-network-breadcrumbs')(), require('@bugsnag/plugin-interaction-breadcrumbs')(), require('@bugsnag/plugin-console-breadcrumbs'), - require('@bugsnag/plugin-electron-event-sync/renderer-event-sync')(window.__bugsnag_ipc__) + require('@bugsnag/plugin-electron-renderer-event-data')(window.__bugsnag_ipc__) ] const bugsnag = new Client(opts, schema, internalPlugins, require('../id')) diff --git a/packages/plugin-electron-client-state-manager/README.md b/packages/plugin-electron-client-state-manager/README.md new file mode 100644 index 0000000000..19ca377ff7 --- /dev/null +++ b/packages/plugin-electron-client-state-manager/README.md @@ -0,0 +1,14 @@ +# @bugsnag/plugin-client-state-manager + +This plugin provides a wrapper around the parts of state that need to be synchronised, providing a way for listeners to be notified of changes. + +The plugin runs in the main Electron process, and patches each of the client mutators whose state we need to synchronise: + + - `setUser()` + - `setContext()` + - `addMetadata()` + - `clearMetadata()` + +Any call to these methods (which will be from a developer or a plugin calling `Bugsnag.()` in the main process) will emit an event signifying the change and updated value. + +Separately, we expose a `bulkUpdate` method for a new renderer to deliver an full state update in one pass. \ No newline at end of file diff --git a/packages/plugin-electron-state-sync/state-sync.js b/packages/plugin-electron-client-state-manager/client-state-manager.js similarity index 76% rename from packages/plugin-electron-state-sync/state-sync.js rename to packages/plugin-electron-client-state-manager/client-state-manager.js index 5ae487a9bb..874fc30f5c 100644 --- a/packages/plugin-electron-state-sync/state-sync.js +++ b/packages/plugin-electron-client-state-manager/client-state-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') module.exports = { - name: 'stateSync', + name: 'clientStateManager', load: (client) => { const emitter = new EventEmitter() @@ -11,14 +11,14 @@ module.exports = { const origSetUser = client.setUser client.setUser = (...args) => { const ret = origSetUser.call(client, ...args) - emitter.emit('UserUpdate', client.getUser(), null) + emitter.emit('UserUpdate', client.getUser()) return ret } const origSetContext = client.setContext client.setContext = (...args) => { const ret = origSetContext.call(client, ...args) - emitter.emit('ContextUpdate', client.getContext(), null) + emitter.emit('ContextUpdate', client.getContext()) return ret } @@ -28,7 +28,7 @@ module.exports = { const [section] = args if (typeof section === 'string') { const values = client.getMetadata(section) - emitter.emit('MetadataUpdate', { section, values }, null) + emitter.emit('MetadataUpdate', { section, values }) } return ret } @@ -39,7 +39,7 @@ module.exports = { const [section] = args if (typeof section === 'string') { const values = client.getMetadata(section) - emitter.emit('MetadataUpdate', { section, values }, null) + emitter.emit('MetadataUpdate', { section, values }) } return ret } @@ -56,14 +56,10 @@ module.exports = { for (const section in metadata) { origAddMetadata.call(client, section, metadata[section]) } - emitter.emit('MetadataReplace', { metadata: client._metadata }) + emitter.emit('MetadataReplace', client._metadata) } } - return { - events: ['UserUpdate', 'ContextUpdate', 'MetadataUpdate', 'MetadataReplace'], - emitter, - bulkUpdate - } + return { emitter, bulkUpdate } } } diff --git a/packages/plugin-electron-state-sync/package-lock.json b/packages/plugin-electron-client-state-manager/package-lock.json similarity index 100% rename from packages/plugin-electron-state-sync/package-lock.json rename to packages/plugin-electron-client-state-manager/package-lock.json diff --git a/packages/plugin-electron-state-sync/package.json b/packages/plugin-electron-client-state-manager/package.json similarity index 81% rename from packages/plugin-electron-state-sync/package.json rename to packages/plugin-electron-client-state-manager/package.json index e21e30a0de..77b1f96374 100644 --- a/packages/plugin-electron-state-sync/package.json +++ b/packages/plugin-electron-client-state-manager/package.json @@ -1,7 +1,7 @@ { - "name": "@bugsnag/plugin-electron-state-sync", + "name": "@bugsnag/plugin-electron-client-state-manager", "version": "1.0.0", - "main": "state-sync.js", + "main": "client-state-manager.js", "description": "@bugsnag/electron plugin to sync state between various processes", "homepage": "https://www.bugsnag.com/", "repository": { @@ -17,7 +17,7 @@ "license": "MIT", "gypfile": true, "files": [ - "state-sync.js" + "client-state-manager.js" ], "dependencies": { }, diff --git a/packages/plugin-electron-client-state-manager/test/client-state-manager.test.ts b/packages/plugin-electron-client-state-manager/test/client-state-manager.test.ts new file mode 100644 index 0000000000..f0e0d3eb4f --- /dev/null +++ b/packages/plugin-electron-client-state-manager/test/client-state-manager.test.ts @@ -0,0 +1,109 @@ +import stateManager from '../client-state-manager' +import Client from '@bugsnag/core/client' + +describe('@bugsnag/plugin-electron-client-state-manager', () => { + it('should emit events when user changes', done => { + const client = new Client({}, {}, [stateManager], {}) + const { emitter } = client.getPlugin('clientStateManager') + emitter.on('UserUpdate', user => { + expect(user).toEqual({ id: '123', email: 'jim@jim.com', name: 'Jim' }) + done() + }) + client.setUser('123', 'jim@jim.com', 'Jim') + }) + + it('should emit events when context changes', done => { + const client = new Client({}, {}, [stateManager], {}) + const { emitter } = client.getPlugin('clientStateManager') + emitter.on('ContextUpdate', (context) => { + expect(context).toBe('ctx') + done() + }) + client.setContext('ctx') + }) + + it('should emit events when metadata is added', done => { + const client = new Client({}, {}, [stateManager], {}) + const { emitter } = client.getPlugin('clientStateManager') + emitter.on('MetadataUpdate', (payload) => { + expect(payload.section).toBe('section') + expect(payload.values).toEqual({ key: 'value' }) + done() + }) + client.addMetadata('section', 'key', 'value') + }) + + it('should emit events when metadata is cleared', done => { + const client = new Client({}, {}, [stateManager], {}) + const { emitter } = client.getPlugin('clientStateManager') + emitter.on('MetadataUpdate', (payload) => { + expect(payload.section).toBe('section') + expect(payload.values).toBe(undefined) + done() + }) + client.clearMetadata('section', 'key') + }) + + it('should support bulk updates', () => { + const client = new Client({}, {}, [stateManager], {}) + const { emitter, bulkUpdate } = client.getPlugin('clientStateManager') + + const metadataCb = jest.fn() + const contextCb = jest.fn() + const userCb = jest.fn() + + emitter.on('MetadataReplace', metadataCb) + emitter.on('ContextUpdate', contextCb) + emitter.on('UserUpdate', userCb) + + // update everything + bulkUpdate({ + user: { + id: '123', name: 'Jim', email: 'jim@jim.com' + }, + context: 'ctx', + metadata: { + section: { key: 'value' } + } + }) + + expect(metadataCb).toHaveBeenCalledWith({ section: { key: 'value' } }) + expect(contextCb).toHaveBeenCalledWith('ctx') + expect(userCb).toHaveBeenCalledWith({ id: '123', name: 'Jim', email: 'jim@jim.com' }) + + jest.resetAllMocks() + + // update just context + bulkUpdate({ + context: 'ctx' + }) + + expect(metadataCb).not.toHaveBeenCalled() + expect(contextCb).toHaveBeenCalledWith('ctx') + expect(userCb).not.toHaveBeenCalled() + + jest.resetAllMocks() + + // update just user + bulkUpdate({ + user: { id: '123', name: 'Jim', email: 'jim@jim.com' } + }) + + expect(metadataCb).not.toHaveBeenCalled() + expect(contextCb).not.toHaveBeenCalledWith() + expect(userCb).toHaveBeenCalledWith({ id: '123', name: 'Jim', email: 'jim@jim.com' }) + + jest.resetAllMocks() + + // update just metadata + bulkUpdate({ + metadata: { + section: { key: 'value' } + } + }) + + expect(metadataCb).toHaveBeenCalledWith({ section: { key: 'value' } }) + expect(contextCb).not.toHaveBeenCalled() + expect(userCb).not.toHaveBeenCalled() + }) +}) diff --git a/packages/plugin-electron-native-client-sync/binding.gyp b/packages/plugin-electron-client-state-persistence/binding.gyp similarity index 58% rename from packages/plugin-electron-native-client-sync/binding.gyp rename to packages/plugin-electron-client-state-persistence/binding.gyp index 78e2d4a797..a4d916140d 100644 --- a/packages/plugin-electron-native-client-sync/binding.gyp +++ b/packages/plugin-electron-client-state-persistence/binding.gyp @@ -1,10 +1,10 @@ { "targets": [ { - "target_name": "bugsnag_plugin_electron_client_sync_bindings", + "target_name": "bugsnag_plugin_electron_client_state_persistence_bindings", "sources": [ "src/api.c", - "src/bugsnag_electron_client_sync.c", + "src/bugsnag_electron_client_state_persistence.c", "src/signal_handler.c", "src/deps/parson/parson.c", "src/deps/tinycthread/tinycthread.c" diff --git a/packages/plugin-electron-native-client-sync/clib.json b/packages/plugin-electron-client-state-persistence/clib.json similarity index 100% rename from packages/plugin-electron-native-client-sync/clib.json rename to packages/plugin-electron-client-state-persistence/clib.json diff --git a/packages/plugin-electron-native-client-sync/client-sync.js b/packages/plugin-electron-client-state-persistence/client-state-persistence.js similarity index 69% rename from packages/plugin-electron-native-client-sync/client-sync.js rename to packages/plugin-electron-client-state-persistence/client-state-persistence.js index 9564794e6d..f2013c99c0 100644 --- a/packages/plugin-electron-native-client-sync/client-sync.js +++ b/packages/plugin-electron-client-state-persistence/client-state-persistence.js @@ -8,9 +8,9 @@ module.exports = (NativeClient) => ({ } }, true) - const stateSync = client.getPlugin('stateSync') + const clientStateManager = client.getPlugin('clientStateManager') - stateSync.emitter.on('UserUpdate', user => { + clientStateManager.emitter.on('UserUpdate', user => { try { NativeClient.updateUser(user.id, user.email, user.name) } catch (e) { @@ -18,7 +18,7 @@ module.exports = (NativeClient) => ({ } }) - stateSync.emitter.on('ContextUpdate', context => { + clientStateManager.emitter.on('ContextUpdate', context => { try { NativeClient.updateContext(context) } catch (e) { @@ -26,7 +26,7 @@ module.exports = (NativeClient) => ({ } }) - stateSync.emitter.on('MetadataUpdate', ({ section, values }) => { + clientStateManager.emitter.on('MetadataUpdate', ({ section, values }) => { try { NativeClient.updateMetadata(section, values) } catch (e) { @@ -34,7 +34,7 @@ module.exports = (NativeClient) => ({ } }) - stateSync.emitter.on('MetadataReplace', ({ metadata }) => { + clientStateManager.emitter.on('MetadataReplace', (metadata) => { try { NativeClient.updateMetadata(metadata) } catch (e) { diff --git a/packages/plugin-electron-native-client-sync/package-lock.json b/packages/plugin-electron-client-state-persistence/package-lock.json similarity index 100% rename from packages/plugin-electron-native-client-sync/package-lock.json rename to packages/plugin-electron-client-state-persistence/package-lock.json diff --git a/packages/plugin-electron-native-client-sync/package.json b/packages/plugin-electron-client-state-persistence/package.json similarity index 79% rename from packages/plugin-electron-native-client-sync/package.json rename to packages/plugin-electron-client-state-persistence/package.json index a0e7ffb68e..e055359e92 100644 --- a/packages/plugin-electron-native-client-sync/package.json +++ b/packages/plugin-electron-client-state-persistence/package.json @@ -1,7 +1,7 @@ { - "name": "@bugsnag/plugin-electron-native-client-sync", + "name": "@bugsnag/plugin-electron-client-state-persistence", "version": "1.0.0", - "main": "client-sync.js", + "main": "client-state-persistence.js", "description": "@bugsnag/electron plugin to sync information between JS and native layer", "homepage": "https://www.bugsnag.com/", "repository": { @@ -20,10 +20,10 @@ "gypfile": true, "files": [ "binding.gyp", - "client-sync.js", + "client-state-persistence.js", "src/api.c", - "src/bugsnag_electron_client_sync.c", - "src/bugsnag_electron_client_sync.h", + "src/bugsnag_electron_client_state_persistence.c", + "src/bugsnag_electron_client_state_persistence.h", "src/signal_handler.c", "src/signal_handler.h", "src/deps/parson/package.json", @@ -40,7 +40,7 @@ "devDependencies": { "@bugsnag/core": "^7.9.2-alpha.0", "@types/bindings": "^1.5.0", - "@bugsnag/plugin-electron-state-sync": "^1.0.0" + "@bugsnag/plugin-electron-client-state-manager": "^1.0.0" }, "peerDependencies": { "@bugsnag/core": "^7.9.2-alpha.0" diff --git a/packages/plugin-electron-native-client-sync/src/api.c b/packages/plugin-electron-client-state-persistence/src/api.c similarity index 90% rename from packages/plugin-electron-native-client-sync/src/api.c rename to packages/plugin-electron-client-state-persistence/src/api.c index edfe47c45e..fe02503f09 100644 --- a/packages/plugin-electron-native-client-sync/src/api.c +++ b/packages/plugin-electron-client-state-persistence/src/api.c @@ -4,7 +4,7 @@ #include #include -#include "bugsnag_electron_client_sync.h" +#include "bugsnag_electron_client_state_persistence.h" static napi_value json_stringify(napi_env env, napi_value json_obj) { napi_value global; @@ -78,25 +78,25 @@ static char *read_json_string_value(napi_env env, napi_value arg, } } -static bool throw_error_from_status(napi_env env, BECS_STATUS status) { +static bool throw_error_from_status(napi_env env, BECSP_STATUS status) { const char *code = "BugsnagSyncError"; switch (status) { - case BECS_STATUS_SUCCESS: + case BECSP_STATUS_SUCCESS: return false; - case BECS_STATUS_INVALID_JSON: + case BECSP_STATUS_INVALID_JSON: napi_throw_error(env, code, "Failed to convert argument to JSON"); break; - case BECS_STATUS_EXPECTED_JSON_OBJECT: + case BECSP_STATUS_EXPECTED_JSON_OBJECT: napi_throw_type_error(env, code, "Wrong argument type, expected object"); break; - case BECS_STATUS_NULL_PARAM: + case BECSP_STATUS_NULL_PARAM: napi_throw_type_error(env, code, "Expected argument to be non-null"); break; - case BECS_STATUS_NOT_INSTALLED: + case BECSP_STATUS_NOT_INSTALLED: napi_throw_error(env, code, "Sync layer is not installed, first call install()"); break; - case BECS_STATUS_UNKNOWN_FAILURE: + case BECSP_STATUS_UNKNOWN_FAILURE: napi_throw_error(env, code, "Failed to synchronize data"); break; } @@ -104,7 +104,7 @@ static bool throw_error_from_status(napi_env env, BECS_STATUS status) { } static void set_object_or_null(napi_env env, napi_value obj, - BECS_STATUS (*setter)(const char *)) { + BECSP_STATUS (*setter)(const char *)) { napi_valuetype valuetype; napi_status status = napi_typeof(env, obj, &valuetype); assert(status == napi_ok); @@ -119,7 +119,7 @@ static void set_object_or_null(napi_env env, napi_value obj, throw_error_from_status(env, setter(value)); free(value); } else { - throw_error_from_status(env, BECS_STATUS_INVALID_JSON); + throw_error_from_status(env, BECSP_STATUS_INVALID_JSON); } } break; default: @@ -128,7 +128,7 @@ static void set_object_or_null(napi_env env, napi_value obj, } static napi_value Uninstall(napi_env env, napi_callback_info info) { - becs_uninstall(); + becsp_uninstall(); return NULL; } @@ -174,7 +174,7 @@ static napi_value Install(napi_env env, napi_callback_info info) { if (valuetype2 == napi_object) { char *state = read_string_value(env, json_stringify(env, args[2]), true); - becs_install(filepath, max_crumbs, state); + becsp_install(filepath, max_crumbs, state); free(state); } else { napi_throw_type_error( @@ -182,7 +182,7 @@ static napi_value Install(napi_env env, napi_callback_info info) { "Wrong argument types, expected (string, number, object?)"); } } else { - becs_install(filepath, max_crumbs, NULL); + becsp_install(filepath, max_crumbs, NULL); } free(filepath); @@ -207,10 +207,10 @@ static napi_value UpdateContext(napi_env env, napi_callback_info info) { if (valuetype0 == napi_string) { char *context = read_string_value(env, args[0], false); - throw_error_from_status(env, becs_set_context(context)); + throw_error_from_status(env, becsp_set_context(context)); free(context); } else if (valuetype0 == napi_null) { - becs_set_context(NULL); + becsp_set_context(NULL); } else { napi_throw_type_error(env, NULL, "Wrong argument type, expected string or null"); @@ -233,7 +233,7 @@ static napi_value UpdateUser(napi_env env, napi_callback_info info) { char *id = read_string_value(env, args[0], true); char *email = read_string_value(env, args[1], true); char *name = read_string_value(env, args[2], true); - throw_error_from_status(env, becs_set_user(id, email, name)); + throw_error_from_status(env, becsp_set_user(id, email, name)); free(id); free(email); @@ -259,11 +259,11 @@ static void UpdateMetadataTab(napi_env env, size_t argc, napi_value *args) { } if (should_clear) { // clearing the tab - throw_error_from_status(env, becs_update_metadata(tab, NULL)); + throw_error_from_status(env, becsp_update_metadata(tab, NULL)); } else { char *values = read_string_value(env, json_stringify(env, args[1]), true); if (values) { - throw_error_from_status(env, becs_update_metadata(tab, values)); + throw_error_from_status(env, becsp_update_metadata(tab, values)); free(values); } } @@ -290,7 +290,7 @@ static napi_value UpdateMetadata(napi_env env, napi_callback_info info) { switch (valuetype0) { case napi_object: { // setting all metadata char *metadata = read_string_value(env, json_stringify(env, args[0]), true); - throw_error_from_status(env, becs_set_metadata(metadata)); + throw_error_from_status(env, becsp_set_metadata(metadata)); free(metadata); }; break; case napi_string: { // setting / clearing a single tab @@ -317,7 +317,7 @@ static napi_value SetApp(napi_env env, napi_callback_info info) { return NULL; } - set_object_or_null(env, args[0], becs_set_app); + set_object_or_null(env, args[0], becsp_set_app); return NULL; } @@ -333,7 +333,7 @@ static napi_value SetDevice(napi_env env, napi_callback_info info) { return NULL; } - set_object_or_null(env, args[0], becs_set_device); + set_object_or_null(env, args[0], becsp_set_device); return NULL; } @@ -349,7 +349,7 @@ static napi_value SetSession(napi_env env, napi_callback_info info) { return NULL; } - set_object_or_null(env, args[0], becs_set_session); + set_object_or_null(env, args[0], becsp_set_session); return NULL; } @@ -367,7 +367,7 @@ static napi_value LeaveBreadcrumb(napi_env env, napi_callback_info info) { char *breadcrumb = read_json_string_value(env, args[0], false); if (breadcrumb) { - throw_error_from_status(env, becs_add_breadcrumb(breadcrumb)); + throw_error_from_status(env, becsp_add_breadcrumb(breadcrumb)); free(breadcrumb); } @@ -375,7 +375,7 @@ static napi_value LeaveBreadcrumb(napi_env env, napi_callback_info info) { } static napi_value PersistState(napi_env env, napi_callback_info info) { - becs_persist_to_disk(); + becsp_persist_to_disk(); return NULL; } diff --git a/packages/plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.c b/packages/plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.c similarity index 78% rename from packages/plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.c rename to packages/plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.c index 2143891a0c..4cd88aec2b 100644 --- a/packages/plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.c +++ b/packages/plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.c @@ -9,7 +9,7 @@ #include #endif -#include "bugsnag_electron_client_sync.h" +#include "bugsnag_electron_client_state_persistence.h" #include "deps/parson/parson.h" #include "deps/tinycthread/tinycthread.h" #include "signal_handler.h" @@ -27,12 +27,12 @@ typedef struct { size_t serialized_data_len; // A lock for synchronizing access to the JSON object mtx_t lock; -} becs_context; +} becsp_context; // Maximum size for all serialized data -static const int BECS_SERIALIZED_DATA_LEN = 1024 * 1024; +static const int BECSP_SERIALIZED_DATA_LEN = 1024 * 1024; // Local context for storing cached data -static becs_context g_context = {0}; +static becsp_context g_context = {0}; // Field constants static const char *const key_app = "app"; static const char *const key_breadcrumbs = "breadcrumbs"; @@ -46,11 +46,11 @@ static const char *const keypath_user_name = "user.name"; static const char *const keypath_user_email = "user.email"; static void handle_crash_signal(int sig) { - becs_persist_to_disk(); + becsp_persist_to_disk(); // Uninstall handlers - becs_signal_uninstall(); + becsp_signal_uninstall(); // Invoke previous handler - becs_signal_raise(sig); + becsp_signal_raise(sig); } static void serialize_data() { @@ -59,7 +59,7 @@ static void serialize_data() { g_context.serialized_data_len = json_serialization_size(g_context.data) - 1; // Serialize object to buffer json_serialize_to_buffer(g_context.data, g_context.serialized_data, - BECS_SERIALIZED_DATA_LEN); + BECSP_SERIALIZED_DATA_LEN); } } @@ -97,7 +97,7 @@ static JSON_Value *initialize_context(const char *state) { return json_value_init_object(); } -void becs_install(const char *save_file_path, uint8_t max_crumbs, +void becsp_install(const char *save_file_path, uint8_t max_crumbs, const char *state) { if (g_context.data != NULL) { return; @@ -113,18 +113,18 @@ void becs_install(const char *save_file_path, uint8_t max_crumbs, g_context.data = initialize_context(state); // Allocate a buffer for the serialized JSON string - g_context.serialized_data = calloc(BECS_SERIALIZED_DATA_LEN, sizeof(char)); + g_context.serialized_data = calloc(BECSP_SERIALIZED_DATA_LEN, sizeof(char)); // Cache the empty objects as a JSON string serialize_data(); // Install signal handler - becs_signal_install(handle_crash_signal); + becsp_signal_install(handle_crash_signal); } -void becs_uninstall() { +void becsp_uninstall() { if (!g_context.data) { return; } - becs_signal_uninstall(); + becsp_signal_uninstall(); free((void *)g_context.save_file_path); free(g_context.serialized_data); json_value_free(g_context.data); @@ -135,19 +135,19 @@ void becs_uninstall() { g_context.serialized_data = NULL; } -BECS_STATUS becs_add_breadcrumb(const char *val) { +BECSP_STATUS becsp_add_breadcrumb(const char *val) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); - BECS_STATUS status = BECS_STATUS_SUCCESS; + BECSP_STATUS status = BECSP_STATUS_SUCCESS; JSON_Object *obj = json_value_get_object(g_context.data); JSON_Value *breadcrumb = json_parse_string(val); if (!breadcrumb) { - status = BECS_STATUS_INVALID_JSON; + status = BECSP_STATUS_INVALID_JSON; } else if (json_value_get_type(breadcrumb) != JSONObject) { - status = BECS_STATUS_EXPECTED_JSON_OBJECT; + status = BECSP_STATUS_EXPECTED_JSON_OBJECT; json_value_free(breadcrumb); } else { JSON_Value *breadcrumbs_value = json_object_get_value(obj, key_breadcrumbs); @@ -170,9 +170,9 @@ BECS_STATUS becs_add_breadcrumb(const char *val) { return status; } -BECS_STATUS becs_set_context(const char *context) { +BECSP_STATUS becsp_set_context(const char *context) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); @@ -185,16 +185,16 @@ BECS_STATUS becs_set_context(const char *context) { serialize_data(); context_unlock(); - return BECS_STATUS_SUCCESS; + return BECSP_STATUS_SUCCESS; } -BECS_STATUS becs_set_metadata(const char *values) { +BECSP_STATUS becsp_set_metadata(const char *values) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); - BECS_STATUS status = BECS_STATUS_SUCCESS; + BECSP_STATUS status = BECSP_STATUS_SUCCESS; JSON_Object *obj = json_value_get_object(g_context.data); if (values) { @@ -203,11 +203,11 @@ BECS_STATUS becs_set_metadata(const char *values) { if (json_value_get_type(metadata) == JSONObject) { json_object_set_value(obj, key_metadata, metadata); } else { - status = BECS_STATUS_EXPECTED_JSON_OBJECT; + status = BECSP_STATUS_EXPECTED_JSON_OBJECT; json_value_free(metadata); } } else { - status = BECS_STATUS_INVALID_JSON; + status = BECSP_STATUS_INVALID_JSON; } } else { json_object_remove(obj, key_metadata); @@ -218,17 +218,17 @@ BECS_STATUS becs_set_metadata(const char *values) { return status; } -BECS_STATUS becs_update_metadata(const char *tab, const char *val) { +BECSP_STATUS becsp_update_metadata(const char *tab, const char *val) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } if (!tab) { - return BECS_STATUS_NULL_PARAM; + return BECSP_STATUS_NULL_PARAM; } context_lock(); - BECS_STATUS status = BECS_STATUS_SUCCESS; + BECSP_STATUS status = BECSP_STATUS_SUCCESS; JSON_Object *obj = json_value_get_object(g_context.data); JSON_Value *metadata_value = json_object_get_value(obj, key_metadata); @@ -246,7 +246,7 @@ BECS_STATUS becs_update_metadata(const char *tab, const char *val) { if (tab_values) { json_object_set_value(metadata, tab, tab_values); } else { - status = BECS_STATUS_INVALID_JSON; + status = BECSP_STATUS_INVALID_JSON; } } else { // Clear the tab contents json_object_remove(metadata, tab); @@ -257,19 +257,19 @@ BECS_STATUS becs_update_metadata(const char *tab, const char *val) { return status; } -BECS_STATUS becs_set_app(const char *value) { +BECSP_STATUS becsp_set_app(const char *value) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); - BECS_STATUS status = BECS_STATUS_SUCCESS; + BECSP_STATUS status = BECSP_STATUS_SUCCESS; JSON_Object *obj = json_value_get_object(g_context.data); if (value) { JSON_Value *pairs = json_parse_string(value); if (!pairs) { - status = BECS_STATUS_INVALID_JSON; + status = BECSP_STATUS_INVALID_JSON; } else if (json_value_get_type(pairs) != JSONObject) { - status = BECS_STATUS_EXPECTED_JSON_OBJECT; + status = BECSP_STATUS_EXPECTED_JSON_OBJECT; json_value_free(pairs); } else { json_object_set_value(obj, key_app, pairs); @@ -283,19 +283,19 @@ BECS_STATUS becs_set_app(const char *value) { return status; } -BECS_STATUS becs_set_device(const char *value) { +BECSP_STATUS becsp_set_device(const char *value) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); - BECS_STATUS status = BECS_STATUS_SUCCESS; + BECSP_STATUS status = BECSP_STATUS_SUCCESS; JSON_Object *obj = json_value_get_object(g_context.data); if (value) { JSON_Value *pairs = json_parse_string(value); if (!pairs) { - status = BECS_STATUS_INVALID_JSON; + status = BECSP_STATUS_INVALID_JSON; } else if (json_value_get_type(pairs) != JSONObject) { - status = BECS_STATUS_EXPECTED_JSON_OBJECT; + status = BECSP_STATUS_EXPECTED_JSON_OBJECT; json_value_free(pairs); } else { json_object_set_value(obj, key_device, pairs); @@ -309,19 +309,19 @@ BECS_STATUS becs_set_device(const char *value) { return status; } -BECS_STATUS becs_set_session(const char *value) { +BECSP_STATUS becsp_set_session(const char *value) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); - BECS_STATUS status = BECS_STATUS_SUCCESS; + BECSP_STATUS status = BECSP_STATUS_SUCCESS; JSON_Object *obj = json_value_get_object(g_context.data); if (value) { JSON_Value *pairs = json_parse_string(value); if (!pairs) { - status = BECS_STATUS_INVALID_JSON; + status = BECSP_STATUS_INVALID_JSON; } else if (json_value_get_type(pairs) != JSONObject) { - status = BECS_STATUS_EXPECTED_JSON_OBJECT; + status = BECSP_STATUS_EXPECTED_JSON_OBJECT; json_value_free(pairs); } else { json_object_set_value(obj, key_session, pairs); @@ -335,9 +335,9 @@ BECS_STATUS becs_set_session(const char *value) { return status; } -BECS_STATUS becs_set_user(const char *id, const char *email, const char *name) { +BECSP_STATUS becsp_set_user(const char *id, const char *email, const char *name) { if (!g_context.data) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } context_lock(); @@ -360,24 +360,24 @@ BECS_STATUS becs_set_user(const char *id, const char *email, const char *name) { serialize_data(); context_unlock(); - return BECS_STATUS_SUCCESS; + return BECSP_STATUS_SUCCESS; } // Must be async-signal-safe -BECS_STATUS becs_persist_to_disk() { +BECSP_STATUS becsp_persist_to_disk() { if (!g_context.save_file_path) { - return BECS_STATUS_NOT_INSTALLED; + return BECSP_STATUS_NOT_INSTALLED; } // Open save file path int fd = open(g_context.save_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { - return BECS_STATUS_UNKNOWN_FAILURE; + return BECSP_STATUS_UNKNOWN_FAILURE; } // Write serialized_data size_t len = write(fd, g_context.serialized_data, g_context.serialized_data_len); // Close save file path close(fd); - return len == g_context.serialized_data_len ? BECS_STATUS_SUCCESS - : BECS_STATUS_UNKNOWN_FAILURE; + return len == g_context.serialized_data_len ? BECSP_STATUS_SUCCESS + : BECSP_STATUS_UNKNOWN_FAILURE; } diff --git a/packages/plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.h b/packages/plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.h similarity index 69% rename from packages/plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.h rename to packages/plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.h index 3b02fd92e0..0f604312fa 100644 --- a/packages/plugin-electron-native-client-sync/src/bugsnag_electron_client_sync.h +++ b/packages/plugin-electron-client-state-persistence/src/bugsnag_electron_client_state_persistence.h @@ -11,28 +11,28 @@ extern "C" { */ typedef enum { /** All good */ - BECS_STATUS_SUCCESS, + BECSP_STATUS_SUCCESS, /** - * The synchronizing layer has not yet been configured, call becs_install() + * The synchronizing layer has not yet been configured, call becsp_install() */ - BECS_STATUS_NOT_INSTALLED, + BECSP_STATUS_NOT_INSTALLED, /** * JSON value sent as a parameter could not be parsed */ - BECS_STATUS_INVALID_JSON, + BECSP_STATUS_INVALID_JSON, /** * JSON value sent as a parameter used an unexpected type */ - BECS_STATUS_EXPECTED_JSON_OBJECT, + BECSP_STATUS_EXPECTED_JSON_OBJECT, /** * Required parameter was NULL */ - BECS_STATUS_NULL_PARAM, + BECSP_STATUS_NULL_PARAM, /** * Something went wrong but we don't know what */ - BECS_STATUS_UNKNOWN_FAILURE, -} BECS_STATUS; + BECSP_STATUS_UNKNOWN_FAILURE, +} BECSP_STATUS; /** * Install a handler which will write to disk in the event of a crash @@ -42,31 +42,31 @@ typedef enum { * @param max_crumbs The maximum number of breadcrumbs to save * @param state Stringified JSON of the initial cached state */ -void becs_install(const char *save_file_path, uint8_t max_crumbs, +void becsp_install(const char *save_file_path, uint8_t max_crumbs, const char *state); -void becs_uninstall(void); +void becsp_uninstall(void); /** * Append a breadcrumb * * @param val Breadcrumb JSON value serialized to string */ -BECS_STATUS becs_add_breadcrumb(const char *val); +BECSP_STATUS becsp_add_breadcrumb(const char *val); /** * Set the event context * * @param val the new context value or NULL to unset */ -BECS_STATUS becs_set_context(const char *context); +BECSP_STATUS becsp_set_context(const char *context); /** * Set event user * * @param val JSON-serialized user value */ -BECS_STATUS becs_set_user(const char *id, const char *email, const char *name); +BECSP_STATUS becsp_set_user(const char *id, const char *email, const char *name); /** * Set cached metadata value for an entire tab @@ -75,7 +75,7 @@ BECS_STATUS becs_set_user(const char *id, const char *email, const char *name); * @param val Metadata JSON key/value pairs serialized to string or NULL to * clear */ -BECS_STATUS becs_update_metadata(const char *tab, const char *val); +BECSP_STATUS becsp_update_metadata(const char *tab, const char *val); /** * Set cached metadata @@ -83,33 +83,33 @@ BECS_STATUS becs_update_metadata(const char *tab, const char *val); * @param tab Metadata object serialized as JSON * clear */ -BECS_STATUS becs_set_metadata(const char *metadata); +BECSP_STATUS becsp_set_metadata(const char *metadata); /** * Set cached top-level app value * * @param value JSON value serialized to string containing key/value pairs */ -BECS_STATUS becs_set_app(const char *value); +BECSP_STATUS becsp_set_app(const char *value); /** * Set cached top-level device value * * @param value JSON value serialized to string containing key/value pairs */ -BECS_STATUS becs_set_device(const char *value); +BECSP_STATUS becsp_set_device(const char *value); /** * Set the current session * * @param value JSON value serialized to string or NULL to remove session info */ -BECS_STATUS becs_set_session(const char *value); +BECSP_STATUS becsp_set_session(const char *value); /** * Write cached event context to disk */ -BECS_STATUS becs_persist_to_disk(void); +BECSP_STATUS becsp_persist_to_disk(void); #ifdef __cplusplus } #endif diff --git a/packages/plugin-electron-native-client-sync/src/deps/parson/package.json b/packages/plugin-electron-client-state-persistence/src/deps/parson/package.json similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/parson/package.json rename to packages/plugin-electron-client-state-persistence/src/deps/parson/package.json diff --git a/packages/plugin-electron-native-client-sync/src/deps/parson/parson.c b/packages/plugin-electron-client-state-persistence/src/deps/parson/parson.c similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/parson/parson.c rename to packages/plugin-electron-client-state-persistence/src/deps/parson/parson.c diff --git a/packages/plugin-electron-native-client-sync/src/deps/parson/parson.h b/packages/plugin-electron-client-state-persistence/src/deps/parson/parson.h similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/parson/parson.h rename to packages/plugin-electron-client-state-persistence/src/deps/parson/parson.h diff --git a/packages/plugin-electron-native-client-sync/src/deps/tinycthread/README.txt b/packages/plugin-electron-client-state-persistence/src/deps/tinycthread/README.txt similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/tinycthread/README.txt rename to packages/plugin-electron-client-state-persistence/src/deps/tinycthread/README.txt diff --git a/packages/plugin-electron-native-client-sync/src/deps/tinycthread/package.json b/packages/plugin-electron-client-state-persistence/src/deps/tinycthread/package.json similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/tinycthread/package.json rename to packages/plugin-electron-client-state-persistence/src/deps/tinycthread/package.json diff --git a/packages/plugin-electron-native-client-sync/src/deps/tinycthread/tinycthread.c b/packages/plugin-electron-client-state-persistence/src/deps/tinycthread/tinycthread.c similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/tinycthread/tinycthread.c rename to packages/plugin-electron-client-state-persistence/src/deps/tinycthread/tinycthread.c diff --git a/packages/plugin-electron-native-client-sync/src/deps/tinycthread/tinycthread.h b/packages/plugin-electron-client-state-persistence/src/deps/tinycthread/tinycthread.h similarity index 100% rename from packages/plugin-electron-native-client-sync/src/deps/tinycthread/tinycthread.h rename to packages/plugin-electron-client-state-persistence/src/deps/tinycthread/tinycthread.h diff --git a/packages/plugin-electron-native-client-sync/src/signal_handler.c b/packages/plugin-electron-client-state-persistence/src/signal_handler.c similarity index 90% rename from packages/plugin-electron-native-client-sync/src/signal_handler.c rename to packages/plugin-electron-client-state-persistence/src/signal_handler.c index 42fe345bb4..8471af608e 100644 --- a/packages/plugin-electron-native-client-sync/src/signal_handler.c +++ b/packages/plugin-electron-client-state-persistence/src/signal_handler.c @@ -18,21 +18,21 @@ static const int bsg_native_signals[] = {SIGILL, SIGTRAP, SIGABRT, static void (*prev_handlers[SIGNAL_COUNT])(int); -void becs_signal_install(void (*func)(int)) { +void becsp_signal_install(void (*func)(int)) { for (int i = 0; i < SIGNAL_COUNT; i++) { const int sig = bsg_native_signals[i]; prev_handlers[i] = signal(sig, func); } } -void becs_signal_uninstall() { +void becsp_signal_uninstall() { for (int i = 0; i < SIGNAL_COUNT; i++) { const int sig = bsg_native_signals[i]; signal(sig, prev_handlers[i]); } } -void becs_signal_raise(int _sig) { +void becsp_signal_raise(int _sig) { for (int i = 0; i < SIGNAL_COUNT; i++) { const int sig = bsg_native_signals[i]; if (sig == _sig) { diff --git a/packages/plugin-electron-client-state-persistence/src/signal_handler.h b/packages/plugin-electron-client-state-persistence/src/signal_handler.h new file mode 100644 index 0000000000..e34f33667c --- /dev/null +++ b/packages/plugin-electron-client-state-persistence/src/signal_handler.h @@ -0,0 +1,7 @@ +#pragma once + +void becsp_signal_install(void (*func)(int)); + +void becsp_signal_uninstall(void); + +void becsp_signal_raise(int signal); diff --git a/packages/plugin-electron-native-client-sync/test/client-sync.test.ts b/packages/plugin-electron-client-state-persistence/test/client-sync.test.ts similarity index 95% rename from packages/plugin-electron-native-client-sync/test/client-sync.test.ts rename to packages/plugin-electron-client-state-persistence/test/client-sync.test.ts index e986bb15d4..0ddcb84e0b 100644 --- a/packages/plugin-electron-native-client-sync/test/client-sync.test.ts +++ b/packages/plugin-electron-client-state-persistence/test/client-sync.test.ts @@ -1,14 +1,14 @@ import Client from '@bugsnag/core/client' import plugin from '../' import { Breadcrumb, Logger } from '@bugsnag/core' -import stateSyncPlugin from '@bugsnag/plugin-electron-state-sync' +import stateManager from '@bugsnag/plugin-electron-client-state-manager' describe('plugin: electron client sync', () => { it('updates context', done => { const c = new Client({ apiKey: 'api_key', plugins: [ - stateSyncPlugin, + stateManager, plugin({ updateContext: (update: any) => { expect(update).toBe('1234') @@ -24,7 +24,7 @@ describe('plugin: electron client sync', () => { const c = new Client({ apiKey: 'api_key', plugins: [ - stateSyncPlugin, + stateManager, plugin({ updateMetadata: (key: string, updates: any) => { expect(key).toBe('widget') @@ -45,7 +45,7 @@ describe('plugin: electron client sync', () => { const c = new Client({ apiKey: 'api_key', plugins: [ - stateSyncPlugin, + stateManager, plugin({ addMetadata: () => {}, clearMetadata: () => {} @@ -65,7 +65,7 @@ describe('plugin: electron client sync', () => { const c = new Client({ apiKey: 'api_key', plugins: [ - stateSyncPlugin, + stateManager, plugin({ updateUser: (id: string, email: string, name: string) => { expect(id).toBe('1234') @@ -84,7 +84,7 @@ describe('plugin: electron client sync', () => { const c = new Client({ apiKey: 'api_key', plugins: [ - stateSyncPlugin, + stateManager, plugin({ leaveBreadcrumb: ({ message, metadata, type, timestamp }: Breadcrumb) => { expect(message).toBe('Spin') @@ -108,7 +108,7 @@ describe('plugin: electron client sync', () => { } const client = new Client({ apiKey: 'api_key', - plugins: [stateSyncPlugin, plugin(NativeClient)], + plugins: [stateManager, plugin(NativeClient)], logger }) return [client, logger] diff --git a/packages/plugin-electron-native-client-sync/test/error-handling.test.ts b/packages/plugin-electron-client-state-persistence/test/error-handling.test.ts similarity index 98% rename from packages/plugin-electron-native-client-sync/test/error-handling.test.ts rename to packages/plugin-electron-client-state-persistence/test/error-handling.test.ts index 719be60f42..956b25981a 100644 --- a/packages/plugin-electron-native-client-sync/test/error-handling.test.ts +++ b/packages/plugin-electron-client-state-persistence/test/error-handling.test.ts @@ -1,7 +1,7 @@ import * as bindings from 'bindings' describe('handling poor inputs', () => { - const NativeClient = bindings.default('bugsnag_plugin_electron_client_sync_bindings') + const NativeClient = bindings.default('bugsnag_plugin_electron_client_state_persistence_bindings') beforeAll(() => NativeClient.install('/tmp/file.json', 10)) diff --git a/packages/plugin-electron-native-client-sync/test/persistence.test.ts b/packages/plugin-electron-client-state-persistence/test/persistence.test.ts similarity index 99% rename from packages/plugin-electron-native-client-sync/test/persistence.test.ts rename to packages/plugin-electron-client-state-persistence/test/persistence.test.ts index 7729048d42..76943942a2 100644 --- a/packages/plugin-electron-native-client-sync/test/persistence.test.ts +++ b/packages/plugin-electron-client-state-persistence/test/persistence.test.ts @@ -5,7 +5,7 @@ import * as bindings from 'bindings' const { mkdtemp, readFile, rmdir } = promises describe('persisting changes to disk', () => { - const NativeClient = bindings.default('bugsnag_plugin_electron_client_sync_bindings') + const NativeClient = bindings.default('bugsnag_plugin_electron_client_state_persistence_bindings') let tempdir: string = '' let filepath: string = '' diff --git a/packages/plugin-electron-event-sync/main-event-sync.js b/packages/plugin-electron-event-sync/main-event-sync.js deleted file mode 100644 index a8bfc50586..0000000000 --- a/packages/plugin-electron-event-sync/main-event-sync.js +++ /dev/null @@ -1,84 +0,0 @@ -const Client = require('@bugsnag/core/client') -const Event = require('@bugsnag/core/event') -const runCallbacks = require('@bugsnag/core/lib/callback-runner') - -module.exports = { - name: 'mainEventSync', - load: client => { - const onCallbackError = (err, cb) => { - // errors in callbacks are tolerated but we want to log them out - client._logger.error('Error occurred in onError callback, continuing anyway…') - client._logger.error(err) - } - - const getPayloadInfo = () => { - return new Promise((resolve, reject) => { - const event = new Event('BugsnagInternalError', 'Extracting event info from main process for event in renderer') - event.app = { - ...event.app, - releaseStage: client._config.releaseStage, - version: client._config.appVersion, - type: client._config.appType - } - event.context = event.context || client._context - event._metadata = { ...event._metadata, ...client._metadata } - event._user = { ...event._user, ...client._user } - event.breadcrumbs = client._breadcrumbs.slice() - - // run the event through just the internal onError callbacks - const callbacks = client._cbs.e.filter(e => e._internal) - runCallbacks(callbacks, event, onCallbackError, (err, shouldSend) => { - if (err) onCallbackError(err) - if (!shouldSend) return resolve({ shouldSend: false }) - - // extract just the properties we want from the event - const { app, breadcrumbs, context, device, _metadata, user } = event - resolve({ app, breadcrumbs, context, device, metadata: _metadata, user }) - }) - }) - } - - const dispatch = (event) => { - const originalSeverity = event.severity - - const callbacks = client._cbs.e.filter(e => !e._internal) - runCallbacks(callbacks, event, onCallbackError, (err, shouldSend) => { - if (err) onCallbackError(err) - - if (!shouldSend) { - client._logger.debug('Event not sent due to onError callback') - } - - if (client._config.enabledBreadcrumbTypes.includes('error')) { - // only leave a crumb for the error if actually got sent - Client.prototype.leaveBreadcrumb.call(client, event.errors[0].errorClass, { - errorClass: event.errors[0].errorClass, - errorMessage: event.errors[0].errorMessage, - severity: event.severity - }, 'error') - } - - if (originalSeverity !== event.severity) { - event._handledState.severityReason = { type: 'userCallbackSetSeverity' } - } - - if (event.unhandled !== event._handledState.unhandled) { - event._handledState.severityReason.unhandledOverridden = true - event._handledState.unhandled = event.unhandled - } - - if (client._session) { - client._session._track(event) - event._session = client._session - } - - client._delivery.sendEvent({ - apiKey: event.apiKey || client._config.apiKey, - notifier: client._notifier, - events: [event] - }) - }) - } - return { getPayloadInfo, dispatch } - } -} diff --git a/packages/plugin-electron-event-sync/main-internal-plugin-marker.js b/packages/plugin-electron-event-sync/main-internal-plugin-marker.js deleted file mode 100644 index d8a1a9bab4..0000000000 --- a/packages/plugin-electron-event-sync/main-internal-plugin-marker.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports.firstPlugin = { - load: (client) => { - // patch onError so we can track which onError callbacks are added by internal plugins - const _origAddOnError = client.addOnError - client.addOnError = function (fn) { - fn._internal = true - _origAddOnError.call(client, ...arguments) - } - client.addOnError._restore = () => { - client.addOnError = _origAddOnError - } - } -} - -module.exports.lastPlugin = { - load: (client) => { - client.addOnError._restore() - } -} diff --git a/packages/plugin-electron-ipc/bugsnag-ipc-main.js b/packages/plugin-electron-ipc/bugsnag-ipc-main.js index 13909fe0ea..8c5c17c10d 100644 --- a/packages/plugin-electron-ipc/bugsnag-ipc-main.js +++ b/packages/plugin-electron-ipc/bugsnag-ipc-main.js @@ -1,15 +1,14 @@ const Event = require('@bugsnag/core/event') +const Client = require('@bugsnag/core/client') const Breadcrumb = require('@bugsnag/core/breadcrumb') +const runCallbacks = require('@bugsnag/core/lib/callback-runner') module.exports = class BugsnagIpcMain { constructor (client) { this.client = client - this.stateSync = client.getPlugin('stateSync') - if (!this.stateSync) throw new Error('Expected @bugsnag/plugin-electron-state-sync to be loaded first') - - this.mainEventSync = client.getPlugin('mainEventSync') - if (!this.mainEventSync) throw new Error('Expected @bugsnag/plugin-electron-event-sync to be loaded first') + this.clientStateManager = client.getPlugin('clientStateManager') + if (!this.clientStateManager) throw new Error('Expected @bugsnag/plugin-electron-client-state-manager to be loaded first') this.methodMap = this.toMap() @@ -27,24 +26,97 @@ module.exports = class BugsnagIpcMain { } update ({ context, user, metadata }) { - return this.stateSync.bulkUpdate({ context, user, metadata }) + return this.clientStateManager.bulkUpdate({ context, user, metadata }) } dispatch (eventObject) { - const event = new Event() + try { + const event = new Event() - // copy all properties from 'eventObject' to 'event' - Object.keys(event) - .filter(Object.hasOwnProperty.bind(event)) - .forEach(key => { event[key] = eventObject[key] }) + // copy all properties from 'eventObject' to 'event' + Object.keys(event) + .filter(Object.hasOwnProperty.bind(event)) + .forEach(key => { event[key] = eventObject[key] }) - event.breadcrumbs = event.breadcrumbs.map(b => new Breadcrumb(b.message, b.metadata, b.type, b.timestamp)) + // rehydrate breadcrumbs so they get serialised properly (Breadcrumb class has a .toJSON() method) + event.breadcrumbs = event.breadcrumbs.map(b => + new Breadcrumb(b.message, b.metadata, b.type, b.timestamp) + ) + + this._dispatch(event) + } catch (e) { + this.client._logger.error('Error dispatching event from renderer', e) + } + } - this.mainEventSync.dispatch(event) + _dispatch (event) { + const originalSeverity = event.severity + + const callbacks = this.client._cbs.e.filter(e => !e._internal) + runCallbacks(callbacks, event, this._onCallbackError, (err, shouldSend) => { + if (err) this._onCallbackError(err) + + if (!shouldSend) { + this.client._logger.debug('Event not sent due to onError callback') + return + } + + if (this.client._config.enabledBreadcrumbTypes.includes('error')) { + // only leave a crumb for the error if actually got sent + Client.prototype.leaveBreadcrumb.call(this.client, event.errors[0].errorClass, { + errorClass: event.errors[0].errorClass, + errorMessage: event.errors[0].errorMessage, + severity: event.severity + }, 'error') + } + + if (originalSeverity !== event.severity) { + event._handledState.severityReason = { type: 'userCallbackSetSeverity' } + } + + if (event.unhandled !== event._handledState.unhandled) { + event._handledState.severityReason.unhandledOverridden = true + event._handledState.unhandled = event.unhandled + } + + if (this.client._session) { + this.client._session._track(event) + event._session = this.client._session + } + + this.client._delivery.sendEvent({ + apiKey: event.apiKey || this.client._config.apiKey, + notifier: this.client._notifier, + events: [event] + }) + }) } getPayloadInfo () { - return this.mainEventSync.getPayloadInfo() + return new Promise((resolve, reject) => { + const event = new Event('BugsnagInternalError', 'Extracting event info from main process for event in renderer') + event.app = { + ...event.app, + releaseStage: this.client._config.releaseStage, + version: this.client._config.appVersion, + type: this.client._config.appType + } + event.context = event.context || this.client._context + event._metadata = { ...event._metadata, ...this.client._metadata } + event._user = { ...event._user, ...this.client._user } + event.breadcrumbs = this.client._breadcrumbs.slice() + + // run the event through just the internal onError callbacks + const callbacks = this.client._cbs.e.filter(e => e._internal) + runCallbacks(callbacks, event, this._onCallbackError, (err, shouldSend) => { + if (err) this._onCallbackError(err) + if (!shouldSend) return resolve({ shouldSend: false }) + + // extract just the properties we want from the event + const { app, breadcrumbs, context, device, _metadata, _user } = event + resolve({ app, breadcrumbs, context, device, metadata: _metadata, user: _user }) + }) + }) } handle (_event, methodName, ...args) { @@ -70,6 +142,12 @@ module.exports = class BugsnagIpcMain { } } + _onCallbackError (err) { + // errors in callbacks are tolerated but we want to log them out + this.client._logger.error('Error occurred in onError callback, continuing anyway…') + this.client._logger.error(err) + } + toMap () { return new Map([ ['leaveBreadcrumb', this.leaveBreadcrumb.bind(this)], diff --git a/packages/plugin-electron-ipc/test/bugsnag-ipc-main.test.ts b/packages/plugin-electron-ipc/test/bugsnag-ipc-main.test.ts index 0f1c7cfcdc..3d2d470d4c 100644 --- a/packages/plugin-electron-ipc/test/bugsnag-ipc-main.test.ts +++ b/packages/plugin-electron-ipc/test/bugsnag-ipc-main.test.ts @@ -1,13 +1,9 @@ import BugsnagIpcMain from '../bugsnag-ipc-main' import Client from '@bugsnag/core/client' +import Event from '@bugsnag/core/event' -const mockStateSyncPlugin = { - name: 'stateSync', - load: () => ({}) -} - -const mockEventSyncPlugin = { - name: 'mainEventSync', +const mockClientStateManagerPlugin = { + name: 'clientStateManager', load: () => ({}) } @@ -15,32 +11,25 @@ afterEach(() => jest.clearAllMocks()) describe('BugsnagIpcMain', () => { describe('constructor()', () => { - it('should throw if the state sync plugin is not loaded first', () => { + it('should throw if the state manager plugin is not loaded first', () => { const client = new Client({}, {}, [], {}) expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const bugsnagIpcMain = new BugsnagIpcMain(client) - }).toThrowError('Expected @bugsnag/plugin-electron-state-sync to be loaded first') - }) - it('should throw if the main event sync plugin is not loaded first', () => { - const client = new Client({}, {}, [mockStateSyncPlugin], {}) - expect(() => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const bugsnagIpcMain = new BugsnagIpcMain(client) - }).toThrowError('Expected @bugsnag/plugin-electron-event-sync to be loaded first') + }).toThrowError('Expected @bugsnag/plugin-electron-client-state-manager to be loaded first') }) - it('should work when the state sync plugin is loaded first', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + it('should work when the state manager plugin is loaded first', () => { + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) expect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const bugsnagIpcMain = new BugsnagIpcMain(client) - }).not.toThrowError('Expected @bugsnag/plugin-electron-state-sync to be loaded first') + }).not.toThrowError('Expected @bugsnag/plugin-electron-client-state-manager to be loaded first') }) }) describe('handle()', () => { it('works for updating context', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.setContext = jest.fn() const bugsnagIpcMain = new BugsnagIpcMain(client) bugsnagIpcMain.handle({}, 'setContext', JSON.stringify('new context')) @@ -48,7 +37,7 @@ describe('BugsnagIpcMain', () => { }) it('returns the current context', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.setContext('today') const bugsnagIpcMain = new BugsnagIpcMain(client) const event = { returnValue: undefined } @@ -57,7 +46,7 @@ describe('BugsnagIpcMain', () => { }) it('works for updating user', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.setUser = jest.fn() const bugsnagIpcMain = new BugsnagIpcMain(client) // all fields set @@ -69,7 +58,7 @@ describe('BugsnagIpcMain', () => { }) it('returns the current user', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.setUser('81676', null, 'Cal') const bugsnagIpcMain = new BugsnagIpcMain(client) const event = { returnValue: undefined } @@ -78,7 +67,7 @@ describe('BugsnagIpcMain', () => { }) it('works for adding metadata', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.addMetadata = jest.fn() const bugsnagIpcMain = new BugsnagIpcMain(client) const stubWebContents = { /* this would be a WebContents instance */ } @@ -88,7 +77,7 @@ describe('BugsnagIpcMain', () => { }) it('works for removing metadata', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.clearMetadata = jest.fn() const bugsnagIpcMain = new BugsnagIpcMain(client) bugsnagIpcMain.handle({}, 'clearMetadata', JSON.stringify('section')) @@ -96,7 +85,7 @@ describe('BugsnagIpcMain', () => { }) it('returns metadata content', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.addMetadata('section', 'content', 'X') const bugsnagIpcMain = new BugsnagIpcMain(client) const event = { returnValue: undefined } @@ -110,7 +99,7 @@ describe('BugsnagIpcMain', () => { }) it('works for managing sessions', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client._sessionDelegate = { startSession: jest.fn(), resumeSession: jest.fn(), pauseSession: jest.fn() } const bugsnagIpcMain = new BugsnagIpcMain(client) // start @@ -125,7 +114,7 @@ describe('BugsnagIpcMain', () => { }) it('works for breadcrumbs', (done) => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) client.addOnBreadcrumb(b => { expect(b.message).toBe('hi IPC') expect(b.type).toBe('manual') @@ -142,7 +131,7 @@ describe('BugsnagIpcMain', () => { it('works for bulk updates', done => { const client = new Client({}, {}, [{ - name: 'stateSync', + name: 'clientStateManager', load: () => ({ bulkUpdate: ({ context, user, metadata }) => { expect(context).toEqual('current context') @@ -151,7 +140,7 @@ describe('BugsnagIpcMain', () => { done() } }) - }, mockEventSyncPlugin], {}) + }], {}) const bugsnagIpcMain = new BugsnagIpcMain(client) bugsnagIpcMain.handle( {}, @@ -161,15 +150,153 @@ describe('BugsnagIpcMain', () => { }) it('is resilient to unknown methods', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) const bugsnagIpcMain = new BugsnagIpcMain(client) expect(() => bugsnagIpcMain.handle({}, 'explodePlease', JSON.stringify({ data: 123 }))).not.toThrowError() }) it('is resilient to bad JSON', () => { - const client = new Client({}, {}, [mockStateSyncPlugin, mockEventSyncPlugin], {}) + const client = new Client({}, {}, [mockClientStateManagerPlugin], {}) const bugsnagIpcMain = new BugsnagIpcMain(client) expect(() => bugsnagIpcMain.handle({}, 'leaveBreadcrumb', 'not json')).not.toThrowError() }) }) + + describe('getPayloadInfo()', () => { + it('should run an event through internal callbacks and return its properties', async () => { + const internalPlugins = [ + { + load: (client) => { + // mock an internal plugin that adds app data + const cb = (event) => { + event.app = { ...event.app, name: 'testApp', type: 'test' } + event.addMetadata('app', 'testingMode', 'unit') + } + cb._internal = true // simulate what @bugsnag/plugin-internal-callback-marker does + client.addOnError(cb) + } + }, + { + load: (client) => { + // mock an internal plugin that adds device data + const cb = (event) => { + event.device = { ...event.device, id: '123' } + event.addMetadata('device', 'isOutdated', true) + } + cb._internal = true // simulate what @bugsnag/plugin-internal-callback-marker does + client.addOnError(cb) + } + } + ] + + const client = new Client({ + apiKey: '123' + }, undefined, [...internalPlugins, mockClientStateManagerPlugin], {}) + + // add a non-internal callback and ensure it does not run + const nonInternalCb = jest.fn((event) => { + event.addMetadata('nonInternal', 'ran', true) + }) + client.addOnError(nonInternalCb) + + client.leaveBreadcrumb('hi') + client.setContext('ctx') + client.setUser('123', 'jim@jim.com', 'Jim') + + const bugsnagIpcMain = new BugsnagIpcMain(client) + const payloadInfo = await bugsnagIpcMain.getPayloadInfo() + + expect(payloadInfo.metadata.nonInternal).toBeUndefined() + expect(nonInternalCb).not.toHaveBeenCalled() + + expect(payloadInfo.device).toEqual({ id: '123' }) + expect(payloadInfo.metadata.device).toEqual({ isOutdated: true }) + + expect(payloadInfo.app).toEqual({ releaseStage: 'production', name: 'testApp', type: 'test' }) + expect(payloadInfo.metadata.app).toEqual({ testingMode: 'unit' }) + + expect(payloadInfo.breadcrumbs.length).toBe(1) + expect(payloadInfo.context).toBe('ctx') + expect(payloadInfo.user).toEqual({ id: '123', email: 'jim@jim.com', name: 'Jim' }) + expect(payloadInfo.shouldSend).toBeUndefined() + }) + + it('should return shouldSend=false when a callback returns false', async () => { + const cb = jest.fn(() => false) + // @ts-expect-error _internal is not a property of jest mocks but if we + // set it on the callback and then wrap it, it isn't accessible + cb._internal = true + // plugin that returns false for all events + const preventAllPlugin = { + load: (client) => { + client.addOnError(cb) + } + } + const client = new Client({ + apiKey: '123' + }, undefined, [ + preventAllPlugin, + mockClientStateManagerPlugin + ], {}) + + const bugsnagIpcMain = new BugsnagIpcMain(client) + const payloadInfo = await bugsnagIpcMain.getPayloadInfo() + expect(payloadInfo).toEqual({ shouldSend: false }) + expect(cb).toHaveBeenCalledTimes(1) + }) + }) + + describe('dispatch()', () => { + it('should take a serialised event object, rehydrate it, run it through callbacks and send it', () => { + const client = new Client({ + apiKey: '123' + }, undefined, [ + mockClientStateManagerPlugin + ], {}) + + // add an internal callback and ensure it does not run + const internalCb = jest.fn((event) => { + event.addMetadata('internal', 'ran', true) + }) + // @ts-expect-error + internalCb._internal = true + client.addOnError(internalCb) + + // add a non-internal callback and ensure it runs + const nonInternalCb = jest.fn((event) => { + event.addMetadata('nonInternal', 'ran', true) + }) + client.addOnError(nonInternalCb) + + const mockDelivery = { sendEvent: jest.fn(), sendSession: jest.fn() } + client._setDelivery(client => mockDelivery) + + const bugsnagIpcMain = new BugsnagIpcMain(client) + const event = new Event('Error', 'Something bad happened', []) + bugsnagIpcMain.dispatch(Object.assign({}, event)) + + expect(mockDelivery.sendEvent).toHaveBeenCalledWith(expect.objectContaining({ + apiKey: '123', + notifier: expect.any(Object), + events: [ + expect.objectContaining({ + context: undefined, + errors: [ + expect.objectContaining({ + errorClass: 'Error', + errorMessage: 'Something bad happened', + stacktrace: [] + }) + ], + _metadata: { + nonInternal: { ran: true } + } + }) + ] + })) + + expect(internalCb).not.toHaveBeenCalled() + expect(nonInternalCb).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/packages/plugin-electron-native-client-sync/src/signal_handler.h b/packages/plugin-electron-native-client-sync/src/signal_handler.h deleted file mode 100644 index 543d9929f7..0000000000 --- a/packages/plugin-electron-native-client-sync/src/signal_handler.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -void becs_signal_install(void (*func)(int)); - -void becs_signal_uninstall(void); - -void becs_signal_raise(int signal); diff --git a/packages/plugin-electron-renderer-client-sync/README.md b/packages/plugin-electron-renderer-client-state-updates/README.md similarity index 100% rename from packages/plugin-electron-renderer-client-sync/README.md rename to packages/plugin-electron-renderer-client-state-updates/README.md diff --git a/packages/plugin-electron-renderer-client-sync/client-sync.js b/packages/plugin-electron-renderer-client-state-updates/client-state-updates.js similarity index 100% rename from packages/plugin-electron-renderer-client-sync/client-sync.js rename to packages/plugin-electron-renderer-client-state-updates/client-state-updates.js diff --git a/packages/plugin-electron-renderer-client-sync/package-lock.json b/packages/plugin-electron-renderer-client-state-updates/package-lock.json similarity index 100% rename from packages/plugin-electron-renderer-client-sync/package-lock.json rename to packages/plugin-electron-renderer-client-state-updates/package-lock.json diff --git a/packages/plugin-electron-renderer-client-sync/package.json b/packages/plugin-electron-renderer-client-state-updates/package.json similarity index 80% rename from packages/plugin-electron-renderer-client-sync/package.json rename to packages/plugin-electron-renderer-client-state-updates/package.json index 0c29780eb9..a0d5c59be8 100644 --- a/packages/plugin-electron-renderer-client-sync/package.json +++ b/packages/plugin-electron-renderer-client-state-updates/package.json @@ -1,7 +1,7 @@ { - "name": "@bugsnag/plugin-electron-renderer-client-sync", + "name": "@bugsnag/plugin-electron-renderer-client-state-updates", "version": "1.0.0", - "main": "client-sync.js", + "main": "client-state-updates.js", "description": "@bugsnag/electron plugin to sync information between JS renderers", "homepage": "https://www.bugsnag.com/", "repository": { @@ -17,7 +17,7 @@ "license": "MIT", "gypfile": true, "files": [ - "client-sync.js" + "client-state-updates.js" ], "dependencies": { }, diff --git a/packages/plugin-electron-renderer-client-sync/test/client-sync.test.ts b/packages/plugin-electron-renderer-client-state-updates/test/client-state-updates.test.ts similarity index 78% rename from packages/plugin-electron-renderer-client-sync/test/client-sync.test.ts rename to packages/plugin-electron-renderer-client-state-updates/test/client-state-updates.test.ts index 48f9b7e1f3..8bd5dfbd78 100644 --- a/packages/plugin-electron-renderer-client-sync/test/client-sync.test.ts +++ b/packages/plugin-electron-renderer-client-state-updates/test/client-state-updates.test.ts @@ -1,13 +1,13 @@ -import clientSyncPlugin from '../client-sync' +import clientStateUpdatesPlugin from '../client-state-updates' import Client from '@bugsnag/core/client' -describe('clientSyncPlugin', () => { +describe('clientStateUpdatesPlugin', () => { describe('propagation of changes to the IPC layer', () => { it('propagates context changes', () => { const mockBugsnagIpcRenderer = { setContext: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.setContext('ctx') expect(mockBugsnagIpcRenderer.setContext).toHaveBeenCalledWith('ctx') @@ -17,7 +17,7 @@ describe('clientSyncPlugin', () => { const mockBugsnagIpcRenderer = { getContext: () => 'ctx' } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) expect(client.getContext()).toBe('ctx') }) @@ -25,7 +25,7 @@ describe('clientSyncPlugin', () => { const mockBugsnagIpcRenderer = { setUser: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.setUser('123', 'jim@jim.com', 'Jim') expect(mockBugsnagIpcRenderer.setUser).toHaveBeenCalledWith('123', 'jim@jim.com', 'Jim') @@ -35,7 +35,7 @@ describe('clientSyncPlugin', () => { const mockBugsnagIpcRenderer = { getUser: () => { return { id: '123', email: 'jim@jim.com', name: 'Jim' } } } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) expect(client.getUser()).toEqual({ id: '123', email: 'jim@jim.com', name: 'Jim' }) }) @@ -45,7 +45,7 @@ describe('clientSyncPlugin', () => { addMetadata: jest.fn(), clearMetadata: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.addMetadata('section', { key0: 123, key1: 234 }) expect(mockBugsnagIpcRenderer.addMetadata).toHaveBeenCalledWith('section', { key0: 123, key1: 234 }) @@ -65,7 +65,7 @@ describe('clientSyncPlugin', () => { done() } } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.getMetadata('layers', 'strawberry') }) @@ -73,7 +73,7 @@ describe('clientSyncPlugin', () => { const mockBugsnagIpcRenderer = { leaveBreadcrumb: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.leaveBreadcrumb('hi') expect(mockBugsnagIpcRenderer.leaveBreadcrumb).toHaveBeenCalledWith(expect.objectContaining({ message: 'hi', @@ -91,7 +91,7 @@ describe('clientSyncPlugin', () => { clearMetadata: jest.fn().mockImplementation(throwError), leaveBreadcrumb: jest.fn().mockImplementation(throwError) } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) expect(() => { client.setContext('ctx') client.setUser('123') @@ -112,7 +112,7 @@ describe('clientSyncPlugin', () => { metadata: { section: { key: 'value' } }, context: 'renderer config', user: { id: 'ab23' } - }, undefined, [clientSyncPlugin(mockBugsnagIpcRenderer)]) + }, undefined, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)]) expect(mockBugsnagIpcRenderer.update).toHaveBeenCalledWith({ metadata: { section: { key: 'value' } }, @@ -131,7 +131,7 @@ describe('clientSyncPlugin', () => { apiKey: '123', metadata: { section: { key: 'value' } }, user: {} - }, undefined, [clientSyncPlugin(mockBugsnagIpcRenderer)]) + }, undefined, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)]) expect(mockBugsnagIpcRenderer.update).toHaveBeenCalledWith({ metadata: { section: { key: 'value' } } @@ -141,7 +141,7 @@ describe('clientSyncPlugin', () => { it('starts sessions', () => { const mockBugsnagIpcRenderer = { startSession: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) const returnValue = client.startSession() expect(mockBugsnagIpcRenderer.startSession).toHaveBeenCalled() expect(returnValue).toBe(client) @@ -150,7 +150,7 @@ describe('clientSyncPlugin', () => { it('stops sessions', () => { const mockBugsnagIpcRenderer = { stopSession: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.stopSession() expect(mockBugsnagIpcRenderer.stopSession).toHaveBeenCalled() }) @@ -158,7 +158,7 @@ describe('clientSyncPlugin', () => { it('pauses sessions', () => { const mockBugsnagIpcRenderer = { pauseSession: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) client.pauseSession() expect(mockBugsnagIpcRenderer.pauseSession).toHaveBeenCalled() }) @@ -166,7 +166,7 @@ describe('clientSyncPlugin', () => { it('resumes sessions', () => { const mockBugsnagIpcRenderer = { resumeSession: jest.fn() } - const client = new Client({}, {}, [clientSyncPlugin(mockBugsnagIpcRenderer)], {}) + const client = new Client({}, {}, [clientStateUpdatesPlugin(mockBugsnagIpcRenderer)], {}) const returnValue = client.resumeSession() expect(mockBugsnagIpcRenderer.resumeSession).toHaveBeenCalled() expect(returnValue).toBe(client) diff --git a/packages/plugin-electron-event-sync/package-lock.json b/packages/plugin-electron-renderer-event-data/package-lock.json similarity index 100% rename from packages/plugin-electron-event-sync/package-lock.json rename to packages/plugin-electron-renderer-event-data/package-lock.json diff --git a/packages/plugin-electron-event-sync/package.json b/packages/plugin-electron-renderer-event-data/package.json similarity index 62% rename from packages/plugin-electron-event-sync/package.json rename to packages/plugin-electron-renderer-event-data/package.json index 2087f5983b..ed1dfa9d7d 100644 --- a/packages/plugin-electron-event-sync/package.json +++ b/packages/plugin-electron-renderer-event-data/package.json @@ -1,8 +1,8 @@ { - "name": "@bugsnag/plugin-electron-event-sync", + "name": "@bugsnag/plugin-electron-renderer-event-data", "version": "1.0.0", - "main": "client-sync.js", - "description": "@bugsnag/electron plugin to sync event information between JS renderers and the main process", + "main": "renderer-event-data.js", + "description": "@bugsnag/electron plugin to get event fully populated event data in renderer callbacks", "homepage": "https://www.bugsnag.com/", "repository": { "type": "git", @@ -17,9 +17,7 @@ "license": "MIT", "gypfile": true, "files": [ - "main-event-sync.js", - "internal-plugin-marker.js", - "renderer-event-sync.js" + "renderer-event-data.js" ], "dependencies": { }, diff --git a/packages/plugin-electron-event-sync/renderer-event-sync.js b/packages/plugin-electron-renderer-event-data/renderer-event-data.js similarity index 100% rename from packages/plugin-electron-event-sync/renderer-event-sync.js rename to packages/plugin-electron-renderer-event-data/renderer-event-data.js diff --git a/packages/plugin-electron-state-sync/README.md b/packages/plugin-electron-state-sync/README.md deleted file mode 100644 index a3dd84e0c8..0000000000 --- a/packages/plugin-electron-state-sync/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# @bugsnag/plugin-electron-state-sync - -Parts of client state need to be synchronised between various client instances running in difference processes. - -This plugin provides a wrapper around the parts of state that need to be synchronised, providing a way for each process to update them, and a way for each process to be notified of changes. - -The plugin runs in the main Electron process, and patches each of the client mutators whose state we need to synchronise: - - - `setUser()` - - `setContext()` - - `addMetadata()` - - `clearMetadata()` - -Any call to these methods (which will be from a developer or a plugin calling `Bugsnag.()` in the main process) will emit an event signifying the change and updated value and what the "source" of the change was (in this case the main process). - -Separately, we maintain handles to the mutators to manage state changes that were initiated in renderer processes. An inbound -change from a renderer process must call `setX(args)` causing that change to be applied to the main client. - -## API - -```typescript -import * as EventEmitter from 'events' -import { WebContents } from 'electron' - -interface BugsnagElectronStateSyncPlugin { - name: 'stateSync' - load: (client: BugsnagClient) => StateSyncPluginResult -} - -interface StateSyncPluginResult { - emitter: EventEmitter - setContext: (...args: any[]) => void - setUser: (...args: any[]) => void - addMetadata: (...args: any[]) => void - clearMetadata:(...args: any[]) => void -} - -// Event types and payloads - -type EVENT_TYPES = 'ContextUpdate' | 'UserUpdate' | 'AddMetadata' | 'ClearMetadata' - -interface ContextUpdatePayload: string - -interface UserUpdatePayload { - id?: string - email?: string - name?: string -} - -interface AddMetadataPayload { - section: string - values: Record | undefined -} -``` - -## Usage - -```js -// listening for changes -const { emitter } = client.getPlugin('stateSync') -emitter.on('ContextUpdate', (event, context) => { - console.log(context) // the new value -}) - -const { setContext } = client.getPlugin('stateSync') -ipcMain.handle('', (event, methodName, ...args) => { - if (methodName === 'setContext') setContext(args[0]) - // etc. -}) -client.setContext('new context') -``` - -## License - -This package is free software released under the MIT License. See [LICENSE.txt](./LICENSE.txt) for details. diff --git a/packages/plugin-electron-state-sync/test/state-sync.test.ts b/packages/plugin-electron-state-sync/test/state-sync.test.ts deleted file mode 100644 index 44102df8a1..0000000000 --- a/packages/plugin-electron-state-sync/test/state-sync.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import stateSyncPlugin from '../state-sync' -import Client from '@bugsnag/core/client' - -describe('@bugsnag/plugin-electron-state-sync', () => { - it('should emit events when user changes', done => { - const client = new Client({}, {}, [stateSyncPlugin], {}) - const { emitter } = client.getPlugin('stateSync') - emitter.on('UserUpdate', user => { - expect(user).toEqual({ id: '123', email: 'jim@jim.com', name: 'Jim' }) - done() - }) - client.setUser('123', 'jim@jim.com', 'Jim') - }) - - it('should emit events when context changes', done => { - const client = new Client({}, {}, [stateSyncPlugin], {}) - const { emitter } = client.getPlugin('stateSync') - emitter.on('ContextUpdate', (context) => { - expect(context).toBe('ctx') - done() - }) - client.setContext('ctx') - }) - - it('should emit events when metadata is added', done => { - const client = new Client({}, {}, [stateSyncPlugin], {}) - const { emitter } = client.getPlugin('stateSync') - emitter.on('MetadataUpdate', (payload) => { - expect(payload.section).toBe('section') - expect(payload.values).toEqual({ key: 'value' }) - done() - }) - client.addMetadata('section', 'key', 'value') - }) - - it('should emit events when metadata is cleared', done => { - const client = new Client({}, {}, [stateSyncPlugin], {}) - const { emitter } = client.getPlugin('stateSync') - emitter.on('MetadataUpdate', (payload) => { - expect(payload.section).toBe('section') - expect(payload.values).toBe(undefined) - done() - }) - client.clearMetadata('section', 'key') - }) -}) diff --git a/packages/plugin-electron-event-sync/internal-plugin-marker.js b/packages/plugin-internal-callback-marker/internal-callback-marker.js similarity index 87% rename from packages/plugin-electron-event-sync/internal-plugin-marker.js rename to packages/plugin-internal-callback-marker/internal-callback-marker.js index 8bc2eefe51..ffc6b6c766 100644 --- a/packages/plugin-electron-event-sync/internal-plugin-marker.js +++ b/packages/plugin-internal-callback-marker/internal-callback-marker.js @@ -1,4 +1,4 @@ -module.exports.firstPlugin = { +module.exports.FirstPlugin = { load: (client) => { // patch onError so we can track which onError callbacks are added by internal plugins const _origAddOnError = client.addOnError @@ -12,7 +12,7 @@ module.exports.firstPlugin = { } } -module.exports.lastPlugin = { +module.exports.LastPlugin = { load: (client) => { client.addOnError._restore() } diff --git a/packages/plugin-internal-callback-marker/package-lock.json b/packages/plugin-internal-callback-marker/package-lock.json new file mode 100644 index 0000000000..750f9aa0d2 --- /dev/null +++ b/packages/plugin-internal-callback-marker/package-lock.json @@ -0,0 +1,131 @@ +{ + "name": "@bugsnag/plugin-internal-callback-marker", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@bugsnag/plugin-internal-callback-marker", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@bugsnag/core": "^7.9.2-alpha.0" + }, + "peerDependencies": { + "@bugsnag/core": "^7.9.2-alpha.0" + } + }, + "node_modules/@bugsnag/core": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.9.2.tgz", + "integrity": "sha512-iz18qkEhrF0Bra0lpEP4VC0EJa48R+3QDDiTtfHW9fiZGKw+ADrUhwW7pHJn+LDqWfq4kMqJNuQC+8s4dV3MYg==", + "dev": true, + "dependencies": { + "@bugsnag/cuid": "^3.0.0", + "@bugsnag/safe-json-stringify": "^6.0.0", + "error-stack-parser": "^2.0.3", + "iserror": "0.0.2", + "stack-generator": "^2.0.3" + } + }, + "node_modules/@bugsnag/cuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.0.tgz", + "integrity": "sha512-LOt8aaBI+KvOQGneBtpuCz3YqzyEAehd1f3nC5yr9TIYW1+IzYKa2xWS4EiMz5pPOnRPHkyyS5t/wmSmN51Gjg==", + "dev": true + }, + "node_modules/@bugsnag/safe-json-stringify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", + "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==", + "dev": true + }, + "node_modules/error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, + "node_modules/iserror": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", + "integrity": "sha1-vVNFH+L2aLnyQCwZZnh6qix8C/U=", + "dev": true + }, + "node_modules/stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, + "node_modules/stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + } + }, + "dependencies": { + "@bugsnag/core": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.9.2.tgz", + "integrity": "sha512-iz18qkEhrF0Bra0lpEP4VC0EJa48R+3QDDiTtfHW9fiZGKw+ADrUhwW7pHJn+LDqWfq4kMqJNuQC+8s4dV3MYg==", + "dev": true, + "requires": { + "@bugsnag/cuid": "^3.0.0", + "@bugsnag/safe-json-stringify": "^6.0.0", + "error-stack-parser": "^2.0.3", + "iserror": "0.0.2", + "stack-generator": "^2.0.3" + } + }, + "@bugsnag/cuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.0.tgz", + "integrity": "sha512-LOt8aaBI+KvOQGneBtpuCz3YqzyEAehd1f3nC5yr9TIYW1+IzYKa2xWS4EiMz5pPOnRPHkyyS5t/wmSmN51Gjg==", + "dev": true + }, + "@bugsnag/safe-json-stringify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-6.0.0.tgz", + "integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==", + "dev": true + }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "requires": { + "stackframe": "^1.1.1" + } + }, + "iserror": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", + "integrity": "sha1-vVNFH+L2aLnyQCwZZnh6qix8C/U=", + "dev": true + }, + "stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "dev": true, + "requires": { + "stackframe": "^1.1.1" + } + }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + } + } +} diff --git a/packages/plugin-internal-callback-marker/package.json b/packages/plugin-internal-callback-marker/package.json new file mode 100644 index 0000000000..b0dfdb7094 --- /dev/null +++ b/packages/plugin-internal-callback-marker/package.json @@ -0,0 +1,29 @@ +{ + "name": "@bugsnag/plugin-internal-callback-marker", + "version": "1.0.0", + "main": "internal-callback-marker.js", + "description": "@bugsnag/js plugin to annotate all OnError callbacks added by internal plugins", + "homepage": "https://www.bugsnag.com/", + "repository": { + "type": "git", + "url": "git@github.com:bugsnag/bugsnag-electron.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + }, + "author": "Bugsnag", + "license": "MIT", + "files": [ + "internal-callback-marker.js" + ], + "dependencies": { + }, + "devDependencies": { + "@bugsnag/core": "^7.9.2-alpha.0" + }, + "peerDependencies": { + "@bugsnag/core": "^7.9.2-alpha.0" + } +} diff --git a/packages/plugin-internal-callback-marker/test/internal-callback-marker.test.ts b/packages/plugin-internal-callback-marker/test/internal-callback-marker.test.ts new file mode 100644 index 0000000000..400e9c5c5f --- /dev/null +++ b/packages/plugin-internal-callback-marker/test/internal-callback-marker.test.ts @@ -0,0 +1,46 @@ +import { FirstPlugin, LastPlugin } from '../internal-callback-marker' +import Client from '@bugsnag/core/client' + +describe('@bugsnag/plugin-internal-callback-marker', () => { + it('should annotate callbacks added by internal plugins with _internal:true', () => { + interface OnErrorCallback { + (): void + _internal?: boolean + } + + const internalOnError: OnErrorCallback = () => {} + const externalOnErrorViaConfig: OnErrorCallback = () => {} + const externalOnErrorViaMethod: OnErrorCallback = () => {} + const externalOnErrorViaPlugin: OnErrorCallback = () => {} + + const internalPlugins = [ + { + load: (client) => { + client.addOnError(internalOnError) + } + } + ] + + const externalPlugins = [ + { + load: (client) => { + client.addOnError(externalOnErrorViaPlugin) + } + } + ] + + const client = new Client({ + apiKey: '123', + onError: externalOnErrorViaConfig, + plugins: externalPlugins + }, undefined, [FirstPlugin, ...internalPlugins, LastPlugin]) + + client.addOnError(externalOnErrorViaMethod) + expect(client._cbs.e.length).toBe(4) + + expect(internalOnError._internal).toBe(true) + expect(externalOnErrorViaConfig._internal).toBeUndefined() + expect(externalOnErrorViaMethod._internal).toBeUndefined() + expect(externalOnErrorViaPlugin._internal).toBeUndefined() + }) +}) From dd336b24326c30dc87b2c1cebdf85e2bef48bc48 Mon Sep 17 00:00:00 2001 From: Ben Gourley Date: Thu, 15 Apr 2021 11:00:07 +0100 Subject: [PATCH 2/3] refactor: Rename file --- .../{client-sync.test.ts => client-state-persistence.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/plugin-electron-client-state-persistence/test/{client-sync.test.ts => client-state-persistence.test.ts} (100%) diff --git a/packages/plugin-electron-client-state-persistence/test/client-sync.test.ts b/packages/plugin-electron-client-state-persistence/test/client-state-persistence.test.ts similarity index 100% rename from packages/plugin-electron-client-state-persistence/test/client-sync.test.ts rename to packages/plugin-electron-client-state-persistence/test/client-state-persistence.test.ts From d9e2bb31a67911292fff6d8b939032f68a6e8654 Mon Sep 17 00:00:00 2001 From: Ben Gourley Date: Thu, 15 Apr 2021 14:28:57 +0100 Subject: [PATCH 3/3] Fix readme typo Co-authored-by: Joe Haines --- packages/plugin-electron-client-state-manager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-electron-client-state-manager/README.md b/packages/plugin-electron-client-state-manager/README.md index 19ca377ff7..f61cbe9d9f 100644 --- a/packages/plugin-electron-client-state-manager/README.md +++ b/packages/plugin-electron-client-state-manager/README.md @@ -11,4 +11,4 @@ The plugin runs in the main Electron process, and patches each of the client mut Any call to these methods (which will be from a developer or a plugin calling `Bugsnag.()` in the main process) will emit an event signifying the change and updated value. -Separately, we expose a `bulkUpdate` method for a new renderer to deliver an full state update in one pass. \ No newline at end of file +Separately, we expose a `bulkUpdate` method for a new renderer to deliver a full state update in one pass.