diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 13:40:47 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-23 15:48:30 +0100 |
commit | 950fb9be8061e2a26e0536b98c6a3ee230618f54 (patch) | |
tree | b136dcc9add0d268a2e7a6288ec934a86d03b652 /packages/main | |
parent | feat: Add shared package for electron ipc (diff) | |
download | sophie-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/main')
-rw-r--r-- | packages/main/package.json | 6 | ||||
-rw-r--r-- | packages/main/src/devTools.ts | 49 | ||||
-rw-r--r-- | packages/main/src/index.ts | 66 | ||||
-rw-r--r-- | packages/main/src/stores/RootStore.ts | 12 |
4 files changed, 97 insertions, 36 deletions
diff --git a/packages/main/package.json b/packages/main/package.json index 63da432..22afafa 100644 --- a/packages/main/package.json +++ b/packages/main/package.json | |||
@@ -9,11 +9,13 @@ | |||
9 | }, | 9 | }, |
10 | "dependencies": { | 10 | "dependencies": { |
11 | "@sophie/shared": "workspace:*", | 11 | "@sophie/shared": "workspace:*", |
12 | "electron": "^16.0.5" | 12 | "electron": "^16.0.5", |
13 | "mobx": "^6.3.10", | ||
14 | "mobx-state-tree": "^5.1.0" | ||
13 | }, | 15 | }, |
14 | "devDependencies": { | 16 | "devDependencies": { |
15 | "@types/electron-devtools-installer": "^2.2.0", | 17 | "@types/electron-devtools-installer": "^2.2.0", |
16 | "@types/node": "^16.11.15", | 18 | "@types/node": "^16.11.17", |
17 | "electron-devtools-installer": "^3.2.0", | 19 | "electron-devtools-installer": "^3.2.0", |
18 | "typescript": "^4.5.4", | 20 | "typescript": "^4.5.4", |
19 | "vite": "^2.7.6" | 21 | "vite": "^2.7.6" |
diff --git a/packages/main/src/devTools.ts b/packages/main/src/devTools.ts new file mode 100644 index 0000000..d02bfbf --- /dev/null +++ b/packages/main/src/devTools.ts | |||
@@ -0,0 +1,49 @@ | |||
1 | import type { App, BrowserWindow } from 'electron'; | ||
2 | |||
3 | /** | ||
4 | * Installs the react and redux developer tools extensions. | ||
5 | * | ||
6 | * We use the redux devtools and connect the mobx store to it with `mst-middlewares`, | ||
7 | * because the mobx-state-tree devtools are currently unmaintained. | ||
8 | * | ||
9 | * @param app The electron application instance. | ||
10 | */ | ||
11 | export function installDevToolsExtensions(app: App): void { | ||
12 | app.whenReady().then(async () => { | ||
13 | const { | ||
14 | default: installExtension, | ||
15 | REACT_DEVELOPER_TOOLS, | ||
16 | REDUX_DEVTOOLS, | ||
17 | } = await import('electron-devtools-installer'); | ||
18 | installExtension( | ||
19 | [ | ||
20 | REACT_DEVELOPER_TOOLS, | ||
21 | REDUX_DEVTOOLS, | ||
22 | ], | ||
23 | { | ||
24 | forceDownload: false, | ||
25 | loadExtensionOptions: { | ||
26 | allowFileAccess: true, | ||
27 | }, | ||
28 | }, | ||
29 | ); | ||
30 | }).catch((err) => { | ||
31 | console.error('Failed to install devtools extension', err); | ||
32 | }); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Opens the developer tools while applying a workaround to enable the redux devtools. | ||
37 | * | ||
38 | * @param browserWindow The browser window to open the devtools in. | ||
39 | * @see https://github.com/MarshallOfSound/electron-devtools-installer/issues/195#issuecomment-998872878 | ||
40 | */ | ||
41 | export function openDevToolsWhenReady(browserWindow: BrowserWindow): void { | ||
42 | const { webContents } = browserWindow; | ||
43 | webContents.once('dom-ready', () => { | ||
44 | webContents.once('devtools-opened', () => { | ||
45 | browserWindow?.focus(); | ||
46 | }); | ||
47 | webContents.openDevTools(); | ||
48 | }); | ||
49 | } | ||
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index cd04276..dbe6890 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts | |||
@@ -1,8 +1,18 @@ | |||
1 | import { app, BrowserWindow } from 'electron'; | 1 | import { app, BrowserWindow } from 'electron'; |
2 | import { getSnapshot, onPatch } from 'mobx-state-tree'; | ||
2 | import { join } from 'path'; | 3 | import { join } from 'path'; |
3 | import { RendererIpcMessage } from '@sophie/shared'; | 4 | import { |
5 | MainToRendererIpcMessage, | ||
6 | RendererToMainIpcMessage, | ||
7 | } from '@sophie/shared'; | ||
4 | import { URL } from 'url'; | 8 | import { URL } from 'url'; |
5 | 9 | ||
10 | import { | ||
11 | installDevToolsExtensions, | ||
12 | openDevToolsWhenReady, | ||
13 | } from './devTools'; | ||
14 | import { rootStore } from './stores/RootStore'; | ||
15 | |||
6 | const isSingleInstance = app.requestSingleInstanceLock(); | 16 | const isSingleInstance = app.requestSingleInstanceLock(); |
7 | const isDevelopment = import.meta.env.MODE === 'development'; | 17 | const isDevelopment = import.meta.env.MODE === 'development'; |
8 | 18 | ||
@@ -14,62 +24,46 @@ if (!isSingleInstance) { | |||
14 | app.enableSandbox(); | 24 | app.enableSandbox(); |
15 | 25 | ||
16 | if (isDevelopment) { | 26 | if (isDevelopment) { |
17 | app.whenReady().then(async () => { | 27 | installDevToolsExtensions(app); |
18 | const { | ||
19 | default: installExtension, | ||
20 | MOBX_DEVTOOLS, | ||
21 | REACT_DEVELOPER_TOOLS, | ||
22 | } = await import('electron-devtools-installer'); | ||
23 | installExtension( | ||
24 | [ | ||
25 | MOBX_DEVTOOLS, | ||
26 | REACT_DEVELOPER_TOOLS, | ||
27 | ], | ||
28 | { | ||
29 | forceDownload: false, | ||
30 | loadExtensionOptions: { | ||
31 | allowFileAccess: true, | ||
32 | }, | ||
33 | }, | ||
34 | ); | ||
35 | }).catch((err) => { | ||
36 | console.error('Failed to install devtools extension', err); | ||
37 | }); | ||
38 | } | 28 | } |
39 | 29 | ||
40 | let mainWindow: BrowserWindow | null = null; | 30 | let mainWindow: BrowserWindow | null = null; |
41 | 31 | ||
32 | const store = rootStore.create({ | ||
33 | shared: { | ||
34 | clickCount: 1, | ||
35 | }, | ||
36 | }); | ||
37 | |||
42 | function createWindow(): Promise<void> { | 38 | function createWindow(): Promise<void> { |
43 | mainWindow = new BrowserWindow({ | 39 | mainWindow = new BrowserWindow({ |
44 | show: false, | 40 | show: false, |
41 | autoHideMenuBar: true, | ||
45 | webPreferences: { | 42 | webPreferences: { |
46 | nativeWindowOpen: true, | 43 | nativeWindowOpen: true, |
47 | webviewTag: false, | 44 | webviewTag: false, |
48 | sandbox: true, | 45 | sandbox: true, |
49 | preload: join(__dirname, '../../preload/dist/index.cjs'), | 46 | preload: join(__dirname, '../../preload/dist/index.cjs'), |
50 | } | 47 | }, |
51 | }); | 48 | }); |
52 | 49 | ||
53 | const { webContents } = mainWindow; | ||
54 | |||
55 | // See https://github.com/MarshallOfSound/electron-devtools-installer/issues/195#issuecomment-998872878 | ||
56 | if (isDevelopment) { | 50 | if (isDevelopment) { |
57 | webContents.once('dom-ready', () => { | 51 | openDevToolsWhenReady(mainWindow); |
58 | webContents.once('devtools-opened', () => { | ||
59 | mainWindow?.focus(); | ||
60 | }); | ||
61 | webContents.openDevTools(); | ||
62 | }); | ||
63 | } | 52 | } |
64 | 53 | ||
65 | mainWindow.on('ready-to-show', () => { | 54 | mainWindow.on('ready-to-show', () => { |
66 | mainWindow?.show(); | 55 | mainWindow?.show(); |
67 | }); | 56 | }); |
68 | 57 | ||
58 | const { webContents } = mainWindow; | ||
59 | |||
69 | webContents.on('ipc-message', (_event, channel, ...args) => { | 60 | webContents.on('ipc-message', (_event, channel, ...args) => { |
70 | switch (channel) { | 61 | switch (channel) { |
71 | case RendererIpcMessage.ButtonClicked: | 62 | case RendererToMainIpcMessage.SharedStoreSnapshotRequest: |
72 | console.log('Button clicked'); | 63 | webContents.send(MainToRendererIpcMessage.SharedStoreSnapshot, getSnapshot(store.shared)); |
64 | break; | ||
65 | case RendererToMainIpcMessage.ButtonClick: | ||
66 | store.buttonClick(); | ||
73 | break; | 67 | break; |
74 | default: | 68 | default: |
75 | console.warn('Unknown IPC message:', channel, args); | 69 | console.warn('Unknown IPC message:', channel, args); |
@@ -77,6 +71,10 @@ function createWindow(): Promise<void> { | |||
77 | } | 71 | } |
78 | }); | 72 | }); |
79 | 73 | ||
74 | onPatch(store.shared, (patch) => { | ||
75 | webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch); | ||
76 | }); | ||
77 | |||
80 | const pageUrl = (isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined) | 78 | const pageUrl = (isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined) |
81 | ? import.meta.env.VITE_DEV_SERVER_URL | 79 | ? import.meta.env.VITE_DEV_SERVER_URL |
82 | : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString(); | 80 | : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString(); |
diff --git a/packages/main/src/stores/RootStore.ts b/packages/main/src/stores/RootStore.ts new file mode 100644 index 0000000..a9c1290 --- /dev/null +++ b/packages/main/src/stores/RootStore.ts | |||
@@ -0,0 +1,12 @@ | |||
1 | import { Instance, types } from 'mobx-state-tree'; | ||
2 | import { sharedStore } from '@sophie/shared'; | ||
3 | |||
4 | export const rootStore = types.model('RootStore', { | ||
5 | shared: sharedStore, | ||
6 | }).actions((self) => ({ | ||
7 | buttonClick() { | ||
8 | self.shared.clickCount += 1; | ||
9 | }, | ||
10 | })); | ||
11 | |||
12 | export interface RootStore extends Instance<typeof rootStore> {} | ||