/** * Init script injected in every page before any site script runs. Removes the usual headless / * automation tells that anti-bot vendors probe (webdriver flag, missing `chrome` object, empty * plugin list, SwiftShader WebGL renderer, permission API quirks, notification permission * mismatch, `outerWidth === 0`, headless UA hints). */ export function stealthScript(opts: { platform: string; languages: string[]; hardwareConcurrency: number; deviceMemory: number; vendor: string; renderer: string; mobile: boolean }): string { return `(() => { const opts = ${JSON.stringify(opts)}; const define = (obj, key, value) => { try { Object.defineProperty(obj, key, { get: () => value, configurable: true }); } catch {} }; // navigator.webdriver → false (and not enumerable like real Chrome) try { Object.defineProperty(Navigator.prototype, 'webdriver', { get: () => false, configurable: true }); } catch {} // window.chrome (present in every Chrome, absent in headless-shell) if (!window.chrome) { const chrome = { app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } }, runtime: { OnInstalledReason: { CHROME_UPDATE: 'chrome_update', INSTALL: 'install', SHARED_MODULE_UPDATE: 'shared_module_update', UPDATE: 'update' }, PlatformArch: { ARM: 'arm', ARM64: 'arm64', MIPS: 'mips', MIPS64: 'mips64', X86_32: 'x86-32', X86_64: 'x86-64' }, PlatformOs: { ANDROID: 'android', CROS: 'cros', LINUX: 'linux', MAC: 'mac', OPENBSD: 'openbsd', WIN: 'win' }, RequestUpdateCheckStatus: { NO_UPDATE: 'no_update', THROTTLED: 'throttled', UPDATE_AVAILABLE: 'update_available' }, connect: function() {}, sendMessage: function() {}, id: undefined }, csi: function() { return { onloadT: Date.now(), pageT: Math.random() * 1000, startE: Date.now(), tran: 15 }; }, loadTimes: function() { const t = Date.now() / 1000; return { commitLoadTime: t, connectionInfo: 'h2', finishDocumentLoadTime: t, finishLoadTime: t, firstPaintAfterLoadTime: 0, firstPaintTime: t, navigationType: 'Other', npnNegotiatedProtocol: 'h2', requestTime: t - 0.5, startLoadTime: t - 0.4, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true }; }, }; define(window, 'chrome', chrome); } // Plugins & mimeTypes (Chrome ships 5 PDF-related plugins) try { const mk = (name, filename, description) => ({ name, filename, description, length: 1, item: () => null, namedItem: () => null, 0: { type: 'application/pdf', suffixes: 'pdf', description } }); const list = [mk('PDF Viewer', 'internal-pdf-viewer', 'Portable Document Format'), mk('Chrome PDF Viewer', 'internal-pdf-viewer', 'Portable Document Format'), mk('Chromium PDF Viewer', 'internal-pdf-viewer', 'Portable Document Format'), mk('Microsoft Edge PDF Viewer', 'internal-pdf-viewer', 'Portable Document Format'), mk('WebKit built-in PDF', 'internal-pdf-viewer', 'Portable Document Format')]; const plugins = Object.create(PluginArray.prototype); list.forEach((p, i) => { plugins[i] = p; }); define(plugins, 'length', opts.mobile ? 0 : list.length); plugins.item = (i) => plugins[i] ?? null; plugins.namedItem = (n) => list.find((p) => p.name === n) ?? null; plugins.refresh = () => {}; if (navigator.plugins.length === 0) define(Navigator.prototype, 'plugins', plugins); } catch {} define(Navigator.prototype, 'languages', opts.languages); define(Navigator.prototype, 'platform', opts.platform); define(Navigator.prototype, 'hardwareConcurrency', opts.hardwareConcurrency); define(Navigator.prototype, 'deviceMemory', opts.deviceMemory); if (opts.mobile) define(Navigator.prototype, 'maxTouchPoints', 5); // Permissions: headless reports 'denied' for notifications while Notification.permission is 'default' try { const q = Permissions.prototype.query; Permissions.prototype.query = function(p) { if (p && p.name === 'notifications') return Promise.resolve({ state: Notification.permission === 'default' ? 'prompt' : Notification.permission, onchange: null }); return q.call(this, p); }; } catch {} // WebGL vendor/renderer (SwiftShader / "Google Inc. (Google)" is a headless tell) try { const patch = (proto) => { const gp = proto.getParameter; proto.getParameter = function(param) { if (param === 37445) return opts.vendor; if (param === 37446) return opts.renderer; return gp.call(this, param); }; }; if (window.WebGLRenderingContext) patch(WebGLRenderingContext.prototype); if (window.WebGL2RenderingContext) patch(WebGL2RenderingContext.prototype); } catch {} // outerWidth/outerHeight are 0 in headless if (!window.outerWidth) define(window, 'outerWidth', window.innerWidth); if (!window.outerHeight) define(window, 'outerHeight', window.innerHeight + 85); // Hide automation-only properties leaking through Function.prototype.toString try { const nativeToString = Function.prototype.toString; const patched = new WeakSet([Permissions.prototype.query]); Function.prototype.toString = function() { if (patched.has(this)) return 'function query() { [native code] }'; return nativeToString.call(this); }; } catch {} // Connection / battery plausibility try { if (navigator.connection) define(navigator.connection, 'rtt', 50); } catch {} })();`; } const TIMEZONES: Record = { CA: "America/Toronto", US: "America/New_York", GB: "Europe/London", FR: "Europe/Paris", DE: "Europe/Berlin", ES: "Europe/Madrid", IT: "Europe/Rome", NL: "Europe/Amsterdam", BE: "Europe/Brussels", CH: "Europe/Zurich", SE: "Europe/Stockholm", PL: "Europe/Warsaw", PT: "Europe/Lisbon", IE: "Europe/Dublin", AU: "Australia/Sydney", NZ: "Pacific/Auckland", JP: "Asia/Tokyo", KR: "Asia/Seoul", IN: "Asia/Kolkata", SG: "Asia/Singapore", HK: "Asia/Hong_Kong", BR: "America/Sao_Paulo", MX: "America/Mexico_City", AR: "America/Argentina/Buenos_Aires", ZA: "Africa/Johannesburg", AE: "Asia/Dubai", TR: "Europe/Istanbul", RU: "Europe/Moscow", IL: "Asia/Jerusalem", }; export function timezoneFor(country: string | null | undefined): string { return (country && TIMEZONES[country.toUpperCase()]) || "America/New_York"; } export function webglFor(platform: string, tls: string): { vendor: string; renderer: string } { if (tls === "safari" || /mac/i.test(platform)) return { vendor: "Google Inc. (Apple)", renderer: "ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)" }; if (/linux/i.test(platform) && !/arm/i.test(platform)) return { vendor: "Google Inc. (Intel)", renderer: "ANGLE (Intel, Mesa Intel(R) UHD Graphics 630 (CFL GT2), OpenGL 4.6)" }; if (/arm/i.test(platform)) return { vendor: "Google Inc. (Qualcomm)", renderer: "ANGLE (Qualcomm, Adreno (TM) 750, OpenGL ES 3.2 V@0744.0)" }; return { vendor: "Google Inc. (NVIDIA)", renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002504) Direct3D11 vs_5_0 ps_5_0, D3D11)" }; }