aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts')
-rw-r--r--packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts169
1 files changed, 169 insertions, 0 deletions
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts b/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts
new file mode 100644
index 0000000..cff7957
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts
@@ -0,0 +1,169 @@
1/*
2 * Copyright (C) 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} from '@sophie/shared';
26import { BrowserWindow, ipcMain, IpcMainEvent } from 'electron';
27import type { IJsonPatch } from 'mobx-state-tree';
28
29import type MainStore from '../../../stores/MainStore';
30import { getLogger } from '../../../utils/log';
31import RendererBridge from '../RendererBridge';
32import type { MainWindow, ServiceView } from '../types';
33
34import ElectronServiceView from './ElectronServiceView';
35import type ElectronViewFactory from './ElectronViewFactory';
36import { openDevToolsWhenReady } from './devTools';
37import lockWebContentsToFile from './lockWebContentsToFile';
38
39const log = getLogger('ElectronMainWindow');
40
41export default class ElectronMainWindow implements MainWindow {
42 private readonly browserWindow: BrowserWindow;
43
44 private readonly bridge: RendererBridge;
45
46 private readonly dispatchActionHandler = (
47 event: IpcMainEvent,
48 rawAction: unknown,
49 ): void => {
50 const { id } = event.sender;
51 if (id !== this.browserWindow.webContents.id) {
52 log.warn(
53 'Unexpected',
54 RendererToMainIpcMessage.DispatchAction,
55 'from webContents',
56 id,
57 );
58 return;
59 }
60 try {
61 const action = Action.parse(rawAction);
62 this.store.dispatch(action);
63 } catch (error) {
64 log.error('Error while dispatching renderer action', rawAction, error);
65 }
66 };
67
68 constructor(
69 private readonly store: MainStore,
70 private readonly parent: ElectronViewFactory,
71 ) {
72 this.browserWindow = new BrowserWindow({
73 show: false,
74 autoHideMenuBar: true,
75 darkTheme: store.shared.shouldUseDarkColors,
76 webPreferences: {
77 sandbox: true,
78 devTools: parent.devMode,
79 preload: parent.resources.getPath('preload', 'index.cjs'),
80 },
81 });
82
83 const { webContents } = this.browserWindow;
84
85 ipcMain.handle(RendererToMainIpcMessage.GetSharedStoreSnapshot, (event) => {
86 const { id } = event.sender;
87 if (id !== webContents.id) {
88 log.warn(
89 'Unexpected',
90 RendererToMainIpcMessage.GetSharedStoreSnapshot,
91 'from webContents',
92 id,
93 );
94 throw new Error('Invalid IPC call');
95 }
96 return this.bridge.snapshot;
97 });
98
99 this.bridge = new RendererBridge(store, (patch) => {
100 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch);
101 });
102
103 ipcMain.on(
104 RendererToMainIpcMessage.DispatchAction,
105 this.dispatchActionHandler,
106 );
107
108 webContents.userAgent = parent.userAgents.mainWindowUserAgent;
109
110 this.browserWindow.on('ready-to-show', () => this.browserWindow.show());
111
112 this.browserWindow.on('close', () => this.dispose());
113
114 if (parent.devMode) {
115 openDevToolsWhenReady(this.browserWindow);
116 }
117 }
118
119 bringToForeground(): void {
120 if (!this.browserWindow.isVisible()) {
121 this.browserWindow.show();
122 }
123 if (this.browserWindow.isMinimized()) {
124 this.browserWindow.restore();
125 }
126 this.browserWindow.focus();
127 }
128
129 setServiceView(serviceView: ServiceView | undefined) {
130 if (serviceView === undefined) {
131 // eslint-disable-next-line unicorn/no-null -- Electron API requires passing `null`.
132 this.browserWindow.setBrowserView(null);
133 return;
134 }
135 if (serviceView instanceof ElectronServiceView) {
136 this.browserWindow.setBrowserView(serviceView.browserView);
137 serviceView.browserView.setBackgroundColor('#fff');
138 return;
139 }
140 throw new TypeError(
141 'Unexpected ServiceView with no underlying BrowserView',
142 );
143 }
144
145 dispose() {
146 this.bridge.dispose();
147 this.browserWindow.destroy();
148 ipcMain.removeHandler(RendererToMainIpcMessage.GetSharedStoreSnapshot);
149 ipcMain.removeListener(
150 RendererToMainIpcMessage.DispatchAction,
151 this.dispatchActionHandler,
152 );
153 }
154
155 loadInterface(): Promise<void> {
156 return lockWebContentsToFile(
157 this.parent.resources,
158 'index.html',
159 this.browserWindow.webContents,
160 );
161 }
162
163 sendSharedStorePatch(patch: IJsonPatch[]): void {
164 this.browserWindow.webContents.send(
165 MainToRendererIpcMessage.SharedStorePatch,
166 patch,
167 );
168 }
169}