aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/main/.eslintrc.cjs6
-rw-r--r--packages/main/.eslintrc.json6
-rw-r--r--packages/main/esbuild.config.js8
-rw-r--r--packages/main/package.json2
-rw-r--r--packages/main/src/controllers/__tests__/initConfig.spec.ts (renamed from packages/main/src/controllers/__tests__/config.spec.ts)18
-rw-r--r--packages/main/src/controllers/__tests__/initNativeTheme.spec.ts (renamed from packages/main/src/controllers/__tests__/nativeTheme.spec.ts)4
-rw-r--r--packages/main/src/controllers/initConfig.ts (renamed from packages/main/src/controllers/config.ts)12
-rw-r--r--packages/main/src/controllers/initNativeTheme.ts (renamed from packages/main/src/controllers/nativeTheme.ts)8
-rw-r--r--packages/main/src/devTools.ts7
-rw-r--r--packages/main/src/index.ts109
-rw-r--r--packages/main/src/init.ts (renamed from packages/main/src/compositionRoot.ts)10
-rw-r--r--packages/main/src/services/ConfigPersistenceService.ts6
-rw-r--r--packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts20
-rw-r--r--packages/main/src/stores/Config.ts2
-rw-r--r--packages/main/src/stores/MainStore.ts2
-rw-r--r--packages/main/src/stores/SharedStore.ts2
-rw-r--r--packages/main/src/utils/Disposer.ts (renamed from packages/main/src/utils/disposer.ts)4
-rw-r--r--packages/main/src/utils/log.ts (renamed from packages/main/src/utils/logging.ts)10
-rw-r--r--packages/main/tsconfig.json10
-rw-r--r--packages/preload/.eslintrc.cjs6
-rw-r--r--packages/preload/esbuild.config.js6
-rw-r--r--packages/preload/jest.config.js2
-rw-r--r--packages/preload/package.json6
-rw-r--r--packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts (renamed from packages/preload/src/contextBridge/__tests__/SophieRendererImpl.spec.ts)26
-rw-r--r--packages/preload/src/contextBridge/createSophieRenderer.ts (renamed from packages/preload/src/contextBridge/SophieRendererImpl.ts)42
-rw-r--r--packages/preload/src/index.ts2
-rw-r--r--packages/preload/tsconfig.json8
-rw-r--r--packages/renderer/.eslinrc.cjs11
-rw-r--r--packages/renderer/.eslintrc.json5
-rw-r--r--packages/renderer/package.json10
-rw-r--r--packages/renderer/src/components/App.tsx6
-rw-r--r--packages/renderer/src/components/BrowserViewPlaceholder.tsx14
-rw-r--r--packages/renderer/src/components/Sidebar.tsx4
-rw-r--r--packages/renderer/src/components/StoreProvider.tsx2
-rw-r--r--packages/renderer/src/components/ThemeProvider.tsx8
-rw-r--r--packages/renderer/src/components/ToggleDarkModeButton.tsx9
-rw-r--r--packages/renderer/src/devTools.ts21
-rw-r--r--packages/renderer/src/index.tsx13
-rw-r--r--packages/renderer/src/stores/RendererEnv.ts4
-rw-r--r--packages/renderer/src/stores/RendererStore.ts21
-rw-r--r--packages/renderer/src/utils/log.ts50
-rw-r--r--packages/renderer/tsconfig.json8
-rw-r--r--packages/renderer/vite.config.js5
-rw-r--r--packages/service-inject/.eslintrc.cjs6
-rw-r--r--packages/service-inject/esbuild.config.js6
-rw-r--r--packages/service-inject/package.json6
-rw-r--r--packages/service-inject/tsconfig.json7
-rw-r--r--packages/service-preload/.eslintrc.cjs6
-rw-r--r--packages/service-preload/esbuild.config.js6
-rw-r--r--packages/service-preload/package.json10
-rw-r--r--packages/service-preload/src/index.ts12
-rw-r--r--packages/service-preload/src/utils/log.ts49
-rw-r--r--packages/service-preload/tsconfig.json8
-rw-r--r--packages/service-preload/types/importMeta.d.ts7
-rw-r--r--packages/service-shared/.eslintrc.cjs7
-rw-r--r--packages/service-shared/esbuild.config.js6
-rw-r--r--packages/service-shared/package.json7
-rw-r--r--packages/service-shared/src/index.ts2
-rw-r--r--packages/service-shared/src/ipc.ts3
-rw-r--r--packages/service-shared/tsconfig.build.json12
-rw-r--r--packages/service-shared/tsconfig.json14
-rw-r--r--packages/shared/.eslintrc.cjs7
-rw-r--r--packages/shared/esbuild.config.js6
-rw-r--r--packages/shared/package.json7
-rw-r--r--packages/shared/src/contextBridge/SophieRenderer.ts7
-rw-r--r--packages/shared/src/index.ts5
-rw-r--r--packages/shared/tsconfig.build.json12
-rw-r--r--packages/shared/tsconfig.json14
68 files changed, 485 insertions, 282 deletions
diff --git a/packages/main/.eslintrc.cjs b/packages/main/.eslintrc.cjs
new file mode 100644
index 0000000..548ea34
--- /dev/null
+++ b/packages/main/.eslintrc.cjs
@@ -0,0 +1,6 @@
1module.exports = {
2 env: {
3 node: true,
4 browser: false,
5 },
6};
diff --git a/packages/main/.eslintrc.json b/packages/main/.eslintrc.json
deleted file mode 100644
index 6b736e2..0000000
--- a/packages/main/.eslintrc.json
+++ /dev/null
@@ -1,6 +0,0 @@
1{
2 "globals": {
3 "NodeJS": false,
4 "require": false
5 }
6}
diff --git a/packages/main/esbuild.config.js b/packages/main/esbuild.config.js
index c24d6e1..49fba6b 100644
--- a/packages/main/esbuild.config.js
+++ b/packages/main/esbuild.config.js
@@ -1,8 +1,8 @@
1/* eslint-disable no-process-env */
2import getRepoInfo from 'git-repo-info'; 1import getRepoInfo from 'git-repo-info';
2
3import { node } from '../../config/buildConstants.js'; 3import { node } from '../../config/buildConstants.js';
4import { getConfig } from '../../config/esbuildConfig.js'; 4import fileURLToDirname from '../../config/fileURLToDirname.js';
5import { fileURLToDirname } from '../../config/utils.js'; 5import getEsbuildConfig from '../../config/getEsbuildConfig.js';
6 6
7const externalPackages = ['electron']; 7const externalPackages = ['electron'];
8 8
@@ -12,7 +12,7 @@ if (process.env.MODE !== 'development') {
12 12
13const gitInfo = getRepoInfo(); 13const gitInfo = getRepoInfo();
14 14
15export default getConfig({ 15export default getEsbuildConfig({
16 absWorkingDir: fileURLToDirname(import.meta.url), 16 absWorkingDir: fileURLToDirname(import.meta.url),
17 entryPoints: [ 17 entryPoints: [
18 'src/index.ts', 18 'src/index.ts',
diff --git a/packages/main/package.json b/packages/main/package.json
index e1b3f49..d9abf51 100644
--- a/packages/main/package.json
+++ b/packages/main/package.json
@@ -5,7 +5,7 @@
5 "type": "module", 5 "type": "module",
6 "types": "dist-types/index.d.ts", 6 "types": "dist-types/index.d.ts",
7 "scripts": { 7 "scripts": {
8 "typecheck": "tsc" 8 "typecheck:workspace": "yarn g:typecheck"
9 }, 9 },
10 "dependencies": { 10 "dependencies": {
11 "@sophie/service-shared": "workspace:*", 11 "@sophie/service-shared": "workspace:*",
diff --git a/packages/main/src/controllers/__tests__/config.spec.ts b/packages/main/src/controllers/__tests__/initConfig.spec.ts
index eb67df0..e386a07 100644
--- a/packages/main/src/controllers/__tests__/config.spec.ts
+++ b/packages/main/src/controllers/__tests__/initConfig.spec.ts
@@ -22,20 +22,20 @@ import { jest } from '@jest/globals';
22import { mocked } from 'jest-mock'; 22import { mocked } from 'jest-mock';
23import ms from 'ms'; 23import ms from 'ms';
24 24
25import { initConfig } from '../config'; 25import type ConfigPersistenceService from '../../services/ConfigPersistenceService';
26import type { ConfigPersistenceService } from '../../services/ConfigPersistenceService';
27import { Config, config as configModel } from '../../stores/Config'; 26import { Config, config as configModel } from '../../stores/Config';
28import { Disposer } from '../../utils/disposer'; 27import type Disposer from '../../utils/Disposer';
29import { silenceLogger } from '../../utils/logging'; 28import { silenceLogger } from '../../utils/log';
29import initConfig from '../initConfig';
30 30
31let config: Config; 31let config: Config;
32let persistenceService: ConfigPersistenceService = { 32const persistenceService: ConfigPersistenceService = {
33 readConfig: jest.fn(), 33 readConfig: jest.fn(),
34 writeConfig: jest.fn(), 34 writeConfig: jest.fn(),
35 watchConfig: jest.fn(), 35 watchConfig: jest.fn(),
36}; 36};
37let lessThanThrottleMs = ms('0.1s'); 37const lessThanThrottleMs = ms('0.1s');
38let throttleMs = ms('1s'); 38const throttleMs = ms('1s');
39 39
40beforeAll(() => { 40beforeAll(() => {
41 jest.useFakeTimers(); 41 jest.useFakeTimers();
@@ -108,7 +108,7 @@ describe('when initializing', () => {
108 108
109describe('when it has loaded the config', () => { 109describe('when it has loaded the config', () => {
110 let sutDisposer: Disposer; 110 let sutDisposer: Disposer;
111 let watcherDisposer: Disposer = jest.fn(); 111 const watcherDisposer: Disposer = jest.fn();
112 let configChangedCallback: () => Promise<void>; 112 let configChangedCallback: () => Promise<void>;
113 113
114 beforeEach(async () => { 114 beforeEach(async () => {
@@ -118,7 +118,7 @@ describe('when it has loaded the config', () => {
118 }); 118 });
119 mocked(persistenceService.watchConfig).mockReturnValueOnce(watcherDisposer); 119 mocked(persistenceService.watchConfig).mockReturnValueOnce(watcherDisposer);
120 sutDisposer = await initConfig(config, persistenceService, throttleMs); 120 sutDisposer = await initConfig(config, persistenceService, throttleMs);
121 configChangedCallback = mocked(persistenceService.watchConfig).mock.calls[0][0]; 121 [[configChangedCallback]] = mocked(persistenceService.watchConfig).mock.calls;
122 jest.resetAllMocks(); 122 jest.resetAllMocks();
123 }); 123 });
124 124
diff --git a/packages/main/src/controllers/__tests__/nativeTheme.spec.ts b/packages/main/src/controllers/__tests__/initNativeTheme.spec.ts
index 85d6dd2..bd33f48 100644
--- a/packages/main/src/controllers/__tests__/nativeTheme.spec.ts
+++ b/packages/main/src/controllers/__tests__/initNativeTheme.spec.ts
@@ -22,7 +22,7 @@ import { jest } from '@jest/globals';
22import { mocked } from 'jest-mock'; 22import { mocked } from 'jest-mock';
23 23
24import { createMainStore, MainStore } from '../../stores/MainStore'; 24import { createMainStore, MainStore } from '../../stores/MainStore';
25import { Disposer } from '../../utils/disposer'; 25import type Disposer from '../../utils/Disposer';
26 26
27let shouldUseDarkColors = false; 27let shouldUseDarkColors = false;
28 28
@@ -38,7 +38,7 @@ jest.unstable_mockModule('electron', () => ({
38})); 38}));
39 39
40const { nativeTheme } = await import('electron'); 40const { nativeTheme } = await import('electron');
41const { initNativeTheme } = await import('../nativeTheme'); 41const { default: initNativeTheme } = await import('../initNativeTheme');
42 42
43let store: MainStore; 43let store: MainStore;
44let disposeSut: Disposer; 44let disposeSut: Disposer;
diff --git a/packages/main/src/controllers/config.ts b/packages/main/src/controllers/initConfig.ts
index deaeac2..1d40762 100644
--- a/packages/main/src/controllers/config.ts
+++ b/packages/main/src/controllers/initConfig.ts
@@ -19,19 +19,19 @@
19 */ 19 */
20 20
21import { debounce } from 'lodash-es'; 21import { debounce } from 'lodash-es';
22import ms from 'ms';
23import { applySnapshot, getSnapshot, onSnapshot } from 'mobx-state-tree'; 22import { applySnapshot, getSnapshot, onSnapshot } from 'mobx-state-tree';
23import ms from 'ms';
24 24
25import type { ConfigPersistenceService } from '../services/ConfigPersistenceService.js'; 25import type ConfigPersistenceService from '../services/ConfigPersistenceService';
26import type { Config, ConfigSnapshotOut } from '../stores/Config.js'; 26import type { Config, ConfigSnapshotOut } from '../stores/Config';
27import { Disposer } from '../utils/disposer'; 27import type Disposer from '../utils/Disposer';
28import { getLogger } from '../utils/logging'; 28import { getLogger } from '../utils/log';
29 29
30const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); 30const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s');
31 31
32const log = getLogger('config'); 32const log = getLogger('config');
33 33
34export async function initConfig( 34export default async function initConfig(
35 config: Config, 35 config: Config,
36 persistenceService: ConfigPersistenceService, 36 persistenceService: ConfigPersistenceService,
37 debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, 37 debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME,
diff --git a/packages/main/src/controllers/nativeTheme.ts b/packages/main/src/controllers/initNativeTheme.ts
index ccd12d8..d2074ab 100644
--- a/packages/main/src/controllers/nativeTheme.ts
+++ b/packages/main/src/controllers/initNativeTheme.ts
@@ -21,13 +21,13 @@
21import { nativeTheme } from 'electron'; 21import { nativeTheme } from 'electron';
22import { autorun } from 'mobx'; 22import { autorun } from 'mobx';
23 23
24import type { MainStore } from '../stores/MainStore.js'; 24import type { MainStore } from '../stores/MainStore';
25import { Disposer } from '../utils/disposer'; 25import type Disposer from '../utils/Disposer';
26import { getLogger } from '../utils/logging'; 26import { getLogger } from '../utils/log';
27 27
28const log = getLogger('nativeTheme'); 28const log = getLogger('nativeTheme');
29 29
30export function initNativeTheme(store: MainStore): Disposer { 30export default function initNativeTheme(store: MainStore): Disposer {
31 log.trace('Initializing nativeTheme controller'); 31 log.trace('Initializing nativeTheme controller');
32 32
33 const disposeThemeSourceReaction = autorun(() => { 33 const disposeThemeSourceReaction = autorun(() => {
diff --git a/packages/main/src/devTools.ts b/packages/main/src/devTools.ts
index 398904c..0486c36 100644
--- a/packages/main/src/devTools.ts
+++ b/packages/main/src/devTools.ts
@@ -46,7 +46,12 @@ export async function installDevToolsExtensions(): Promise<void> {
46 default: installExtension, 46 default: installExtension,
47 REACT_DEVELOPER_TOOLS, 47 REACT_DEVELOPER_TOOLS,
48 REDUX_DEVTOOLS, 48 REDUX_DEVTOOLS,
49 } = require('electron-devtools-installer'); 49 /* eslint-disable-next-line
50 import/no-extraneous-dependencies,
51 global-require,
52 @typescript-eslint/no-var-requires
53 */
54 } = require('electron-devtools-installer') as typeof import('electron-devtools-installer');
50 await installExtension( 55 await installExtension(
51 [ 56 [
52 REACT_DEVELOPER_TOOLS, 57 REACT_DEVELOPER_TOOLS,
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index d0191b7..bc10b4c 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -19,18 +19,10 @@
19 * SPDX-License-Identifier: AGPL-3.0-only 19 * SPDX-License-Identifier: AGPL-3.0-only
20 */ 20 */
21 21
22import {
23 app,
24 BrowserView,
25 BrowserWindow,
26 ipcMain,
27} from 'electron';
28import { arch } from 'os'; 22import { arch } from 'os';
29import osName from 'os-name';
30import { ensureDirSync, readFile, readFileSync } from 'fs-extra';
31import { autorun } from 'mobx';
32import { getSnapshot, onPatch } from 'mobx-state-tree';
33import { join } from 'path'; 23import { join } from 'path';
24import { URL } from 'url';
25
34import { 26import {
35 ServiceToMainIpcMessage, 27 ServiceToMainIpcMessage,
36 unreadCount, 28 unreadCount,
@@ -41,18 +33,30 @@ import {
41 MainToRendererIpcMessage, 33 MainToRendererIpcMessage,
42 RendererToMainIpcMessage, 34 RendererToMainIpcMessage,
43} from '@sophie/shared'; 35} from '@sophie/shared';
44import { URL } from 'url'; 36import {
37 app,
38 BrowserView,
39 BrowserWindow,
40 ipcMain,
41} from 'electron';
42import { ensureDirSync, readFile, readFileSync } from 'fs-extra';
43import { autorun } from 'mobx';
44import { getSnapshot, onPatch } from 'mobx-state-tree';
45import osName from 'os-name';
45 46
46import { init } from './compositionRoot';
47import { 47import {
48 DEVMODE_ALLOWED_URL_PREFIXES, 48 DEVMODE_ALLOWED_URL_PREFIXES,
49 installDevToolsExtensions, 49 installDevToolsExtensions,
50 openDevToolsWhenReady, 50 openDevToolsWhenReady,
51} from './devTools'; 51} from './devTools';
52import init from './init';
52import { createMainStore } from './stores/MainStore'; 53import { createMainStore } from './stores/MainStore';
54import { getLogger } from './utils/log';
53 55
54const isDevelopment = import.meta.env.MODE === 'development'; 56const isDevelopment = import.meta.env.MODE === 'development';
55 57
58const log = getLogger('index');
59
56// Always enable sandboxing. 60// Always enable sandboxing.
57app.enableSandbox(); 61app.enableSandbox();
58 62
@@ -93,7 +97,7 @@ app.setAboutPanelOptions({
93 `Node.js: ${process.versions.node}`, 97 `Node.js: ${process.versions.node}`,
94 `Platform: ${osName()}`, 98 `Platform: ${osName()}`,
95 `Arch: ${arch()}`, 99 `Arch: ${arch()}`,
96 `Build date: ${new Date(Number(import.meta.env.BUILD_DATE))}`, 100 `Build date: ${new Date(Number(import.meta.env.BUILD_DATE)).toLocaleString()}`,
97 `Git SHA: ${import.meta.env.GIT_SHA}`, 101 `Git SHA: ${import.meta.env.GIT_SHA}`,
98 `Git branch: ${import.meta.env.GIT_BRANCH}`, 102 `Git branch: ${import.meta.env.GIT_BRANCH}`,
99 ].join('\n'), 103 ].join('\n'),
@@ -109,9 +113,9 @@ function getResourceUrl(relativePath: string): string {
109 return new URL(relativePath, baseUrl).toString(); 113 return new URL(relativePath, baseUrl).toString();
110} 114}
111 115
112let serviceInjectRelativePath = '../../service-inject/dist/index.js'; 116const serviceInjectRelativePath = '../../service-inject/dist/index.js';
113let serviceInjectPath = getResourcePath(serviceInjectRelativePath); 117const serviceInjectPath = getResourcePath(serviceInjectRelativePath);
114let serviceInject: WebSource = { 118const serviceInject: WebSource = {
115 code: readFileSync(serviceInjectPath, 'utf8'), 119 code: readFileSync(serviceInjectPath, 'utf8'),
116 url: getResourceUrl(serviceInjectRelativePath), 120 url: getResourceUrl(serviceInjectRelativePath),
117}; 121};
@@ -122,7 +126,7 @@ const store = createMainStore();
122init(store).then((disposeCompositionRoot) => { 126init(store).then((disposeCompositionRoot) => {
123 app.on('will-quit', disposeCompositionRoot); 127 app.on('will-quit', disposeCompositionRoot);
124}).catch((err) => { 128}).catch((err) => {
125 console.log('Failed to initialize application', err); 129 log.log('Failed to initialize application', err);
126}); 130});
127 131
128const rendererBaseUrl = getResourceUrl('../renderer/'); 132const rendererBaseUrl = getResourceUrl('../renderer/');
@@ -211,7 +215,7 @@ async function createWindow(): Promise<unknown> {
211 215
212 ipcMain.handle(RendererToMainIpcMessage.GetSharedStoreSnapshot, (event) => { 216 ipcMain.handle(RendererToMainIpcMessage.GetSharedStoreSnapshot, (event) => {
213 if (event.sender.id !== webContents.id) { 217 if (event.sender.id !== webContents.id) {
214 console.warn( 218 log.warn(
215 'Unexpected', 219 'Unexpected',
216 RendererToMainIpcMessage.GetSharedStoreSnapshot, 220 RendererToMainIpcMessage.GetSharedStoreSnapshot,
217 'from webContents', 221 'from webContents',
@@ -224,7 +228,7 @@ async function createWindow(): Promise<unknown> {
224 228
225 ipcMain.on(RendererToMainIpcMessage.DispatchAction, (event, rawAction) => { 229 ipcMain.on(RendererToMainIpcMessage.DispatchAction, (event, rawAction) => {
226 if (event.sender.id !== webContents.id) { 230 if (event.sender.id !== webContents.id) {
227 console.warn( 231 log.warn(
228 'Unexpected', 232 'Unexpected',
229 RendererToMainIpcMessage.DispatchAction, 233 RendererToMainIpcMessage.DispatchAction,
230 'from webContents', 234 'from webContents',
@@ -242,17 +246,26 @@ async function createWindow(): Promise<unknown> {
242 store.config.setThemeSource(actionToDispatch.themeSource); 246 store.config.setThemeSource(actionToDispatch.themeSource);
243 break; 247 break;
244 case 'reload-all-services': 248 case 'reload-all-services':
245 readFile(serviceInjectPath, 'utf8').then((data) => { 249 readFile(serviceInjectPath, 'utf8')
246 serviceInject.code = data; 250 .then((data) => {
247 }).catch((err) => { 251 serviceInject.code = data;
248 console.error('Error while reloading', serviceInjectPath, err); 252 })
249 }).then(() => { 253 .catch((err) => {
250 browserView.webContents.reload(); 254 log.error('Error while reloading', serviceInjectPath, err);
251 }); 255 })
256 .then(() => {
257 browserView.webContents.reload();
258 })
259 .catch((err) => {
260 log.error('Failed to reload browserView', err);
261 });
262 break;
263 default:
264 log.error('Unexpected action from UI renderer:', actionToDispatch);
252 break; 265 break;
253 } 266 }
254 } catch (err) { 267 } catch (err) {
255 console.error('Error while dispatching renderer action', rawAction, err); 268 log.error('Error while dispatching renderer action', rawAction, err);
256 } 269 }
257 }); 270 });
258 271
@@ -260,11 +273,10 @@ async function createWindow(): Promise<unknown> {
260 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch); 273 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch);
261 }); 274 });
262 275
263 ipcMain.handle(ServiceToMainIpcMessage.ApiExposedInMainWorld, (event) => { 276 ipcMain.handle(
264 return event.sender.id === browserView.webContents.id 277 ServiceToMainIpcMessage.ApiExposedInMainWorld,
265 ? serviceInject 278 (event) => (event.sender.id === browserView.webContents.id ? serviceInject : null),
266 : null; 279 );
267 });
268 280
269 browserView.webContents.on('ipc-message', (_event, channel, ...args) => { 281 browserView.webContents.on('ipc-message', (_event, channel, ...args) => {
270 try { 282 try {
@@ -274,14 +286,14 @@ async function createWindow(): Promise<unknown> {
274 // otherwise electron emits a no handler registered warning. 286 // otherwise electron emits a no handler registered warning.
275 break; 287 break;
276 case ServiceToMainIpcMessage.SetUnreadCount: 288 case ServiceToMainIpcMessage.SetUnreadCount:
277 console.log('Unread count:', unreadCount.parse(args[0])); 289 log.log('Unread count:', unreadCount.parse(args[0]));
278 break; 290 break;
279 default: 291 default:
280 console.error('Unknown IPC message:', channel, args); 292 log.error('Unknown IPC message:', channel, args);
281 break; 293 break;
282 } 294 }
283 } catch (err) { 295 } catch (err) {
284 console.error('Error while processing IPC message:', channel, args, err); 296 log.error('Error while processing IPC message:', channel, args, err);
285 } 297 }
286 }); 298 });
287 299
@@ -291,17 +303,22 @@ async function createWindow(): Promise<unknown> {
291 }, 303 },
292 ); 304 );
293 305
294 browserView.webContents.session.webRequest.onBeforeSendHeaders(({ url, requestHeaders }, callback) => { 306 browserView.webContents.session.webRequest.onBeforeSendHeaders(
295 if (url.match(/^[^:]+:\/\/accounts\.google\.[^.\/]+\//)) { 307 ({ url, requestHeaders }, callback) => {
296 requestHeaders['User-Agent'] = chromelessUserAgent; 308 const requestUserAgent = url.match(/^[^:]+:\/\/accounts\.google\.[^./]+\//)
297 } else { 309 ? chromelessUserAgent
298 requestHeaders['User-Agent'] = userAgent; 310 : userAgent;
299 } 311 callback({
300 callback({ requestHeaders }); 312 requestHeaders: {
301 }); 313 ...requestHeaders,
314 'User-Agent': requestUserAgent,
315 },
316 });
317 },
318 );
302 319
303 browserView.webContents.loadURL('https://gitlab.com/say-hi-to-sophie/sophie').catch((err) => { 320 browserView.webContents.loadURL('https://gitlab.com/say-hi-to-sophie/sophie').catch((err) => {
304 console.error('Failed to load browser', err); 321 log.error('Failed to load browser', err);
305 }); 322 });
306 323
307 return mainWindow.loadURL(pageUrl); 324 return mainWindow.loadURL(pageUrl);
@@ -330,12 +347,12 @@ app.whenReady().then(async () => {
330 try { 347 try {
331 await installDevToolsExtensions(); 348 await installDevToolsExtensions();
332 } catch (err) { 349 } catch (err) {
333 console.error('Failed to install devtools extensions', err); 350 log.error('Failed to install devtools extensions', err);
334 } 351 }
335 } 352 }
336 353
337 return createWindow(); 354 return createWindow();
338}).catch((err) => { 355}).catch((err) => {
339 console.error('Failed to create window', err); 356 log.error('Failed to create window', err);
340 process.exit(1); 357 process.exit(1);
341}); 358});
diff --git a/packages/main/src/compositionRoot.ts b/packages/main/src/init.ts
index 76835a1..4487cc4 100644
--- a/packages/main/src/compositionRoot.ts
+++ b/packages/main/src/init.ts
@@ -20,13 +20,13 @@
20 20
21import { app } from 'electron'; 21import { app } from 'electron';
22 22
23import { initConfig } from './controllers/config'; 23import initConfig from './controllers/initConfig';
24import { initNativeTheme } from './controllers/nativeTheme'; 24import initNativeTheme from './controllers/initNativeTheme';
25import { ConfigPersistenceServiceImpl } from './services/impl/ConfigPersistenceServiceImpl'; 25import ConfigPersistenceServiceImpl from './services/impl/ConfigPersistenceServiceImpl';
26import { MainStore } from './stores/MainStore'; 26import { MainStore } from './stores/MainStore';
27import { Disposer } from './utils/disposer'; 27import type Disposer from './utils/Disposer';
28 28
29export async function init(store: MainStore): Promise<Disposer> { 29export default async function init(store: MainStore): Promise<Disposer> {
30 const configPersistenceService = new ConfigPersistenceServiceImpl(app.getPath('userData')); 30 const configPersistenceService = new ConfigPersistenceServiceImpl(app.getPath('userData'));
31 const disposeConfigController = await initConfig(store.config, configPersistenceService); 31 const disposeConfigController = await initConfig(store.config, configPersistenceService);
32 const disposeNativeThemeController = initNativeTheme(store); 32 const disposeNativeThemeController = initNativeTheme(store);
diff --git a/packages/main/src/services/ConfigPersistenceService.ts b/packages/main/src/services/ConfigPersistenceService.ts
index aed0ba3..7d508c5 100644
--- a/packages/main/src/services/ConfigPersistenceService.ts
+++ b/packages/main/src/services/ConfigPersistenceService.ts
@@ -18,12 +18,12 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import type { ConfigSnapshotOut } from '../stores/Config'; 21import type { ConfigSnapshotOut } from '../stores/Config';
22import { Disposer } from '../utils/disposer'; 22import type Disposer from '../utils/Disposer';
23 23
24export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; 24export type ReadConfigResult = { found: true; data: unknown; } | { found: false; };
25 25
26export interface ConfigPersistenceService { 26export default interface ConfigPersistenceService {
27 readConfig(): Promise<ReadConfigResult>; 27 readConfig(): Promise<ReadConfigResult>;
28 28
29 writeConfig(configSnapshot: ConfigSnapshotOut): Promise<void>; 29 writeConfig(configSnapshot: ConfigSnapshotOut): Promise<void>;
diff --git a/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts b/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts
index 2d19632..df8c807 100644
--- a/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts
+++ b/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts
@@ -19,18 +19,20 @@
19 */ 19 */
20import { watch } from 'fs'; 20import { watch } from 'fs';
21import { readFile, stat, writeFile } from 'fs/promises'; 21import { readFile, stat, writeFile } from 'fs/promises';
22import JSON5 from 'json5';
23import { throttle } from 'lodash-es';
24import { join } from 'path'; 22import { join } from 'path';
25 23
26import type { ConfigPersistenceService, ReadConfigResult } from '../ConfigPersistenceService.js'; 24import JSON5 from 'json5';
27import type { ConfigSnapshotOut } from '../../stores/Config.js'; 25import throttle from 'lodash-es/throttle';
28import { Disposer } from '../../utils/disposer'; 26
29import { getLogger } from '../../utils/logging'; 27import type { ConfigSnapshotOut } from '../../stores/Config';
28import type Disposer from '../../utils/Disposer';
29import { getLogger } from '../../utils/log';
30import type ConfigPersistenceService from '../ConfigPersistenceService';
31import type { ReadConfigResult } from '../ConfigPersistenceService';
30 32
31const log = getLogger('configPersistence'); 33const log = getLogger('configPersistence');
32 34
33export class ConfigPersistenceServiceImpl implements ConfigPersistenceService { 35export default class ConfigPersistenceServiceImpl implements ConfigPersistenceService {
34 private readonly configFilePath: string; 36 private readonly configFilePath: string;
35 37
36 private writingConfig = false; 38 private writingConfig = false;
@@ -103,7 +105,7 @@ export class ConfigPersistenceServiceImpl implements ConfigPersistenceService {
103 'whish is newer than last written', 105 'whish is newer than last written',
104 this.timeLastWritten, 106 this.timeLastWritten,
105 ); 107 );
106 return callback(); 108 await callback();
107 } 109 }
108 }, throttleMs); 110 }, throttleMs);
109 111
@@ -115,7 +117,7 @@ export class ConfigPersistenceServiceImpl implements ConfigPersistenceService {
115 if (eventType === 'change' 117 if (eventType === 'change'
116 && (filename === this.configFileName || filename === null)) { 118 && (filename === this.configFileName || filename === null)) {
117 configChanged()?.catch((err) => { 119 configChanged()?.catch((err) => {
118 console.log('Unhandled error while listening for config changes', err); 120 log.error('Unhandled error while listening for config changes', err);
119 }); 121 });
120 } 122 }
121 }); 123 });
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts
index 7d1168f..06dbdeb 100644
--- a/packages/main/src/stores/Config.ts
+++ b/packages/main/src/stores/Config.ts
@@ -18,13 +18,13 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { Instance } from 'mobx-state-tree';
22import { 21import {
23 config as originalConfig, 22 config as originalConfig,
24 ConfigSnapshotIn, 23 ConfigSnapshotIn,
25 ConfigSnapshotOut, 24 ConfigSnapshotOut,
26 ThemeSource, 25 ThemeSource,
27} from '@sophie/shared'; 26} from '@sophie/shared';
27import { Instance } from 'mobx-state-tree';
28 28
29export const config = originalConfig.actions((self) => ({ 29export const config = originalConfig.actions((self) => ({
30 setThemeSource(mode: ThemeSource) { 30 setThemeSource(mode: ThemeSource) {
diff --git a/packages/main/src/stores/MainStore.ts b/packages/main/src/stores/MainStore.ts
index f8a09d6..7b26c52 100644
--- a/packages/main/src/stores/MainStore.ts
+++ b/packages/main/src/stores/MainStore.ts
@@ -18,8 +18,8 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { applySnapshot, Instance, types } from 'mobx-state-tree';
22import { BrowserViewBounds } from '@sophie/shared'; 21import { BrowserViewBounds } from '@sophie/shared';
22import { applySnapshot, Instance, types } from 'mobx-state-tree';
23 23
24import type { Config } from './Config.js'; 24import type { Config } from './Config.js';
25import { sharedStore } from './SharedStore'; 25import { sharedStore } from './SharedStore';
diff --git a/packages/main/src/stores/SharedStore.ts b/packages/main/src/stores/SharedStore.ts
index e20150d..c023fc7 100644
--- a/packages/main/src/stores/SharedStore.ts
+++ b/packages/main/src/stores/SharedStore.ts
@@ -18,8 +18,8 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { Instance, types } from 'mobx-state-tree';
22import { sharedStore as originalSharedStore } from '@sophie/shared'; 21import { sharedStore as originalSharedStore } from '@sophie/shared';
22import { Instance, types } from 'mobx-state-tree';
23 23
24import { config } from './Config'; 24import { config } from './Config';
25 25
diff --git a/packages/main/src/utils/disposer.ts b/packages/main/src/utils/Disposer.ts
index 0d469dd..2e0ca25 100644
--- a/packages/main/src/utils/disposer.ts
+++ b/packages/main/src/utils/Disposer.ts
@@ -20,4 +20,6 @@
20 20
21import { IDisposer } from 'mobx-state-tree'; 21import { IDisposer } from 'mobx-state-tree';
22 22
23export type Disposer = IDisposer; 23type Disposer = IDisposer;
24
25export default Disposer;
diff --git a/packages/main/src/utils/logging.ts b/packages/main/src/utils/log.ts
index f703749..c704797 100644
--- a/packages/main/src/utils/logging.ts
+++ b/packages/main/src/utils/log.ts
@@ -45,7 +45,11 @@ prefix.reg(loglevel);
45prefix.apply(loglevel, { 45prefix.apply(loglevel, {
46 format(level, name, timestamp) { 46 format(level, name, timestamp) {
47 const levelColor = getColor(level); 47 const levelColor = getColor(level);
48 return `${chalk.gray(`[${timestamp}]`)} ${levelColor(level)} ${chalk.green(`${name}:`)}`; 48 const timeStr = timestamp.toString();
49 const nameStr = typeof name === 'undefined'
50 ? levelColor(':')
51 : ` ${chalk.green(`${name}:`)}`;
52 return `${chalk.gray(`[${timeStr}]`)} ${levelColor(level)}${nameStr}`;
49 }, 53 },
50}); 54});
51 55
@@ -56,7 +60,7 @@ export function getLogger(loggerName: string): Logger {
56export function silenceLogger(): void { 60export function silenceLogger(): void {
57 loglevel.disableAll(); 61 loglevel.disableAll();
58 const loggers = loglevel.getLoggers(); 62 const loggers = loglevel.getLoggers();
59 for (const loggerName of Object.keys(loggers)) { 63 Object.keys(loggers).forEach((loggerName) => {
60 loggers[loggerName].disableAll(); 64 loggers[loggerName].disableAll();
61 } 65 });
62} 66}
diff --git a/packages/main/tsconfig.json b/packages/main/tsconfig.json
index 1401445..00a1985 100644
--- a/packages/main/tsconfig.json
+++ b/packages/main/tsconfig.json
@@ -1,5 +1,5 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "noEmit": true, 4 "noEmit": true,
5 "types": [ 5 "types": [
@@ -9,14 +9,16 @@
9 }, 9 },
10 "references": [ 10 "references": [
11 { 11 {
12 "path": "../service-shared" 12 "path": "../service-shared/tsconfig.build.json"
13 }, 13 },
14 { 14 {
15 "path": "../shared" 15 "path": "../shared/tsconfig.build.json"
16 } 16 }
17 ], 17 ],
18 "include": [ 18 "include": [
19 "src/**/*.ts", 19 "src/**/*.ts",
20 "types/**/*.d.ts" 20 "types/**/*.d.ts",
21 "esbuild.config.js",
22 "jest.config.js"
21 ] 23 ]
22} 24}
diff --git a/packages/preload/.eslintrc.cjs b/packages/preload/.eslintrc.cjs
new file mode 100644
index 0000000..02fab21
--- /dev/null
+++ b/packages/preload/.eslintrc.cjs
@@ -0,0 +1,6 @@
1module.exports = {
2 env: {
3 node: true,
4 browser: true,
5 },
6};
diff --git a/packages/preload/esbuild.config.js b/packages/preload/esbuild.config.js
index b73a071..66f5e84 100644
--- a/packages/preload/esbuild.config.js
+++ b/packages/preload/esbuild.config.js
@@ -1,8 +1,8 @@
1import { chrome } from '../../config/buildConstants.js'; 1import { chrome } from '../../config/buildConstants.js';
2import { getConfig } from '../../config/esbuildConfig.js'; 2import fileURLToDirname from '../../config/fileURLToDirname.js';
3import { fileURLToDirname } from '../../config/utils.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileURLToDirname(import.meta.url),
7 entryPoints: [ 7 entryPoints: [
8 'src/index.ts', 8 'src/index.ts',
diff --git a/packages/preload/jest.config.js b/packages/preload/jest.config.js
index e474c4c..27af475 100644
--- a/packages/preload/jest.config.js
+++ b/packages/preload/jest.config.js
@@ -1,6 +1,6 @@
1import rootConfig from '../../config/jest.config.base.js'; 1import rootConfig from '../../config/jest.config.base.js';
2 2
3/** @type {import('ts-jest').InitialOptionsTsJest} */ 3/** @type {import('@jest/types').Config.InitialOptions} */
4export default { 4export default {
5 ...rootConfig, 5 ...rootConfig,
6 testEnvironment: 'jsdom', 6 testEnvironment: 'jsdom',
diff --git a/packages/preload/package.json b/packages/preload/package.json
index 0957aaf..a03d7d9 100644
--- a/packages/preload/package.json
+++ b/packages/preload/package.json
@@ -6,7 +6,7 @@
6 "type": "module", 6 "type": "module",
7 "types": "dist-types/index.d.ts", 7 "types": "dist-types/index.d.ts",
8 "scripts": { 8 "scripts": {
9 "typecheck": "tsc" 9 "typecheck:workspace": "yarn g:typecheck"
10 }, 10 },
11 "dependencies": { 11 "dependencies": {
12 "@sophie/shared": "workspace:*", 12 "@sophie/shared": "workspace:*",
@@ -20,8 +20,6 @@
20 "@types/jest": "^27.4.0", 20 "@types/jest": "^27.4.0",
21 "jest": "^27.4.7", 21 "jest": "^27.4.7",
22 "jest-mock": "^27.4.6", 22 "jest-mock": "^27.4.6",
23 "jsdom": "^19.0.0", 23 "jsdom": "^19.0.0"
24 "rimraf": "^3.0.2",
25 "typescript": "^4.5.4"
26 } 24 }
27} 25}
diff --git a/packages/preload/src/contextBridge/__tests__/SophieRendererImpl.spec.ts b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
index ff77a63..a38dbac 100644
--- a/packages/preload/src/contextBridge/__tests__/SophieRendererImpl.spec.ts
+++ b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
@@ -19,9 +19,6 @@
19 */ 19 */
20 20
21import { jest } from '@jest/globals'; 21import { jest } from '@jest/globals';
22import { mocked } from 'jest-mock';
23import log from 'loglevel';
24import type { IJsonPatch } from 'mobx-state-tree';
25import { 22import {
26 Action, 23 Action,
27 MainToRendererIpcMessage, 24 MainToRendererIpcMessage,
@@ -29,6 +26,9 @@ import {
29 SharedStoreSnapshotIn, 26 SharedStoreSnapshotIn,
30 SophieRenderer, 27 SophieRenderer,
31} from '@sophie/shared'; 28} from '@sophie/shared';
29import { mocked } from 'jest-mock';
30import log from 'loglevel';
31import type { IJsonPatch } from 'mobx-state-tree';
32 32
33jest.unstable_mockModule('electron', () => ({ 33jest.unstable_mockModule('electron', () => ({
34 ipcRenderer: { 34 ipcRenderer: {
@@ -40,7 +40,7 @@ jest.unstable_mockModule('electron', () => ({
40 40
41const { ipcRenderer } = await import('electron'); 41const { ipcRenderer } = await import('electron');
42 42
43const { createSophieRenderer } = await import('../SophieRendererImpl'); 43const { default: createSophieRenderer } = await import('../createSophieRenderer');
44 44
45const event: Electron.IpcRendererEvent = null as unknown as Electron.IpcRendererEvent; 45const event: Electron.IpcRendererEvent = null as unknown as Electron.IpcRendererEvent;
46 46
@@ -81,10 +81,10 @@ describe('createSophieRenderer', () => {
81 }); 81 });
82}); 82});
83 83
84describe('SophieRendererImpl', () => { 84describe('SharedStoreConnector', () => {
85 let sut: SophieRenderer; 85 let sut: SophieRenderer;
86 let onSharedStorePatch: (event1: Electron.IpcRendererEvent, patch1: unknown) => void; 86 let onSharedStorePatch: (eventArg: Electron.IpcRendererEvent, patchArg: unknown) => void;
87 let listener = { 87 const listener = {
88 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 88 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
89 onSnapshot: jest.fn((_snapshot: SharedStoreSnapshotIn) => {}), 89 onSnapshot: jest.fn((_snapshot: SharedStoreSnapshotIn) => {}),
90 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars 90 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
@@ -93,9 +93,9 @@ describe('SophieRendererImpl', () => {
93 93
94 beforeEach(() => { 94 beforeEach(() => {
95 sut = createSophieRenderer(false); 95 sut = createSophieRenderer(false);
96 onSharedStorePatch = mocked(ipcRenderer.on).mock.calls.find(([channel]) => { 96 [, onSharedStorePatch] = mocked(ipcRenderer.on).mock.calls.find(
97 return channel === MainToRendererIpcMessage.SharedStorePatch; 97 ([channel]) => channel === MainToRendererIpcMessage.SharedStorePatch,
98 })?.[1]!; 98 )!;
99 }); 99 });
100 100
101 describe('onSharedStoreChange', () => { 101 describe('onSharedStoreChange', () => {
@@ -140,15 +140,15 @@ describe('SophieRendererImpl', () => {
140 }); 140 });
141 }); 141 });
142 142
143 function itRefusesToRegisterAnotherListener() { 143 function itRefusesToRegisterAnotherListener(): void {
144 it('should refuse to register another listener', async () => { 144 it('should refuse to register another listener', async () => {
145 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(Error); 145 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(Error);
146 }); 146 });
147 } 147 }
148 148
149 function itDoesNotPassPatchesToTheListener( 149 function itDoesNotPassPatchesToTheListener(
150 name: string = 'should not pass patches to the listener', 150 name = 'should not pass patches to the listener',
151 ) { 151 ): void {
152 it(name, () => { 152 it(name, () => {
153 onSharedStorePatch(event, patch); 153 onSharedStorePatch(event, patch);
154 expect(listener.onPatch).not.toBeCalled(); 154 expect(listener.onPatch).not.toBeCalled();
diff --git a/packages/preload/src/contextBridge/SophieRendererImpl.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts
index f3c07c5..2055080 100644
--- a/packages/preload/src/contextBridge/SophieRendererImpl.ts
+++ b/packages/preload/src/contextBridge/createSophieRenderer.ts
@@ -18,9 +18,6 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { ipcRenderer } from 'electron';
22import log from 'loglevel';
23import type { IJsonPatch } from 'mobx-state-tree';
24import { 21import {
25 Action, 22 Action,
26 action, 23 action,
@@ -30,9 +27,12 @@ import {
30 SharedStoreListener, 27 SharedStoreListener,
31 SophieRenderer, 28 SophieRenderer,
32} from '@sophie/shared'; 29} from '@sophie/shared';
30import { ipcRenderer } from 'electron';
31import log from 'loglevel';
32import type { IJsonPatch } from 'mobx-state-tree';
33 33
34class SophieRendererImpl implements SophieRenderer { 34class SharedStoreConnector {
35 private onSharedStoreChangeCalled: boolean = false; 35 private onSharedStoreChangeCalled = false;
36 36
37 private listener: SharedStoreListener | null = null; 37 private listener: SharedStoreListener | null = null;
38 38
@@ -71,26 +71,26 @@ class SophieRendererImpl implements SophieRenderer {
71 } 71 }
72 throw new Error('Failed to connect to shared store'); 72 throw new Error('Failed to connect to shared store');
73 } 73 }
74}
74 75
75 dispatchAction(actionToDispatch: Action): void { 76function dispatchAction(actionToDispatch: Action): void {
76 // Let the full zod parse error bubble up to the main world, 77 // Let the full zod parse error bubble up to the main world,
77 // since all data it may contain was provided from the main world. 78 // since all data it may contain was provided from the main world.
78 const parsedAction = action.parse(actionToDispatch); 79 const parsedAction = action.parse(actionToDispatch);
79 try { 80 try {
80 ipcRenderer.send(RendererToMainIpcMessage.DispatchAction, parsedAction); 81 ipcRenderer.send(RendererToMainIpcMessage.DispatchAction, parsedAction);
81 } catch (err) { 82 } catch (err) {
82 // Do not leak IPC failure details into the main world. 83 // Do not leak IPC failure details into the main world.
83 const message = 'Failed to dispatch action'; 84 const message = 'Failed to dispatch action';
84 log.error(message, actionToDispatch, err); 85 log.error(message, actionToDispatch, err);
85 throw new Error(message); 86 throw new Error(message);
86 }
87 } 87 }
88} 88}
89 89
90export function createSophieRenderer(allowReplaceListener: boolean): SophieRenderer { 90export default function createSophieRenderer(allowReplaceListener: boolean): SophieRenderer {
91 const impl = new SophieRendererImpl(allowReplaceListener); 91 const connector = new SharedStoreConnector(allowReplaceListener);
92 return { 92 return {
93 onSharedStoreChange: impl.onSharedStoreChange.bind(impl), 93 onSharedStoreChange: connector.onSharedStoreChange.bind(connector),
94 dispatchAction: impl.dispatchAction.bind(impl), 94 dispatchAction,
95 }; 95 };
96} 96}
diff --git a/packages/preload/src/index.ts b/packages/preload/src/index.ts
index de91742..f13220c 100644
--- a/packages/preload/src/index.ts
+++ b/packages/preload/src/index.ts
@@ -20,7 +20,7 @@
20 20
21import { contextBridge } from 'electron'; 21import { contextBridge } from 'electron';
22 22
23import { createSophieRenderer } from './contextBridge/SophieRendererImpl'; 23import createSophieRenderer from './contextBridge/createSophieRenderer';
24 24
25const isDevelopment = import.meta.env.MODE === 'development'; 25const isDevelopment = import.meta.env.MODE === 'development';
26 26
diff --git a/packages/preload/tsconfig.json b/packages/preload/tsconfig.json
index 741d435..ff49538 100644
--- a/packages/preload/tsconfig.json
+++ b/packages/preload/tsconfig.json
@@ -1,5 +1,5 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "noEmit": true, 4 "noEmit": true,
5 "lib": [ 5 "lib": [
@@ -13,11 +13,13 @@
13 }, 13 },
14 "references": [ 14 "references": [
15 { 15 {
16 "path": "../shared" 16 "path": "../shared/tsconfig.build.json"
17 } 17 }
18 ], 18 ],
19 "include": [ 19 "include": [
20 "src/**/*.ts", 20 "src/**/*.ts",
21 "types/**/*.d.ts" 21 "types/**/*.d.ts",
22 "esbuild.config.js",
23 "jest.config.js"
22 ] 24 ]
23} 25}
diff --git a/packages/renderer/.eslinrc.cjs b/packages/renderer/.eslinrc.cjs
new file mode 100644
index 0000000..3385ac5
--- /dev/null
+++ b/packages/renderer/.eslinrc.cjs
@@ -0,0 +1,11 @@
1module.exports = {
2 extends: [
3 'airbnb',
4 'airbnb/hooks',
5 'airbnb-typescript',
6 ],
7 env: {
8 node: false,
9 browser: true,
10 },
11};
diff --git a/packages/renderer/.eslintrc.json b/packages/renderer/.eslintrc.json
deleted file mode 100644
index a28aec9..0000000
--- a/packages/renderer/.eslintrc.json
+++ /dev/null
@@ -1,5 +0,0 @@
1{
2 "globals": {
3 "JSX": false
4 }
5}
diff --git a/packages/renderer/package.json b/packages/renderer/package.json
index df15abb..fde4c28 100644
--- a/packages/renderer/package.json
+++ b/packages/renderer/package.json
@@ -5,7 +5,7 @@
5 "type": "module", 5 "type": "module",
6 "types": "dist-types/index.d.ts", 6 "types": "dist-types/index.d.ts",
7 "scripts": { 7 "scripts": {
8 "typecheck": "tsc" 8 "typecheck:workspace": "yarn g:typecheck"
9 }, 9 },
10 "dependencies": { 10 "dependencies": {
11 "@emotion/react": "^11.7.1", 11 "@emotion/react": "^11.7.1",
@@ -14,7 +14,9 @@
14 "@mui/icons-material": "^5.2.5", 14 "@mui/icons-material": "^5.2.5",
15 "@mui/material": "^5.2.7", 15 "@mui/material": "^5.2.7",
16 "@sophie/shared": "workspace:*", 16 "@sophie/shared": "workspace:*",
17 "lodash": "^4.17.21", 17 "lodash-es": "^4.17.21",
18 "loglevel": "^1.8.0",
19 "loglevel-plugin-prefix": "^0.8.4",
18 "mobx": "^6.3.12", 20 "mobx": "^6.3.12",
19 "mobx-react-lite": "^3.2.3", 21 "mobx-react-lite": "^3.2.3",
20 "mobx-state-tree": "^5.1.0", 22 "mobx-state-tree": "^5.1.0",
@@ -22,14 +24,12 @@
22 "react-dom": "^17.0.2" 24 "react-dom": "^17.0.2"
23 }, 25 },
24 "devDependencies": { 26 "devDependencies": {
25 "@types/lodash": "^4.14.178", 27 "@types/lodash-es": "^4.14.178",
26 "@types/react": "^17.0.38", 28 "@types/react": "^17.0.38",
27 "@types/react-dom": "^17.0.11", 29 "@types/react-dom": "^17.0.11",
28 "@vitejs/plugin-react": "^1.1.4", 30 "@vitejs/plugin-react": "^1.1.4",
29 "mst-middlewares": "^5.1.0", 31 "mst-middlewares": "^5.1.0",
30 "remotedev": "^0.2.9", 32 "remotedev": "^0.2.9",
31 "rimraf": "^3.0.2",
32 "typescript": "^4.5.4",
33 "vite": "^2.7.10" 33 "vite": "^2.7.10"
34 } 34 }
35} 35}
diff --git a/packages/renderer/src/components/App.tsx b/packages/renderer/src/components/App.tsx
index 8bd3dd8..1174bbb 100644
--- a/packages/renderer/src/components/App.tsx
+++ b/packages/renderer/src/components/App.tsx
@@ -21,10 +21,10 @@
21import Box from '@mui/material/Box'; 21import Box from '@mui/material/Box';
22import React from 'react'; 22import React from 'react';
23 23
24import { BrowserViewPlaceholder } from './BrowserViewPlaceholder'; 24import BrowserViewPlaceholder from './BrowserViewPlaceholder';
25import { Sidebar } from './Sidebar'; 25import Sidebar from './Sidebar';
26 26
27export function App(): JSX.Element { 27export default function App(): JSX.Element {
28 return ( 28 return (
29 <Box 29 <Box
30 sx={{ 30 sx={{
diff --git a/packages/renderer/src/components/BrowserViewPlaceholder.tsx b/packages/renderer/src/components/BrowserViewPlaceholder.tsx
index 6aa6b7b..c671983 100644
--- a/packages/renderer/src/components/BrowserViewPlaceholder.tsx
+++ b/packages/renderer/src/components/BrowserViewPlaceholder.tsx
@@ -18,17 +18,15 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { throttle } from 'lodash';
22import { observer } from 'mobx-react-lite';
23import Box from '@mui/material/Box'; 21import Box from '@mui/material/Box';
22import throttle from 'lodash-es/throttle';
23import { observer } from 'mobx-react-lite';
24import React, { useCallback, useRef } from 'react'; 24import React, { useCallback, useRef } from 'react';
25 25
26import { useStore } from './StoreProvider'; 26import { useStore } from './StoreProvider';
27 27
28export const BrowserViewPlaceholder = observer(function BrowserViewPlaceholder() { 28export default observer(() => {
29 const { 29 const store = useStore();
30 setBrowserViewBounds,
31 } = useStore();
32 30
33 const onResize = useCallback(throttle(([entry]: ResizeObserverEntry[]) => { 31 const onResize = useCallback(throttle(([entry]: ResizeObserverEntry[]) => {
34 if (entry) { 32 if (entry) {
@@ -38,14 +36,14 @@ export const BrowserViewPlaceholder = observer(function BrowserViewPlaceholder()
38 width, 36 width,
39 height, 37 height,
40 } = entry.target.getBoundingClientRect(); 38 } = entry.target.getBoundingClientRect();
41 setBrowserViewBounds({ 39 store.setBrowserViewBounds({
42 x, 40 x,
43 y, 41 y,
44 width, 42 width,
45 height, 43 height,
46 }); 44 });
47 } 45 }
48 }, 100), [setBrowserViewBounds]); 46 }, 100), [store]);
49 47
50 const resizeObserverRef = useRef<ResizeObserver | null>(null); 48 const resizeObserverRef = useRef<ResizeObserver | null>(null);
51 49
diff --git a/packages/renderer/src/components/Sidebar.tsx b/packages/renderer/src/components/Sidebar.tsx
index 6c79932..44a47b0 100644
--- a/packages/renderer/src/components/Sidebar.tsx
+++ b/packages/renderer/src/components/Sidebar.tsx
@@ -21,9 +21,9 @@
21import Box from '@mui/material/Box'; 21import Box from '@mui/material/Box';
22import React from 'react'; 22import React from 'react';
23 23
24import { ToggleDarkModeButton } from './ToggleDarkModeButton'; 24import ToggleDarkModeButton from './ToggleDarkModeButton';
25 25
26export function Sidebar(): JSX.Element { 26export default function Sidebar(): JSX.Element {
27 return ( 27 return (
28 <Box 28 <Box
29 sx={(theme) => ({ 29 sx={(theme) => ({
diff --git a/packages/renderer/src/components/StoreProvider.tsx b/packages/renderer/src/components/StoreProvider.tsx
index da1e699..cde6a31 100644
--- a/packages/renderer/src/components/StoreProvider.tsx
+++ b/packages/renderer/src/components/StoreProvider.tsx
@@ -32,7 +32,7 @@ export function useStore(): RendererStore {
32 return store; 32 return store;
33} 33}
34 34
35export function StoreProvider({ children, store }: { 35export default function StoreProvider({ children, store }: {
36 children: JSX.Element | JSX.Element[], 36 children: JSX.Element | JSX.Element[],
37 store: RendererStore, 37 store: RendererStore,
38}): JSX.Element { 38}): JSX.Element {
diff --git a/packages/renderer/src/components/ThemeProvider.tsx b/packages/renderer/src/components/ThemeProvider.tsx
index 9215f5c..eacaa52 100644
--- a/packages/renderer/src/components/ThemeProvider.tsx
+++ b/packages/renderer/src/components/ThemeProvider.tsx
@@ -18,18 +18,18 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { observer } from 'mobx-react-lite';
22import { 21import {
23 unstable_createMuiStrictModeTheme as createTheme, 22 unstable_createMuiStrictModeTheme as createTheme,
24 ThemeProvider as MuiThemeProvider, 23 ThemeProvider as MuiThemeProvider,
25} from '@mui/material/styles'; 24} from '@mui/material/styles';
25import { observer } from 'mobx-react-lite';
26import React from 'react'; 26import React from 'react';
27 27
28import { useStore } from './StoreProvider'; 28import { useStore } from './StoreProvider';
29 29
30export const ThemeProvider = observer(function ThemeProvider({ children }: { 30export default observer(({ children }: {
31 children: JSX.Element | JSX.Element[], 31 children: JSX.Element | JSX.Element[];
32}) { 32}) => {
33 const { shared: { shouldUseDarkColors } } = useStore(); 33 const { shared: { shouldUseDarkColors } } = useStore();
34 34
35 const theme = createTheme({ 35 const theme = createTheme({
diff --git a/packages/renderer/src/components/ToggleDarkModeButton.tsx b/packages/renderer/src/components/ToggleDarkModeButton.tsx
index 1b6757e..c8ffdf0 100644
--- a/packages/renderer/src/components/ToggleDarkModeButton.tsx
+++ b/packages/renderer/src/components/ToggleDarkModeButton.tsx
@@ -18,21 +18,22 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { observer } from 'mobx-react-lite';
22import DarkModeIcon from '@mui/icons-material/DarkMode'; 21import DarkModeIcon from '@mui/icons-material/DarkMode';
23import LightModeIcon from '@mui/icons-material/LightMode'; 22import LightModeIcon from '@mui/icons-material/LightMode';
24import IconButton from '@mui/material/IconButton'; 23import IconButton from '@mui/material/IconButton';
24import { observer } from 'mobx-react-lite';
25import React from 'react'; 25import React from 'react';
26 26
27import { useStore } from './StoreProvider'; 27import { useStore } from './StoreProvider';
28 28
29export const ToggleDarkModeButton = observer(function ToggleDarkModeButton() { 29export default observer(() => {
30 const { shared: { shouldUseDarkColors }, toggleDarkMode } = useStore(); 30 const store = useStore();
31 const { shared: { shouldUseDarkColors } } = store;
31 32
32 return ( 33 return (
33 <IconButton 34 <IconButton
34 aria-label="Toggle dark mode" 35 aria-label="Toggle dark mode"
35 onClick={() => toggleDarkMode()} 36 onClick={() => store.toggleDarkMode()}
36 > 37 >
37 {shouldUseDarkColors ? <LightModeIcon /> : <DarkModeIcon />} 38 {shouldUseDarkColors ? <LightModeIcon /> : <DarkModeIcon />}
38 </IconButton> 39 </IconButton>
diff --git a/packages/renderer/src/devTools.ts b/packages/renderer/src/devTools.ts
index 3ec66aa..3d3ba99 100644
--- a/packages/renderer/src/devTools.ts
+++ b/packages/renderer/src/devTools.ts
@@ -32,31 +32,24 @@ import type { IAnyStateTreeNode } from 'mobx-state-tree';
32 * However, we don't bundle `remotedev` in production, so the call would fail anyways. 32 * However, we don't bundle `remotedev` in production, so the call would fail anyways.
33 * 33 *
34 * @param model The store to connect to the redux devtools. 34 * @param model The store to connect to the redux devtools.
35 * @return A promise that resolves when the store was exposed to the devtools.
35 * @see https://github.com/SocketCluster/socketcluster-client/issues/118#issuecomment-469064682 36 * @see https://github.com/SocketCluster/socketcluster-client/issues/118#issuecomment-469064682
36 */ 37 */
37async function exposeToReduxDevtoolsAsync(model: IAnyStateTreeNode): Promise<void> { 38export async function exposeToReduxDevtools(model: IAnyStateTreeNode): Promise<void> {
38 (window as { global?: unknown }).global = window; 39 (window as { global?: unknown }).global = window;
39 40
41 // Hack to load dev dependencies on demand.
40 const [remotedev, { connectReduxDevtools }] = await Promise.all([ 42 const [remotedev, { connectReduxDevtools }] = await Promise.all([
41 // @ts-ignore 43 // @ts-expect-error `remotedev` has no typings.
42 import('remotedev'), 44 // eslint-disable-next-line import/no-extraneous-dependencies
45 import('remotedev') as unknown,
46 // eslint-disable-next-line import/no-extraneous-dependencies
43 import('mst-middlewares'), 47 import('mst-middlewares'),
44 ]); 48 ]);
45 connectReduxDevtools(remotedev, model); 49 connectReduxDevtools(remotedev, model);
46} 50}
47 51
48/** 52/**
49 * Connects the `model` to the redux devtools extension.
50 *
51 * @param model The store to connect to the redux devtools.
52 */
53export function exposeToReduxDevtools(model: IAnyStateTreeNode): void {
54 exposeToReduxDevtoolsAsync(model).catch((err) => {
55 console.error('Could not connect to Redux devtools', err);
56 });
57}
58
59/**
60 * Sends a message to the main process to reload all services when 53 * Sends a message to the main process to reload all services when
61 * `build/watch.js` sends a reload event on bundle write. 54 * `build/watch.js` sends a reload event on bundle write.
62 */ 55 */
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx
index 1626bef..d900e50 100644
--- a/packages/renderer/src/index.tsx
+++ b/packages/renderer/src/index.tsx
@@ -26,14 +26,17 @@ import CssBaseline from '@mui/material/CssBaseline';
26import React from 'react'; 26import React from 'react';
27import { render } from 'react-dom'; 27import { render } from 'react-dom';
28 28
29import { App } from './components/App'; 29import App from './components/App';
30import { StoreProvider } from './components/StoreProvider'; 30import StoreProvider from './components/StoreProvider';
31import { ThemeProvider } from './components/ThemeProvider'; 31import ThemeProvider from './components/ThemeProvider';
32import { exposeToReduxDevtools, hotReloadServices } from './devTools'; 32import { exposeToReduxDevtools, hotReloadServices } from './devTools';
33import { createAndConnectRendererStore } from './stores/RendererStore'; 33import { createAndConnectRendererStore } from './stores/RendererStore';
34import { getLogger } from './utils/log';
34 35
35const isDevelopment = import.meta.env.MODE === 'development'; 36const isDevelopment = import.meta.env.MODE === 'development';
36 37
38const log = getLogger('index');
39
37if (isDevelopment) { 40if (isDevelopment) {
38 hotReloadServices(); 41 hotReloadServices();
39 document.title = `[dev] ${document.title}`; 42 document.title = `[dev] ${document.title}`;
@@ -42,7 +45,9 @@ if (isDevelopment) {
42const store = createAndConnectRendererStore(window.sophieRenderer); 45const store = createAndConnectRendererStore(window.sophieRenderer);
43 46
44if (isDevelopment) { 47if (isDevelopment) {
45 exposeToReduxDevtools(store); 48 exposeToReduxDevtools(store).catch((err) => {
49 log.error('Cannot initialize redux devtools', err);
50 });
46} 51}
47 52
48function Root(): JSX.Element { 53function Root(): JSX.Element {
diff --git a/packages/renderer/src/stores/RendererEnv.ts b/packages/renderer/src/stores/RendererEnv.ts
index d687738..f0a5a51 100644
--- a/packages/renderer/src/stores/RendererEnv.ts
+++ b/packages/renderer/src/stores/RendererEnv.ts
@@ -18,10 +18,10 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { getEnv as getAnyEnv, IAnyStateTreeNode } from 'mobx-state-tree';
22import type { Action } from '@sophie/shared'; 21import type { Action } from '@sophie/shared';
22import { getEnv as getAnyEnv, IAnyStateTreeNode } from 'mobx-state-tree';
23 23
24export interface RendererEnv { 24export default interface RendererEnv {
25 dispatchMainAction(action: Action): void; 25 dispatchMainAction(action: Action): void;
26} 26}
27 27
diff --git a/packages/renderer/src/stores/RendererStore.ts b/packages/renderer/src/stores/RendererStore.ts
index 037b212..e684759 100644
--- a/packages/renderer/src/stores/RendererStore.ts
+++ b/packages/renderer/src/stores/RendererStore.ts
@@ -19,19 +19,24 @@
19 */ 19 */
20 20
21import { 21import {
22 applySnapshot,
23 applyPatch,
24 Instance,
25 types,
26} from 'mobx-state-tree';
27import {
28 BrowserViewBounds, 22 BrowserViewBounds,
29 sharedStore, 23 sharedStore,
30 SophieRenderer, 24 SophieRenderer,
31 ThemeSource, 25 ThemeSource,
32} from '@sophie/shared'; 26} from '@sophie/shared';
27import {
28 applySnapshot,
29 applyPatch,
30 Instance,
31 types,
32} from 'mobx-state-tree';
33
34import { getLogger } from '../utils/log';
35
36import type RendererEnv from './RendererEnv';
37import { getEnv } from './RendererEnv';
33 38
34import { getEnv, RendererEnv } from './RendererEnv'; 39const log = getLogger('RendererStore');
35 40
36export const rendererStore = types.model('RendererStore', { 41export const rendererStore = types.model('RendererStore', {
37 shared: types.optional(sharedStore, {}), 42 shared: types.optional(sharedStore, {}),
@@ -81,7 +86,7 @@ export function createAndConnectRendererStore(ipc: SophieRenderer): RendererStor
81 applyPatch(store.shared, patch); 86 applyPatch(store.shared, patch);
82 }, 87 },
83 }).catch((err) => { 88 }).catch((err) => {
84 console.error('Failed to connect to shared store', err); 89 log.error('Failed to connect to shared store', err);
85 }); 90 });
86 91
87 return store; 92 return store;
diff --git a/packages/renderer/src/utils/log.ts b/packages/renderer/src/utils/log.ts
new file mode 100644
index 0000000..c17fc2a
--- /dev/null
+++ b/packages/renderer/src/utils/log.ts
@@ -0,0 +1,50 @@
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 loglevel, { Logger } from 'loglevel';
22import prefix from 'loglevel-plugin-prefix';
23
24if (import.meta.env?.DEV) {
25 loglevel.setLevel('debug');
26} else {
27 // No devtools in production, so there's not point to log anything.
28 loglevel.disableAll();
29}
30
31prefix.reg(loglevel);
32prefix.apply(loglevel, {
33 format(level, name, timestamp) {
34 const timeStr = timestamp.toString();
35 const nameStr = typeof name === 'undefined' ? '' : ` ${name}`;
36 return `[${timeStr}] ${level}${nameStr}:`;
37 },
38});
39
40export function getLogger(loggerName: string): Logger {
41 return loglevel.getLogger(loggerName);
42}
43
44export function silenceLogger(): void {
45 loglevel.disableAll();
46 const loggers = loglevel.getLoggers();
47 Object.keys(loggers).forEach((loggerName) => {
48 loggers[loggerName].disableAll();
49 });
50}
diff --git a/packages/renderer/tsconfig.json b/packages/renderer/tsconfig.json
index 8746462..14c3e0c 100644
--- a/packages/renderer/tsconfig.json
+++ b/packages/renderer/tsconfig.json
@@ -1,5 +1,5 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "noEmit": true, 4 "noEmit": true,
5 "jsx": "react", 5 "jsx": "react",
@@ -14,12 +14,14 @@
14 }, 14 },
15 "references": [ 15 "references": [
16 { 16 {
17 "path": "../shared" 17 "path": "../shared/tsconfig.build.json"
18 } 18 }
19 ], 19 ],
20 "include": [ 20 "include": [
21 "src/**/*.ts", 21 "src/**/*.ts",
22 "src/**/*.tsx", 22 "src/**/*.tsx",
23 "types/**/*.d.ts" 23 "types/**/*.d.ts",
24 ".eslintrc.cjs",
25 "vite.config.js"
24 ] 26 ]
25} 27}
diff --git a/packages/renderer/vite.config.js b/packages/renderer/vite.config.js
index bcd1975..6440ead 100644
--- a/packages/renderer/vite.config.js
+++ b/packages/renderer/vite.config.js
@@ -3,10 +3,11 @@
3 3
4import { builtinModules } from 'module'; 4import { builtinModules } from 'module';
5import { join } from 'path'; 5import { join } from 'path';
6
6import react from '@vitejs/plugin-react'; 7import react from '@vitejs/plugin-react';
7 8
8import { banner, chrome } from '../../config/buildConstants.js'; 9import { banner, chrome } from '../../config/buildConstants.js';
9import { fileURLToDirname } from '../../config/utils.js'; 10import fileURLToDirname from '../../config/fileURLToDirname.js';
10 11
11const thisDir = fileURLToDirname(import.meta.url); 12const thisDir = fileURLToDirname(import.meta.url);
12 13
@@ -45,7 +46,7 @@ export default {
45 preserveSymlinks: true, 46 preserveSymlinks: true,
46 }, 47 },
47 optimizeDeps: { 48 optimizeDeps: {
48 link: [ 49 exclude: [
49 '@sophie/shared', 50 '@sophie/shared',
50 ], 51 ],
51 }, 52 },
diff --git a/packages/service-inject/.eslintrc.cjs b/packages/service-inject/.eslintrc.cjs
new file mode 100644
index 0000000..6ae3faf
--- /dev/null
+++ b/packages/service-inject/.eslintrc.cjs
@@ -0,0 +1,6 @@
1module.exports = {
2 env: {
3 node: false,
4 browser: true,
5 },
6};
diff --git a/packages/service-inject/esbuild.config.js b/packages/service-inject/esbuild.config.js
index 2169c8e..d0b04bb 100644
--- a/packages/service-inject/esbuild.config.js
+++ b/packages/service-inject/esbuild.config.js
@@ -1,8 +1,8 @@
1import { chrome } from '../../config/buildConstants.js'; 1import { chrome } from '../../config/buildConstants.js';
2import { getConfig } from '../../config/esbuildConfig.js'; 2import fileURLToDirname from '../../config/fileURLToDirname.js';
3import { fileURLToDirname } from '../../config/utils.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileURLToDirname(import.meta.url),
7 entryPoints: [ 7 entryPoints: [
8 'src/index.ts', 8 'src/index.ts',
diff --git a/packages/service-inject/package.json b/packages/service-inject/package.json
index 7c496fd..c045500 100644
--- a/packages/service-inject/package.json
+++ b/packages/service-inject/package.json
@@ -6,13 +6,9 @@
6 "type": "module", 6 "type": "module",
7 "types": "dist-types/index.d.ts", 7 "types": "dist-types/index.d.ts",
8 "scripts": { 8 "scripts": {
9 "typecheck": "tsc" 9 "typecheck:workspace": "yarn g:typecheck"
10 }, 10 },
11 "dependencies": { 11 "dependencies": {
12 "@sophie/service-shared": "workspace:*" 12 "@sophie/service-shared": "workspace:*"
13 },
14 "devDependencies": {
15 "rimraf": "^3.0.2",
16 "typescript": "^4.5.4"
17 } 13 }
18} 14}
diff --git a/packages/service-inject/tsconfig.json b/packages/service-inject/tsconfig.json
index 638690b..cc61d63 100644
--- a/packages/service-inject/tsconfig.json
+++ b/packages/service-inject/tsconfig.json
@@ -1,5 +1,5 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "noEmit": true, 4 "noEmit": true,
5 "lib": [ 5 "lib": [
@@ -10,10 +10,11 @@
10 }, 10 },
11 "references": [ 11 "references": [
12 { 12 {
13 "path": "../service-shared" 13 "path": "../service-shared/tsconfig.build.json"
14 } 14 }
15 ], 15 ],
16 "include": [ 16 "include": [
17 "src/**/*.ts" 17 "src/**/*.ts",
18 "esbuild.config.js"
18 ] 19 ]
19} 20}
diff --git a/packages/service-preload/.eslintrc.cjs b/packages/service-preload/.eslintrc.cjs
new file mode 100644
index 0000000..02fab21
--- /dev/null
+++ b/packages/service-preload/.eslintrc.cjs
@@ -0,0 +1,6 @@
1module.exports = {
2 env: {
3 node: true,
4 browser: true,
5 },
6};
diff --git a/packages/service-preload/esbuild.config.js b/packages/service-preload/esbuild.config.js
index b73a071..66f5e84 100644
--- a/packages/service-preload/esbuild.config.js
+++ b/packages/service-preload/esbuild.config.js
@@ -1,8 +1,8 @@
1import { chrome } from '../../config/buildConstants.js'; 1import { chrome } from '../../config/buildConstants.js';
2import { getConfig } from '../../config/esbuildConfig.js'; 2import fileURLToDirname from '../../config/fileURLToDirname.js';
3import { fileURLToDirname } from '../../config/utils.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileURLToDirname(import.meta.url),
7 entryPoints: [ 7 entryPoints: [
8 'src/index.ts', 8 'src/index.ts',
diff --git a/packages/service-preload/package.json b/packages/service-preload/package.json
index 26215a3..14717f8 100644
--- a/packages/service-preload/package.json
+++ b/packages/service-preload/package.json
@@ -5,14 +5,12 @@
5 "type": "module", 5 "type": "module",
6 "types": "dist-types/index.d.ts", 6 "types": "dist-types/index.d.ts",
7 "scripts": { 7 "scripts": {
8 "typecheck": "tsc" 8 "typecheck:workspace": "yarn g:typecheck"
9 }, 9 },
10 "dependencies": { 10 "dependencies": {
11 "@sophie/service-shared": "workspace:*", 11 "@sophie/service-shared": "workspace:*",
12 "electron": "16.0.6" 12 "electron": "16.0.6",
13 }, 13 "loglevel": "^1.8.0",
14 "devDependencies": { 14 "loglevel-plugin-prefix": "^0.8.4"
15 "rimraf": "^3.0.2",
16 "typescript": "^4.5.4"
17 } 15 }
18} 16}
diff --git a/packages/service-preload/src/index.ts b/packages/service-preload/src/index.ts
index d1ea13c..2bbfefd 100644
--- a/packages/service-preload/src/index.ts
+++ b/packages/service-preload/src/index.ts
@@ -18,8 +18,12 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { ipcRenderer, webFrame } from 'electron';
22import { ServiceToMainIpcMessage, webSource } from '@sophie/service-shared'; 21import { ServiceToMainIpcMessage, webSource } from '@sophie/service-shared';
22import { ipcRenderer, webFrame } from 'electron';
23
24import { getLogger } from './utils/log';
25
26const log = getLogger('index');
23 27
24if (webFrame.parent === null) { 28if (webFrame.parent === null) {
25 // Inject CSS to simulate `browserView.setBackgroundColor`. 29 // Inject CSS to simulate `browserView.setBackgroundColor`.
@@ -49,14 +53,14 @@ if (webFrame.parent === null) {
49 * @see https://www.electronjs.org/docs/latest/api/web-contents#contentsexecutejavascriptinisolatedworldworldid-scripts-usergesture 53 * @see https://www.electronjs.org/docs/latest/api/web-contents#contentsexecutejavascriptinisolatedworldworldid-scripts-usergesture
50 */ 54 */
51async function fetchAndExecuteInjectScript(): Promise<void> { 55async function fetchAndExecuteInjectScript(): Promise<void> {
52 const apiExposedResponse = await ipcRenderer.invoke( 56 const apiExposedResponse: unknown = await ipcRenderer.invoke(
53 ServiceToMainIpcMessage.ApiExposedInMainWorld, 57 ServiceToMainIpcMessage.ApiExposedInMainWorld,
54 ); 58 );
55 const injectSource = webSource.parse(apiExposedResponse); 59 const injectSource = webSource.parse(apiExposedResponse);
56 // Isolated world 0 is the main world. 60 // Isolated world 0 is the main world.
57 return webFrame.executeJavaScriptInIsolatedWorld(0, [injectSource]); 61 await webFrame.executeJavaScriptInIsolatedWorld(0, [injectSource]);
58} 62}
59 63
60fetchAndExecuteInjectScript().catch((err) => { 64fetchAndExecuteInjectScript().catch((err) => {
61 console.log('Failed to fetch inject source:', err); 65 log.error('Failed to fetch inject source:', err);
62}); 66});
diff --git a/packages/service-preload/src/utils/log.ts b/packages/service-preload/src/utils/log.ts
new file mode 100644
index 0000000..0c35319
--- /dev/null
+++ b/packages/service-preload/src/utils/log.ts
@@ -0,0 +1,49 @@
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 loglevel, { Logger } from 'loglevel';
22import prefix from 'loglevel-plugin-prefix';
23
24if (import.meta.env?.DEV) {
25 loglevel.setLevel('debug');
26} else {
27 loglevel.setLevel('info');
28}
29
30prefix.reg(loglevel);
31prefix.apply(loglevel, {
32 format(level, name, timestamp) {
33 const timeStr = timestamp.toString();
34 const nameStr = typeof name === 'undefined' ? '' : ` ${name}`;
35 return `[${timeStr}] ${level}${nameStr}:`;
36 },
37});
38
39export function getLogger(loggerName: string): Logger {
40 return loglevel.getLogger(loggerName);
41}
42
43export function silenceLogger(): void {
44 loglevel.disableAll();
45 const loggers = loglevel.getLoggers();
46 Object.keys(loggers).forEach((loggerName) => {
47 loggers[loggerName].disableAll();
48 });
49}
diff --git a/packages/service-preload/tsconfig.json b/packages/service-preload/tsconfig.json
index 638690b..0372dde 100644
--- a/packages/service-preload/tsconfig.json
+++ b/packages/service-preload/tsconfig.json
@@ -1,5 +1,5 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "noEmit": true, 4 "noEmit": true,
5 "lib": [ 5 "lib": [
@@ -10,10 +10,12 @@
10 }, 10 },
11 "references": [ 11 "references": [
12 { 12 {
13 "path": "../service-shared" 13 "path": "../service-shared/tsconfig.build.json"
14 } 14 }
15 ], 15 ],
16 "include": [ 16 "include": [
17 "src/**/*.ts" 17 "src/**/*.ts",
18 "types/**/*.ts",
19 "esbuild.config.js"
18 ] 20 ]
19} 21}
diff --git a/packages/service-preload/types/importMeta.d.ts b/packages/service-preload/types/importMeta.d.ts
new file mode 100644
index 0000000..9b73170
--- /dev/null
+++ b/packages/service-preload/types/importMeta.d.ts
@@ -0,0 +1,7 @@
1interface ImportMeta {
2 env: {
3 DEV: boolean;
4 MODE: string;
5 PROD: boolean;
6 }
7}
diff --git a/packages/service-shared/.eslintrc.cjs b/packages/service-shared/.eslintrc.cjs
new file mode 100644
index 0000000..71d6ec4
--- /dev/null
+++ b/packages/service-shared/.eslintrc.cjs
@@ -0,0 +1,7 @@
1module.exports = {
2 env: {
3 // We must run in both node and browser, so we can't depend on either of them.
4 node: false,
5 browser: false,
6 },
7};
diff --git a/packages/service-shared/esbuild.config.js b/packages/service-shared/esbuild.config.js
index 08941a4..ccee72c 100644
--- a/packages/service-shared/esbuild.config.js
+++ b/packages/service-shared/esbuild.config.js
@@ -1,8 +1,8 @@
1import { chrome, node } from '../../config/buildConstants.js'; 1import { chrome, node } from '../../config/buildConstants.js';
2import { getConfig } from '../../config/esbuildConfig.js'; 2import fileURLToDirname from '../../config/fileURLToDirname.js';
3import { fileURLToDirname } from '../../config/utils.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileURLToDirname(import.meta.url),
7 entryPoints: [ 7 entryPoints: [
8 'src/index.ts', 8 'src/index.ts',
diff --git a/packages/service-shared/package.json b/packages/service-shared/package.json
index 9d75fc8..5338c8c 100644
--- a/packages/service-shared/package.json
+++ b/packages/service-shared/package.json
@@ -7,13 +7,10 @@
7 "exports": "./dist/index.mjs", 7 "exports": "./dist/index.mjs",
8 "types": "dist/index.d.ts", 8 "types": "dist/index.d.ts",
9 "scripts": { 9 "scripts": {
10 "typecheck": "tsc" 10 "typecheck:workspace": "yarn g:typecheck",
11 "types": "yarn g:types"
11 }, 12 },
12 "dependencies": { 13 "dependencies": {
13 "zod": "^3.11.6" 14 "zod": "^3.11.6"
14 },
15 "devDependencies": {
16 "rimraf": "^3.0.2",
17 "typescript": "^4.5.4"
18 } 15 }
19} 16}
diff --git a/packages/service-shared/src/index.ts b/packages/service-shared/src/index.ts
index 564ebe8..e111347 100644
--- a/packages/service-shared/src/index.ts
+++ b/packages/service-shared/src/index.ts
@@ -18,7 +18,7 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21export { ServiceToMainIpcMessage } from './ipc'; 21export { MainToServiceIpcMessage, ServiceToMainIpcMessage } from './ipc';
22 22
23export type { 23export type {
24 UnreadCount, 24 UnreadCount,
diff --git a/packages/service-shared/src/ipc.ts b/packages/service-shared/src/ipc.ts
index 4f991c5..c0dab11 100644
--- a/packages/service-shared/src/ipc.ts
+++ b/packages/service-shared/src/ipc.ts
@@ -18,6 +18,9 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21export enum MainToServiceIpcMessage {
22}
23
21export enum ServiceToMainIpcMessage { 24export enum ServiceToMainIpcMessage {
22 ApiExposedInMainWorld = 'sophie-service-to-main:api-exposed-in-main-world', 25 ApiExposedInMainWorld = 'sophie-service-to-main:api-exposed-in-main-world',
23 SetUnreadCount = 'sophie-service-to-main:set-unread-count', 26 SetUnreadCount = 'sophie-service-to-main:set-unread-count',
diff --git a/packages/service-shared/tsconfig.build.json b/packages/service-shared/tsconfig.build.json
new file mode 100644
index 0000000..9a0c835
--- /dev/null
+++ b/packages/service-shared/tsconfig.build.json
@@ -0,0 +1,12 @@
1{
2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": {
4 "composite": true,
5 "declarationDir": "dist",
6 "emitDeclarationOnly": true,
7 "rootDir": "src"
8 },
9 "include": [
10 "src/**/*.ts"
11 ]
12}
diff --git a/packages/service-shared/tsconfig.json b/packages/service-shared/tsconfig.json
index ff5a29b..79889d2 100644
--- a/packages/service-shared/tsconfig.json
+++ b/packages/service-shared/tsconfig.json
@@ -1,12 +1,14 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "./tsconfig.build.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "composite": true, 4 "composite": false,
5 "declarationDir": "dist", 5 "emitDeclarationOnly": false,
6 "emitDeclarationOnly": true, 6 "declarationDir": null,
7 "rootDir": "src" 7 "noEmit": true,
8 "rootDir": null
8 }, 9 },
9 "include": [ 10 "include": [
10 "src/**/*.ts" 11 "src/**/*.ts",
12 "esbuild.config.js"
11 ] 13 ]
12} 14}
diff --git a/packages/shared/.eslintrc.cjs b/packages/shared/.eslintrc.cjs
new file mode 100644
index 0000000..71d6ec4
--- /dev/null
+++ b/packages/shared/.eslintrc.cjs
@@ -0,0 +1,7 @@
1module.exports = {
2 env: {
3 // We must run in both node and browser, so we can't depend on either of them.
4 node: false,
5 browser: false,
6 },
7};
diff --git a/packages/shared/esbuild.config.js b/packages/shared/esbuild.config.js
index 66d6658..78249ab 100644
--- a/packages/shared/esbuild.config.js
+++ b/packages/shared/esbuild.config.js
@@ -1,8 +1,8 @@
1import { chrome, node } from '../../config/buildConstants.js'; 1import { chrome, node } from '../../config/buildConstants.js';
2import { getConfig } from '../../config/esbuildConfig.js'; 2import fileURLToDirname from '../../config/fileURLToDirname.js';
3import { fileURLToDirname } from '../../config/utils.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileURLToDirname(import.meta.url),
7 entryPoints: [ 7 entryPoints: [
8 'src/index.ts', 8 'src/index.ts',
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 0c06643..d77261d 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -7,15 +7,12 @@
7 "exports": "./dist/index.mjs", 7 "exports": "./dist/index.mjs",
8 "types": "dist/index.d.ts", 8 "types": "dist/index.d.ts",
9 "scripts": { 9 "scripts": {
10 "typecheck": "tsc" 10 "typecheck:workspace": "yarn g:typecheck",
11 "types": "yarn g:types"
11 }, 12 },
12 "dependencies": { 13 "dependencies": {
13 "mobx": "^6.3.12", 14 "mobx": "^6.3.12",
14 "mobx-state-tree": "^5.1.0", 15 "mobx-state-tree": "^5.1.0",
15 "zod": "^3.11.6" 16 "zod": "^3.11.6"
16 },
17 "devDependencies": {
18 "rimraf": "^3.0.2",
19 "typescript": "^4.5.4"
20 } 17 }
21} 18}
diff --git a/packages/shared/src/contextBridge/SophieRenderer.ts b/packages/shared/src/contextBridge/SophieRenderer.ts
index fc43b6e..9858aa9 100644
--- a/packages/shared/src/contextBridge/SophieRenderer.ts
+++ b/packages/shared/src/contextBridge/SophieRenderer.ts
@@ -18,12 +18,11 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { SharedStoreListener } from '../stores/SharedStore';
22
23import { Action } from '../schemas'; 21import { Action } from '../schemas';
22import { SharedStoreListener } from '../stores/SharedStore';
24 23
25export interface SophieRenderer { 24export interface SophieRenderer {
26 onSharedStoreChange(listener: SharedStoreListener): Promise<void>; 25 onSharedStoreChange(this: void, listener: SharedStoreListener): Promise<void>;
27 26
28 dispatchAction(action: Action): void; 27 dispatchAction(this: void, action: Action): void;
29} 28}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 2f7146c..9828ec4 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -20,10 +20,7 @@
20 20
21export type { SophieRenderer } from './contextBridge/SophieRenderer'; 21export type { SophieRenderer } from './contextBridge/SophieRenderer';
22 22
23export { 23export { MainToRendererIpcMessage, RendererToMainIpcMessage } from './ipc';
24 MainToRendererIpcMessage,
25 RendererToMainIpcMessage,
26} from './ipc';
27 24
28export type { 25export type {
29 Action, 26 Action,
diff --git a/packages/shared/tsconfig.build.json b/packages/shared/tsconfig.build.json
new file mode 100644
index 0000000..9a0c835
--- /dev/null
+++ b/packages/shared/tsconfig.build.json
@@ -0,0 +1,12 @@
1{
2 "extends": "../../config/tsconfig.base.json",
3 "compilerOptions": {
4 "composite": true,
5 "declarationDir": "dist",
6 "emitDeclarationOnly": true,
7 "rootDir": "src"
8 },
9 "include": [
10 "src/**/*.ts"
11 ]
12}
diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json
index ff5a29b..79889d2 100644
--- a/packages/shared/tsconfig.json
+++ b/packages/shared/tsconfig.json
@@ -1,12 +1,14 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "./tsconfig.build.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "composite": true, 4 "composite": false,
5 "declarationDir": "dist", 5 "emitDeclarationOnly": false,
6 "emitDeclarationOnly": true, 6 "declarationDir": null,
7 "rootDir": "src" 7 "noEmit": true,
8 "rootDir": null
8 }, 9 },
9 "include": [ 10 "include": [
10 "src/**/*.ts" 11 "src/**/*.ts",
12 "esbuild.config.js"
11 ] 13 ]
12} 14}