aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-24 19:00:07 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-24 19:13:56 +0100
commit90471302835dad5251ea568091cfd6c21d757fd3 (patch)
tree66ba2d5181ef4c4cb552c53936369bcb530c524c
parentfix: Typings in js config files (diff)
downloadsophie-90471302835dad5251ea568091cfd6c21d757fd3.tar.gz
sophie-90471302835dad5251ea568091cfd6c21d757fd3.tar.zst
sophie-90471302835dad5251ea568091cfd6c21d757fd3.zip
feat: User agent data simulator
-rw-r--r--package.json2
-rw-r--r--packages/main/src/index.ts56
-rw-r--r--packages/main/src/userAgent.ts61
-rw-r--r--packages/service-inject/package.json21
-rw-r--r--packages/service-inject/src/index.ts27
-rw-r--r--packages/service-inject/src/shims/userAgentData.ts102
-rw-r--r--packages/service-inject/src/utils.ts104
-rw-r--r--packages/service-inject/tsconfig.json22
-rw-r--r--packages/service-inject/vite.config.js28
-rw-r--r--packages/service-preload/package.json1
-rw-r--r--packages/service-preload/src/index.ts7
-rw-r--r--packages/service-shared/src/ipc.ts1
-rw-r--r--scripts/watch.js15
-rw-r--r--yarn.lock11
14 files changed, 380 insertions, 78 deletions
diff --git a/package.json b/package.json
index b03bdd4..5ea6ecb 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
25 "main": "yarn workspace @sophie/main", 25 "main": "yarn workspace @sophie/main",
26 "preload": "yarn workspace @sophie/preload", 26 "preload": "yarn workspace @sophie/preload",
27 "renderer": "yarn workspace @sophie/renderer", 27 "renderer": "yarn workspace @sophie/renderer",
28 "service-inject": "yarn workspace @sophie/service-inject",
28 "service-preload": "yarn workspace @sophie/service-preload", 29 "service-preload": "yarn workspace @sophie/service-preload",
29 "service-shared": "yarn workspace @sophie/service-shared", 30 "service-shared": "yarn workspace @sophie/service-shared",
30 "shared": "yarn workspace @sophie/shared" 31 "shared": "yarn workspace @sophie/shared"
@@ -33,6 +34,7 @@
33 "packages/main", 34 "packages/main",
34 "packages/preload", 35 "packages/preload",
35 "packages/renderer", 36 "packages/renderer",
37 "packages/service-inject",
36 "packages/service-preload", 38 "packages/service-preload",
37 "packages/service-shared", 39 "packages/service-shared",
38 "packages/shared" 40 "packages/shared"
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index b6df67b..70acb15 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -19,6 +19,7 @@
19 */ 19 */
20 20
21import { app, BrowserView, BrowserWindow } from 'electron'; 21import { app, BrowserView, BrowserWindow } from 'electron';
22import { readFile, readFileSync } from 'fs';
22import { autorun } from 'mobx'; 23import { autorun } from 'mobx';
23import { getSnapshot, onPatch } from 'mobx-state-tree'; 24import { getSnapshot, onPatch } from 'mobx-state-tree';
24import { join } from 'path'; 25import { join } from 'path';
@@ -39,7 +40,6 @@ import {
39 openDevToolsWhenReady, 40 openDevToolsWhenReady,
40} from './devTools'; 41} from './devTools';
41import { createRootStore } from './stores/RootStore'; 42import { createRootStore } from './stores/RootStore';
42import { reduceUserAgent } from './userAgent';
43 43
44const isDevelopment = import.meta.env.MODE === 'development'; 44const isDevelopment = import.meta.env.MODE === 'development';
45 45
@@ -67,12 +67,33 @@ app.commandLine.appendSwitch(
67 67
68// Remove sophie and electron from the user-agent string to avoid detection. 68// Remove sophie and electron from the user-agent string to avoid detection.
69const originalUserAgent = app.userAgentFallback; 69const originalUserAgent = app.userAgentFallback;
70const userAgent = reduceUserAgent(originalUserAgent); 70const userAgent = originalUserAgent.replaceAll(/\s(sophie|Electron)\/\S+/g, '');
71const platformInUa = userAgent.match(/\((Win|Mac|X11; L)/);
72let platform = 'Unknown';
73if (platformInUa !== null) {
74 switch (platformInUa[1]) {
75 case 'Win':
76 platform = 'Windows';
77 break;
78 case 'Mac':
79 platform = 'macOS';
80 break;
81 case 'X11; L':
82 platform = 'Linux';
83 break;
84 }
85}
86const chromiumVersion = process.versions.chrome.split('.')[0];
71// Removing the electron version breaks redux devtools, so we only do this in production. 87// Removing the electron version breaks redux devtools, so we only do this in production.
72if (!isDevelopment) { 88if (!isDevelopment) {
73 app.userAgentFallback = userAgent; 89 app.userAgentFallback = userAgent;
74} 90}
75 91
92let serviceInjectRelativePath = '../../service-inject/dist/index.cjs';
93let serviceInjectPath = join(__dirname, serviceInjectRelativePath);
94let serviceInjectUrl = new URL(serviceInjectRelativePath, `file://${__dirname}`).toString();
95let serviceInject: string = readFileSync(serviceInjectPath, 'utf8');
96
76if (isDevelopment) { 97if (isDevelopment) {
77 installDevToolsExtensions(app); 98 installDevToolsExtensions(app);
78} 99}
@@ -92,7 +113,7 @@ function createWindow(): Promise<unknown> {
92 }); 113 });
93 114
94 if (isDevelopment) { 115 if (isDevelopment) {
95 openDevToolsWhenReady(mainWindow); 116 //openDevToolsWhenReady(mainWindow);
96 } 117 }
97 118
98 mainWindow.on('ready-to-show', () => { 119 mainWindow.on('ready-to-show', () => {
@@ -131,7 +152,14 @@ function createWindow(): Promise<unknown> {
131 store.setPaletteMode(paletteMode.parse(args[0])) 152 store.setPaletteMode(paletteMode.parse(args[0]))
132 break; 153 break;
133 case RendererToMainIpcMessage.ReloadAllServices: 154 case RendererToMainIpcMessage.ReloadAllServices:
134 browserView.webContents.reload(); 155 readFile(serviceInjectPath, 'utf8', (err, data) => {
156 if (err === null) {
157 serviceInject = data;
158 } else {
159 console.error('Error while reloading', serviceInjectPath, err);
160 }
161 browserView.webContents.reload();
162 });
135 break; 163 break;
136 default: 164 default:
137 console.error('Unknown IPC message:', channel, args); 165 console.error('Unknown IPC message:', channel, args);
@@ -149,6 +177,16 @@ function createWindow(): Promise<unknown> {
149 browserView.webContents.on('ipc-message', (_event, channel, ...args) => { 177 browserView.webContents.on('ipc-message', (_event, channel, ...args) => {
150 try { 178 try {
151 switch (channel) { 179 switch (channel) {
180 case ServiceToMainIpcMessage.ApiExposedInMainWorld:
181 browserView.webContents.executeJavaScriptInIsolatedWorld(0, [
182 {
183 code: serviceInject,
184 url: serviceInjectUrl,
185 }
186 ]).catch((err) => {
187 console.error('Cannot inject script:', err);
188 });
189 break;
152 case ServiceToMainIpcMessage.SetUnreadCount: 190 case ServiceToMainIpcMessage.SetUnreadCount:
153 console.log('Unread count:', unreadCount.parse(args[0])); 191 console.log('Unread count:', unreadCount.parse(args[0]));
154 break; 192 break;
@@ -172,13 +210,21 @@ function createWindow(): Promise<unknown> {
172 ); 210 );
173 }); 211 });
174 212
213 browserView.webContents.session.webRequest.onBeforeSendHeaders(({ requestHeaders }, callback) => {
214 requestHeaders['User-Agent'] = userAgent;
215 requestHeaders['Sec-CH-UA'] = `" Not A;Brand";v="99", "Chromium";v="${chromiumVersion}"`;
216 requestHeaders['Sec-CH-UA-Mobile'] = '?0';
217 requestHeaders['Sec-CH-UA-Platform'] = platform;
218 callback({ requestHeaders });
219 });
220
175 const pageUrl = (isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined) 221 const pageUrl = (isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined)
176 ? import.meta.env.VITE_DEV_SERVER_URL 222 ? import.meta.env.VITE_DEV_SERVER_URL
177 : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString(); 223 : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString();
178 224
179 return Promise.all([ 225 return Promise.all([
180 mainWindow.loadURL(pageUrl), 226 mainWindow.loadURL(pageUrl),
181 browserView.webContents.loadURL('https://git.marussy.com/sophie/about'), 227 browserView.webContents.loadURL('https://gmail.com').then(() => browserView.webContents.openDevTools()),
182 ]); 228 ]);
183} 229}
184 230
diff --git a/packages/main/src/userAgent.ts b/packages/main/src/userAgent.ts
deleted file mode 100644
index 298e565..0000000
--- a/packages/main/src/userAgent.ts
+++ /dev/null
@@ -1,61 +0,0 @@
1/*
2 * Copyright (C) 2021-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
21/**
22 * @file Based on the javascript code snippet available at
23 * https://github.com/GoogleChrome/developer.chrome.com/blob/c33f7f4f2964e7deb28aee44de745c3725b70063/site/en/docs/privacy-sandbox/user-agent/snippets/index.md
24 *
25 * Used under the Apache 2.0 license according to
26 * https://github.com/GoogleChrome/developer.chrome.com/blob/c33f7f4f2964e7deb28aee44de745c3725b70063/LICENSE
27 */
28
29const electronUAParts = / (sophie|Electron)\/[^\s]+/g;
30const chromeUAs = /^Mozilla\/5\.0 \(((?<platform>Lin|Win|Mac|X11; C|X11; L)+[^\)]+)\) AppleWebKit\/537.36 \(KHTML, like Gecko\) Chrome\/(?<major>\d+)[\d\.]+(?<mobile>[ Mobile]*) Safari\/537\.36$/;
31const unifiedPlatform = {
32 'Lin': 'Linux; Android 10; K',
33 'Win': 'Windows NT 10.0; Win64; x64',
34 'Mac': 'Macintosh; Intel Mac OS X 10_15_7',
35 'X11; C': 'X11; CrOS x86_64',
36 'X11; L': 'X11; Linux x86_64',
37};
38
39/**
40 * Reduces the information exposed in the user-agent string.
41 *
42 * @param userAgent The original user-agent string.
43 * @returns The reduces user-agent string.
44 * @see https://developer.chrome.com/docs/privacy-sandbox/user-agent/
45 */
46export function reduceUserAgent(userAgent: string): string {
47 const userAgentWithoutElectron = userAgent.replaceAll(electronUAParts, '');
48 const matched = chromeUAs.exec(userAgentWithoutElectron) as unknown as {
49 groups: {
50 platform: 'Lin' | 'Win' | 'Mac' | 'X11; C' | 'X11; L',
51 major: string,
52 mobile: string,
53 }
54 };
55 if (matched) {
56 return `Mozilla/5.0 (${unifiedPlatform[matched.groups.platform]}) ` +
57 `AppleWebKit/537.36 (KHTML, like Gecko) ` +
58 `Chrome/${matched.groups.major}.0.0.0${matched.groups.mobile} Safari/537.36`
59 }
60 return userAgentWithoutElectron;
61}
diff --git a/packages/service-inject/package.json b/packages/service-inject/package.json
new file mode 100644
index 0000000..e2d03bf
--- /dev/null
+++ b/packages/service-inject/package.json
@@ -0,0 +1,21 @@
1{
2 "name": "@sophie/service-inject",
3 "version": "0.1.0",
4 "private": true,
5 "sideEffects": false,
6 "main": "dist/index.cjs",
7 "types": "dist-types/index.d.ts",
8 "scripts": {
9 "clean": "rimraf dist dist-types tsconfig.tsbuildinfo",
10 "build": "vite build",
11 "typecheck": "tsc"
12 },
13 "dependencies": {
14 "@sophie/service-shared": "workspace:*"
15 },
16 "devDependencies": {
17 "rimraf": "^3.0.2",
18 "typescript": "^4.5.4",
19 "vite": "^2.7.6"
20 }
21}
diff --git a/packages/service-inject/src/index.ts b/packages/service-inject/src/index.ts
new file mode 100644
index 0000000..7f48fdf
--- /dev/null
+++ b/packages/service-inject/src/index.ts
@@ -0,0 +1,27 @@
1/*
2 * Copyright (C) 2021-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 { shimUserAgentData } from './shims/userAgentData';
22
23try {
24 shimUserAgentData();
25} catch (err) {
26 console.log('Failed to execute injected script:', err);
27}
diff --git a/packages/service-inject/src/shims/userAgentData.ts b/packages/service-inject/src/shims/userAgentData.ts
new file mode 100644
index 0000000..be43823
--- /dev/null
+++ b/packages/service-inject/src/shims/userAgentData.ts
@@ -0,0 +1,102 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import {
22 defineProtoProperty,
23 deleteProtoProperty,
24 simulateNativeClass,
25 simulateNativeFunction,
26} from '../utils';
27
28export function shimUserAgentData(): void {
29 const brands = [
30 {
31 brand: ' Not A; Brand',
32 version: '99',
33 },
34 {
35 brand: 'Chromium',
36 version: '96',
37 }
38 ];
39 const mobile = false;
40 const platform = 'Linux';
41
42 const simulatedNavigatorUa = simulateNativeClass('NavigatorUAData', function NavigatorUAData() {
43 // Nothing to initiailize.
44 }, {
45 brands: {
46 configurable: true,
47 enumerable: true,
48 get: simulateNativeFunction('brands', () => brands),
49 },
50 mobile: {
51 configurable: true,
52 enumerable: true,
53 get: simulateNativeFunction('mobile', () => mobile),
54 },
55 platform: {
56 configurable: true,
57 enumerable: true,
58 get: simulateNativeFunction('platform', () => platform),
59 },
60 getHighEntropyValues: {
61 configurable: true,
62 enumerable: false,
63 value: simulateNativeFunction('getHighEntropyValues', (...args: unknown[]) => {
64 if (args.length == 0) {
65 throw new TypeError("Failed to execute 'getHighEntropyValues' on 'NavigatorUAData': 1 argument required, but only 0 present.");
66 }
67 const hints = Array.from(args[0] as Iterable<string>);
68 if (hints.length === 0) {
69 return {};
70 }
71 const data: Record<string, unknown> = {
72 brands,
73 mobile,
74 }
75 if (hints.includes('platform')) {
76 data['platform'] = platform;
77 }
78 return Promise.resolve(data);
79 })
80 },
81 toJSON: {
82 configurable: true,
83 enumerable: false,
84 value: simulateNativeFunction('toJSON', () => ({
85 brands,
86 mobile,
87 })),
88 writable: false,
89 },
90 });
91
92 const simulatedUserAgentData = Reflect.construct(simulatedNavigatorUa, []);
93 defineProtoProperty(window.navigator, 'userAgentData', {
94 configurable: true,
95 enumerable: true,
96 get: simulateNativeFunction('userAgentData', () => simulatedUserAgentData),
97 });
98}
99
100export function deleteUserAgentData(): void {
101 deleteProtoProperty(window.navigator, 'userAgentData');
102}
diff --git a/packages/service-inject/src/utils.ts b/packages/service-inject/src/utils.ts
new file mode 100644
index 0000000..4bb3fba
--- /dev/null
+++ b/packages/service-inject/src/utils.ts
@@ -0,0 +1,104 @@
1/*
2 * Copyright (C) 2021-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
21/**
22 * Simulates a function defined in native code, i.e., one with
23 * `[native code]` in its `toString`.
24 *
25 * @param name The name of the function.
26 * @param f The function to transform.
27 * @return The transformed function.
28 */
29export function simulateNativeFunction<T, P extends unknown[]>(
30 name: string,
31 f: (this: null, ...args: P) => T,
32): (...args: P) => T {
33 // Bound functions say `[native code]`, but unfortunately they omit the function name:
34 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString#description
35 // The type of `f` contains type variables, so we need some magic type casting.
36 const boundFunc = f.bind(null as ThisParameterType<typeof f>);
37 Object.defineProperty(boundFunc, 'name', {
38 configurable: true,
39 enumerable: false,
40 value: name,
41 writable: false,
42 });
43 return boundFunc;
44}
45
46/**
47 * Simulates a native class available on `globalThis`.
48 *
49 * @param name The name of the class.
50 * @param constructor The constructor function. Must already be a constructor (a named `function`).
51 * @param properties The properties to define on the prototype.
52 */
53export function simulateNativeClass(
54 name: string,
55 constructor: () => void,
56 properties: PropertyDescriptorMap,
57) {
58 Object.defineProperties(constructor.prototype, {
59 [Symbol.toStringTag]: {
60 configurable: true,
61 enumerable: false,
62 value: name,
63 writable: false,
64 },
65 ...properties,
66 });
67 const simulatedConstructor = simulateNativeFunction(name, constructor);
68 Object.defineProperty(globalThis, name, {
69 configurable: true,
70 enumerable: true,
71 value: simulatedConstructor,
72 writable: true,
73 });
74 return simulatedConstructor;
75}
76
77/**
78 * Defines a property on the prototype of an object.
79 *
80 * Only use this with singleton objects, e.g., `window.navigator`.
81 *
82 * @param o The object to modify. Must be a singleton.
83 * @param property The key of the property being defined or modified.
84 * @param attributes The descriptor of the property being defined or modified.
85 */
86export function defineProtoProperty(
87 o: object,
88 property: PropertyKey,
89 attributes: PropertyDescriptor,
90): void {
91 Object.defineProperty(Object.getPrototypeOf(o), property, attributes);
92}
93
94/**
95 * Deletes a property from the prototype of an object.
96 *
97 * Only use this with singleton objects, e.g., `window.navigator`.
98 *
99 * @param o The object to modify. Must be a singleton.
100 * @param property The key of the property being deleted.
101 */
102export function deleteProtoProperty(o: object, property: PropertyKey): void {
103 Reflect.deleteProperty(Object.getPrototypeOf(o), property);
104}
diff --git a/packages/service-inject/tsconfig.json b/packages/service-inject/tsconfig.json
new file mode 100644
index 0000000..4007f60
--- /dev/null
+++ b/packages/service-inject/tsconfig.json
@@ -0,0 +1,22 @@
1{
2 "extends": "../../tsconfig.json",
3 "compilerOptions": {
4 "composite": true,
5 "declarationDir": "dist-types",
6 "emitDeclarationOnly": true,
7 "rootDir": "src",
8 "lib": [
9 "dom",
10 "dom.iterable",
11 "esnext"
12 ]
13 },
14 "references": [
15 {
16 "path": "../service-shared"
17 }
18 ],
19 "include": [
20 "src/**/*.ts"
21 ]
22}
diff --git a/packages/service-inject/vite.config.js b/packages/service-inject/vite.config.js
new file mode 100644
index 0000000..9c65c15
--- /dev/null
+++ b/packages/service-inject/vite.config.js
@@ -0,0 +1,28 @@
1// @ts-check
2
3import { builtinModules } from 'module';
4
5import { chrome, makeConfig } from '../../config/vite-common';
6
7/** @type {string} */
8const PACKAGE_ROOT = __dirname;
9
10/**
11 * @type {import('vite').UserConfig}
12 * @see https://vitejs.dev/config/
13 */
14const config = makeConfig({
15 root: PACKAGE_ROOT,
16 build: {
17 target: chrome,
18 lib: {
19 entry: 'src/index.ts',
20 formats: ['cjs'],
21 },
22 rollupOptions: {
23 external: builtinModules,
24 },
25 },
26});
27
28export default config;
diff --git a/packages/service-preload/package.json b/packages/service-preload/package.json
index 1296cda..e6358a0 100644
--- a/packages/service-preload/package.json
+++ b/packages/service-preload/package.json
@@ -2,7 +2,6 @@
2 "name": "@sophie/service-preload", 2 "name": "@sophie/service-preload",
3 "version": "0.1.0", 3 "version": "0.1.0",
4 "private": true, 4 "private": true,
5 "sideEffects": false,
6 "main": "dist/index.cjs", 5 "main": "dist/index.cjs",
7 "types": "dist-types/index.d.ts", 6 "types": "dist-types/index.d.ts",
8 "scripts": { 7 "scripts": {
diff --git a/packages/service-preload/src/index.ts b/packages/service-preload/src/index.ts
index 4e93a07..3f54c0b 100644
--- a/packages/service-preload/src/index.ts
+++ b/packages/service-preload/src/index.ts
@@ -21,9 +21,4 @@
21import { ipcRenderer } from 'electron'; 21import { ipcRenderer } from 'electron';
22import { ServiceToMainIpcMessage } from '@sophie/service-shared'; 22import { ServiceToMainIpcMessage } from '@sophie/service-shared';
23 23
24let i = 1; 24ipcRenderer.send(ServiceToMainIpcMessage.ApiExposedInMainWorld);
25
26setInterval(() => {
27 ipcRenderer.send(ServiceToMainIpcMessage.SetUnreadCount, { direct: i });
28 i += 1;
29}, 1000);
diff --git a/packages/service-shared/src/ipc.ts b/packages/service-shared/src/ipc.ts
index ad2ceca..4f991c5 100644
--- a/packages/service-shared/src/ipc.ts
+++ b/packages/service-shared/src/ipc.ts
@@ -19,5 +19,6 @@
19 */ 19 */
20 20
21export enum ServiceToMainIpcMessage { 21export enum ServiceToMainIpcMessage {
22 ApiExposedInMainWorld = 'sophie-service-to-main:api-exposed-in-main-world',
22 SetUnreadCount = 'sophie-service-to-main:set-unread-count', 23 SetUnreadCount = 'sophie-service-to-main:set-unread-count',
23} 24}
diff --git a/scripts/watch.js b/scripts/watch.js
index 243710c..82dc01e 100644
--- a/scripts/watch.js
+++ b/scripts/watch.js
@@ -131,12 +131,13 @@ function setupPreloadPackageWatcher(viteDevServer) {
131 * Reload services when source files are changed. 131 * Reload services when source files are changed.
132 * 132 *
133 * @param {import('vite').ViteDevServer} viteDevServer 133 * @param {import('vite').ViteDevServer} viteDevServer
134 * @param {string} packageName The name of the package to watch.
134 * @returns {Promise<unknown>} 135 * @returns {Promise<unknown>}
135 */ 136 */
136function setupServicePreloadPackageWatcher(viteDevServer) { 137function setupServicePreloadPackageWatcher(viteDevServer, packageName) {
137 return getWatcher({ 138 return getWatcher({
138 name: 'reload-services-on-service-preload-package-change', 139 name: `reload-services-on-${packageName}-package-change`,
139 configFile: 'packages/service-preload/vite.config.js', 140 configFile: `packages/${packageName}/vite.config.js`,
140 writeBundle() { 141 writeBundle() {
141 console.log('wrote'); 142 console.log('wrote');
142 viteDevServer.ws.send({ 143 viteDevServer.ws.send({
@@ -164,6 +165,8 @@ function setupSharedPackageWatcher(packageName) {
164 * @returns {Promise<unknown>} 165 * @returns {Promise<unknown>}
165 */ 166 */
166async function setupDevEnvironment() { 167async function setupDevEnvironment() {
168 const serviceSharedWatcher = setupSharedPackageWatcher('service-shared');
169
167 await setupSharedPackageWatcher('shared'); 170 await setupSharedPackageWatcher('shared');
168 171
169 const viteDevServer = await createServer({ 172 const viteDevServer = await createServer({
@@ -174,8 +177,10 @@ async function setupDevEnvironment() {
174 177
175 await Promise.all([ 178 await Promise.all([
176 setupPreloadPackageWatcher(viteDevServer), 179 setupPreloadPackageWatcher(viteDevServer),
177 setupServicePreloadPackageWatcher(viteDevServer), 180 serviceSharedWatcher.then(() => Promise.all([
178 setupSharedPackageWatcher('shared'), 181 setupServicePreloadPackageWatcher(viteDevServer, 'service-inject'),
182 setupServicePreloadPackageWatcher(viteDevServer, 'service-preload'),
183 ])),
179 ]); 184 ]);
180 185
181 return setupMainPackageWatcher(viteDevServer); 186 return setupMainPackageWatcher(viteDevServer);
diff --git a/yarn.lock b/yarn.lock
index ef16435..4f98c8e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -878,6 +878,17 @@ __metadata:
878 languageName: unknown 878 languageName: unknown
879 linkType: soft 879 linkType: soft
880 880
881"@sophie/service-inject@workspace:packages/service-inject":
882 version: 0.0.0-use.local
883 resolution: "@sophie/service-inject@workspace:packages/service-inject"
884 dependencies:
885 "@sophie/service-shared": "workspace:*"
886 rimraf: ^3.0.2
887 typescript: ^4.5.4
888 vite: ^2.7.6
889 languageName: unknown
890 linkType: soft
891
881"@sophie/service-preload@workspace:packages/service-preload": 892"@sophie/service-preload@workspace:packages/service-preload":
882 version: 0.0.0-use.local 893 version: 0.0.0-use.local
883 resolution: "@sophie/service-preload@workspace:packages/service-preload" 894 resolution: "@sophie/service-preload@workspace:packages/service-preload"