diff options
Diffstat (limited to 'packages/preload')
-rw-r--r-- | packages/preload/esbuild.config.js | 8 | ||||
-rw-r--r-- | packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts | 56 | ||||
-rw-r--r-- | packages/preload/src/contextBridge/createSophieRenderer.ts | 29 | ||||
-rw-r--r-- | packages/preload/tsconfig.json | 10 | ||||
-rw-r--r-- | packages/preload/types/importMeta.d.ts | 2 |
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 | ||
5 | export default getEsbuildConfig({ | 5 | export 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 | ||
41 | const { ipcRenderer } = await import('electron'); | 41 | const { ipcRenderer } = await import('electron'); |
42 | 42 | ||
43 | const { default: createSophieRenderer } = await import('../createSophieRenderer'); | 43 | const { default: createSophieRenderer } = await import( |
44 | '../createSophieRenderer' | ||
45 | ); | ||
44 | 46 | ||
45 | const event: Electron.IpcRendererEvent = null as unknown as Electron.IpcRendererEvent; | 47 | const event: Electron.IpcRendererEvent = |
48 | null as unknown as Electron.IpcRendererEvent; | ||
46 | 49 | ||
47 | const snapshot: SharedStoreSnapshotIn = { | 50 | const snapshot: SharedStoreSnapshotIn = { |
48 | shouldUseDarkColors: true, | 51 | shouldUseDarkColors: true, |
@@ -83,7 +86,10 @@ describe('createSophieRenderer', () => { | |||
83 | 86 | ||
84 | describe('SharedStoreConnector', () => { | 87 | describe('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 | ||
90 | export default function createSophieRenderer(allowReplaceListener: boolean): SophieRenderer { | 95 | export 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 | } |