import { ObjectCreate, ObjectFreeze as freeze, ReflectApply as apply, SymbolIterator, TypeError, WeakMap, WeakMapPrototypeSet as set, } from "ext:core/primordials"; import { onRuntimeStart } from "ext:core/bootstrap"; import { op_navigator_metadata as getMetadata, op_navigator_user_agent_data as getUserAgentData, } from "ext:core/ops"; import { isObject, required, state, string, usv } from "./idl.js"; import { registerUncloneable } from "./serialization.js"; const secret = ObjectCreate(null) as { readonly navigatorToken: unique symbol }; interface NavigatorMetadata { platform: string; hardwareConcurrency: number; } interface Brand { brand: string; version: string; } interface UserAgentData { brands: readonly Brand[]; mobile: boolean; platform: string; architecture: string; bitness: string; model: string; platformVersion: string; uaFullVersion: string; fullVersionList: readonly Brand[]; wow64: boolean; } interface EntropyValues { brands: Brand[]; mobile: boolean; platform: string; architecture?: string; bitness?: string; model?: string; platformVersion?: string; uaFullVersion?: string; fullVersionList?: Brand[]; wow64?: boolean; } const navigators = new WeakMap(); registerUncloneable(navigators); const metadata = ObjectCreate(null) as NavigatorMetadata, uaRecord = ObjectCreate(null) as UserAgentData; let userAgent: string; const languages = freeze(["en-US"]); onRuntimeStart(() => { const info = getMetadata(null); metadata.platform = info[0]; metadata.hardwareConcurrency = info[1]; userAgent = info[2]; const raw = getUserAgentData(null); const frozenBrands: Brand[] = []; for (let i = 0; i < raw.brands.length; i++) { frozenBrands[frozenBrands.length] = freeze({ brand: raw.brands[i].brand, version: raw.brands[i].version, }); } uaRecord.brands = freeze(frozenBrands); uaRecord.mobile = raw.mobile; uaRecord.platform = raw.platform; uaRecord.architecture = raw.architecture; uaRecord.bitness = raw.bitness; uaRecord.model = raw.model; uaRecord.platformVersion = raw.platformVersion; uaRecord.uaFullVersion = raw.uaFullVersion; uaRecord.fullVersionList = raw.fullVersionList; uaRecord.wow64 = raw.wow64; }); const uaDataStates = new WeakMap(); registerUncloneable(uaDataStates); function copyBrands(list: readonly Brand[]): Brand[] { const out: Brand[] = []; for (let i = 0; i < list.length; i++) { out[out.length] = { brand: list[i].brand, version: list[i].version }; } return out; } class NavigatorUAData { constructor(key: typeof secret); constructor() { if (arguments[0] !== secret) throw new TypeError("Illegal constructor"); set(uaDataStates, this, uaRecord); } get brands() { return state(uaDataStates, this).brands; } get mobile() { return state(uaDataStates, this).mobile; } get platform() { return state(uaDataStates, this).platform; } async getHighEntropyValues(hints: unknown): Promise { const current = state(uaDataStates, this); required(arguments.length, 1); if (!isObject(hints)) { throw new TypeError("Expected an iterable of strings"); } const method = hints[SymbolIterator]; if (typeof method !== "function") { throw new TypeError("Expected an iterable of strings"); } const iterator = apply(method, hints, []); if (!isObject(iterator)) throw new TypeError("Invalid hints iterator"); const next = iterator.next; if (typeof next !== "function") { throw new TypeError("Invalid hints iterator"); } const result: EntropyValues = { brands: copyBrands(current.brands), mobile: current.mobile, platform: current.platform, }; while (true) { const step = apply(next, iterator, []); if (!isObject(step)) throw new TypeError("Invalid hints iterator result"); if (step.done) break; const hint = usv(step.value); if (hint === "architecture") result.architecture = current.architecture; else if (hint === "bitness") result.bitness = current.bitness; else if (hint === "model") result.model = current.model; else if (hint === "platformVersion") { result.platformVersion = current.platformVersion; } else if (hint === "uaFullVersion") { result.uaFullVersion = current.uaFullVersion; } else if (hint === "fullVersionList") { result.fullVersionList = copyBrands(current.fullVersionList); } else if (hint === "wow64") result.wow64 = current.wow64; } return result; } toJSON(): Pick { const current = state(uaDataStates, this); return { brands: copyBrands(current.brands), mobile: current.mobile, platform: current.platform, }; } } const uaData = new NavigatorUAData(secret); class WorkerNavigator { constructor(key: typeof secret); constructor() { if (arguments[0] !== secret) throw new TypeError("Illegal constructor"); set(navigators, this, metadata); } get appCodeName() { state(navigators, this); return "Mozilla"; } get appName() { state(navigators, this); return "Netscape"; } get appVersion() { state(navigators, this); return "4.0"; } get platform() { return state(navigators, this).platform; } get product() { state(navigators, this); return "Gecko"; } get userAgent() { state(navigators, this); return userAgent; } set userAgent(value) { state(navigators, this); userAgent = string(value); } get language() { state(navigators, this); return languages[0]; } get languages() { state(navigators, this); return languages; } get hardwareConcurrency() { return state(navigators, this).hardwareConcurrency; } get onLine() { state(navigators, this); return true; } get userAgentData() { state(navigators, this); return uaData; } } const navigator = new WorkerNavigator(secret); export { navigator, NavigatorUAData, userAgent, WorkerNavigator };