aboutsummaryrefslogtreecommitdiffstats
path: root/packages/preload
diff options
context:
space:
mode:
Diffstat (limited to 'packages/preload')
-rw-r--r--packages/preload/esbuild.config.js8
-rw-r--r--packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts56
-rw-r--r--packages/preload/src/contextBridge/createSophieRenderer.ts29
-rw-r--r--packages/preload/tsconfig.json10
-rw-r--r--packages/preload/types/importMeta.d.ts2
5 files changed, 62 insertions, 43 deletions
diff --git a/packages/preload/esbuild.config.js b/packages/preload/esbuild.config.js
index 66f5e84..d888987 100644
--- a/packages/preload/esbuild.config.js
+++ b/packages/preload/esbuild.config.js
@@ -4,15 +4,11 @@ import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getEsbuildConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileURLToDirname(import.meta.url),
7 entryPoints: [ 7 entryPoints: ['src/index.ts'],
8 'src/index.ts',
9 ],
10 outfile: 'dist/index.cjs', 8 outfile: 'dist/index.cjs',
11 format: 'cjs', 9 format: 'cjs',
12 platform: 'node', 10 platform: 'node',
13 target: chrome, 11 target: chrome,
14 sourcemap: 'inline', 12 sourcemap: 'inline',
15 external: [ 13 external: ['electron'],
16 'electron',
17 ],
18}); 14});
diff --git a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
index a38dbac..f63c3f6 100644
--- a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
+++ b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
@@ -40,9 +40,12 @@ jest.unstable_mockModule('electron', () => ({
40 40
41const { ipcRenderer } = await import('electron'); 41const { ipcRenderer } = await import('electron');
42 42
43const { default: createSophieRenderer } = await import('../createSophieRenderer'); 43const { default: createSophieRenderer } = await import(
44 '../createSophieRenderer'
45);
44 46
45const event: Electron.IpcRendererEvent = null as unknown as Electron.IpcRendererEvent; 47const event: Electron.IpcRendererEvent =
48 null as unknown as Electron.IpcRendererEvent;
46 49
47const snapshot: SharedStoreSnapshotIn = { 50const snapshot: SharedStoreSnapshotIn = {
48 shouldUseDarkColors: true, 51 shouldUseDarkColors: true,
@@ -83,7 +86,10 @@ describe('createSophieRenderer', () => {
83 86
84describe('SharedStoreConnector', () => { 87describe('SharedStoreConnector', () => {
85 let sut: SophieRenderer; 88 let sut: SophieRenderer;
86 let onSharedStorePatch: (eventArg: Electron.IpcRendererEvent, patchArg: unknown) => void; 89 let onSharedStorePatch: (
90 eventArg: Electron.IpcRendererEvent,
91 patchArg: unknown,
92 ) => void;
87 const listener = { 93 const listener = {
88 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 94 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
89 onSnapshot: jest.fn((_snapshot: SharedStoreSnapshotIn) => {}), 95 onSnapshot: jest.fn((_snapshot: SharedStoreSnapshotIn) => {}),
@@ -102,22 +108,25 @@ describe('SharedStoreConnector', () => {
102 it('should request a snapshot from the main process', async () => { 108 it('should request a snapshot from the main process', async () => {
103 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot); 109 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot);
104 await sut.onSharedStoreChange(listener); 110 await sut.onSharedStoreChange(listener);
105 expect(ipcRenderer.invoke).toBeCalledWith(RendererToMainIpcMessage.GetSharedStoreSnapshot); 111 expect(ipcRenderer.invoke).toBeCalledWith(
112 RendererToMainIpcMessage.GetSharedStoreSnapshot,
113 );
106 expect(listener.onSnapshot).toBeCalledWith(snapshot); 114 expect(listener.onSnapshot).toBeCalledWith(snapshot);
107 }); 115 });
108 116
109 it('should catch IPC errors without exposing them', async () => { 117 it('should catch IPC errors without exposing them', async () => {
110 mocked(ipcRenderer.invoke).mockRejectedValue(new Error('s3cr3t')); 118 mocked(ipcRenderer.invoke).mockRejectedValue(new Error('s3cr3t'));
111 await expect(sut.onSharedStoreChange(listener)).rejects.not.toHaveProperty( 119 await expect(
112 'message', 120 sut.onSharedStoreChange(listener),
113 expect.stringMatching(/s3cr3t/), 121 ).rejects.not.toHaveProperty('message', expect.stringMatching(/s3cr3t/));
114 );
115 expect(listener.onSnapshot).not.toBeCalled(); 122 expect(listener.onSnapshot).not.toBeCalled();
116 }); 123 });
117 124
118 it('should not pass on invalid snapshots', async () => { 125 it('should not pass on invalid snapshots', async () => {
119 mocked(ipcRenderer.invoke).mockResolvedValueOnce(invalidSnapshot); 126 mocked(ipcRenderer.invoke).mockResolvedValueOnce(invalidSnapshot);
120 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(Error); 127 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(
128 Error,
129 );
121 expect(listener.onSnapshot).not.toBeCalled(); 130 expect(listener.onSnapshot).not.toBeCalled();
122 }); 131 });
123 }); 132 });
@@ -125,7 +134,10 @@ describe('SharedStoreConnector', () => {
125 describe('dispatchAction', () => { 134 describe('dispatchAction', () => {
126 it('should dispatch valid actions', () => { 135 it('should dispatch valid actions', () => {
127 sut.dispatchAction(action); 136 sut.dispatchAction(action);
128 expect(ipcRenderer.send).toBeCalledWith(RendererToMainIpcMessage.DispatchAction, action); 137 expect(ipcRenderer.send).toBeCalledWith(
138 RendererToMainIpcMessage.DispatchAction,
139 action,
140 );
129 }); 141 });
130 142
131 it('should not dispatch invalid actions', () => { 143 it('should not dispatch invalid actions', () => {
@@ -142,7 +154,9 @@ describe('SharedStoreConnector', () => {
142 154
143 function itRefusesToRegisterAnotherListener(): void { 155 function itRefusesToRegisterAnotherListener(): void {
144 it('should refuse to register another listener', async () => { 156 it('should refuse to register another listener', async () => {
145 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(Error); 157 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(
158 Error,
159 );
146 }); 160 });
147 } 161 }
148 162
@@ -167,7 +181,9 @@ describe('SharedStoreConnector', () => {
167 }); 181 });
168 182
169 it('should catch listener errors', () => { 183 it('should catch listener errors', () => {
170 mocked(listener.onPatch).mockImplementation(() => { throw new Error(); }); 184 mocked(listener.onPatch).mockImplementation(() => {
185 throw new Error();
186 });
171 onSharedStorePatch(event, patch); 187 onSharedStorePatch(event, patch);
172 }); 188 });
173 189
@@ -175,7 +191,9 @@ describe('SharedStoreConnector', () => {
175 191
176 describe('after the listener threw in onPatch', () => { 192 describe('after the listener threw in onPatch', () => {
177 beforeEach(() => { 193 beforeEach(() => {
178 mocked(listener.onPatch).mockImplementation(() => { throw new Error(); }); 194 mocked(listener.onPatch).mockImplementation(() => {
195 throw new Error();
196 });
179 onSharedStorePatch(event, patch); 197 onSharedStorePatch(event, patch);
180 listener.onPatch.mockRestore(); 198 listener.onPatch.mockRestore();
181 }); 199 });
@@ -217,7 +235,9 @@ describe('SharedStoreConnector', () => {
217 describe('when a listener failed to register due to listener error', () => { 235 describe('when a listener failed to register due to listener error', () => {
218 beforeEach(async () => { 236 beforeEach(async () => {
219 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot); 237 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot);
220 mocked(listener.onSnapshot).mockImplementation(() => { throw new Error(); }); 238 mocked(listener.onSnapshot).mockImplementation(() => {
239 throw new Error();
240 });
221 try { 241 try {
222 await sut.onSharedStoreChange(listener); 242 await sut.onSharedStoreChange(listener);
223 } catch { 243 } catch {
@@ -236,15 +256,17 @@ describe('SharedStoreConnector', () => {
236 }; 256 };
237 const listener2 = { 257 const listener2 = {
238 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 258 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
239 onSnapshot: jest.fn((_snapshot: SharedStoreSnapshotIn) => { }), 259 onSnapshot: jest.fn((_snapshot: SharedStoreSnapshotIn) => {}),
240 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 260 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
241 onPatch: jest.fn((_patch: IJsonPatch) => { }), 261 onPatch: jest.fn((_patch: IJsonPatch) => {}),
242 }; 262 };
243 263
244 it('should fetch a second snapshot', async () => { 264 it('should fetch a second snapshot', async () => {
245 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot2); 265 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot2);
246 await sut.onSharedStoreChange(listener2); 266 await sut.onSharedStoreChange(listener2);
247 expect(ipcRenderer.invoke).toBeCalledWith(RendererToMainIpcMessage.GetSharedStoreSnapshot); 267 expect(ipcRenderer.invoke).toBeCalledWith(
268 RendererToMainIpcMessage.GetSharedStoreSnapshot,
269 );
248 expect(listener2.onSnapshot).toBeCalledWith(snapshot2); 270 expect(listener2.onSnapshot).toBeCalledWith(snapshot2);
249 }); 271 });
250 272
diff --git a/packages/preload/src/contextBridge/createSophieRenderer.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts
index 2055080..b97503d 100644
--- a/packages/preload/src/contextBridge/createSophieRenderer.ts
+++ b/packages/preload/src/contextBridge/createSophieRenderer.ts
@@ -37,15 +37,18 @@ class SharedStoreConnector {
37 private listener: SharedStoreListener | null = null; 37 private listener: SharedStoreListener | null = null;
38 38
39 constructor(private readonly allowReplaceListener: boolean) { 39 constructor(private readonly allowReplaceListener: boolean) {
40 ipcRenderer.on(MainToRendererIpcMessage.SharedStorePatch, (_event, patch) => { 40 ipcRenderer.on(
41 try { 41 MainToRendererIpcMessage.SharedStorePatch,
42 // `mobx-state-tree` will validate the patch, so we can safely cast here. 42 (_event, patch) => {
43 this.listener?.onPatch(patch as IJsonPatch); 43 try {
44 } catch (err) { 44 // `mobx-state-tree` will validate the patch, so we can safely cast here.
45 log.error('Shared store listener onPatch failed', err); 45 this.listener?.onPatch(patch as IJsonPatch);
46 this.listener = null; 46 } catch (err) {
47 } 47 log.error('Shared store listener onPatch failed', err);
48 }); 48 this.listener = null;
49 }
50 },
51 );
49 } 52 }
50 53
51 async onSharedStoreChange(listener: SharedStoreListener): Promise<void> { 54 async onSharedStoreChange(listener: SharedStoreListener): Promise<void> {
@@ -56,7 +59,9 @@ class SharedStoreConnector {
56 let success = false; 59 let success = false;
57 let snapshot: unknown | null = null; 60 let snapshot: unknown | null = null;
58 try { 61 try {
59 snapshot = await ipcRenderer.invoke(RendererToMainIpcMessage.GetSharedStoreSnapshot); 62 snapshot = await ipcRenderer.invoke(
63 RendererToMainIpcMessage.GetSharedStoreSnapshot,
64 );
60 success = true; 65 success = true;
61 } catch (err) { 66 } catch (err) {
62 log.error('Failed to get initial shared store snapshot', err); 67 log.error('Failed to get initial shared store snapshot', err);
@@ -87,7 +92,9 @@ function dispatchAction(actionToDispatch: Action): void {
87 } 92 }
88} 93}
89 94
90export default function createSophieRenderer(allowReplaceListener: boolean): SophieRenderer { 95export default function createSophieRenderer(
96 allowReplaceListener: boolean,
97): SophieRenderer {
91 const connector = new SharedStoreConnector(allowReplaceListener); 98 const connector = new SharedStoreConnector(allowReplaceListener);
92 return { 99 return {
93 onSharedStoreChange: connector.onSharedStoreChange.bind(connector), 100 onSharedStoreChange: connector.onSharedStoreChange.bind(connector),
diff --git a/packages/preload/tsconfig.json b/packages/preload/tsconfig.json
index 0cb1390..18c72b4 100644
--- a/packages/preload/tsconfig.json
+++ b/packages/preload/tsconfig.json
@@ -2,14 +2,8 @@
2 "extends": "../../config/tsconfig.base.json", 2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "noEmit": true, 4 "noEmit": true,
5 "lib": [ 5 "lib": ["dom", "dom.iterable", "esnext"],
6 "dom", 6 "types": ["@types/jest"]
7 "dom.iterable",
8 "esnext"
9 ],
10 "types": [
11 "@types/jest"
12 ]
13 }, 7 },
14 "references": [ 8 "references": [
15 { 9 {
diff --git a/packages/preload/types/importMeta.d.ts b/packages/preload/types/importMeta.d.ts
index 9b73170..ff3b17c 100644
--- a/packages/preload/types/importMeta.d.ts
+++ b/packages/preload/types/importMeta.d.ts
@@ -3,5 +3,5 @@ interface ImportMeta {
3 DEV: boolean; 3 DEV: boolean;
4 MODE: string; 4 MODE: string;
5 PROD: boolean; 5 PROD: boolean;
6 } 6 };
7} 7}