aboutsummaryrefslogtreecommitdiffstats
path: root/packages/preload
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-23 13:40:47 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-23 15:48:30 +0100
commit950fb9be8061e2a26e0536b98c6a3ee230618f54 (patch)
treeb136dcc9add0d268a2e7a6288ec934a86d03b652 /packages/preload
parentfeat: Add shared package for electron ipc (diff)
downloadsophie-950fb9be8061e2a26e0536b98c6a3ee230618f54.tar.gz
sophie-950fb9be8061e2a26e0536b98c6a3ee230618f54.tar.zst
sophie-950fb9be8061e2a26e0536b98c6a3ee230618f54.zip
feat: Main to renderer store synchronization
Patches are send in one direction only, from the main to the renderer, so all actions have to go through the context bridge and the renderer IPC to modify the store in the renderer. This makes the store in the main process a single source of truth, which simplifies debugging and state persistence. The store in the renderer is connected to redux devtools for inspection, but playing back the state in the devtools won't change the sotre in main process.
Diffstat (limited to 'packages/preload')
-rw-r--r--packages/preload/package.json4
-rw-r--r--packages/preload/src/SophieRendererImpl.ts74
-rw-r--r--packages/preload/src/index.ts11
3 files changed, 81 insertions, 8 deletions
diff --git a/packages/preload/package.json b/packages/preload/package.json
index 5556c10..2ad215c 100644
--- a/packages/preload/package.json
+++ b/packages/preload/package.json
@@ -10,7 +10,9 @@
10 }, 10 },
11 "dependencies": { 11 "dependencies": {
12 "@sophie/shared": "workspace:*", 12 "@sophie/shared": "workspace:*",
13 "electron": "^16.0.5" 13 "electron": "^16.0.5",
14 "mobx": "^6.3.10",
15 "mobx-state-tree": "^5.1.0"
14 }, 16 },
15 "devDependencies": { 17 "devDependencies": {
16 "typescript": "^4.5.4", 18 "typescript": "^4.5.4",
diff --git a/packages/preload/src/SophieRendererImpl.ts b/packages/preload/src/SophieRendererImpl.ts
new file mode 100644
index 0000000..e03c89e
--- /dev/null
+++ b/packages/preload/src/SophieRendererImpl.ts
@@ -0,0 +1,74 @@
1import { ipcRenderer } from 'electron';
2import type { IJsonPatch } from 'mobx-state-tree';
3import {
4 MainToRendererIpcMessage,
5 RendererToMainIpcMessage,
6 sharedStore,
7 SharedStoreListener,
8 SharedStoreSnapshotIn,
9 SophieRenderer
10} from '@sophie/shared';
11
12export type MessageSender = (channel: RendererToMainIpcMessage, ...args: unknown[]) => void;
13
14export class SophieRendererImpl implements SophieRenderer {
15 readonly #send: MessageSender;
16
17 #listener: SharedStoreListener | null = null;
18
19 #snapshot: SharedStoreSnapshotIn | null = null;
20
21 constructor(send: MessageSender) {
22 this.#send = send;
23 }
24
25 sharedStoreSnapshotReceived(snapshot: unknown): void {
26 if (sharedStore.is(snapshot)) {
27 if (this.#listener === null) {
28 this.#snapshot = snapshot;
29 } else {
30 this.#listener.onSnapshot(snapshot);
31 }
32 } else {
33 console.error('Received invalid snapshot', snapshot);
34 this.#snapshot = null;
35 }
36 }
37
38 sharedStorePatchReceived(patch: unknown): void {
39 if (this.#listener !== null) {
40 // `mobx-state-tree` will validate the patch, so we can safely cast here.
41 this.#listener.onPatch(patch as IJsonPatch);
42 }
43 }
44
45 setSharedStoreListener(listener: SharedStoreListener): void {
46 this.#listener = listener;
47 if (this.#snapshot !== null) {
48 listener.onSnapshot(this.#snapshot);
49 this.#snapshot = null;
50 }
51 this.#send(RendererToMainIpcMessage.SharedStoreSnapshotRequest);
52 }
53
54 buttonClick(): void {
55 this.#send(RendererToMainIpcMessage.ButtonClick);
56 }
57}
58
59export function createSophieRenderer(): SophieRenderer {
60 const impl = new SophieRendererImpl(ipcRenderer.send);
61
62 ipcRenderer.on(MainToRendererIpcMessage.SharedStoreSnapshot, (_event, snapshot) => {
63 impl.sharedStoreSnapshotReceived(snapshot);
64 });
65
66 ipcRenderer.on(MainToRendererIpcMessage.SharedStorePatch, (_event, patch) => {
67 impl.sharedStorePatchReceived(patch);
68 });
69
70 return {
71 setSharedStoreListener: impl.setSharedStoreListener.bind(impl),
72 buttonClick: impl.buttonClick.bind(impl),
73 };
74}
diff --git a/packages/preload/src/index.ts b/packages/preload/src/index.ts
index 830901a..19d1285 100644
--- a/packages/preload/src/index.ts
+++ b/packages/preload/src/index.ts
@@ -1,10 +1,7 @@
1import { contextBridge, ipcRenderer } from 'electron'; 1import { contextBridge } from 'electron';
2import { RendererIpcMessage, SophieRenderer } from '@sophie/shared';
3 2
4const sophieRenderer: SophieRenderer = { 3import { createSophieRenderer } from './SophieRendererImpl';
5 buttonClicked() { 4
6 ipcRenderer.send(RendererIpcMessage.ButtonClicked); 5const sophieRenderer = createSophieRenderer();
7 }
8};
9 6
10contextBridge.exposeInMainWorld('sophieRenderer', sophieRenderer); 7contextBridge.exposeInMainWorld('sophieRenderer', sophieRenderer);