aboutsummaryrefslogtreecommitdiffstats
path: root/packages/preload/src/contextBridge/createSophieRenderer.ts
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-03-30 21:47:45 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-05-16 00:54:57 +0200
commit85d91c64b5b3ec31df8acecd68a1fa6a68d57ff9 (patch)
tree277ab45a66a1c74e2d0a885c8a354aea27128d12 /packages/preload/src/contextBridge/createSophieRenderer.ts
parentfeat(main): Translation hot reloading during development (diff)
downloadsophie-85d91c64b5b3ec31df8acecd68a1fa6a68d57ff9.tar.gz
sophie-85d91c64b5b3ec31df8acecd68a1fa6a68d57ff9.tar.zst
sophie-85d91c64b5b3ec31df8acecd68a1fa6a68d57ff9.zip
feat(renderer): Renderer translations
Add react-i18n to make us able to use i18next translations in the renderer process just like we do in the main process. Translations are hot-reloaded automatically. Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages/preload/src/contextBridge/createSophieRenderer.ts')
-rw-r--r--packages/preload/src/contextBridge/createSophieRenderer.ts88
1 files changed, 12 insertions, 76 deletions
diff --git a/packages/preload/src/contextBridge/createSophieRenderer.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts
index 8bdf07e..3de9d9e 100644
--- a/packages/preload/src/contextBridge/createSophieRenderer.ts
+++ b/packages/preload/src/contextBridge/createSophieRenderer.ts
@@ -18,85 +18,21 @@
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,
23 MainToRendererIpcMessage,
24 RendererToMainIpcMessage,
25 SharedStoreListener,
26 SharedStoreSnapshotIn,
27 SophieRenderer,
28} from '@sophie/shared';
29import { ipcRenderer } from 'electron';
30import log from 'loglevel';
31import type { IJsonPatch } from 'mobx-state-tree';
32 22
33class SharedStoreConnector { 23import SharedStoreConnector, { dispatchAction } from './SharedStoreConnector';
34 readonly #allowReplaceListener: boolean; 24import TranslationsConnector, { getTranslation } from './TranslationsConnector';
35 25
36 #onSharedStoreChangeCalled = false; 26export default function createSophieRenderer(devMode: boolean): SophieRenderer {
37 27 const sharedStoreConnector = new SharedStoreConnector(devMode);
38 #listener: SharedStoreListener | undefined; 28 const translationsConnector = new TranslationsConnector(devMode);
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 { 29 return {
99 onSharedStoreChange: connector.onSharedStoreChange.bind(connector), 30 onSharedStoreChange:
31 sharedStoreConnector.onSharedStoreChange.bind(sharedStoreConnector),
100 dispatchAction, 32 dispatchAction,
33 getTranslation,
34 onReloadTranslations: translationsConnector.onReloadTranslations.bind(
35 translationsConnector,
36 ),
101 }; 37 };
102} 38}