aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/infrastructure
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-02-08 21:40:40 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-02-14 12:13:22 +0100
commitcc214bb1afd37068c2bbc93f33990ca93f9a900f (patch)
tree7707415d5c4e42b2542d3f482060d4935c892fe1 /packages/main/src/infrastructure
parentfeat: Unread message badges (diff)
downloadsophie-cc214bb1afd37068c2bbc93f33990ca93f9a900f.tar.gz
sophie-cc214bb1afd37068c2bbc93f33990ca93f9a900f.tar.zst
sophie-cc214bb1afd37068c2bbc93f33990ca93f9a900f.zip
feat: Load and switch services
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages/main/src/infrastructure')
-rw-r--r--packages/main/src/infrastructure/config/ReadConfigResult.ts23
-rw-r--r--packages/main/src/infrastructure/electron/RendererBridge.ts103
-rw-r--r--packages/main/src/infrastructure/electron/UserAgents.ts46
-rw-r--r--packages/main/src/infrastructure/electron/impl/ElectronMainWindow.ts169
-rw-r--r--packages/main/src/infrastructure/electron/impl/ElectronPartition.ts59
-rw-r--r--packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts121
-rw-r--r--packages/main/src/infrastructure/electron/impl/ElectronViewFactory.ts119
-rw-r--r--packages/main/src/infrastructure/electron/types.ts67
8 files changed, 684 insertions, 23 deletions
diff --git a/packages/main/src/infrastructure/config/ReadConfigResult.ts b/packages/main/src/infrastructure/config/ReadConfigResult.ts
deleted file mode 100644
index 3b3ee55..0000000
--- a/packages/main/src/infrastructure/config/ReadConfigResult.ts
+++ /dev/null
@@ -1,23 +0,0 @@
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
21type ReadConfigResult = { found: true; data: unknown } | { found: false };
22
23export default ReadConfigResult;
diff --git a/packages/main/src/infrastructure/electron/RendererBridge.ts b/packages/main/src/infrastructure/electron/RendererBridge.ts
new file mode 100644
index 0000000..8633b9d
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/RendererBridge.ts
@@ -0,0 +1,103 @@
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 type { SharedStoreSnapshotOut } from '@sophie/shared';
22import {
23 addMiddleware,
24 getSnapshot,
25 IJsonPatch,
26 IMiddlewareEvent,
27 onPatch,
28} from 'mobx-state-tree';
29
30import type MainStore from '../../stores/MainStore';
31import Disposer from '../../utils/Disposer';
32import { getLogger } from '../../utils/log';
33
34const log = getLogger('RendererBridge');
35
36export type PatchListener = (patch: IJsonPatch[]) => void;
37
38export default class RendererBridge {
39 snapshot: SharedStoreSnapshotOut;
40
41 private readonly disposeOnPatch: Disposer;
42
43 private readonly disposeMiddleware: Disposer;
44
45 constructor(store: MainStore, listener: PatchListener) {
46 this.snapshot = getSnapshot(store.shared);
47
48 // The call for the currently pending action, if any.
49 let topLevelCall: IMiddlewareEvent | undefined;
50 // An array of accumulated patches if we're in an action, `undefined` otherwise.
51 let patches: IJsonPatch[] | undefined;
52
53 this.disposeOnPatch = onPatch(store.shared, (patch) => {
54 if (patches === undefined) {
55 // Update unprotected stores (outside an action) right away.
56 listener([patch]);
57 } else {
58 patches.push(patch);
59 }
60 });
61
62 this.disposeMiddleware = addMiddleware(store, (call, next, abort) => {
63 if (call.parentActionEvent !== undefined) {
64 // We're already in an action, there's no need to enter one.
65 next(call);
66 return;
67 }
68 if (patches !== undefined) {
69 log.error(
70 'Unexpected call',
71 call,
72 'during dispatching another call',
73 topLevelCall,
74 'with accumulated patches',
75 patches,
76 );
77 abort(undefined);
78 return;
79 }
80 // Make shure that the saved snapshot is consistent with the patches we're going to send.
81 this.snapshot = getSnapshot(store.shared);
82 topLevelCall = call;
83 patches = [];
84 try {
85 next(call);
86 } finally {
87 try {
88 if (patches.length > 0) {
89 listener(patches);
90 }
91 } finally {
92 topLevelCall = undefined;
93 patches = undefined;
94 }
95 }
96 });
97 }
98
99 dispose(): void {
100 this.disposeMiddleware();
101 this.disposeOnPatch();
102 }
103}
diff --git a/packages/main/src/infrastructure/electron/UserAgents.ts b/packages/main/src/infrastructure/electron/UserAgents.ts
new file mode 100644
index 0000000..af7f049
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/UserAgents.ts
@@ -0,0 +1,46 @@
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
21const CHROMELESS_USER_AGENT_REGEX = /^[^:]+:\/\/accounts\.google\.[^./]+\//;
22
23export default class UserAgents {
24 private readonly default: string;
25
26 private readonly chromeless: string;
27
28 constructor(readonly mainWindowUserAgent: string) {
29 this.default = mainWindowUserAgent.replaceAll(
30 /\s(sophie|Electron)\/\S+/g,
31 '',
32 );
33 this.chromeless = this.default.replace(/ Chrome\/\S+/, '');
34 }
35
36 serviceUserAgent(url?: string | undefined): string {
37 return url !== undefined && CHROMELESS_USER_AGENT_REGEX.test(url)
38 ? this.chromeless
39 : this.default;
40 }
41
42 fallbackUserAgent(devMode: boolean): string {
43 // Removing the electron version breaks redux devtools, so we only do this in production.
44 return devMode ? this.mainWindowUserAgent : this.default;
45 }
46}
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}
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronPartition.ts b/packages/main/src/infrastructure/electron/impl/ElectronPartition.ts
new file mode 100644
index 0000000..e60ce21
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/impl/ElectronPartition.ts
@@ -0,0 +1,59 @@
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 { Session, session } from 'electron';
22
23import type Profile from '../../../stores/Profile';
24import type { Partition } from '../types';
25
26import type ElectronViewFactory from './ElectronViewFactory';
27
28export default class ElectronPartition implements Partition {
29 readonly id: string;
30
31 readonly session: Session;
32
33 constructor(profile: Profile, parent: ElectronViewFactory) {
34 this.id = profile.id;
35 this.session = session.fromPartition(`persist:${profile.id}`);
36 this.session.setPermissionRequestHandler(
37 (_webContents, permission, callback) => {
38 // TODO Handle screen sharing.
39 callback(permission === 'notifications');
40 },
41 );
42 this.session.setUserAgent(parent.userAgents.serviceUserAgent());
43 this.session.webRequest.onBeforeSendHeaders(
44 ({ url, requestHeaders }, callback) => {
45 callback({
46 requestHeaders: {
47 ...requestHeaders,
48 'User-Agent': parent.userAgents.serviceUserAgent(url),
49 },
50 });
51 },
52 );
53 }
54
55 // eslint-disable-next-line class-methods-use-this -- Implementing interface method.
56 dispose(): void {
57 // No reactions to dispose yet.
58 }
59}
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts
new file mode 100644
index 0000000..c4f7e4d
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts
@@ -0,0 +1,121 @@
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 type { BrowserViewBounds } from '@sophie/shared';
22import { BrowserView } from 'electron';
23
24import type Service from '../../../stores/Service';
25import type Resources from '../../resources/Resources';
26import type { ServiceView } from '../types';
27
28import ElectronPartition from './ElectronPartition';
29import type ElectronViewFactory from './ElectronViewFactory';
30
31export default class ElectronServiceView implements ServiceView {
32 readonly id: string;
33
34 readonly partitionId: string;
35
36 readonly browserView: BrowserView;
37
38 constructor(
39 service: Service,
40 resources: Resources,
41 partition: ElectronPartition,
42 private readonly parent: ElectronViewFactory,
43 ) {
44 this.id = service.id;
45 this.partitionId = partition.id;
46 this.browserView = new BrowserView({
47 webPreferences: {
48 sandbox: true,
49 nodeIntegrationInSubFrames: true,
50 preload: resources.getPath('service-preload', 'index.cjs'),
51 session: partition.session,
52 },
53 });
54
55 const { webContents } = this.browserView;
56
57 function setLocation(url: string) {
58 service.setLocation({
59 url,
60 canGoBack: webContents.canGoBack(),
61 canGoForward: webContents.canGoForward(),
62 });
63 }
64
65 webContents.on('did-navigate', (_event, url) => {
66 setLocation(url);
67 });
68
69 webContents.on('did-navigate-in-page', (_event, url, isMainFrame) => {
70 if (isMainFrame) {
71 setLocation(url);
72 }
73 });
74
75 webContents.on('page-title-updated', (_event, title) => {
76 service.setTitle(title);
77 });
78
79 webContents.on('did-start-loading', () => {
80 service.startedLoading();
81 });
82
83 webContents.on('did-finish-load', () => {
84 service.finishedLoading();
85 });
86
87 webContents.on('render-process-gone', () => {
88 service.crashed();
89 });
90 }
91
92 get webContentsId(): number {
93 return this.browserView.webContents.id;
94 }
95
96 loadURL(url: string): Promise<void> {
97 return this.browserView.webContents.loadURL(url);
98 }
99
100 goBack(): void {
101 this.browserView.webContents.goBack();
102 }
103
104 goForward(): void {
105 this.browserView.webContents.goForward();
106 }
107
108 setBounds(bounds: BrowserViewBounds): void {
109 this.browserView.setBounds(bounds);
110 }
111
112 dispose(): void {
113 this.parent.unregisterServiceView(this.webContentsId);
114 setImmediate(() => {
115 // Undocumented electron API, see e.g., https://github.com/electron/electron/issues/29626
116 (
117 this.browserView.webContents as unknown as { destroy(): void }
118 ).destroy();
119 });
120 }
121}
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronViewFactory.ts b/packages/main/src/infrastructure/electron/impl/ElectronViewFactory.ts
new file mode 100644
index 0000000..f8b4a36
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/impl/ElectronViewFactory.ts
@@ -0,0 +1,119 @@
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 { readFile } from 'node:fs/promises';
22
23import { ServiceToMainIpcMessage } from '@sophie/service-shared';
24import { ipcMain, WebSource } from 'electron';
25
26import type MainStore from '../../../stores/MainStore';
27import type Profile from '../../../stores/Profile';
28import type Service from '../../../stores/Service';
29import { getLogger } from '../../../utils/log';
30import type Resources from '../../resources/Resources';
31import type UserAgents from '../UserAgents';
32import type { MainWindow, Partition, ServiceView, ViewFactory } from '../types';
33
34import ElectronMainWindow from './ElectronMainWindow';
35import ElectronPartition from './ElectronPartition';
36import ElectronServiceView from './ElectronServiceView';
37
38const log = getLogger('ElectronViewFactory');
39
40export default class ElectronViewFactory implements ViewFactory {
41 private readonly webContentsIdToServiceView = new Map<
42 number,
43 ElectronServiceView
44 >();
45
46 private serviceInjectSource: WebSource | undefined;
47
48 constructor(
49 readonly userAgents: UserAgents,
50 readonly resources: Resources,
51 readonly devMode: boolean,
52 ) {
53 ipcMain.handle(ServiceToMainIpcMessage.ApiExposedInMainWorld, (event) => {
54 if (!this.webContentsIdToServiceView.has(event.sender.id)) {
55 log.error(
56 'Unexpected',
57 ServiceToMainIpcMessage.ApiExposedInMainWorld,
58 'IPC message from webContents',
59 event.sender.id,
60 );
61 throw new Error('Invalid IPC call');
62 }
63 if (this.serviceInjectSource === undefined) {
64 log.error('Service inject source was not loaded');
65 }
66 return this.serviceInjectSource;
67 });
68 }
69
70 async createMainWindow(store: MainStore): Promise<MainWindow> {
71 const mainWindow = new ElectronMainWindow(store, this);
72 await mainWindow.loadInterface();
73 return mainWindow;
74 }
75
76 createPartition(profile: Profile): Partition {
77 return new ElectronPartition(profile, this);
78 }
79
80 createServiceView(service: Service, partition: Partition): ServiceView {
81 if (partition instanceof ElectronPartition) {
82 const serviceView = new ElectronServiceView(
83 service,
84 this.resources,
85 partition,
86 this,
87 );
88 this.webContentsIdToServiceView.set(
89 serviceView.webContentsId,
90 serviceView,
91 );
92 return serviceView;
93 }
94 throw new TypeError('Unexpected ProfileSession is not a WrappedSession');
95 }
96
97 async loadServiceInject(): Promise<void> {
98 const injectPackage = 'service-inject';
99 const injectFile = 'index.js';
100 const injectPath = this.resources.getPath(injectPackage, injectFile);
101 this.serviceInjectSource = {
102 code: await readFile(injectPath, 'utf-8'),
103 url: this.resources.getFileURL(injectPackage, injectFile),
104 };
105 }
106
107 dispose(): void {
108 if (this.webContentsIdToServiceView.size > 0) {
109 throw new Error(
110 'Must dispose all ServiceView instances before disposing ViewFactory',
111 );
112 }
113 ipcMain.removeHandler(ServiceToMainIpcMessage.ApiExposedInMainWorld);
114 }
115
116 unregisterServiceView(id: number): void {
117 this.webContentsIdToServiceView.delete(id);
118 }
119}
diff --git a/packages/main/src/infrastructure/electron/types.ts b/packages/main/src/infrastructure/electron/types.ts
new file mode 100644
index 0000000..9f03214
--- /dev/null
+++ b/packages/main/src/infrastructure/electron/types.ts
@@ -0,0 +1,67 @@
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 type { BrowserViewBounds } from '@sophie/shared';
22
23import type MainStore from '../../stores/MainStore';
24import type Profile from '../../stores/Profile';
25import type Service from '../../stores/Service';
26
27export interface ViewFactory {
28 createMainWindow(store: MainStore): Promise<MainWindow>;
29
30 createPartition(profile: Profile): Partition;
31
32 createServiceView(service: Service, partition: Partition): ServiceView;
33
34 loadServiceInject(): Promise<void>;
35
36 dispose(): void;
37}
38
39export interface MainWindow {
40 bringToForeground(): void;
41
42 setServiceView(serviceView: ServiceView | undefined): void;
43
44 dispose(): void;
45}
46
47export interface Partition {
48 readonly id: string;
49
50 dispose(): void;
51}
52
53export interface ServiceView {
54 readonly id: string;
55
56 readonly partitionId: string;
57
58 loadURL(url: string): Promise<void>;
59
60 goBack(): void;
61
62 goForward(): void;
63
64 setBounds(bounds: BrowserViewBounds): void;
65
66 dispose(): void;
67}