aboutsummaryrefslogtreecommitdiffstats
path: root/packages/preload/src/contextBridge/SharedStoreConnector.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/preload/src/contextBridge/SharedStoreConnector.ts')
-rw-r--r--packages/preload/src/contextBridge/SharedStoreConnector.ts88
1 files changed, 88 insertions, 0 deletions
diff --git a/packages/preload/src/contextBridge/SharedStoreConnector.ts b/packages/preload/src/contextBridge/SharedStoreConnector.ts
new file mode 100644
index 0000000..13f9ac1
--- /dev/null
+++ b/packages/preload/src/contextBridge/SharedStoreConnector.ts
@@ -0,0 +1,88 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import {
22 Action,
23 MainToRendererIpcMessage,
24 RendererToMainIpcMessage,
25 SharedStoreListener,
26 SharedStoreSnapshotIn,
27} from '@sophie/shared';
28import { ipcRenderer } from 'electron';
29import log from 'loglevel';
30import type { IJsonPatch } from 'mobx-state-tree';
31
32export default class SharedStoreConnector {
33 private onSharedStoreChangeCalled = false;
34
35 private listener: SharedStoreListener | undefined;
36
37 constructor(private readonly allowReplaceListener: boolean) {
38 ipcRenderer.on(
39 MainToRendererIpcMessage.SharedStorePatch,
40 (_event, patch) => {
41 try {
42 // `mobx-state-tree` will validate the patch, so we can safely cast here.
43 this.listener?.onPatch(patch as IJsonPatch[]);
44 } catch (error) {
45 log.error('Shared store listener onPatch failed', error);
46 this.listener = undefined;
47 }
48 },
49 );
50 }
51
52 async onSharedStoreChange(listener: SharedStoreListener): Promise<void> {
53 if (this.onSharedStoreChangeCalled && !this.allowReplaceListener) {
54 throw new Error('Shared store change listener was already set');
55 }
56 this.onSharedStoreChangeCalled = true;
57 let success = false;
58 let snapshot: unknown;
59 try {
60 snapshot = await ipcRenderer.invoke(
61 RendererToMainIpcMessage.GetSharedStoreSnapshot,
62 );
63 success = true;
64 } catch (error) {
65 log.error('Failed to get initial shared store snapshot', error);
66 }
67 if (!success) {
68 throw new Error('Failed to connect to shared store');
69 }
70 // `mobx-state-tree` will validate the snapshot, so we can safely cast here.
71 listener.onSnapshot(snapshot as SharedStoreSnapshotIn);
72 this.listener = listener;
73 }
74}
75
76export function dispatchAction(actionToDispatch: Action): void {
77 // Let the full zod parse error bubble up to the main world,
78 // since all data it may contain was provided from the main world.
79 const parsedAction = Action.parse(actionToDispatch);
80 try {
81 ipcRenderer.send(RendererToMainIpcMessage.DispatchAction, parsedAction);
82 } catch (error) {
83 // Do not leak IPC failure details into the main world.
84 const message = 'Failed to dispatch action';
85 log.error(message, actionToDispatch, error);
86 throw new Error(message);
87 }
88}