Skip to content

Commit

Permalink
wip: 🔕 temporary commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tarampampam committed Apr 29, 2024
1 parent 6490fad commit 4d25c83
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 27 deletions.
2 changes: 0 additions & 2 deletions src/entrypoints/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
script.setAttribute('id', __UNIQUE_INJECT_FILENAME__)
script.src = chrome.runtime.getURL(__UNIQUE_INJECT_FILENAME__)

Object.defineProperty(Navigator.prototype, 'userAgent', { get: () => 'foobar' })

parent.prepend(script)
} catch (err) {
console.warn('🧨 RUA: An error occurred in the content script', err)
Expand Down
213 changes: 189 additions & 24 deletions src/entrypoints/content/inject.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
// ⚠ DO NOT IMPORT ANYTHING EXCEPT TYPES HERE DUE THE `import()` ERRORS ⚠
import type { ContentScriptPayload } from '~/shared/types'
import type { DeepWriteable } from '~/types'

// wrap everything to avoid polluting the global scope
;(() => {
// prevent the script from running multiple times
{
const [key, ds] = [__UNIQUE_HEADER_KEY_NAME__.toLowerCase(), document.documentElement.dataset]

if (ds[key] === 'true') {
return
}

ds[key] = 'true'
}

const debug = (m: string, ...a: unknown[]): void => console.debug(`%c💣 [inject.js]: ${m}`, 'font-weight:bold', ...a)

debug('Injected script is running')
Expand Down Expand Up @@ -50,16 +62,79 @@ import type { ContentScriptPayload } from '~/shared/types'
/** @link https://developer.mozilla.org/en-US/docs/Web/API/Navigator */
const patchNavigator = (n: Navigator): void => {
/** Overloads the navigator object property with the new value. */
const overload = <T extends Navigator>(target: T, prop: keyof T | 'oscpu', value: unknown): void => {
const descriptor = Object.getOwnPropertyDescriptor(target, prop)
const overload = <T = Navigator>(
t: T,
prop: T extends Navigator ? keyof T | 'oscpu' : keyof T,
value: unknown,
force: boolean = false
): void => {
let target: T = t

try {
while (target !== null) {
const descriptor = Object.getOwnPropertyDescriptor(target, prop)

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
if (descriptor && descriptor.configurable) {
const newAttributes: PropertyDescriptor = { configurable: false, enumerable: true }

// respect the original value getting method
if (descriptor.get) {
newAttributes.get = () => value
} else {
newAttributes.value = value
newAttributes.writable = false
}

Object.defineProperty(target, prop, newAttributes)
} else if (force) {
Object.defineProperty(target, prop, {
value,
configurable: false,
enumerable: true,
writable: false,
})
}

if (descriptor && descriptor.configurable) {
Object.defineProperty(target, prop, { get: () => value })
target = Object.getPrototypeOf(target)
}
} catch (_) {
// do nothing
}
}

overload(n, 'userAgent', payload.current.userAgent)
// to test, execute in the console: `console.log(navigator.userAgent)`
overload(
n,
'userAgent',
((): string => {
switch (payload.current.browser) {
case 'chrome':
case 'opera':
case 'edge': // blink engine
// mask the browser (and under the hood) versions, keeping only the major version (e.g., 92.0.4515.107 -> 92.0.0.0)
const masked = payload.current.userAgent.replaceAll(
payload.current.version.browser.full,
payload.current.version.browser.major +
'.0'.repeat(Math.max(0, payload.current.version.browser.full.split('.').length - 1))
)

if (payload.current.version.underHood) {
return masked.replaceAll(
payload.current.version.underHood.full || '',
payload.current.version.underHood.major +
'.0'.repeat(Math.max(0, payload.current.version.underHood.full.split('.').length - 1))
)
}

return masked
}

return payload.current.userAgent
})()
)

// to test, execute in the console: `console.log(navigator.appVersion)`
overload(
n,
'appVersion',
Expand All @@ -78,39 +153,39 @@ import type { ContentScriptPayload } from '~/shared/types'
return payload.current.userAgent.replace(/^Mozilla\//i, '')
})()
)

// to test, execute in the console: : `debug(navigator.platform, navigator.oscpu)`
switch (payload.platform) {
case 'Windows':
debug('payload', payload)
// to test, execute in the console: `console.log(navigator.platform, navigator.oscpu)`
switch (payload.current.os) {
case 'windows':
overload(n, 'platform', 'Win32')
overload(n, 'oscpu', 'Windows NT; Win64; x64')
overload(n, 'oscpu', payload.current.browser === 'firefox' ? 'Windows NT; Win64; x64' : undefined, true)
break

case 'Linux':
case 'linux':
overload(n, 'platform', 'Linux x86_64')
overload(n, 'oscpu', 'Linux x86_64')
overload(n, 'oscpu', payload.current.browser === 'firefox' ? 'Linux x86_64' : undefined, true)
break

case 'Android':
case 'android':
overload(n, 'platform', 'Linux armv8l')
overload(n, 'oscpu', 'Linux armv8l')
overload(n, 'oscpu', payload.current.browser === 'firefox' ? 'Linux armv8l' : undefined, true)
break

case 'macOS':
overload(n, 'platform', 'MacIntel')
overload(n, 'oscpu', 'Mac OS X')
overload(n, 'oscpu', payload.current.browser === 'firefox' ? 'Mac OS X' : undefined, true)
break

case 'iOS':
overload(n, 'platform', 'iPhone')
overload(n, 'oscpu', 'Mac OS X')
overload(n, 'oscpu', payload.current.browser === 'firefox' ? 'Mac OS X' : undefined, true)
break

default:
overload(n, 'oscpu', undefined)
overload(n, 'oscpu', undefined, true)
}

// to test, execute in the console: : `debug(navigator.vendor)`
// to test, execute in the console: `console.log(navigator.vendor)`
switch (payload.current.browser) {
case 'chrome':
case 'opera':
Expand All @@ -123,15 +198,105 @@ import type { ContentScriptPayload } from '~/shared/types'
break

case 'safari': // webkit engine
overload(n, 'vendor', 'Apple Computer Inc.')
overload(n, 'vendor', 'Apple Computer, Inc.')
break

default:
overload(n, 'vendor', undefined)
}

/** @link https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData#browser_compatibility */
// to test, execute in the console: : `debug(navigator.userAgentData, navigator.userAgentData.toJSON())`
if ('userAgentData' in n) {
// TODO
/**
* @link https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData#browser_compatibility
* @link https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/core/frame/navigator_ua_data.cc
*/
switch (payload.current.browser) {
case 'firefox':
// FireFox does not support the `userAgentData` property yet
overload(n, 'userAgentData', undefined)
break

case 'safari':
const staticData = { brands: [], mobile: false, platform: '' }

overload(n, 'userAgentData', { ...staticData, toJSON: () => staticData })
break

default:
// TODO: write a code HERE
}
// if ('userAgentData' in n && typeof n.userAgentData === 'object') {
// // to test, execute in the console: `console.log(navigator.userAgentData.toJSON())`
// overload(
// n.userAgentData,
// 'toJSON',
// new Proxy(n.userAgentData.toJSON, {
// apply(target, self, args) {
// return payload.current.browser === 'firefox' || payload.current.browser === 'safari'
// ? { brands: [], mobile: false, platform: '' }
// : {
// ...Reflect.apply(target, self, args),
// brands: payload.brands.major.map(({ brand, version }) => ({ brand, version })),
// mobile: payload.isMobile,
// platform: payload.platform,
// }
// },
// })
// )
//
// // to test, execute in the console: `console.log(await navigator.userAgentData.getHighEntropyValues([...]))`
// overload(
// n.userAgentData,
// 'getHighEntropyValues',
// new Proxy(n.userAgentData.getHighEntropyValues, {
// apply(target, self, args) {
// return new Promise((resolve: (v: UADataValues) => void, reject: () => void): void => {
// if (payload.current.browser === 'firefox' || payload.current.browser === 'safari') {
// // TODO: how it looks like in Firefox and Safari?
// return resolve({ brands: [], mobile: false, platform: '' })
// }
//
// // get the original high entropy values
// Reflect.apply(target, self, args)
// .then((values: UADataValues): void => {
// const data: DeepWriteable<UADataValues> = {
// ...values,
// brands: payload.brands.major.map(({ brand, version }) => ({ brand, version })),
// fullVersionList: payload.brands.full.map(({ brand, version }) => ({ brand, version })),
// mobile: payload.isMobile,
// model: '',
// platform: payload.platform,
// platformVersion: ((): string => {
// switch (payload.platform) {
// case 'Windows':
// return '10.0.0'
// case 'Linux':
// return '6.5.0'
// case 'Android':
// return '13.0.0'
// case 'macOS':
// case 'iOS':
// return '14.2.1'
// }
//
// return ''
// })(),
// }
//
// if ('uaFullVersion' in values) {
// data.uaFullVersion = payload.current.version.browser.full
// }
//
// resolve(data)
// })
// .catch(reject)
// })
// },
// })
// )
//
// // to test, execute in the console: `console.log(navigator.userAgentData.brands)`
// // overload(n.userAgentData, 'brands')
// }
}

/** Patches the navigator object for the iframe. */
Expand All @@ -152,7 +317,7 @@ import type { ContentScriptPayload } from '~/shared/types'
}

// patch the current navigator object
patchNavigator(Navigator.prototype)
patchNavigator(navigator)

// patch iframes navigators
{
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"types": ["vite/client", "node", "chrome", "user-agent-data-types"],
"useDefineForClassFields": true,
"lib": [
"ES2020",
"ES2022",
"DOM",
"DOM.Iterable"
],
Expand Down

0 comments on commit 4d25c83

Please sign in to comment.