diff options
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | packages/main/src/index.ts | 56 | ||||
-rw-r--r-- | packages/main/src/userAgent.ts | 61 | ||||
-rw-r--r-- | packages/service-inject/package.json | 21 | ||||
-rw-r--r-- | packages/service-inject/src/index.ts | 27 | ||||
-rw-r--r-- | packages/service-inject/src/shims/userAgentData.ts | 102 | ||||
-rw-r--r-- | packages/service-inject/src/utils.ts | 104 | ||||
-rw-r--r-- | packages/service-inject/tsconfig.json | 22 | ||||
-rw-r--r-- | packages/service-inject/vite.config.js | 28 | ||||
-rw-r--r-- | packages/service-preload/package.json | 1 | ||||
-rw-r--r-- | packages/service-preload/src/index.ts | 7 | ||||
-rw-r--r-- | packages/service-shared/src/ipc.ts | 1 | ||||
-rw-r--r-- | scripts/watch.js | 15 | ||||
-rw-r--r-- | yarn.lock | 11 |
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 | ||
21 | import { app, BrowserView, BrowserWindow } from 'electron'; | 21 | import { app, BrowserView, BrowserWindow } from 'electron'; |
22 | import { readFile, readFileSync } from 'fs'; | ||
22 | import { autorun } from 'mobx'; | 23 | import { autorun } from 'mobx'; |
23 | import { getSnapshot, onPatch } from 'mobx-state-tree'; | 24 | import { getSnapshot, onPatch } from 'mobx-state-tree'; |
24 | import { join } from 'path'; | 25 | import { join } from 'path'; |
@@ -39,7 +40,6 @@ import { | |||
39 | openDevToolsWhenReady, | 40 | openDevToolsWhenReady, |
40 | } from './devTools'; | 41 | } from './devTools'; |
41 | import { createRootStore } from './stores/RootStore'; | 42 | import { createRootStore } from './stores/RootStore'; |
42 | import { reduceUserAgent } from './userAgent'; | ||
43 | 43 | ||
44 | const isDevelopment = import.meta.env.MODE === 'development'; | 44 | const 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. |
69 | const originalUserAgent = app.userAgentFallback; | 69 | const originalUserAgent = app.userAgentFallback; |
70 | const userAgent = reduceUserAgent(originalUserAgent); | 70 | const userAgent = originalUserAgent.replaceAll(/\s(sophie|Electron)\/\S+/g, ''); |
71 | const platformInUa = userAgent.match(/\((Win|Mac|X11; L)/); | ||
72 | let platform = 'Unknown'; | ||
73 | if (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 | } | ||
86 | const 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. |
72 | if (!isDevelopment) { | 88 | if (!isDevelopment) { |
73 | app.userAgentFallback = userAgent; | 89 | app.userAgentFallback = userAgent; |
74 | } | 90 | } |
75 | 91 | ||
92 | let serviceInjectRelativePath = '../../service-inject/dist/index.cjs'; | ||
93 | let serviceInjectPath = join(__dirname, serviceInjectRelativePath); | ||
94 | let serviceInjectUrl = new URL(serviceInjectRelativePath, `file://${__dirname}`).toString(); | ||
95 | let serviceInject: string = readFileSync(serviceInjectPath, 'utf8'); | ||
96 | |||
76 | if (isDevelopment) { | 97 | if (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 | |||
29 | const electronUAParts = / (sophie|Electron)\/[^\s]+/g; | ||
30 | const 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$/; | ||
31 | const 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 | */ | ||
46 | export 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 | |||
21 | import { shimUserAgentData } from './shims/userAgentData'; | ||
22 | |||
23 | try { | ||
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 | |||
21 | import { | ||
22 | defineProtoProperty, | ||
23 | deleteProtoProperty, | ||
24 | simulateNativeClass, | ||
25 | simulateNativeFunction, | ||
26 | } from '../utils'; | ||
27 | |||
28 | export 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 | |||
100 | export 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 | */ | ||
29 | export 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 | */ | ||
53 | export 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 | */ | ||
86 | export 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 | */ | ||
102 | export 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 | |||
3 | import { builtinModules } from 'module'; | ||
4 | |||
5 | import { chrome, makeConfig } from '../../config/vite-common'; | ||
6 | |||
7 | /** @type {string} */ | ||
8 | const PACKAGE_ROOT = __dirname; | ||
9 | |||
10 | /** | ||
11 | * @type {import('vite').UserConfig} | ||
12 | * @see https://vitejs.dev/config/ | ||
13 | */ | ||
14 | const 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 | |||
28 | export 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 @@ | |||
21 | import { ipcRenderer } from 'electron'; | 21 | import { ipcRenderer } from 'electron'; |
22 | import { ServiceToMainIpcMessage } from '@sophie/service-shared'; | 22 | import { ServiceToMainIpcMessage } from '@sophie/service-shared'; |
23 | 23 | ||
24 | let i = 1; | 24 | ipcRenderer.send(ServiceToMainIpcMessage.ApiExposedInMainWorld); |
25 | |||
26 | setInterval(() => { | ||
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 | ||
21 | export enum ServiceToMainIpcMessage { | 21 | export 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 | */ |
136 | function setupServicePreloadPackageWatcher(viteDevServer) { | 137 | function 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 | */ |
166 | async function setupDevEnvironment() { | 167 | async 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); |
@@ -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" |