diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-25 00:01:18 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-25 00:01:18 +0100 |
commit | e321534fbea9f09b139d440584f6b84ad0afb80f (patch) | |
tree | c4b4df109589475dd5f40a0d31f47a6aa9e43195 /packages/service-inject/src | |
parent | feat: Shim userAgentData in all frames and workers (diff) | |
download | sophie-e321534fbea9f09b139d440584f6b84ad0afb80f.tar.gz sophie-e321534fbea9f09b139d440584f6b84ad0afb80f.tar.zst sophie-e321534fbea9f09b139d440584f6b84ad0afb80f.zip |
refactor: Simplify script injection
Inject CSS and main world scripts synchronously to avoid race conditions
with page loading.
Don't try to miming userAgentData for now, since it won't bypass
google's checks. However, simply omitting chrome from the user agent
does bypass them, at least for now.
Diffstat (limited to 'packages/service-inject/src')
-rw-r--r-- | packages/service-inject/src/index.ts | 8 | ||||
-rw-r--r-- | packages/service-inject/src/shims/userAgentData.ts | 103 | ||||
-rw-r--r-- | packages/service-inject/src/utils.ts | 104 |
3 files changed, 1 insertions, 214 deletions
diff --git a/packages/service-inject/src/index.ts b/packages/service-inject/src/index.ts index f699f11..a7ada84 100644 --- a/packages/service-inject/src/index.ts +++ b/packages/service-inject/src/index.ts | |||
@@ -18,10 +18,4 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { shimUserAgentData } from './shims/userAgentData'; | 21 | export {} |
22 | |||
23 | try { | ||
24 | shimUserAgentData('96', 'Linux'); | ||
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 deleted file mode 100644 index 7e2c825..0000000 --- a/packages/service-inject/src/shims/userAgentData.ts +++ /dev/null | |||
@@ -1,103 +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 | import { | ||
22 | defineProtoProperty, | ||
23 | deleteProtoProperty, | ||
24 | simulateNativeClass, | ||
25 | simulateNativeFunction, | ||
26 | } from '../utils'; | ||
27 | |||
28 | export function shimUserAgentData(chromeVersion: string | null, platform: string): void { | ||
29 | const brands = [ | ||
30 | { | ||
31 | brand: ' Not A; Brand', | ||
32 | version: '99', | ||
33 | }, | ||
34 | ]; | ||
35 | if (chromeVersion !== null) { | ||
36 | brands.push({ | ||
37 | brand: 'Chromium', | ||
38 | version: '96', | ||
39 | }); | ||
40 | } | ||
41 | const mobile = false; | ||
42 | |||
43 | const simulatedNavigatorUa = simulateNativeClass('NavigatorUAData', function NavigatorUAData() { | ||
44 | // Nothing to initiailize. | ||
45 | }, { | ||
46 | brands: { | ||
47 | configurable: true, | ||
48 | enumerable: true, | ||
49 | get: simulateNativeFunction('brands', () => brands), | ||
50 | }, | ||
51 | mobile: { | ||
52 | configurable: true, | ||
53 | enumerable: true, | ||
54 | get: simulateNativeFunction('mobile', () => mobile), | ||
55 | }, | ||
56 | platform: { | ||
57 | configurable: true, | ||
58 | enumerable: true, | ||
59 | get: simulateNativeFunction('platform', () => platform), | ||
60 | }, | ||
61 | getHighEntropyValues: { | ||
62 | configurable: true, | ||
63 | enumerable: false, | ||
64 | value: simulateNativeFunction('getHighEntropyValues', (...args: unknown[]) => { | ||
65 | if (args.length == 0) { | ||
66 | throw new TypeError("Failed to execute 'getHighEntropyValues' on 'NavigatorUAData': 1 argument required, but only 0 present."); | ||
67 | } | ||
68 | const hints = Array.from(args[0] as Iterable<string>); | ||
69 | if (hints.length === 0) { | ||
70 | return {}; | ||
71 | } | ||
72 | const data: Record<string, unknown> = { | ||
73 | brands, | ||
74 | mobile, | ||
75 | } | ||
76 | if (hints.includes('platform')) { | ||
77 | data['platform'] = platform; | ||
78 | } | ||
79 | return Promise.resolve(data); | ||
80 | }) | ||
81 | }, | ||
82 | toJSON: { | ||
83 | configurable: true, | ||
84 | enumerable: false, | ||
85 | value: simulateNativeFunction('toJSON', () => ({ | ||
86 | brands, | ||
87 | mobile, | ||
88 | })), | ||
89 | writable: false, | ||
90 | }, | ||
91 | }); | ||
92 | |||
93 | const simulatedUserAgentData = Reflect.construct(simulatedNavigatorUa, []); | ||
94 | defineProtoProperty(globalThis.navigator, 'userAgentData', { | ||
95 | configurable: true, | ||
96 | enumerable: true, | ||
97 | get: simulateNativeFunction('userAgentData', () => simulatedUserAgentData), | ||
98 | }); | ||
99 | } | ||
100 | |||
101 | export function deleteUserAgentData(): void { | ||
102 | deleteProtoProperty(globalThis.navigator, 'userAgentData'); | ||
103 | } | ||
diff --git a/packages/service-inject/src/utils.ts b/packages/service-inject/src/utils.ts deleted file mode 100644 index 4bb3fba..0000000 --- a/packages/service-inject/src/utils.ts +++ /dev/null | |||
@@ -1,104 +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 | * 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 | } | ||