Skip to main content

FINGERPRINT_PAYLOAD_SOURCE

Constant FINGERPRINT_PAYLOAD_SOURCE 

Source
pub const FINGERPRINT_PAYLOAD_SOURCE: &str = "/**\n * Shared fingerprint init payload -- the one copy the three implementations run.\n *\n * This file is not a module. It is source text: every implementation reads it\n * verbatim and sends it to `Page.addScriptToEvaluateOnNewDocument`, which runs\n * a classic script, so it must contain no `import` and no `export`.\n *\n * The same bytes live in three places, because npm, PyPI and crates.io each\n * package one directory and none of them can reference a file outside it:\n *\n * - js/src/fingerprint/init-payload.js\n * - python/src/browser_commander/fingerprint/init_payload.js\n * - rust/src/fingerprint/init_payload.js\n *\n * `scripts/check-shared-fingerprint-assets.sh` fails the build when they differ.\n * Editing one and forgetting the others is the failure mode that rotted\n * selenium-stealth and playwright_stealth -- see\n * docs/case-studies/issue-79/prior-art.md -- so the check exists to make a\n * silent divergence impossible rather than unlikely.\n */\n\n/**\n * Runs inside the page, before any page script, in every frame.\n *\n * @param {object} config Plain JSON produced by `buildInitScriptConfig`.\n */\nfunction fingerprintPayload(config) {\n  const scope = globalThis;\n  // Playwright and Puppeteer both re-inject init scripts on same-document\n  // navigations in some versions; patching twice would stack getters and make\n  // the descriptor chain observable.\n  const marker = \'__browserCommanderFingerprint\';\n  if (scope[marker]) {\n    return;\n  }\n  Object.defineProperty(scope, marker, {\n    value: true,\n    configurable: true,\n    enumerable: false,\n    writable: false,\n  });\n\n  const nativeSources = new WeakMap();\n  const functionToString = Function.prototype.toString;\n  const objectDefineProperty = Object.defineProperty;\n\n  /** Make a replacement report `[native code]`, like the accessor it replaces. */\n  function asNative(fn, name) {\n    try {\n      objectDefineProperty(fn, \'name\', {\n        value: name,\n        configurable: true,\n        enumerable: false,\n        writable: false,\n      });\n    } catch {\n      // A frozen function name is not worth failing the whole patch over.\n    }\n    nativeSources.set(fn, `function ${name}() { [native code] }`);\n    return fn;\n  }\n\n  const patchedToString = function toString() {\n    const source = nativeSources.get(this);\n    return source === undefined ? functionToString.call(this) : source;\n  };\n  nativeSources.set(patchedToString, functionToString.call(functionToString));\n  Function.prototype.toString = patchedToString;\n\n  /**\n   * Replace a native accessor on a prototype, keeping the shape a real one has:\n   * an enumerable, configurable getter with no setter, named `get <property>`.\n   */\n  function defineNativeGetter(target, property, produce) {\n    if (!target) {\n      return;\n    }\n    const descriptor = Object.getOwnPropertyDescriptor(target, property);\n    if (descriptor && !descriptor.configurable) {\n      return;\n    }\n    const getter = asNative(() => produce(), `get ${property}`);\n    try {\n      objectDefineProperty(target, property, {\n        get: getter,\n        set: undefined,\n        enumerable: descriptor ? descriptor.enumerable : true,\n        configurable: true,\n      });\n    } catch {\n      // Some engines refuse redefinition on cross-origin-ish prototypes.\n    }\n  }\n\n  const navigatorPrototype = scope.Navigator && scope.Navigator.prototype;\n  const workerNavigatorPrototype =\n    scope.WorkerNavigator && scope.WorkerNavigator.prototype;\n\n  function defineNavigator(property, value) {\n    defineNativeGetter(navigatorPrototype, property, () => value);\n    defineNativeGetter(workerNavigatorPrototype, property, () => value);\n  }\n\n  if (config.webdriver !== undefined) {\n    defineNavigator(\'webdriver\', config.webdriver);\n  }\n  if (config.deviceMemory !== undefined) {\n    defineNavigator(\'deviceMemory\', config.deviceMemory);\n  }\n  if (config.hardwareConcurrency !== undefined) {\n    defineNavigator(\'hardwareConcurrency\', config.hardwareConcurrency);\n  }\n  if (config.vendor !== undefined) {\n    defineNavigator(\'vendor\', config.vendor);\n  }\n  if (config.platform !== undefined) {\n    defineNavigator(\'platform\', config.platform);\n  }\n  if (config.maxTouchPoints !== undefined) {\n    defineNavigator(\'maxTouchPoints\', config.maxTouchPoints);\n  }\n  if (config.doNotTrack !== undefined) {\n    defineNativeGetter(\n      navigatorPrototype,\n      \'doNotTrack\',\n      () => config.doNotTrack\n    );\n  }\n  if (config.languages !== undefined) {\n    // navigator.languages is a frozen array in Chrome; hand out a fresh frozen\n    // copy so page code cannot mutate the one we hold.\n    defineNavigator(\'language\', config.languages[0]);\n    defineNativeGetter(navigatorPrototype, \'languages\', () =>\n      Object.freeze(config.languages.slice())\n    );\n    defineNativeGetter(workerNavigatorPrototype, \'languages\', () =>\n      Object.freeze(config.languages.slice())\n    );\n  }\n\n  const screenPrototype = scope.Screen && scope.Screen.prototype;\n  if (config.screen) {\n    for (const property of Object.keys(config.screen)) {\n      const value = config.screen[property];\n      defineNativeGetter(screenPrototype, property, () => value);\n    }\n  }\n\n  if (config.webgl) {\n    // 0x9245/0x9246 are UNMASKED_VENDOR_WEBGL and UNMASKED_RENDERER_WEBGL from\n    // the WEBGL_debug_renderer_info extension; 0x1F00/0x1F01 are the plain\n    // VENDOR and RENDERER constants that need no extension at all.\n    const replacements = new Map();\n    if (config.webgl.unmaskedVendor !== undefined) {\n      replacements.set(0x9245, config.webgl.unmaskedVendor);\n    }\n    if (config.webgl.unmaskedRenderer !== undefined) {\n      replacements.set(0x9246, config.webgl.unmaskedRenderer);\n    }\n    if (config.webgl.vendor !== undefined) {\n      replacements.set(0x1f00, config.webgl.vendor);\n    }\n    if (config.webgl.renderer !== undefined) {\n      replacements.set(0x1f01, config.webgl.renderer);\n    }\n    for (const name of [\'WebGLRenderingContext\', \'WebGL2RenderingContext\']) {\n      const prototype = scope[name] && scope[name].prototype;\n      if (!prototype || typeof prototype.getParameter !== \'function\') {\n        continue;\n      }\n      const original = prototype.getParameter;\n      const patched = asNative(function getParameter(parameter) {\n        if (replacements.has(parameter)) {\n          return replacements.get(parameter);\n        }\n        return original.call(this, parameter);\n      }, \'getParameter\');\n      prototype.getParameter = patched;\n    }\n  }\n}\n";
Expand description

The shared payload source, embedded at compile time.