aboutsummaryrefslogtreecommitdiffstats
path: root/packages/preload/src/contextBridge/createSophieRenderer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/preload/src/contextBridge/createSophieRenderer.ts')
-rw-r--r--packages/preload/src/contextBridge/createSophieRenderer.ts96
1 files changed, 18 insertions, 78 deletions
diff --git a/packages/preload/src/contextBridge/createSophieRenderer.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts
index 8bdf07e..a05efd0 100644
--- a/packages/preload/src/contextBridge/createSophieRenderer.ts
+++ b/packages/preload/src/contextBridge/createSophieRenderer.ts
@@ -18,85 +18,25 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { 21import { SophieRenderer } from '@sophie/shared';
22 Action, 22
23 MainToRendererIpcMessage, 23import SharedStoreConnector, {
24 RendererToMainIpcMessage, 24 dispatchAction,
25 SharedStoreListener, 25} from './SharedStoreConnector.js';
26 SharedStoreSnapshotIn, 26import TranslationsConnector, {
27 SophieRenderer, 27 getTranslation,
28} from '@sophie/shared'; 28} from './TranslationsConnector.js';
29import { ipcRenderer } from 'electron'; 29
30import log from 'loglevel'; 30export default function createSophieRenderer(devMode: boolean): SophieRenderer {
31import type { IJsonPatch } from 'mobx-state-tree'; 31 const sharedStoreConnector = new SharedStoreConnector(devMode);
32 32 const translationsConnector = new TranslationsConnector(devMode);
33class SharedStoreConnector {
34 readonly #allowReplaceListener: boolean;
35
36 #onSharedStoreChangeCalled = false;
37
38 #listener: SharedStoreListener | undefined;
39
40 constructor(allowReplaceListener: boolean) {
41 this.#allowReplaceListener = allowReplaceListener;
42 ipcRenderer.on(
43 MainToRendererIpcMessage.SharedStorePatch,
44 (_event, patch) => {
45 try {
46 // `mobx-state-tree` will validate the patch, so we can safely cast here.
47 this.#listener?.onPatch(patch as IJsonPatch[]);
48 } catch (error) {
49 log.error('Shared store listener onPatch failed', error);
50 this.#listener = undefined;
51 }
52 },
53 );
54 }
55
56 async onSharedStoreChange(listener: SharedStoreListener): Promise<void> {
57 if (this.#onSharedStoreChangeCalled && !this.#allowReplaceListener) {
58 throw new Error('Shared store change listener was already set');
59 }
60 this.#onSharedStoreChangeCalled = true;
61 let success = false;
62 let snapshot: unknown;
63 try {
64 snapshot = await ipcRenderer.invoke(
65 RendererToMainIpcMessage.GetSharedStoreSnapshot,
66 );
67 success = true;
68 } catch (error) {
69 log.error('Failed to get initial shared store snapshot', error);
70 }
71 if (!success) {
72 throw new Error('Failed to connect to shared store');
73 }
74 // `mobx-state-tree` will validate the snapshot, so we can safely cast here.
75 listener.onSnapshot(snapshot as SharedStoreSnapshotIn);
76 this.#listener = listener;
77 }
78}
79
80function dispatchAction(actionToDispatch: Action): void {
81 // Let the full zod parse error bubble up to the main world,
82 // since all data it may contain was provided from the main world.
83 const parsedAction = Action.parse(actionToDispatch);
84 try {
85 ipcRenderer.send(RendererToMainIpcMessage.DispatchAction, parsedAction);
86 } catch (error) {
87 // Do not leak IPC failure details into the main world.
88 const message = 'Failed to dispatch action';
89 log.error(message, actionToDispatch, error);
90 throw new Error(message);
91 }
92}
93
94export default function createSophieRenderer(
95 allowReplaceListener: boolean,
96): SophieRenderer {
97 const connector = new SharedStoreConnector(allowReplaceListener);
98 return { 33 return {
99 onSharedStoreChange: connector.onSharedStoreChange.bind(connector), 34 onSharedStoreChange:
35 sharedStoreConnector.onSharedStoreChange.bind(sharedStoreConnector),
100 dispatchAction, 36 dispatchAction,
37 getTranslation,
38 onReloadTranslations: translationsConnector.onReloadTranslations.bind(
39 translationsConnector,
40 ),
101 }; 41 };
102} 42}