aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-01-09 22:16:29 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-01-09 22:17:26 +0100
commitfb7118ff1c8f0dcd61f15e51b193512283d83fa1 (patch)
tree721cee6a64b44a56b7f05f39750a65cda5fb4ef6 /packages
parentbuild: Add eslint-plugin-jest (diff)
downloadsophie-fb7118ff1c8f0dcd61f15e51b193512283d83fa1.tar.gz
sophie-fb7118ff1c8f0dcd61f15e51b193512283d83fa1.tar.zst
sophie-fb7118ff1c8f0dcd61f15e51b193512283d83fa1.zip
build: Add eslint-plugin-unicorn
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/main/.eslintrc.cjs4
-rw-r--r--packages/main/esbuild.config.js8
-rw-r--r--packages/main/jest.config.js5
-rw-r--r--packages/main/src/controllers/__tests__/initConfig.spec.ts2
-rw-r--r--packages/main/src/controllers/initConfig.ts14
-rw-r--r--packages/main/src/devTools.ts3
-rw-r--r--packages/main/src/index.ts72
-rw-r--r--packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts24
-rw-r--r--packages/main/src/stores/Config.ts9
-rw-r--r--packages/main/src/utils/log.ts5
-rw-r--r--packages/preload/esbuild.config.js4
-rw-r--r--packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts12
-rw-r--r--packages/preload/src/contextBridge/createSophieRenderer.ts18
-rw-r--r--packages/renderer/src/components/BrowserViewPlaceholder.tsx6
-rw-r--r--packages/renderer/src/components/StoreProvider.tsx5
-rw-r--r--packages/renderer/src/index.tsx4
-rw-r--r--packages/renderer/src/stores/RendererStore.ts4
-rw-r--r--packages/renderer/vite.config.js10
-rw-r--r--packages/service-inject/esbuild.config.js4
-rw-r--r--packages/service-preload/esbuild.config.js4
-rw-r--r--packages/service-preload/src/index.ts4
-rw-r--r--packages/service-shared/esbuild.config.js4
-rw-r--r--packages/shared/esbuild.config.js4
23 files changed, 122 insertions, 107 deletions
diff --git a/packages/main/.eslintrc.cjs b/packages/main/.eslintrc.cjs
index 548ea34..cab227c 100644
--- a/packages/main/.eslintrc.cjs
+++ b/packages/main/.eslintrc.cjs
@@ -3,4 +3,8 @@ module.exports = {
3 node: true, 3 node: true,
4 browser: false, 4 browser: false,
5 }, 5 },
6 rules: {
7 // This is an application, so we're allowed to `exit` from it.
8 'unicorn/no-process-exit': 'off',
9 },
6}; 10};
diff --git a/packages/main/esbuild.config.js b/packages/main/esbuild.config.js
index d5f6f1e..ef0aa71 100644
--- a/packages/main/esbuild.config.js
+++ b/packages/main/esbuild.config.js
@@ -1,7 +1,7 @@
1import getRepoInfo from 'git-repo-info'; 1import getRepoInfo from 'git-repo-info';
2 2
3import { node } from '../../config/buildConstants.js'; 3import { node } from '../../config/buildConstants.js';
4import fileURLToDirname from '../../config/fileURLToDirname.js'; 4import fileUrlToDirname from '../../config/fileUrlToDirname.js';
5import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 5import getEsbuildConfig from '../../config/getEsbuildConfig.js';
6 6
7const externalPackages = ['electron']; 7const externalPackages = ['electron'];
@@ -14,7 +14,7 @@ const gitInfo = getRepoInfo();
14 14
15export default getEsbuildConfig( 15export default getEsbuildConfig(
16 { 16 {
17 absWorkingDir: fileURLToDirname(import.meta.url), 17 absWorkingDir: fileUrlToDirname(import.meta.url),
18 entryPoints: ['src/index.ts'], 18 entryPoints: ['src/index.ts'],
19 outfile: 'dist/index.cjs', 19 outfile: 'dist/index.cjs',
20 format: 'cjs', 20 format: 'cjs',
@@ -23,9 +23,9 @@ export default getEsbuildConfig(
23 external: externalPackages, 23 external: externalPackages,
24 }, 24 },
25 { 25 {
26 VITE_DEV_SERVER_URL: process.env.VITE_DEV_SERVER_URL || null, 26 VITE_DEV_SERVER_URL: process.env.VITE_DEV_SERVER_URL || undefined,
27 GIT_SHA: gitInfo.abbreviatedSha, 27 GIT_SHA: gitInfo.abbreviatedSha,
28 GIT_BRANCH: gitInfo.branch, 28 GIT_BRANCH: gitInfo.branch,
29 BUILD_DATE: new Date().getTime(), 29 BUILD_DATE: Date.now(),
30 }, 30 },
31); 31);
diff --git a/packages/main/jest.config.js b/packages/main/jest.config.js
index b86463c..9aaf344 100644
--- a/packages/main/jest.config.js
+++ b/packages/main/jest.config.js
@@ -1,3 +1,4 @@
1import rootConfig from '../../config/jest.config.base.js'; 1import baseConfig from '../../config/jest.config.base.js';
2 2
3export default rootConfig; 3// eslint-disable-next-line unicorn/prefer-export-from -- Can't export from default.
4export default baseConfig;
diff --git a/packages/main/src/controllers/__tests__/initConfig.spec.ts b/packages/main/src/controllers/__tests__/initConfig.spec.ts
index 9a5f85e..11e7690 100644
--- a/packages/main/src/controllers/__tests__/initConfig.spec.ts
+++ b/packages/main/src/controllers/__tests__/initConfig.spec.ts
@@ -133,7 +133,7 @@ describe('when it has loaded the config', () => {
133 }); 133 });
134 134
135 it('should throttle saving changes to the config file', () => { 135 it('should throttle saving changes to the config file', () => {
136 mocked(persistenceService.writeConfig).mockResolvedValue(undefined); 136 mocked(persistenceService.writeConfig).mockResolvedValue();
137 config.setThemeSource('dark'); 137 config.setThemeSource('dark');
138 jest.advanceTimersByTime(lessThanThrottleMs); 138 jest.advanceTimersByTime(lessThanThrottleMs);
139 config.setThemeSource('light'); 139 config.setThemeSource('light');
diff --git a/packages/main/src/controllers/initConfig.ts b/packages/main/src/controllers/initConfig.ts
index e83b8da..915f451 100644
--- a/packages/main/src/controllers/initConfig.ts
+++ b/packages/main/src/controllers/initConfig.ts
@@ -38,7 +38,7 @@ export default async function initConfig(
38): Promise<Disposer> { 38): Promise<Disposer> {
39 log.trace('Initializing config controller'); 39 log.trace('Initializing config controller');
40 40
41 let lastSnapshotOnDisk: ConfigSnapshotOut | null = null; 41 let lastSnapshotOnDisk: ConfigSnapshotOut | undefined;
42 42
43 async function readConfig(): Promise<boolean> { 43 async function readConfig(): Promise<boolean> {
44 const result = await persistenceService.readConfig(); 44 const result = await persistenceService.readConfig();
@@ -46,8 +46,8 @@ export default async function initConfig(
46 try { 46 try {
47 applySnapshot(config, result.data); 47 applySnapshot(config, result.data);
48 lastSnapshotOnDisk = getSnapshot(config); 48 lastSnapshotOnDisk = getSnapshot(config);
49 } catch (err) { 49 } catch (error) {
50 log.error('Failed to apply config snapshot', result.data, err); 50 log.error('Failed to apply config snapshot', result.data, error);
51 } 51 }
52 } 52 }
53 return result.found; 53 return result.found;
@@ -70,8 +70,8 @@ export default async function initConfig(
70 debounce((snapshot) => { 70 debounce((snapshot) => {
71 // We can compare snapshots by reference, since it is only recreated on store changes. 71 // We can compare snapshots by reference, since it is only recreated on store changes.
72 if (lastSnapshotOnDisk !== snapshot) { 72 if (lastSnapshotOnDisk !== snapshot) {
73 writeConfig().catch((err) => { 73 writeConfig().catch((error) => {
74 log.error('Failed to write config on config change', err); 74 log.error('Failed to write config on config change', error);
75 }); 75 });
76 } 76 }
77 }, debounceTime), 77 }, debounceTime),
@@ -80,8 +80,8 @@ export default async function initConfig(
80 const disposeWatcher = persistenceService.watchConfig(async () => { 80 const disposeWatcher = persistenceService.watchConfig(async () => {
81 try { 81 try {
82 await readConfig(); 82 await readConfig();
83 } catch (err) { 83 } catch (error) {
84 log.error('Failed to read config', err); 84 log.error('Failed to read config', error);
85 } 85 }
86 }, debounceTime); 86 }, debounceTime);
87 87
diff --git a/packages/main/src/devTools.ts b/packages/main/src/devTools.ts
index 69f1514..4ca1bf3 100644
--- a/packages/main/src/devTools.ts
+++ b/packages/main/src/devTools.ts
@@ -49,7 +49,8 @@ export async function installDevToolsExtensions(): Promise<void> {
49 /* eslint-disable-next-line 49 /* eslint-disable-next-line
50 import/no-extraneous-dependencies, 50 import/no-extraneous-dependencies,
51 global-require, 51 global-require,
52 @typescript-eslint/no-var-requires 52 @typescript-eslint/no-var-requires,
53 unicorn/prefer-module
53 */ 54 */
54 } = require('electron-devtools-installer') as typeof import('electron-devtools-installer'); 55 } = require('electron-devtools-installer') as typeof import('electron-devtools-installer');
55 await installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS], { 56 await installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS], {
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index 1f80e44..02e6cda 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -19,9 +19,9 @@
19 * SPDX-License-Identifier: AGPL-3.0-only 19 * SPDX-License-Identifier: AGPL-3.0-only
20 */ 20 */
21 21
22import { arch } from 'os'; 22import { arch } from 'node:os';
23import { join } from 'path'; 23import path from 'node:path';
24import { URL } from 'url'; 24import { URL } from 'node:url';
25 25
26import { 26import {
27 ServiceToMainIpcMessage, 27 ServiceToMainIpcMessage,
@@ -101,11 +101,14 @@ app.setAboutPanelOptions({
101 version: '', 101 version: '',
102}); 102});
103 103
104// eslint-disable-next-line unicorn/prefer-module -- Electron apps run in a commonjs environment.
105const thisDir = __dirname;
106
104function getResourcePath(relativePath: string): string { 107function getResourcePath(relativePath: string): string {
105 return join(__dirname, relativePath); 108 return path.join(thisDir, relativePath);
106} 109}
107 110
108const baseUrl = `file://${__dirname}`; 111const baseUrl = `file://${thisDir}`;
109function getResourceUrl(relativePath: string): string { 112function getResourceUrl(relativePath: string): string {
110 return new URL(relativePath, baseUrl).toString(); 113 return new URL(relativePath, baseUrl).toString();
111} 114}
@@ -117,15 +120,15 @@ const serviceInject: WebSource = {
117 url: getResourceUrl(serviceInjectRelativePath), 120 url: getResourceUrl(serviceInjectRelativePath),
118}; 121};
119 122
120let mainWindow: BrowserWindow | null = null; 123let mainWindow: BrowserWindow | undefined;
121 124
122const store = createMainStore(); 125const store = createMainStore();
123init(store) 126init(store)
124 .then((disposeCompositionRoot) => { 127 .then((disposeCompositionRoot) => {
125 app.on('will-quit', disposeCompositionRoot); 128 app.on('will-quit', disposeCompositionRoot);
126 }) 129 })
127 .catch((err) => { 130 .catch((error) => {
128 log.log('Failed to initialize application', err); 131 log.log('Failed to initialize application', error);
129 }); 132 });
130 133
131const rendererBaseUrl = getResourceUrl('../renderer/'); 134const rendererBaseUrl = getResourceUrl('../renderer/');
@@ -136,7 +139,7 @@ function shouldCancelMainWindowRequest(url: string, method: string): boolean {
136 let normalizedUrl: string; 139 let normalizedUrl: string;
137 try { 140 try {
138 normalizedUrl = new URL(url).toString(); 141 normalizedUrl = new URL(url).toString();
139 } catch (_err) { 142 } catch {
140 return true; 143 return true;
141 } 144 }
142 if (isDevelopment) { 145 if (isDevelopment) {
@@ -233,7 +236,7 @@ async function createWindow(): Promise<unknown> {
233 'from webContents', 236 'from webContents',
234 event.sender.id, 237 event.sender.id,
235 ); 238 );
236 return null; 239 throw new Error('Invalid IPC call');
237 } 240 }
238 return getSnapshot(store.shared); 241 return getSnapshot(store.shared);
239 }); 242 });
@@ -262,22 +265,22 @@ async function createWindow(): Promise<unknown> {
262 .then((data) => { 265 .then((data) => {
263 serviceInject.code = data; 266 serviceInject.code = data;
264 }) 267 })
265 .catch((err) => { 268 .catch((error) => {
266 log.error('Error while reloading', serviceInjectPath, err); 269 log.error('Error while reloading', serviceInjectPath, error);
267 }) 270 })
268 .then(() => { 271 .then(() => {
269 browserView.webContents.reload(); 272 browserView.webContents.reload();
270 }) 273 })
271 .catch((err) => { 274 .catch((error) => {
272 log.error('Failed to reload browserView', err); 275 log.error('Failed to reload browserView', error);
273 }); 276 });
274 break; 277 break;
275 default: 278 default:
276 log.error('Unexpected action from UI renderer:', actionToDispatch); 279 log.error('Unexpected action from UI renderer:', actionToDispatch);
277 break; 280 break;
278 } 281 }
279 } catch (err) { 282 } catch (error) {
280 log.error('Error while dispatching renderer action', rawAction, err); 283 log.error('Error while dispatching renderer action', rawAction, error);
281 } 284 }
282 }); 285 });
283 286
@@ -285,9 +288,18 @@ async function createWindow(): Promise<unknown> {
285 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch); 288 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch);
286 }); 289 });
287 290
288 ipcMain.handle(ServiceToMainIpcMessage.ApiExposedInMainWorld, (event) => 291 ipcMain.handle(ServiceToMainIpcMessage.ApiExposedInMainWorld, (event) => {
289 event.sender.id === browserView.webContents.id ? serviceInject : null, 292 if (event.sender.id !== browserView.webContents.id) {
290 ); 293 log.warn(
294 'Unexpected',
295 ServiceToMainIpcMessage.ApiExposedInMainWorld,
296 'from webContents',
297 event.sender.id,
298 );
299 throw new Error('Invalid IPC call');
300 }
301 return serviceInject;
302 });
291 303
292 browserView.webContents.on('ipc-message', (_event, channel, ...args) => { 304 browserView.webContents.on('ipc-message', (_event, channel, ...args) => {
293 try { 305 try {
@@ -303,8 +315,8 @@ async function createWindow(): Promise<unknown> {
303 log.error('Unknown IPC message:', channel, args); 315 log.error('Unknown IPC message:', channel, args);
304 break; 316 break;
305 } 317 }
306 } catch (err) { 318 } catch (error) {
307 log.error('Error while processing IPC message:', channel, args, err); 319 log.error('Error while processing IPC message:', channel, args, error);
308 } 320 }
309 }); 321 });
310 322
@@ -316,9 +328,7 @@ async function createWindow(): Promise<unknown> {
316 328
317 browserView.webContents.session.webRequest.onBeforeSendHeaders( 329 browserView.webContents.session.webRequest.onBeforeSendHeaders(
318 ({ url, requestHeaders }, callback) => { 330 ({ url, requestHeaders }, callback) => {
319 const requestUserAgent = url.match( 331 const requestUserAgent = /^[^:]+:\/\/accounts\.google\.[^./]+\//.test(url)
320 /^[^:]+:\/\/accounts\.google\.[^./]+\//,
321 )
322 ? chromelessUserAgent 332 ? chromelessUserAgent
323 : userAgent; 333 : userAgent;
324 callback({ 334 callback({
@@ -332,15 +342,15 @@ async function createWindow(): Promise<unknown> {
332 342
333 browserView.webContents 343 browserView.webContents
334 .loadURL('https://gitlab.com/say-hi-to-sophie/sophie') 344 .loadURL('https://gitlab.com/say-hi-to-sophie/sophie')
335 .catch((err) => { 345 .catch((error) => {
336 log.error('Failed to load browser', err); 346 log.error('Failed to load browser', error);
337 }); 347 });
338 348
339 return mainWindow.loadURL(pageUrl); 349 return mainWindow.loadURL(pageUrl);
340} 350}
341 351
342app.on('second-instance', () => { 352app.on('second-instance', () => {
343 if (mainWindow !== null) { 353 if (mainWindow !== undefined) {
344 if (!mainWindow.isVisible()) { 354 if (!mainWindow.isVisible()) {
345 mainWindow.show(); 355 mainWindow.show();
346 } 356 }
@@ -363,14 +373,14 @@ app
363 if (isDevelopment) { 373 if (isDevelopment) {
364 try { 374 try {
365 await installDevToolsExtensions(); 375 await installDevToolsExtensions();
366 } catch (err) { 376 } catch (error) {
367 log.error('Failed to install devtools extensions', err); 377 log.error('Failed to install devtools extensions', error);
368 } 378 }
369 } 379 }
370 380
371 return createWindow(); 381 return createWindow();
372 }) 382 })
373 .catch((err) => { 383 .catch((error) => {
374 log.error('Failed to create window', err); 384 log.error('Failed to create window', error);
375 process.exit(1); 385 process.exit(1);
376 }); 386 });
diff --git a/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts b/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts
index e92f706..a11a9da 100644
--- a/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts
+++ b/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts
@@ -17,9 +17,9 @@
17 * 17 *
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20import { watch } from 'fs'; 20import { watch } from 'node:fs';
21import { readFile, stat, writeFile } from 'fs/promises'; 21import { readFile, stat, writeFile } from 'node:fs/promises';
22import { join } from 'path'; 22import path from 'node:path';
23 23
24import JSON5 from 'json5'; 24import JSON5 from 'json5';
25import throttle from 'lodash-es/throttle'; 25import throttle from 'lodash-es/throttle';
@@ -39,26 +39,26 @@ export default class ConfigPersistenceServiceImpl
39 39
40 private writingConfig = false; 40 private writingConfig = false;
41 41
42 private timeLastWritten: Date | null = null; 42 private timeLastWritten: Date | undefined;
43 43
44 constructor( 44 constructor(
45 private readonly userDataDir: string, 45 private readonly userDataDir: string,
46 private readonly configFileName: string = 'config.json5', 46 private readonly configFileName: string = 'config.json5',
47 ) { 47 ) {
48 this.configFileName = configFileName; 48 this.configFileName = configFileName;
49 this.configFilePath = join(this.userDataDir, this.configFileName); 49 this.configFilePath = path.join(this.userDataDir, this.configFileName);
50 } 50 }
51 51
52 async readConfig(): Promise<ReadConfigResult> { 52 async readConfig(): Promise<ReadConfigResult> {
53 let configStr; 53 let configStr;
54 try { 54 try {
55 configStr = await readFile(this.configFilePath, 'utf8'); 55 configStr = await readFile(this.configFilePath, 'utf8');
56 } catch (err) { 56 } catch (error) {
57 if ((err as NodeJS.ErrnoException).code === 'ENOENT') { 57 if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
58 log.debug('Config file', this.configFilePath, 'was not found'); 58 log.debug('Config file', this.configFilePath, 'was not found');
59 return { found: false }; 59 return { found: false };
60 } 60 }
61 throw err; 61 throw error;
62 } 62 }
63 log.info('Read config file', this.configFilePath); 63 log.info('Read config file', this.configFilePath);
64 return { 64 return {
@@ -92,8 +92,8 @@ export default class ConfigPersistenceServiceImpl
92 const stats = await stat(this.configFilePath); 92 const stats = await stat(this.configFilePath);
93 mtime = stats.mtime; 93 mtime = stats.mtime;
94 log.trace('Config file last modified at', mtime); 94 log.trace('Config file last modified at', mtime);
95 } catch (err) { 95 } catch (error) {
96 if ((err as NodeJS.ErrnoException).code === 'ENOENT') { 96 if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
97 log.debug( 97 log.debug(
98 'Config file', 98 'Config file',
99 this.configFilePath, 99 this.configFilePath,
@@ -101,11 +101,11 @@ export default class ConfigPersistenceServiceImpl
101 ); 101 );
102 return; 102 return;
103 } 103 }
104 throw err; 104 throw error;
105 } 105 }
106 if ( 106 if (
107 !this.writingConfig && 107 !this.writingConfig &&
108 (this.timeLastWritten === null || mtime > this.timeLastWritten) 108 (this.timeLastWritten === undefined || mtime > this.timeLastWritten)
109 ) { 109 ) {
110 log.debug( 110 log.debug(
111 'Found a config file modified at', 111 'Found a config file modified at',
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts
index 06dbdeb..ca90c0c 100644
--- a/packages/main/src/stores/Config.ts
+++ b/packages/main/src/stores/Config.ts
@@ -18,12 +18,7 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { 21import { config as originalConfig, ThemeSource } from '@sophie/shared';
22 config as originalConfig,
23 ConfigSnapshotIn,
24 ConfigSnapshotOut,
25 ThemeSource,
26} from '@sophie/shared';
27import { Instance } from 'mobx-state-tree'; 22import { Instance } from 'mobx-state-tree';
28 23
29export const config = originalConfig.actions((self) => ({ 24export const config = originalConfig.actions((self) => ({
@@ -34,4 +29,4 @@ export const config = originalConfig.actions((self) => ({
34 29
35export interface Config extends Instance<typeof config> {} 30export interface Config extends Instance<typeof config> {}
36 31
37export type { ConfigSnapshotIn, ConfigSnapshotOut }; 32export type { ConfigSnapshotIn, ConfigSnapshotOut } from '@sophie/shared';
diff --git a/packages/main/src/utils/log.ts b/packages/main/src/utils/log.ts
index 5218721..0a632d8 100644
--- a/packages/main/src/utils/log.ts
+++ b/packages/main/src/utils/log.ts
@@ -18,8 +18,9 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21// eslint-disable-next-line unicorn/import-style -- Import the type `ChalkInstance` separately.
21import chalk, { ChalkInstance } from 'chalk'; 22import chalk, { ChalkInstance } from 'chalk';
22import loglevel, { Logger } from 'loglevel'; 23import loglevel from 'loglevel';
23import prefix from 'loglevel-plugin-prefix'; 24import prefix from 'loglevel-plugin-prefix';
24 25
25if (import.meta.env?.DEV) { 26if (import.meta.env?.DEV) {
@@ -54,7 +55,7 @@ prefix.apply(loglevel, {
54 }, 55 },
55}); 56});
56 57
57export function getLogger(loggerName: string): Logger { 58export function getLogger(loggerName: string): loglevel.Logger {
58 return loglevel.getLogger(loggerName); 59 return loglevel.getLogger(loggerName);
59} 60}
60 61
diff --git a/packages/preload/esbuild.config.js b/packages/preload/esbuild.config.js
index d888987..87e91d8 100644
--- a/packages/preload/esbuild.config.js
+++ b/packages/preload/esbuild.config.js
@@ -1,9 +1,9 @@
1import { chrome } from '../../config/buildConstants.js'; 1import { chrome } from '../../config/buildConstants.js';
2import fileURLToDirname from '../../config/fileURLToDirname.js'; 2import fileUrlToDirname from '../../config/fileUrlToDirname.js';
3import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getEsbuildConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileUrlToDirname(import.meta.url),
7 entryPoints: ['src/index.ts'], 7 entryPoints: ['src/index.ts'],
8 outfile: 'dist/index.cjs', 8 outfile: 'dist/index.cjs',
9 format: 'cjs', 9 format: 'cjs',
diff --git a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
index b0af280..88b0077 100644
--- a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
+++ b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
@@ -45,7 +45,7 @@ const { default: createSophieRenderer } = await import(
45); 45);
46 46
47const event: Electron.IpcRendererEvent = 47const event: Electron.IpcRendererEvent =
48 null as unknown as Electron.IpcRendererEvent; 48 undefined as unknown as Electron.IpcRendererEvent;
49 49
50const snapshot: SharedStoreSnapshotIn = { 50const snapshot: SharedStoreSnapshotIn = {
51 shouldUseDarkColors: true, 51 shouldUseDarkColors: true,
@@ -183,7 +183,7 @@ describe('SharedStoreConnector', () => {
183 183
184 it('should catch listener errors', () => { 184 it('should catch listener errors', () => {
185 mocked(listener.onPatch).mockImplementation(() => { 185 mocked(listener.onPatch).mockImplementation(() => {
186 throw new Error(); 186 throw new Error('listener error');
187 }); 187 });
188 expect(() => onSharedStorePatch(event, patch)).not.toThrow(); 188 expect(() => onSharedStorePatch(event, patch)).not.toThrow();
189 }); 189 });
@@ -193,7 +193,7 @@ describe('SharedStoreConnector', () => {
193 describe('after the listener threw in onPatch', () => { 193 describe('after the listener threw in onPatch', () => {
194 beforeEach(() => { 194 beforeEach(() => {
195 mocked(listener.onPatch).mockImplementation(() => { 195 mocked(listener.onPatch).mockImplementation(() => {
196 throw new Error(); 196 throw new Error('listener error');
197 }); 197 });
198 onSharedStorePatch(event, patch); 198 onSharedStorePatch(event, patch);
199 listener.onPatch.mockRestore(); 199 listener.onPatch.mockRestore();
@@ -205,7 +205,9 @@ describe('SharedStoreConnector', () => {
205 205
206 describe('when a listener failed to register due to IPC error', () => { 206 describe('when a listener failed to register due to IPC error', () => {
207 beforeEach(async () => { 207 beforeEach(async () => {
208 mocked(ipcRenderer.invoke).mockRejectedValue(new Error()); 208 mocked(ipcRenderer.invoke).mockRejectedValue(
209 new Error('ipcRenderer error'),
210 );
209 try { 211 try {
210 await sut.onSharedStoreChange(listener); 212 await sut.onSharedStoreChange(listener);
211 } catch { 213 } catch {
@@ -237,7 +239,7 @@ describe('SharedStoreConnector', () => {
237 beforeEach(async () => { 239 beforeEach(async () => {
238 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot); 240 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot);
239 mocked(listener.onSnapshot).mockImplementation(() => { 241 mocked(listener.onSnapshot).mockImplementation(() => {
240 throw new Error(); 242 throw new Error('listener error');
241 }); 243 });
242 try { 244 try {
243 await sut.onSharedStoreChange(listener); 245 await sut.onSharedStoreChange(listener);
diff --git a/packages/preload/src/contextBridge/createSophieRenderer.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts
index b97503d..3174fed 100644
--- a/packages/preload/src/contextBridge/createSophieRenderer.ts
+++ b/packages/preload/src/contextBridge/createSophieRenderer.ts
@@ -34,7 +34,7 @@ import type { IJsonPatch } from 'mobx-state-tree';
34class SharedStoreConnector { 34class SharedStoreConnector {
35 private onSharedStoreChangeCalled = false; 35 private onSharedStoreChangeCalled = false;
36 36
37 private listener: SharedStoreListener | null = null; 37 private listener: SharedStoreListener | undefined;
38 38
39 constructor(private readonly allowReplaceListener: boolean) { 39 constructor(private readonly allowReplaceListener: boolean) {
40 ipcRenderer.on( 40 ipcRenderer.on(
@@ -43,9 +43,9 @@ class SharedStoreConnector {
43 try { 43 try {
44 // `mobx-state-tree` will validate the patch, so we can safely cast here. 44 // `mobx-state-tree` will validate the patch, so we can safely cast here.
45 this.listener?.onPatch(patch as IJsonPatch); 45 this.listener?.onPatch(patch as IJsonPatch);
46 } catch (err) { 46 } catch (error) {
47 log.error('Shared store listener onPatch failed', err); 47 log.error('Shared store listener onPatch failed', error);
48 this.listener = null; 48 this.listener = undefined;
49 } 49 }
50 }, 50 },
51 ); 51 );
@@ -57,14 +57,14 @@ class SharedStoreConnector {
57 } 57 }
58 this.onSharedStoreChangeCalled = true; 58 this.onSharedStoreChangeCalled = true;
59 let success = false; 59 let success = false;
60 let snapshot: unknown | null = null; 60 let snapshot: unknown;
61 try { 61 try {
62 snapshot = await ipcRenderer.invoke( 62 snapshot = await ipcRenderer.invoke(
63 RendererToMainIpcMessage.GetSharedStoreSnapshot, 63 RendererToMainIpcMessage.GetSharedStoreSnapshot,
64 ); 64 );
65 success = true; 65 success = true;
66 } catch (err) { 66 } catch (error) {
67 log.error('Failed to get initial shared store snapshot', err); 67 log.error('Failed to get initial shared store snapshot', error);
68 } 68 }
69 if (success) { 69 if (success) {
70 if (sharedStore.is(snapshot)) { 70 if (sharedStore.is(snapshot)) {
@@ -84,10 +84,10 @@ function dispatchAction(actionToDispatch: Action): void {
84 const parsedAction = action.parse(actionToDispatch); 84 const parsedAction = action.parse(actionToDispatch);
85 try { 85 try {
86 ipcRenderer.send(RendererToMainIpcMessage.DispatchAction, parsedAction); 86 ipcRenderer.send(RendererToMainIpcMessage.DispatchAction, parsedAction);
87 } catch (err) { 87 } catch (error) {
88 // Do not leak IPC failure details into the main world. 88 // Do not leak IPC failure details into the main world.
89 const message = 'Failed to dispatch action'; 89 const message = 'Failed to dispatch action';
90 log.error(message, actionToDispatch, err); 90 log.error(message, actionToDispatch, error);
91 throw new Error(message); 91 throw new Error(message);
92 } 92 }
93} 93}
diff --git a/packages/renderer/src/components/BrowserViewPlaceholder.tsx b/packages/renderer/src/components/BrowserViewPlaceholder.tsx
index 8f055e7..58407ee 100644
--- a/packages/renderer/src/components/BrowserViewPlaceholder.tsx
+++ b/packages/renderer/src/components/BrowserViewPlaceholder.tsx
@@ -44,15 +44,15 @@ export default observer(() => {
44 [store], 44 [store],
45 ); 45 );
46 46
47 const resizeObserverRef = useRef<ResizeObserver | null>(null); 47 const resizeObserverRef = useRef<ResizeObserver | undefined>();
48 48
49 const ref = useCallback( 49 const ref = useCallback(
50 (element: HTMLElement | null) => { 50 (element: HTMLElement | null) => {
51 if (resizeObserverRef.current !== null) { 51 if (resizeObserverRef.current !== undefined) {
52 resizeObserverRef.current.disconnect(); 52 resizeObserverRef.current.disconnect();
53 } 53 }
54 if (element === null) { 54 if (element === null) {
55 resizeObserverRef.current = null; 55 resizeObserverRef.current = undefined;
56 return; 56 return;
57 } 57 }
58 resizeObserverRef.current = new ResizeObserver(onResize); 58 resizeObserverRef.current = new ResizeObserver(onResize);
diff --git a/packages/renderer/src/components/StoreProvider.tsx b/packages/renderer/src/components/StoreProvider.tsx
index bb8495c..3360a43 100644
--- a/packages/renderer/src/components/StoreProvider.tsx
+++ b/packages/renderer/src/components/StoreProvider.tsx
@@ -22,11 +22,12 @@ import React, { createContext, useContext } from 'react';
22 22
23import type { RendererStore } from '../stores/RendererStore'; 23import type { RendererStore } from '../stores/RendererStore';
24 24
25const StoreContext = createContext<RendererStore | null>(null); 25// eslint-disable-next-line unicorn/no-useless-undefined -- `createContext` expects 1 parameter.
26const StoreContext = createContext<RendererStore | undefined>(undefined);
26 27
27export function useStore(): RendererStore { 28export function useStore(): RendererStore {
28 const store = useContext(StoreContext); 29 const store = useContext(StoreContext);
29 if (store === null) { 30 if (store === undefined) {
30 throw new Error('useStore can only be called inside of StoreProvider'); 31 throw new Error('useStore can only be called inside of StoreProvider');
31 } 32 }
32 return store; 33 return store;
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx
index d900e50..a42a30c 100644
--- a/packages/renderer/src/index.tsx
+++ b/packages/renderer/src/index.tsx
@@ -45,8 +45,8 @@ if (isDevelopment) {
45const store = createAndConnectRendererStore(window.sophieRenderer); 45const store = createAndConnectRendererStore(window.sophieRenderer);
46 46
47if (isDevelopment) { 47if (isDevelopment) {
48 exposeToReduxDevtools(store).catch((err) => { 48 exposeToReduxDevtools(store).catch((error) => {
49 log.error('Cannot initialize redux devtools', err); 49 log.error('Cannot initialize redux devtools', error);
50 }); 50 });
51} 51}
52 52
diff --git a/packages/renderer/src/stores/RendererStore.ts b/packages/renderer/src/stores/RendererStore.ts
index 0b78ce1..4cc5163 100644
--- a/packages/renderer/src/stores/RendererStore.ts
+++ b/packages/renderer/src/stores/RendererStore.ts
@@ -86,8 +86,8 @@ export function createAndConnectRendererStore(
86 applyPatch(store.shared, patch); 86 applyPatch(store.shared, patch);
87 }, 87 },
88 }) 88 })
89 .catch((err) => { 89 .catch((error) => {
90 log.error('Failed to connect to shared store', err); 90 log.error('Failed to connect to shared store', error);
91 }); 91 });
92 92
93 return store; 93 return store;
diff --git a/packages/renderer/vite.config.js b/packages/renderer/vite.config.js
index e20e0f1..cb0203c 100644
--- a/packages/renderer/vite.config.js
+++ b/packages/renderer/vite.config.js
@@ -1,15 +1,15 @@
1/* eslint-disable no-process-env */ 1/* eslint-disable no-process-env */
2/* eslint-env node */ 2/* eslint-env node */
3 3
4import { builtinModules } from 'module'; 4import { builtinModules } from 'node:module';
5import { join } from 'path'; 5import path from 'node:path';
6 6
7import react from '@vitejs/plugin-react'; 7import react from '@vitejs/plugin-react';
8 8
9import { banner, chrome } from '../../config/buildConstants.js'; 9import { banner, chrome } from '../../config/buildConstants.js';
10import fileURLToDirname from '../../config/fileURLToDirname.js'; 10import fileUrlToDirname from '../../config/fileUrlToDirname.js';
11 11
12const thisDir = fileURLToDirname(import.meta.url); 12const thisDir = fileUrlToDirname(import.meta.url);
13 13
14const mode = process.env.MODE || 'development'; 14const mode = process.env.MODE || 'development';
15 15
@@ -24,7 +24,7 @@ export default {
24 logLevel: 'info', 24 logLevel: 'info',
25 mode, 25 mode,
26 root: thisDir, 26 root: thisDir,
27 cacheDir: join(thisDir, '../../.vite'), 27 cacheDir: path.join(thisDir, '../../.vite'),
28 plugins: [ 28 plugins: [
29 react({ 29 react({
30 babel: { 30 babel: {
diff --git a/packages/service-inject/esbuild.config.js b/packages/service-inject/esbuild.config.js
index 795b0f6..d8698ac 100644
--- a/packages/service-inject/esbuild.config.js
+++ b/packages/service-inject/esbuild.config.js
@@ -1,9 +1,9 @@
1import { chrome } from '../../config/buildConstants.js'; 1import { chrome } from '../../config/buildConstants.js';
2import fileURLToDirname from '../../config/fileURLToDirname.js'; 2import fileUrlToDirname from '../../config/fileUrlToDirname.js';
3import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getEsbuildConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileUrlToDirname(import.meta.url),
7 entryPoints: ['src/index.ts'], 7 entryPoints: ['src/index.ts'],
8 outfile: 'dist/index.js', 8 outfile: 'dist/index.js',
9 format: 'iife', 9 format: 'iife',
diff --git a/packages/service-preload/esbuild.config.js b/packages/service-preload/esbuild.config.js
index d888987..87e91d8 100644
--- a/packages/service-preload/esbuild.config.js
+++ b/packages/service-preload/esbuild.config.js
@@ -1,9 +1,9 @@
1import { chrome } from '../../config/buildConstants.js'; 1import { chrome } from '../../config/buildConstants.js';
2import fileURLToDirname from '../../config/fileURLToDirname.js'; 2import fileUrlToDirname from '../../config/fileUrlToDirname.js';
3import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getEsbuildConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileUrlToDirname(import.meta.url),
7 entryPoints: ['src/index.ts'], 7 entryPoints: ['src/index.ts'],
8 outfile: 'dist/index.cjs', 8 outfile: 'dist/index.cjs',
9 format: 'cjs', 9 format: 'cjs',
diff --git a/packages/service-preload/src/index.ts b/packages/service-preload/src/index.ts
index 2bbfefd..bb4a62d 100644
--- a/packages/service-preload/src/index.ts
+++ b/packages/service-preload/src/index.ts
@@ -61,6 +61,6 @@ async function fetchAndExecuteInjectScript(): Promise<void> {
61 await webFrame.executeJavaScriptInIsolatedWorld(0, [injectSource]); 61 await webFrame.executeJavaScriptInIsolatedWorld(0, [injectSource]);
62} 62}
63 63
64fetchAndExecuteInjectScript().catch((err) => { 64fetchAndExecuteInjectScript().catch((error) => {
65 log.error('Failed to fetch inject source:', err); 65 log.error('Failed to fetch inject source:', error);
66}); 66});
diff --git a/packages/service-shared/esbuild.config.js b/packages/service-shared/esbuild.config.js
index 2b0dec8..62e3d2a 100644
--- a/packages/service-shared/esbuild.config.js
+++ b/packages/service-shared/esbuild.config.js
@@ -1,9 +1,9 @@
1import { chrome, node } from '../../config/buildConstants.js'; 1import { chrome, node } from '../../config/buildConstants.js';
2import fileURLToDirname from '../../config/fileURLToDirname.js'; 2import fileUrlToDirname from '../../config/fileUrlToDirname.js';
3import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getEsbuildConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileUrlToDirname(import.meta.url),
7 entryPoints: ['src/index.ts'], 7 entryPoints: ['src/index.ts'],
8 outfile: 'dist/index.mjs', 8 outfile: 'dist/index.mjs',
9 format: 'esm', 9 format: 'esm',
diff --git a/packages/shared/esbuild.config.js b/packages/shared/esbuild.config.js
index 44501bd..7a79ce6 100644
--- a/packages/shared/esbuild.config.js
+++ b/packages/shared/esbuild.config.js
@@ -1,9 +1,9 @@
1import { chrome, node } from '../../config/buildConstants.js'; 1import { chrome, node } from '../../config/buildConstants.js';
2import fileURLToDirname from '../../config/fileURLToDirname.js'; 2import fileUrlToDirname from '../../config/fileUrlToDirname.js';
3import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 3import getEsbuildConfig from '../../config/getEsbuildConfig.js';
4 4
5export default getEsbuildConfig({ 5export default getEsbuildConfig({
6 absWorkingDir: fileURLToDirname(import.meta.url), 6 absWorkingDir: fileUrlToDirname(import.meta.url),
7 entryPoints: ['src/index.ts'], 7 entryPoints: ['src/index.ts'],
8 outfile: 'dist/index.mjs', 8 outfile: 'dist/index.mjs',
9 format: 'esm', 9 format: 'esm',