From df4493d553272a948a2683210caa0cb04a2063e3 Mon Sep 17 00:00:00 2001
From: feder-cr <85809106+feder-cr@users.noreply.github.com>
Date: Tue, 9 Jun 2026 17:53:11 +0200
Subject: [PATCH] test(e2e): run the real detectors (BotD + FingerprintJS OSS)
on CI
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Instead of only our hand-rolled signal checks, load the actual MIT detection
libraries against the patched binary and assert it isn't flagged:
- BotD (the client-side bot detector FingerprintJS Pro itself uses): detect()
must return bot=false (no automation/headless tell).
- FingerprintJS OSS: visitorId present and stable across two fresh launches
with the same seed (drift = per-session entropy = a bot tell).
Hermetic: the libs are vendored (tests/vendor/, pinned, MIT) and served from a
localhost server — no external CDN (Firefox tracking-protection blocks it
anyway), no IP/network dependency, runs identically on a dev box and the GitHub
runner. Both green locally against firefox-9.
---
tests/test_detectors_e2e.py | 144 ++++
tests/vendor/README.md | 18 +
tests/vendor/botd-2.0.0.esm.js | 811 ++++++++++++++++++++
tests/vendor/fingerprintjs-5.2.0.umd.min.js | 27 +
4 files changed, 1000 insertions(+)
create mode 100644 tests/test_detectors_e2e.py
create mode 100644 tests/vendor/README.md
create mode 100644 tests/vendor/botd-2.0.0.esm.js
create mode 100644 tests/vendor/fingerprintjs-5.2.0.umd.min.js
diff --git a/tests/test_detectors_e2e.py b/tests/test_detectors_e2e.py
new file mode 100644
index 0000000..54e678c
--- /dev/null
+++ b/tests/test_detectors_e2e.py
@@ -0,0 +1,144 @@
+"""E2E: run the REAL open-source detectors against the patched binary, on CI.
+
+Instead of our own hand-rolled signal checks, this loads the actual detection
+libraries and asserts the stealth build isn't flagged:
+
+ * BotD (@fingerprintjs/botd, MIT) — the client-side bot detector that
+ FingerprintJS Pro itself uses. `detect()` must return ``bot == False``
+ (no automation/headless tell).
+ * FingerprintJS open-source (MIT) — `get().visitorId` must be present and
+ STABLE across two fresh launches with the same seed (an over-randomized
+ spoof would drift; a real browser is stable).
+
+Everything is hermetic: the libraries are vendored (tests/vendor/) and served
+from a localhost HTTP server, so there is no external CDN call (Firefox
+tracking-protection blocks the CDN anyway) and no IP/network dependency. It runs
+identically on a dev box and on a GitHub runner.
+
+NOT covered here: FingerprintJS *Pro* (commercial, server-side, IP/residential
+analysis) — that can't be self-hosted and stays the local/self-hosted realness
+gate. CreepJS's full trust score needs its closed backend; only its client-side
+signals are reachable offline.
+"""
+from __future__ import annotations
+
+import http.server
+import socketserver
+import threading
+from pathlib import Path
+
+import pytest
+
+from invisible_playwright import InvisiblePlaywright
+
+_VENDOR = Path(__file__).parent / "vendor"
+_BOTD = "botd-2.0.0.esm.js"
+_FPJS = "fingerprintjs-5.2.0.umd.min.js"
+
+_PAGE = f"""
+detectors
+
+loading
+"""
+
+
+class _DetectorSite:
+ """Localhost server: `/` → the page; `/` → the vendored bundle."""
+
+ def __init__(self):
+ page = _PAGE.encode()
+ vendor = _VENDOR
+
+ class H(http.server.BaseHTTPRequestHandler):
+ def do_GET(self): # noqa: N802
+ if self.path == "/" or self.path.startswith("/?"):
+ body, ctype = page, "text/html; charset=utf-8"
+ else:
+ f = vendor / Path(self.path.lstrip("/")).name
+ if not f.is_file():
+ self.send_error(404); return
+ body = f.read_bytes()
+ ctype = "text/javascript; charset=utf-8"
+ self.send_response(200)
+ self.send_header("Content-Type", ctype)
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+
+ def log_message(self, *a):
+ pass
+
+ self._srv = socketserver.TCPServer(("127.0.0.1", 0), H)
+ self.port = self._srv.server_address[1]
+ threading.Thread(target=self._srv.serve_forever, daemon=True).start()
+
+ @property
+ def url(self):
+ return f"http://127.0.0.1:{self.port}/"
+
+ def close(self):
+ self._srv.shutdown()
+
+
+@pytest.fixture(scope="module")
+def detector_site():
+ s = _DetectorSite()
+ yield s
+ s.close()
+
+
+def _run_detectors(firefox_binary, url):
+ """Launch the binary, load the page, return (botd_result, fp_result, err)."""
+ with InvisiblePlaywright(seed=42, binary_path=firefox_binary) as browser:
+ page = browser.new_page()
+ page.goto(url, wait_until="load", timeout=45000)
+ # The detectors run async; wait until both finished (or errored).
+ page.wait_for_function(
+ "() => document.getElementById('state').textContent === 'done'",
+ timeout=45000,
+ )
+ botd = page.evaluate("() => window.__botd")
+ fp = page.evaluate("() => window.__fp")
+ err = page.evaluate("() => window.__err")
+ return botd, fp, err
+
+
+@pytest.mark.e2e
+def test_botd_does_not_flag_automation(firefox_binary, detector_site):
+ """The real BotD detector must NOT flag the stealth build as a bot."""
+ botd, _fp, err = _run_detectors(firefox_binary, detector_site.url)
+ assert botd is not None, f"BotD did not produce a result (err:{err!r})"
+ assert botd.get("bot") is False, (
+ f"BotD flagged the build as a bot: {botd!r} "
+ f"(botKind={botd.get('botKind')!r})"
+ )
+
+
+@pytest.mark.e2e
+def test_fingerprintjs_visitorid_stable_across_launches(firefox_binary, detector_site):
+ """FingerprintJS visitorId must be present and identical across two fresh
+ launches with the same seed — a real browser is stable; an over-randomized
+ spoof drifts (and a drifting fingerprint is itself a bot tell)."""
+ _b1, fp1, err1 = _run_detectors(firefox_binary, detector_site.url)
+ _b2, fp2, err2 = _run_detectors(firefox_binary, detector_site.url)
+ assert fp1 and fp1.get("visitorId"), f"no visitorId on run 1 (err:{err1!r})"
+ assert fp2 and fp2.get("visitorId"), f"no visitorId on run 2 (err:{err2!r})"
+ assert fp1["visitorId"] == fp2["visitorId"], (
+ f"FingerprintJS visitorId drifted across launches: "
+ f"{fp1['visitorId']!r} != {fp2['visitorId']!r} (per-session entropy = bot tell)"
+ )
diff --git a/tests/vendor/README.md b/tests/vendor/README.md
new file mode 100644
index 0000000..8b4ae4a
--- /dev/null
+++ b/tests/vendor/README.md
@@ -0,0 +1,18 @@
+# Vendored detection libraries (test-only)
+
+These are upstream, unmodified, MIT-licensed browser-fingerprinting / bot-detection
+libraries, vendored so the detector e2e tests run **hermetically and identically**
+on a dev box and on a GitHub runner (no external CDN at test time — Firefox
+tracking-protection blocks the openfpcdn.io CDN anyway, and we want CI offline).
+
+They are served from a localhost HTTP server and loaded into the patched Firefox;
+the tests assert the REAL detectors don't flag the stealth build (BotD: `bot===false`)
+and that the fingerprint is stable (FingerprintJS: same `visitorId` across launches).
+
+| File | Package | Version | Source | License |
+|---|---|---|---|---|
+| `botd-2.0.0.esm.js` | `@fingerprintjs/botd` | 2.0.0 | https://cdn.jsdelivr.net/npm/@fingerprintjs/botd@2.0.0/dist/botd.esm.js | MIT |
+| `fingerprintjs-5.2.0.umd.min.js` | `@fingerprintjs/fingerprintjs` | 5.2.0 | https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@5.2.0/dist/fp.umd.min.js | MIT |
+
+Both are MIT (Copyright © FingerprintJS, Inc.). To update: download the pinned
+dist from jsdelivr, drop it here, and bump the version in the filename + this table.
diff --git a/tests/vendor/botd-2.0.0.esm.js b/tests/vendor/botd-2.0.0.esm.js
new file mode 100644
index 0000000..3064a78
--- /dev/null
+++ b/tests/vendor/botd-2.0.0.esm.js
@@ -0,0 +1,811 @@
+/**
+ * Fingerprint BotD v2.0.0 - Copyright (c) FingerprintJS, Inc, 2025 (https://fingerprint.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+
+var version = "2.0.0";
+
+/**
+ * Enum for types of bots.
+ * Specific types of bots come first, followed by automation technologies.
+ *
+ * @readonly
+ * @enum {string}
+ */
+const BotKind = {
+ // Object is used instead of Typescript enum to avoid emitting IIFE which might be affected by further tree-shaking.
+ // See example of compiled enums https://stackoverflow.com/q/47363996)
+ Awesomium: 'awesomium',
+ Cef: 'cef',
+ CefSharp: 'cefsharp',
+ CoachJS: 'coachjs',
+ Electron: 'electron',
+ FMiner: 'fminer',
+ Geb: 'geb',
+ NightmareJS: 'nightmarejs',
+ Phantomas: 'phantomas',
+ PhantomJS: 'phantomjs',
+ Rhino: 'rhino',
+ Selenium: 'selenium',
+ Sequentum: 'sequentum',
+ SlimerJS: 'slimerjs',
+ WebDriverIO: 'webdriverio',
+ WebDriver: 'webdriver',
+ HeadlessChrome: 'headless_chrome',
+ Unknown: 'unknown',
+};
+/**
+ * Bot detection error.
+ */
+class BotdError extends Error {
+ /**
+ * Creates a new BotdError.
+ *
+ * @class
+ */
+ constructor(state, message) {
+ super(message);
+ this.state = state;
+ this.name = 'BotdError';
+ Object.setPrototypeOf(this, BotdError.prototype);
+ }
+}
+
+function detect(components, detectors) {
+ const detections = {};
+ let finalDetection = {
+ bot: false,
+ };
+ for (const detectorName in detectors) {
+ const detector = detectors[detectorName];
+ const detectorRes = detector(components);
+ let detection = { bot: false };
+ if (typeof detectorRes === 'string') {
+ detection = { bot: true, botKind: detectorRes };
+ }
+ else if (detectorRes) {
+ detection = { bot: true, botKind: BotKind.Unknown };
+ }
+ detections[detectorName] = detection;
+ if (detection.bot) {
+ finalDetection = detection;
+ }
+ }
+ return [detections, finalDetection];
+}
+async function collect(sources) {
+ const components = {};
+ const sourcesKeys = Object.keys(sources);
+ await Promise.all(sourcesKeys.map(async (sourceKey) => {
+ const res = sources[sourceKey];
+ try {
+ components[sourceKey] = {
+ value: await res(),
+ state: 0 /* State.Success */,
+ };
+ }
+ catch (error) {
+ if (error instanceof BotdError) {
+ components[sourceKey] = {
+ state: error.state,
+ error: `${error.name}: ${error.message}`,
+ };
+ }
+ else {
+ components[sourceKey] = {
+ state: -3 /* State.UnexpectedBehaviour */,
+ error: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
+ };
+ }
+ }
+ }));
+ return components;
+}
+
+function detectAppVersion({ appVersion }) {
+ if (appVersion.state !== 0 /* State.Success */)
+ return false;
+ if (/headless/i.test(appVersion.value))
+ return BotKind.HeadlessChrome;
+ if (/electron/i.test(appVersion.value))
+ return BotKind.Electron;
+ if (/slimerjs/i.test(appVersion.value))
+ return BotKind.SlimerJS;
+}
+
+function arrayIncludes(arr, value) {
+ return arr.indexOf(value) !== -1;
+}
+function strIncludes(str, value) {
+ return str.indexOf(value) !== -1;
+}
+function arrayFind(array, callback) {
+ if ('find' in array)
+ return array.find(callback);
+ for (let i = 0; i < array.length; i++) {
+ if (callback(array[i], i, array))
+ return array[i];
+ }
+ return undefined;
+}
+
+function getObjectProps(obj) {
+ return Object.getOwnPropertyNames(obj);
+}
+function includes(arr, ...keys) {
+ for (const key of keys) {
+ if (typeof key === 'string') {
+ if (arrayIncludes(arr, key))
+ return true;
+ }
+ else {
+ const match = arrayFind(arr, (value) => key.test(value));
+ if (match != null)
+ return true;
+ }
+ }
+ return false;
+}
+function countTruthy(values) {
+ return values.reduce((sum, value) => sum + (value ? 1 : 0), 0);
+}
+
+function detectDocumentAttributes({ documentElementKeys }) {
+ if (documentElementKeys.state !== 0 /* State.Success */)
+ return false;
+ if (includes(documentElementKeys.value, 'selenium', 'webdriver', 'driver')) {
+ return BotKind.Selenium;
+ }
+}
+
+function detectErrorTrace({ errorTrace }) {
+ if (errorTrace.state !== 0 /* State.Success */)
+ return false;
+ if (/PhantomJS/i.test(errorTrace.value))
+ return BotKind.PhantomJS;
+}
+
+function detectEvalLengthInconsistency({ evalLength, browserKind, browserEngineKind, }) {
+ if (evalLength.state !== 0 /* State.Success */ ||
+ browserKind.state !== 0 /* State.Success */ ||
+ browserEngineKind.state !== 0 /* State.Success */)
+ return;
+ const length = evalLength.value;
+ if (browserEngineKind.value === "unknown" /* BrowserEngineKind.Unknown */)
+ return false;
+ return ((length === 37 && !arrayIncludes(["webkit" /* BrowserEngineKind.Webkit */, "gecko" /* BrowserEngineKind.Gecko */], browserEngineKind.value)) ||
+ (length === 39 && !arrayIncludes(["internet_explorer" /* BrowserKind.IE */], browserKind.value)) ||
+ (length === 33 && !arrayIncludes(["chromium" /* BrowserEngineKind.Chromium */], browserEngineKind.value)));
+}
+
+function detectFunctionBind({ functionBind }) {
+ if (functionBind.state === -2 /* State.NotFunction */)
+ return BotKind.PhantomJS;
+}
+
+function detectLanguagesLengthInconsistency({ languages }) {
+ if (languages.state === 0 /* State.Success */ && languages.value.length === 0) {
+ return BotKind.HeadlessChrome;
+ }
+}
+
+function detectMimeTypesConsistent({ mimeTypesConsistent }) {
+ if (mimeTypesConsistent.state === 0 /* State.Success */ && !mimeTypesConsistent.value) {
+ return BotKind.Unknown;
+ }
+}
+
+function detectNotificationPermissions({ notificationPermissions, browserKind, }) {
+ if (browserKind.state !== 0 /* State.Success */ || browserKind.value !== "chrome" /* BrowserKind.Chrome */)
+ return false;
+ if (notificationPermissions.state === 0 /* State.Success */ && notificationPermissions.value) {
+ return BotKind.HeadlessChrome;
+ }
+}
+
+function detectPluginsArray({ pluginsArray }) {
+ if (pluginsArray.state === 0 /* State.Success */ && !pluginsArray.value)
+ return BotKind.HeadlessChrome;
+}
+
+function detectPluginsLengthInconsistency({ pluginsLength, android, browserKind, browserEngineKind, }) {
+ if (pluginsLength.state !== 0 /* State.Success */ ||
+ android.state !== 0 /* State.Success */ ||
+ browserKind.state !== 0 /* State.Success */ ||
+ browserEngineKind.state !== 0 /* State.Success */)
+ return;
+ if (browserKind.value !== "chrome" /* BrowserKind.Chrome */ ||
+ android.value ||
+ browserEngineKind.value !== "chromium" /* BrowserEngineKind.Chromium */)
+ return;
+ if (pluginsLength.value === 0)
+ return BotKind.HeadlessChrome;
+}
+
+function detectProcess({ process }) {
+ var _a;
+ if (process.state !== 0 /* State.Success */)
+ return false;
+ if (process.value.type === 'renderer' || ((_a = process.value.versions) === null || _a === void 0 ? void 0 : _a.electron) != null)
+ return BotKind.Electron;
+}
+
+function detectProductSub({ productSub, browserKind }) {
+ if (productSub.state !== 0 /* State.Success */ || browserKind.state !== 0 /* State.Success */)
+ return false;
+ if ((browserKind.value === "chrome" /* BrowserKind.Chrome */ ||
+ browserKind.value === "safari" /* BrowserKind.Safari */ ||
+ browserKind.value === "opera" /* BrowserKind.Opera */ ||
+ browserKind.value === "wechat" /* BrowserKind.WeChat */) &&
+ productSub.value !== '20030107')
+ return BotKind.Unknown;
+}
+
+function detectUserAgent({ userAgent }) {
+ if (userAgent.state !== 0 /* State.Success */)
+ return false;
+ if (/PhantomJS/i.test(userAgent.value))
+ return BotKind.PhantomJS;
+ if (/Headless/i.test(userAgent.value))
+ return BotKind.HeadlessChrome;
+ if (/Electron/i.test(userAgent.value))
+ return BotKind.Electron;
+ if (/slimerjs/i.test(userAgent.value))
+ return BotKind.SlimerJS;
+}
+
+function detectWebDriver({ webDriver }) {
+ if (webDriver.state === 0 /* State.Success */ && webDriver.value)
+ return BotKind.HeadlessChrome;
+}
+
+function detectWebGL({ webGL }) {
+ if (webGL.state === 0 /* State.Success */) {
+ const { vendor, renderer } = webGL.value;
+ if (vendor == 'Brian Paul' && renderer == 'Mesa OffScreen') {
+ return BotKind.HeadlessChrome;
+ }
+ }
+}
+
+function detectWindowExternal({ windowExternal }) {
+ if (windowExternal.state !== 0 /* State.Success */)
+ return false;
+ if (/Sequentum/i.test(windowExternal.value))
+ return BotKind.Sequentum;
+}
+
+function detectWindowSize({ windowSize, documentFocus }) {
+ if (windowSize.state !== 0 /* State.Success */ || documentFocus.state !== 0 /* State.Success */)
+ return false;
+ const { outerWidth, outerHeight } = windowSize.value;
+ // When a page is opened in a new tab without focusing it right away, the window outer size is 0x0
+ if (!documentFocus.value)
+ return;
+ if (outerWidth === 0 && outerHeight === 0)
+ return BotKind.HeadlessChrome;
+}
+
+function detectDistinctiveProperties({ distinctiveProps }) {
+ if (distinctiveProps.state !== 0 /* State.Success */)
+ return false;
+ const value = distinctiveProps.value;
+ let bot;
+ for (bot in value)
+ if (value[bot])
+ return bot;
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+const detectors = {
+ detectAppVersion,
+ detectDocumentAttributes,
+ detectErrorTrace,
+ detectEvalLengthInconsistency,
+ detectFunctionBind,
+ detectLanguagesLengthInconsistency,
+ detectNotificationPermissions,
+ detectPluginsArray,
+ detectPluginsLengthInconsistency,
+ detectProcess,
+ detectUserAgent,
+ detectWebDriver,
+ detectWebGL,
+ detectWindowExternal,
+ detectWindowSize,
+ detectMimeTypesConsistent,
+ detectProductSub,
+ detectDistinctiveProperties,
+};
+
+function getAppVersion() {
+ const appVersion = navigator.appVersion;
+ if (appVersion == undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.appVersion is undefined');
+ }
+ return appVersion;
+}
+
+function getDocumentElementKeys() {
+ if (document.documentElement === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'document.documentElement is undefined');
+ }
+ const { documentElement } = document;
+ if (typeof documentElement.getAttributeNames !== 'function') {
+ throw new BotdError(-2 /* State.NotFunction */, 'document.documentElement.getAttributeNames is not a function');
+ }
+ return documentElement.getAttributeNames();
+}
+
+function getErrorTrace() {
+ try {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ null[0]();
+ }
+ catch (error) {
+ if (error instanceof Error && error['stack'] != null) {
+ return error.stack.toString();
+ }
+ }
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, 'errorTrace signal unexpected behaviour');
+}
+
+function getEvalLength() {
+ return eval.toString().length;
+}
+
+function getFunctionBind() {
+ if (Function.prototype.bind === undefined) {
+ throw new BotdError(-2 /* State.NotFunction */, 'Function.prototype.bind is undefined');
+ }
+ return Function.prototype.bind.toString();
+}
+
+function getBrowserEngineKind() {
+ var _a, _b;
+ // Based on research in October 2020. Tested to detect Chromium 42-86.
+ const w = window;
+ const n = navigator;
+ if (countTruthy([
+ 'webkitPersistentStorage' in n,
+ 'webkitTemporaryStorage' in n,
+ n.vendor.indexOf('Google') === 0,
+ 'webkitResolveLocalFileSystemURL' in w,
+ 'BatteryManager' in w,
+ 'webkitMediaStream' in w,
+ 'webkitSpeechGrammar' in w,
+ ]) >= 5) {
+ return "chromium" /* BrowserEngineKind.Chromium */;
+ }
+ if (countTruthy([
+ 'ApplePayError' in w,
+ 'CSSPrimitiveValue' in w,
+ 'Counter' in w,
+ n.vendor.indexOf('Apple') === 0,
+ 'getStorageUpdates' in n,
+ 'WebKitMediaKeys' in w,
+ ]) >= 4) {
+ return "webkit" /* BrowserEngineKind.Webkit */;
+ }
+ if (countTruthy([
+ 'buildID' in navigator,
+ 'MozAppearance' in ((_b = (_a = document.documentElement) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0 ? _b : {}),
+ 'onmozfullscreenchange' in w,
+ 'mozInnerScreenX' in w,
+ 'CSSMozDocumentRule' in w,
+ 'CanvasCaptureMediaStream' in w,
+ ]) >= 4) {
+ return "gecko" /* BrowserEngineKind.Gecko */;
+ }
+ return "unknown" /* BrowserEngineKind.Unknown */;
+}
+function getBrowserKind() {
+ var _a;
+ const userAgent = (_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.toLowerCase();
+ if (strIncludes(userAgent, 'edg/')) {
+ return "edge" /* BrowserKind.Edge */;
+ }
+ else if (strIncludes(userAgent, 'trident') || strIncludes(userAgent, 'msie')) {
+ return "internet_explorer" /* BrowserKind.IE */;
+ }
+ else if (strIncludes(userAgent, 'wechat')) {
+ return "wechat" /* BrowserKind.WeChat */;
+ }
+ else if (strIncludes(userAgent, 'firefox')) {
+ return "firefox" /* BrowserKind.Firefox */;
+ }
+ else if (strIncludes(userAgent, 'opera') || strIncludes(userAgent, 'opr')) {
+ return "opera" /* BrowserKind.Opera */;
+ }
+ else if (strIncludes(userAgent, 'chrome')) {
+ return "chrome" /* BrowserKind.Chrome */;
+ }
+ else if (strIncludes(userAgent, 'safari')) {
+ return "safari" /* BrowserKind.Safari */;
+ }
+ else {
+ return "unknown" /* BrowserKind.Unknown */;
+ }
+}
+// Source: https://github.com/fingerprintjs/fingerprintjs/blob/master/src/utils/browser.ts#L223
+function isAndroid() {
+ const browserEngineKind = getBrowserEngineKind();
+ const isItChromium = browserEngineKind === "chromium" /* BrowserEngineKind.Chromium */;
+ const isItGecko = browserEngineKind === "gecko" /* BrowserEngineKind.Gecko */;
+ const w = window;
+ const n = navigator;
+ const c = 'connection';
+ // Chrome removes all words "Android" from `navigator` when desktop version is requested
+ // Firefox keeps "Android" in `navigator.appVersion` when desktop version is requested
+ if (isItChromium) {
+ return (countTruthy([
+ !('SharedWorker' in w),
+ // `typechange` is deprecated, but it's still present on Android (tested on Chrome Mobile 117)
+ // Removal proposal https://bugs.chromium.org/p/chromium/issues/detail?id=699892
+ // Note: this expression returns true on ChromeOS, so additional detectors are required to avoid false-positives
+ n[c] && 'ontypechange' in n[c],
+ !('sinkId' in new Audio()),
+ ]) >= 2);
+ }
+ else if (isItGecko) {
+ return countTruthy(['onorientationchange' in w, 'orientation' in w, /android/i.test(n.appVersion)]) >= 2;
+ }
+ else {
+ // Only 2 browser engines are presented on Android.
+ // Actually, there is also Android 4.1 browser, but it's not worth detecting it at the moment.
+ return false;
+ }
+}
+function getDocumentFocus() {
+ if (document.hasFocus === undefined) {
+ return false;
+ }
+ return document.hasFocus();
+}
+function isChromium86OrNewer() {
+ // Checked in Chrome 85 vs Chrome 86 both on desktop and Android. Checked in macOS Chrome 128, Android Chrome 127.
+ const w = window;
+ return (countTruthy([
+ !('MediaSettingsRange' in w),
+ 'RTCEncodedAudioFrame' in w,
+ '' + w.Intl === '[object Intl]',
+ '' + w.Reflect === '[object Reflect]',
+ ]) >= 3);
+}
+
+function getLanguages() {
+ const n = navigator;
+ const result = [];
+ const language = n.language || n.userLanguage || n.browserLanguage || n.systemLanguage;
+ if (language !== undefined) {
+ result.push([language]);
+ }
+ if (Array.isArray(n.languages)) {
+ const browserEngine = getBrowserEngineKind();
+ // Starting from Chromium 86, there is only a single value in `navigator.language` in Incognito mode:
+ // the value of `navigator.language`. Therefore, the value is ignored in this browser.
+ if (!(browserEngine === "chromium" /* BrowserEngineKind.Chromium */ && isChromium86OrNewer())) {
+ result.push(n.languages);
+ }
+ }
+ else if (typeof n.languages === 'string') {
+ const languages = n.languages;
+ if (languages) {
+ result.push(languages.split(','));
+ }
+ }
+ return result;
+}
+
+function areMimeTypesConsistent() {
+ if (navigator.mimeTypes === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.mimeTypes is undefined');
+ }
+ const { mimeTypes } = navigator;
+ let isConsistent = Object.getPrototypeOf(mimeTypes) === MimeTypeArray.prototype;
+ for (let i = 0; i < mimeTypes.length; i++) {
+ isConsistent && (isConsistent = Object.getPrototypeOf(mimeTypes[i]) === MimeType.prototype);
+ }
+ return isConsistent;
+}
+
+async function getNotificationPermissions() {
+ if (window.Notification === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'window.Notification is undefined');
+ }
+ if (navigator.permissions === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.permissions is undefined');
+ }
+ const { permissions } = navigator;
+ if (typeof permissions.query !== 'function') {
+ throw new BotdError(-2 /* State.NotFunction */, 'navigator.permissions.query is not a function');
+ }
+ try {
+ const permissionStatus = await permissions.query({ name: 'notifications' });
+ return window.Notification.permission === 'denied' && permissionStatus.state === 'prompt';
+ }
+ catch (e) {
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, 'notificationPermissions signal unexpected behaviour');
+ }
+}
+
+function getPluginsArray() {
+ if (navigator.plugins === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.plugins is undefined');
+ }
+ if (window.PluginArray === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'window.PluginArray is undefined');
+ }
+ return navigator.plugins instanceof PluginArray;
+}
+
+function getPluginsLength() {
+ if (navigator.plugins === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.plugins is undefined');
+ }
+ if (navigator.plugins.length === undefined) {
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, 'navigator.plugins.length is undefined');
+ }
+ return navigator.plugins.length;
+}
+
+function getProcess() {
+ const { process } = window;
+ const errorPrefix = 'window.process is';
+ if (process === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, `${errorPrefix} undefined`);
+ }
+ if (process && typeof process !== 'object') {
+ throw new BotdError(-3 /* State.UnexpectedBehaviour */, `${errorPrefix} not an object`);
+ }
+ return process;
+}
+
+function getProductSub() {
+ const { productSub } = navigator;
+ if (productSub === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.productSub is undefined');
+ }
+ return productSub;
+}
+
+function getRTT() {
+ if (navigator.connection === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.connection is undefined');
+ }
+ if (navigator.connection.rtt === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.connection.rtt is undefined');
+ }
+ return navigator.connection.rtt;
+}
+
+function getUserAgent() {
+ return navigator.userAgent;
+}
+
+function getWebDriver() {
+ if (navigator.webdriver == undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'navigator.webdriver is undefined');
+ }
+ return navigator.webdriver;
+}
+
+function getWebGL() {
+ const canvasElement = document.createElement('canvas');
+ if (typeof canvasElement.getContext !== 'function') {
+ throw new BotdError(-2 /* State.NotFunction */, 'HTMLCanvasElement.getContext is not a function');
+ }
+ const webGLContext = canvasElement.getContext('webgl');
+ if (webGLContext === null) {
+ throw new BotdError(-4 /* State.Null */, 'WebGLRenderingContext is null');
+ }
+ if (typeof webGLContext.getParameter !== 'function') {
+ throw new BotdError(-2 /* State.NotFunction */, 'WebGLRenderingContext.getParameter is not a function');
+ }
+ const vendor = webGLContext.getParameter(webGLContext.VENDOR);
+ const renderer = webGLContext.getParameter(webGLContext.RENDERER);
+ return { vendor: vendor, renderer: renderer };
+}
+
+function getWindowExternal() {
+ if (window.external === undefined) {
+ throw new BotdError(-1 /* State.Undefined */, 'window.external is undefined');
+ }
+ const { external } = window;
+ if (typeof external.toString !== 'function') {
+ throw new BotdError(-2 /* State.NotFunction */, 'window.external.toString is not a function');
+ }
+ return external.toString();
+}
+
+function getWindowSize() {
+ return {
+ outerWidth: window.outerWidth,
+ outerHeight: window.outerHeight,
+ innerWidth: window.innerWidth,
+ innerHeight: window.innerHeight,
+ };
+}
+
+function checkDistinctiveProperties() {
+ // The order in the following list matters, because specific types of bots come first, followed by automation technologies.
+ const distinctivePropsList = {
+ [BotKind.Awesomium]: {
+ window: ['awesomium'],
+ },
+ [BotKind.Cef]: {
+ window: ['RunPerfTest'],
+ },
+ [BotKind.CefSharp]: {
+ window: ['CefSharp'],
+ },
+ [BotKind.CoachJS]: {
+ window: ['emit'],
+ },
+ [BotKind.FMiner]: {
+ window: ['fmget_targets'],
+ },
+ [BotKind.Geb]: {
+ window: ['geb'],
+ },
+ [BotKind.NightmareJS]: {
+ window: ['__nightmare', 'nightmare'],
+ },
+ [BotKind.Phantomas]: {
+ window: ['__phantomas'],
+ },
+ [BotKind.PhantomJS]: {
+ window: ['callPhantom', '_phantom'],
+ },
+ [BotKind.Rhino]: {
+ window: ['spawn'],
+ },
+ [BotKind.Selenium]: {
+ window: ['_Selenium_IDE_Recorder', '_selenium', 'calledSelenium', /^([a-z]){3}_.*_(Array|Promise|Symbol)$/],
+ document: ['__selenium_evaluate', 'selenium-evaluate', '__selenium_unwrapped'],
+ },
+ [BotKind.WebDriverIO]: {
+ window: ['wdioElectron'],
+ },
+ [BotKind.WebDriver]: {
+ window: [
+ 'webdriver',
+ '__webdriverFunc',
+ '__lastWatirAlert',
+ '__lastWatirConfirm',
+ '__lastWatirPrompt',
+ '_WEBDRIVER_ELEM_CACHE',
+ 'ChromeDriverw',
+ ],
+ document: [
+ '__webdriver_script_fn',
+ '__driver_evaluate',
+ '__webdriver_evaluate',
+ '__fxdriver_evaluate',
+ '__driver_unwrapped',
+ '__webdriver_unwrapped',
+ '__fxdriver_unwrapped',
+ '__webdriver_script_fn',
+ '__webdriver_script_func',
+ '__webdriver_script_function',
+ '$cdc_asdjflasutopfhvcZLmcf',
+ '$cdc_asdjflasutopfhvcZLmcfl_',
+ '$chrome_asyncScriptInfo',
+ '__$webdriverAsyncExecutor',
+ ],
+ },
+ [BotKind.HeadlessChrome]: {
+ window: ['domAutomation', 'domAutomationController'],
+ },
+ };
+ let botName;
+ const result = {};
+ const windowProps = getObjectProps(window);
+ let documentProps = [];
+ if (window.document !== undefined)
+ documentProps = getObjectProps(window.document);
+ for (botName in distinctivePropsList) {
+ const props = distinctivePropsList[botName];
+ if (props !== undefined) {
+ const windowContains = props.window === undefined ? false : includes(windowProps, ...props.window);
+ const documentContains = props.document === undefined || !documentProps.length ? false : includes(documentProps, ...props.document);
+ result[botName] = windowContains || documentContains;
+ }
+ }
+ return result;
+}
+
+const sources = {
+ android: isAndroid,
+ browserKind: getBrowserKind,
+ browserEngineKind: getBrowserEngineKind,
+ documentFocus: getDocumentFocus,
+ userAgent: getUserAgent,
+ appVersion: getAppVersion,
+ rtt: getRTT,
+ windowSize: getWindowSize,
+ pluginsLength: getPluginsLength,
+ pluginsArray: getPluginsArray,
+ errorTrace: getErrorTrace,
+ productSub: getProductSub,
+ windowExternal: getWindowExternal,
+ mimeTypesConsistent: areMimeTypesConsistent,
+ evalLength: getEvalLength,
+ webGL: getWebGL,
+ webDriver: getWebDriver,
+ languages: getLanguages,
+ notificationPermissions: getNotificationPermissions,
+ documentElementKeys: getDocumentElementKeys,
+ functionBind: getFunctionBind,
+ process: getProcess,
+ distinctiveProps: checkDistinctiveProperties,
+};
+
+/**
+ * Class representing a bot detector.
+ *
+ * @class
+ * @implements {BotDetectorInterface}
+ */
+class BotDetector {
+ constructor() {
+ this.components = undefined;
+ this.detections = undefined;
+ }
+ getComponents() {
+ return this.components;
+ }
+ getDetections() {
+ return this.detections;
+ }
+ /**
+ * @inheritdoc
+ */
+ detect() {
+ if (this.components === undefined) {
+ throw new Error("BotDetector.detect can't be called before BotDetector.collect");
+ }
+ const [detections, finalDetection] = detect(this.components, detectors);
+ this.detections = detections;
+ return finalDetection;
+ }
+ /**
+ * @inheritdoc
+ */
+ async collect() {
+ this.components = await collect(sources);
+ return this.components;
+ }
+}
+
+/**
+ * Sends an unpersonalized AJAX request to collect installation statistics
+ */
+function monitor() {
+ // The FingerprintJS CDN (https://github.com/fingerprintjs/cdn) replaces `window.__fpjs_d_m` with `true`
+ if (window.__fpjs_d_m || Math.random() >= 0.001) {
+ return;
+ }
+ try {
+ const request = new XMLHttpRequest();
+ request.open('get', `https://m1.openfpcdn.io/botd/v${version}/npm-monitoring`, true);
+ request.send();
+ }
+ catch (error) {
+ // console.error is ok here because it's an unexpected error handler
+ // eslint-disable-next-line no-console
+ console.error(error);
+ }
+}
+async function load({ monitoring = true } = {}) {
+ if (monitoring) {
+ monitor();
+ }
+ const detector = new BotDetector();
+ await detector.collect();
+ return detector;
+}
+var index = { load };
+
+export { BotKind, BotdError, collect, index as default, detect, detectors, load, sources };
diff --git a/tests/vendor/fingerprintjs-5.2.0.umd.min.js b/tests/vendor/fingerprintjs-5.2.0.umd.min.js
new file mode 100644
index 0000000..9975db8
--- /dev/null
+++ b/tests/vendor/fingerprintjs-5.2.0.umd.min.js
@@ -0,0 +1,27 @@
+/**
+ * FingerprintJS v5.2.0 - Copyright (c) FingerprintJS, Inc, 2026 (https://fingerprint.com)
+ *
+ * Licensed under MIT License
+ *
+ * Copyright (c) 2025 FingerprintJS, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).FingerprintJS={})}(this,(function(t){"use strict";var e="5.2.0";function n(t,e){return new Promise((n=>setTimeout(n,t,e)))}function o(t){return!!t&&"function"==typeof t.then}function i(t,e){try{const n=t();o(n)?n.then((t=>e(!0,t)),(t=>e(!1,t))):e(!0,n)}catch(n){e(!1,n)}}async function r(t,e,n=16){const o=Array(t.length);let i=Date.now();for(let r=0;r=i+n&&(i=a,await new Promise((t=>{const e=new MessageChannel;e.port1.onmessage=()=>t(),e.port2.postMessage(null)})))}return o}function a(t){return t.then(void 0,(()=>{})),t}function c(t){return parseInt(t)}function s(t){return parseFloat(t)}function u(t,e){return"number"==typeof t&&isNaN(t)?e:t}function l(t){return t.reduce(((t,e)=>t+(e?1:0)),0)}function d(t,e=1){if(Math.abs(e)>=1)return Math.round(t/e)*e;{const n=1/e;return Math.round(t*n)/n}}function m(t,e){const n=t[0]>>>16,o=65535&t[0],i=t[1]>>>16,r=65535&t[1],a=e[0]>>>16,c=65535&e[0],s=e[1]>>>16;let u=0,l=0,d=0,m=0;m+=r+(65535&e[1]),d+=m>>>16,m&=65535,d+=i+s,l+=d>>>16,d&=65535,l+=o+c,u+=l>>>16,l&=65535,u+=n+a,u&=65535,t[0]=u<<16|l,t[1]=d<<16|m}function f(t,e){const n=t[0]>>>16,o=65535&t[0],i=t[1]>>>16,r=65535&t[1],a=e[0]>>>16,c=65535&e[0],s=e[1]>>>16,u=65535&e[1];let l=0,d=0,m=0,f=0;f+=r*u,m+=f>>>16,f&=65535,m+=i*u,d+=m>>>16,m&=65535,m+=r*s,d+=m>>>16,m&=65535,d+=o*u,l+=d>>>16,d&=65535,d+=i*s,l+=d>>>16,d&=65535,d+=r*c,l+=d>>>16,d&=65535,l+=n*u+o*s+i*c+r*a,l&=65535,t[0]=l<<16|d,t[1]=m<<16|f}function p(t,e){const n=t[0];32===(e%=64)?(t[0]=t[1],t[1]=n):e<32?(t[0]=n<>>32-e,t[1]=t[1]<>>32-e):(e-=32,t[0]=t[1]<>>32-e,t[1]=n<>>32-e)}function h(t,e){0!==(e%=64)&&(e<32?(t[0]=t[1]>>>32-e,t[1]=t[1]<>>1];b(t,e),f(t,y),e[1]=t[0]>>>1,b(t,e),f(t,g),e[1]=t[0]>>>1,b(t,e)}const w=[2277735313,289559509],L=[1291169091,658871167],k=[0,5],V=[0,1390208809],S=[0,944331445];function W(t,e){const n=function(t){const e=new Uint8Array(t.length);for(let n=0;n127)return(new TextEncoder).encode(t);e[n]=o}return e}(t);e=e||0;const o=[0,n.length],i=o[1]%16,r=o[1]-i,a=[0,e],c=[0,e],s=[0,0],u=[0,0];let l;for(l=0;l>>0).toString(16)).slice(-8)+("00000000"+(a[1]>>>0).toString(16)).slice(-8)+("00000000"+(c[0]>>>0).toString(16)).slice(-8)+("00000000"+(c[1]>>>0).toString(16)).slice(-8)}function x(t){return"function"!=typeof t}function Z(t,e,n,o){const c=Object.keys(t).filter((t=>!function(t,e){for(let n=0,o=t.length;nfunction(t,e){const n=a(new Promise((n=>{const o=Date.now();i(t.bind(null,e),((...t)=>{const e=Date.now()-o;if(!t[0])return n((()=>({error:t[1],duration:e})));const r=t[1];if(x(r))return n((()=>({value:r,duration:e})));n((()=>new Promise((t=>{const n=Date.now();i(r,((...o)=>{const i=e+Date.now()-n;if(!o[0])return t({error:o[1],duration:i});t({value:o[1],duration:i})}))}))))}))})));return function(){return n.then((t=>t()))}}(t[n],e)),o));return async function(){const t=await s,e=await r(t,(t=>a(t())),o),n=await Promise.all(e),i={};for(let o=0;o=4}function R(){const t=window,e=navigator;return l(["msWriteProfilerMark"in t,"MSStream"in t,"msLaunchUri"in e,"msSaveBlob"in e])>=3&&!M()}function F(){const t=window,e=navigator;return l(["webkitPersistentStorage"in e,"webkitTemporaryStorage"in e,0===(e.vendor||"").indexOf("Google"),"webkitResolveLocalFileSystemURL"in t,"BatteryManager"in t,"webkitMediaStream"in t,"webkitSpeechGrammar"in t])>=5}function G(){const t=window;return l(["ApplePayError"in t,"CSSPrimitiveValue"in t,"Counter"in t,0===navigator.vendor.indexOf("Apple"),"RGBColor"in t,"WebKitMediaKeys"in t])>=4}function I(){const t=window,{HTMLElement:e,Document:n}=t;return l(["safari"in t,!("ongestureend"in t),!("TouchEvent"in t),!("orientation"in t),e&&!("autocapitalize"in e.prototype),n&&"pointerLockElement"in n.prototype])>=4}function C(){const t=window;return e=t.print,/^function\s.*?\{\s*\[native code]\s*}$/.test(String(e))&&"[object WebPageNamespace]"===String(t.browser);var e}function Y(){var t,e;const n=window;return l(["buildID"in navigator,"MozAppearance"in(null!==(e=null===(t=document.documentElement)||void 0===t?void 0:t.style)&&void 0!==e?e:{}),"onmozfullscreenchange"in n,"mozInnerScreenX"in n,"CSSMozDocumentRule"in n,"CanvasCaptureMediaStream"in n])>=4}function P(){const{CSS:t}=window;return l([t.supports("selector(::details-content)"),t.supports("selector(::before::marker)"),t.supports("selector(::after::marker)"),!("locale"in CompositionEvent.prototype)])>=3}function X(){const t=window,e=document,{CSS:n,Promise:o,AudioContext:i}=t;return l([o&&"try"in o,"caretPositionFromPoint"in e,i&&"onerror"in i.prototype,n.supports("ruby-align","space-around")])>=3}function j(){const t=window,e=navigator,{CSS:n,HTMLButtonElement:o}=t;return l([!("getStorageUpdates"in e),o&&"popover"in o.prototype,"CSSCounterStyleRule"in t,n.supports("font-size-adjust: ex-height 0.5"),n.supports("text-transform: full-width")])>=4}function E(){const t=document;return t.fullscreenElement||t.msFullscreenElement||t.mozFullScreenElement||t.webkitFullscreenElement||null}function H(){const t=F(),e=Y(),n=window,o=navigator,i="connection";return t?l([!("SharedWorker"in n),o[i]&&"ontypechange"in o[i],!("sinkId"in new Audio)])>=2:!!e&&l(["onorientationchange"in n,"orientation"in n,/android/i.test(o.appVersion)])>=2}function A(){const t=navigator,e=window,n=Audio.prototype,{visualViewport:o}=e;return l(["srLatency"in n,"srChannelCount"in n,"devicePosture"in t,o&&"segments"in o,"getTextInformation"in Image.prototype])>=3}function N(){const t=window,e=t.OfflineAudioContext||t.webkitOfflineAudioContext;if(!e)return-2;if(G()&&!I()&&!function(){const t=window;return l(["DOMRectList"in t,"RTCPeerConnectionIceEvent"in t,"SVGGeometryElement"in t,"ontransitioncancel"in t])>=3}())return-1;const n=new e(1,5e3,44100),i=n.createOscillator();i.type="triangle",i.frequency.value=1e4;const r=n.createDynamicsCompressor();r.threshold.value=-50,r.knee.value=40,r.ratio.value=12,r.attack.value=0,r.release.value=.25,i.connect(r),r.connect(n.destination),i.start(0);const[c,s]=function(t){const e=3,n=500,i=500,r=5e3;let c=()=>{};const s=new Promise(((s,u)=>{let l=!1,d=0,m=0;t.oncomplete=t=>s(t.renderedBuffer);const f=()=>{setTimeout((()=>u(J("timeout"))),Math.min(i,m+r-Date.now()))},p=()=>{try{const i=t.startRendering();switch(o(i)&&a(i),t.state){case"running":m=Date.now(),l&&f();break;case"suspended":document.hidden||d++,l&&d>=e?u(J("suspended")):setTimeout(p,n)}}catch(i){u(i)}};p(),c=()=>{l||(l=!0,m>0&&f())}}));return[s,c]}(n),u=a(c.then((t=>function(t){let e=0;for(let n=0;n{if("timeout"===t.name||"suspended"===t.name)return-3;throw t})));return()=>(s(),u)}function J(t){const e=new Error(t);return e.name=t,e}async function T(t,e,o=50){var i,r,a;const c=document;for(;!c.body;)await n(o);const s=c.createElement("iframe");try{for((await new Promise(((t,n)=>{let o=!1;const i=()=>{o=!0,t()};s.onload=i,s.onerror=t=>{o=!0,n(t)};const{style:r}=s;r.setProperty("display","block","important"),r.position="absolute",r.top="0",r.left="0",r.visibility="hidden",e&&"srcdoc"in s?s.srcdoc=e:s.src="about:blank",c.body.appendChild(s);const a=()=>{var t,e;o||("complete"===(null===(e=null===(t=s.contentWindow)||void 0===t?void 0:t.document)||void 0===e?void 0:e.readyState)?i():setTimeout(a,10))};a()})));!(null===(r=null===(i=s.contentWindow)||void 0===i?void 0:i.document)||void 0===r?void 0:r.body);)await n(o);return await t(s,s.contentWindow)}finally{null===(a=s.parentNode)||void 0===a||a.removeChild(s)}}function _(t){const[e,n]=function(t){var e,n;const o=`Unexpected syntax '${t}'`,i=/^\s*([a-z-]*)(.*)$/i.exec(t),r=i[1]||void 0,a={},c=/([.:#][\w-]+|\[.+?\])/gi,s=(t,e)=>{a[t]=a[t]||[],a[t].push(e)};for(;;){const t=c.exec(i[2]);if(!t)break;const r=t[0];switch(r[0]){case".":s("class",r.slice(1));break;case"#":s("id",r.slice(1));break;case"[":{const t=/^\[([\w-]+)([~|^$*]?=("(.*?)"|([\w-]+)))?(\s+[is])?\]$/.exec(r);if(!t)throw new Error(o);s(t[1],null!==(n=null!==(e=t[4])&&void 0!==e?e:t[5])&&void 0!==n?n:"");break}default:throw new Error(o)}}return[r,a]}(t),o=document.createElement(null!=e?e:"div");for(const i of Object.keys(n)){const t=n[i].join(" ");"style"===i?D(o.style,t):o.setAttribute(i,t)}return o}function D(t,e){for(const n of e.split(";")){const e=/^\s*([\w-]+)\s*:\s*(.+?)(\s*!([\w-]+))?\s*$/.exec(n);if(e){const[,n,o,,i]=e;t.setProperty(n,o,i||"")}}}const z=["monospace","sans-serif","serif"],B=["sans-serif-thin","ARNO PRO","Agency FB","Arabic Typesetting","Arial Unicode MS","AvantGarde Bk BT","BankGothic Md BT","Batang","Bitstream Vera Sans Mono","Calibri","Century","Century Gothic","Clarendon","EUROSTILE","Franklin Gothic","Futura Bk BT","Futura Md BT","GOTHAM","Gill Sans","HELV","Haettenschweiler","Helvetica Neue","Humanst521 BT","Leelawadee","Letter Gothic","Levenim MT","Lucida Bright","Lucida Sans","Menlo","MS Mincho","MS Outlook","MS Reference Specialty","MS UI Gothic","MT Extra","MYRIAD PRO","Marlett","Meiryo UI","Microsoft Uighur","Minion Pro","Monotype Corsiva","PMingLiU","Pristina","SCRIPTINA","Segoe UI Light","Serifa","SimHei","Small Fonts","Staccato222 BT","TRAJAN PRO","Univers CE 55 Medium","Vrinda","ZWAdobeF"];function O(t){let e,n,o=!1;const[i,r]=function(){const t=document.createElement("canvas");return t.width=1,t.height=1,[t,t.getContext("2d")]}();return!function(t,e){return!(!e||!t.toDataURL)}(i,r)?e=n="unsupported":(o=function(t){return t.rect(0,0,10,10),t.rect(2,2,6,6),!t.isPointInPath(5,5,"evenodd")}(r),t?e=n="skipped":[e,n]=function(t,e){!function(t,e){t.width=240,t.height=60,e.textBaseline="alphabetic",e.fillStyle="#f60",e.fillRect(100,1,62,20),e.fillStyle="#069",e.font='11pt "Times New Roman"';const n=`Cwm fjordbank gly ${String.fromCharCode(55357,56835)}`;e.fillText(n,2,15),e.fillStyle="rgba(102, 204, 0, 0.2)",e.font="18pt Arial",e.fillText(n,4,45)}(t,e);const n=$(t),o=$(t);if(n!==o)return["unstable","unstable"];!function(t,e){t.width=122,t.height=110,e.globalCompositeOperation="multiply";for(const[n,o,i]of[["#f2f",40,40],["#2ff",80,40],["#ff2",60,80]])e.fillStyle=n,e.beginPath(),e.arc(o,i,40,0,2*Math.PI,!0),e.closePath(),e.fill();e.fillStyle="#f9c",e.arc(60,60,60,0,2*Math.PI,!0),e.arc(60,60,20,0,2*Math.PI,!0),e.fill("evenodd")}(t,e);const i=$(t);return[i,n]}(i,r)),{winding:o,geometry:e,text:n}}function $(t){return t.toDataURL()}function U(){const t=screen,e=t=>u(c(t),null),n=[e(t.width),e(t.height)];return n.sort().reverse(),n}const Q=2500;let K,q;function tt(){return function(){if(void 0!==q)return;const t=()=>{const e=et();nt(e)?q=setTimeout(t,Q):(K=e,q=void 0)};t()}(),async()=>{let t=et();if(nt(t)){if(K)return[...K];E()&&(await function(){const t=document;return(t.exitFullscreen||t.msExitFullscreen||t.mozCancelFullScreen||t.webkitExitFullscreen).call(t)}(),t=et())}return nt(t)||(K=t),t}}function et(){const t=screen;return[u(s(t.availTop),null),u(s(t.width)-s(t.availWidth)-u(s(t.availLeft),0),null),u(s(t.height)-s(t.availHeight)-u(s(t.availTop),0),null),u(s(t.availLeft),null)]}function nt(t){for(let e=0;e<4;++e)if(t[e])return!1;return!0}function ot(){return u(c(navigator.hardwareConcurrency),void 0)}function it(t){t.style.setProperty("visibility","hidden","important"),t.style.setProperty("display","block","important")}function rt(t){return matchMedia(`(inverted-colors: ${t})`).matches}function at(t){return matchMedia(`(forced-colors: ${t})`).matches}function ct(t){return matchMedia(`(prefers-contrast: ${t})`).matches}function st(t){return matchMedia(`(prefers-reduced-motion: ${t})`).matches}function ut(t){return matchMedia(`(prefers-reduced-transparency: ${t})`).matches}function lt(t){return matchMedia(`(dynamic-range: ${t})`).matches}const dt=Math,mt=()=>0;const ft="mmMwWLliI0fiflO&1",pt={default:[],apple:[{font:"-apple-system-body"}],serif:[{fontFamily:"serif"}],sans:[{fontFamily:"sans-serif"}],mono:[{fontFamily:"monospace"}],min:[{fontSize:"1px"}],system:[{fontFamily:"system-ui"}]};function ht(t){const e=H()?0:3,n=Math.pow(10,e);return Math.floor(t*n)/n}const bt=function(){let t=window;for(;;){const n=t.parent;if(!n||n===t)return!1;try{if(n.location.origin!==t.location.origin)return!0}catch(e){if(e instanceof Error&&"SecurityError"===e.name)return!0;throw e}t=n}};const yt=new Set([10752,2849,2884,2885,2886,2928,2929,2930,2931,2932,2960,2961,2962,2963,2964,2965,2966,2967,2968,2978,3024,3042,3088,3089,3106,3107,32773,32777,32777,32823,32824,32936,32937,32938,32939,32968,32969,32970,32971,3317,33170,3333,3379,3386,33901,33902,34016,34024,34076,3408,3410,3411,3412,3413,3414,3415,34467,34816,34817,34818,34819,34877,34921,34930,35660,35661,35724,35738,35739,36003,36004,36005,36347,36348,36349,37440,37441,37443,7936,7937,7938]),gt=new Set([34047,35723,36063,34852,34853,34854,34229,36392,36795,38449]),vt=["FRAGMENT_SHADER","VERTEX_SHADER"],wt=["LOW_FLOAT","MEDIUM_FLOAT","HIGH_FLOAT","LOW_INT","MEDIUM_INT","HIGH_INT"],Lt="WEBGL_debug_renderer_info";function kt(t){if(t.webgl)return t.webgl.context;const e=document.createElement("canvas");let n;e.addEventListener("webglCreateContextError",(()=>n=void 0));for(const i of["webgl","experimental-webgl"]){try{n=e.getContext(i)}catch(o){}if(n)break}return t.webgl={context:n},n}function Vt(t,e,n){const o=t.getShaderPrecisionFormat(t[e],t[n]);return o?[o.rangeMin,o.rangeMax,o.precision]:[]}function St(t){return Object.keys(t.__proto__).filter(Wt)}function Wt(t){return"string"==typeof t&&!t.match(/[^A-Z0-9_x]/)}function xt(){return Y()}function Zt(t){return"function"==typeof t.getParameter}const Mt={userAgentData:async function(){const t=navigator.userAgentData;if(!t)return;const e=t.brands.filter((({brand:t})=>!function(t){return/not/i.test(t)}(t))).map((({brand:t})=>t)),n={brands:e.length>1?e.filter((t=>"Chromium"!==t)):e,mobile:t.mobile,platform:t.platform};if(t.getHighEntropyValues)try{const e=await t.getHighEntropyValues(["architecture","bitness","model","platformVersion"]);n.architecture=e.architecture,n.bitness=e.bitness,n.model=e.model,n.platformVersion=e.platformVersion}catch(o){if(!(o instanceof DOMException&&"NotAllowedError"===o.name))throw o;n.highEntropyStatus="not_allowed"}return n},fonts:function(){return T((async(t,{document:e})=>{const n=e.body;n.style.fontSize="48px";const o=e.createElement("div");o.style.setProperty("visibility","hidden","important");const i={},r={},a=t=>{const n=e.createElement("span"),{style:i}=n;return i.position="absolute",i.top="0",i.left="0",i.fontFamily=t,n.textContent="mmMwWLliI0O&1",o.appendChild(n),n},c=(t,e)=>a(`'${t}',${e}`),s=z.map(a),u=(()=>{const t={};for(const e of B)t[e]=z.map((t=>c(e,t)));return t})();n.appendChild(o);for(let l=0;l{return e=u[t],z.some(((t,n)=>e[n].offsetWidth!==i[t]||e[n].offsetHeight!==r[t]));var e}))}))},domBlockers:async function({debug:t}={}){if(!G()&&!H())return;const e=function(){const t=atob;return{abpIndo:["#Iklan-Melayang","#Kolom-Iklan-728","#SidebarIklan-wrapper",'[title="ALIENBOLA" i]',t("I0JveC1CYW5uZXItYWRz")],abpvn:[".quangcao","#mobileCatfish",t("LmNsb3NlLWFkcw=="),'[id^="bn_bottom_fixed_"]',"#pmadv"],adBlockFinland:[".mainostila",t("LnNwb25zb3JpdA=="),".ylamainos",t("YVtocmVmKj0iL2NsaWNrdGhyZ2guYXNwPyJd"),t("YVtocmVmXj0iaHR0cHM6Ly9hcHAucmVhZHBlYWsuY29tL2FkcyJd")],adBlockPersian:["#navbar_notice_50",".kadr",'TABLE[width="140px"]',"#divAgahi",t("YVtocmVmXj0iaHR0cDovL2cxLnYuZndtcm0ubmV0L2FkLyJd")],adBlockWarningRemoval:["#adblock-honeypot",".adblocker-root",".wp_adblock_detect",t("LmhlYWRlci1ibG9ja2VkLWFk"),t("I2FkX2Jsb2NrZXI=")],adGuardAnnoyances:[".hs-sosyal","#cookieconsentdiv",'div[class^="app_gdpr"]',".as-oil",'[data-cypress="soft-push-notification-modal"]'],adGuardBase:[".BetterJsPopOverlay",t("I2FkXzMwMFgyNTA="),t("I2Jhbm5lcmZsb2F0MjI="),t("I2NhbXBhaWduLWJhbm5lcg=="),t("I0FkLUNvbnRlbnQ=")],adGuardChinese:[t("LlppX2FkX2FfSA=="),t("YVtocmVmKj0iLmh0aGJldDM0LmNvbSJd"),"#widget-quan",t("YVtocmVmKj0iLzg0OTkyMDIwLnh5eiJd"),t("YVtocmVmKj0iLjE5NTZobC5jb20vIl0=")],adGuardFrench:["#pavePub",t("LmFkLWRlc2t0b3AtcmVjdGFuZ2xl"),".mobile_adhesion",".widgetadv",t("LmFkc19iYW4=")],adGuardGerman:['aside[data-portal-id="leaderboard"]'],adGuardJapanese:["#kauli_yad_1",t("YVtocmVmXj0iaHR0cDovL2FkMi50cmFmZmljZ2F0ZS5uZXQvIl0="),t("Ll9wb3BJbl9pbmZpbml0ZV9hZA=="),t("LmFkZ29vZ2xl"),t("Ll9faXNib29zdFJldHVybkFk")],adGuardMobile:[t("YW1wLWF1dG8tYWRz"),t("LmFtcF9hZA=="),'amp-embed[type="24smi"]',"#mgid_iframe1",t("I2FkX2ludmlld19hcmVh")],adGuardRussian:[t("YVtocmVmXj0iaHR0cHM6Ly9hZC5sZXRtZWFkcy5jb20vIl0="),t("LnJlY2xhbWE="),'div[id^="smi2adblock"]',t("ZGl2W2lkXj0iQWRGb3hfYmFubmVyXyJd"),"#psyduckpockeball"],adGuardSocial:[t("YVtocmVmXj0iLy93d3cuc3R1bWJsZXVwb24uY29tL3N1Ym1pdD91cmw9Il0="),t("YVtocmVmXj0iLy90ZWxlZ3JhbS5tZS9zaGFyZS91cmw/Il0="),".etsy-tweet","#inlineShare",".popup-social"],adGuardSpanishPortuguese:["#barraPublicidade","#Publicidade","#publiEspecial","#queTooltip",".cnt-publi"],adGuardTrackingProtection:["#qoo-counter",t("YVtocmVmXj0iaHR0cDovL2NsaWNrLmhvdGxvZy5ydS8iXQ=="),t("YVtocmVmXj0iaHR0cDovL2hpdGNvdW50ZXIucnUvdG9wL3N0YXQucGhwIl0="),t("YVtocmVmXj0iaHR0cDovL3RvcC5tYWlsLnJ1L2p1bXAiXQ=="),"#top100counter"],adGuardTurkish:["#backkapat",t("I3Jla2xhbWk="),t("YVtocmVmXj0iaHR0cDovL2Fkc2Vydi5vbnRlay5jb20udHIvIl0="),t("YVtocmVmXj0iaHR0cDovL2l6bGVuemkuY29tL2NhbXBhaWduLyJd"),t("YVtocmVmXj0iaHR0cDovL3d3dy5pbnN0YWxsYWRzLm5ldC8iXQ==")],bulgarian:[t("dGQjZnJlZW5ldF90YWJsZV9hZHM="),"#ea_intext_div",".lapni-pop-over","#xenium_hot_offers"],easyList:[".yb-floorad",t("LndpZGdldF9wb19hZHNfd2lkZ2V0"),t("LnRyYWZmaWNqdW5reS1hZA=="),".textad_headline",t("LnNwb25zb3JlZC10ZXh0LWxpbmtz")],easyListChina:[t("LmFwcGd1aWRlLXdyYXBbb25jbGljayo9ImJjZWJvcy5jb20iXQ=="),t("LmZyb250cGFnZUFkdk0="),"#taotaole","#aafoot.top_box",".cfa_popup"],easyListCookie:[".ezmob-footer",".cc-CookieWarning","[data-cookie-number]",t("LmF3LWNvb2tpZS1iYW5uZXI="),".sygnal24-gdpr-modal-wrap"],easyListCzechSlovak:["#onlajny-stickers",t("I3Jla2xhbW5pLWJveA=="),t("LnJla2xhbWEtbWVnYWJvYXJk"),".sklik",t("W2lkXj0ic2tsaWtSZWtsYW1hIl0=")],easyListDutch:[t("I2FkdmVydGVudGll"),t("I3ZpcEFkbWFya3RCYW5uZXJCbG9jaw=="),".adstekst",t("YVtocmVmXj0iaHR0cHM6Ly94bHR1YmUubmwvY2xpY2svIl0="),"#semilo-lrectangle"],easyListGermany:["#SSpotIMPopSlider",t("LnNwb25zb3JsaW5rZ3J1ZW4="),t("I3dlcmJ1bmdza3k="),t("I3Jla2xhbWUtcmVjaHRzLW1pdHRl"),t("YVtocmVmXj0iaHR0cHM6Ly9iZDc0Mi5jb20vIl0=")],easyListItaly:[t("LmJveF9hZHZfYW5udW5jaQ=="),".sb-box-pubbliredazionale",t("YVtocmVmXj0iaHR0cDovL2FmZmlsaWF6aW9uaWFkcy5zbmFpLml0LyJd"),t("YVtocmVmXj0iaHR0cHM6Ly9hZHNlcnZlci5odG1sLml0LyJd"),t("YVtocmVmXj0iaHR0cHM6Ly9hZmZpbGlhemlvbmlhZHMuc25haS5pdC8iXQ==")],easyListLithuania:[t("LnJla2xhbW9zX3RhcnBhcw=="),t("LnJla2xhbW9zX251b3JvZG9z"),t("aW1nW2FsdD0iUmVrbGFtaW5pcyBza3lkZWxpcyJd"),t("aW1nW2FsdD0iRGVkaWt1b3RpLmx0IHNlcnZlcmlhaSJd"),t("aW1nW2FsdD0iSG9zdGluZ2FzIFNlcnZlcmlhaS5sdCJd")],estonian:[t("QVtocmVmKj0iaHR0cDovL3BheTRyZXN1bHRzMjQuZXUiXQ==")],fanboyAnnoyances:["#ac-lre-player",".navigate-to-top","#subscribe_popup",".newsletter_holder","#back-top"],fanboyAntiFacebook:[".util-bar-module-firefly-visible"],fanboyEnhancedTrackers:[".open.pushModal","#issuem-leaky-paywall-articles-zero-remaining-nag","#sovrn_container",'div[class$="-hide"][zoompage-fontsize][style="display: block;"]',".BlockNag__Card"],fanboySocial:["#FollowUs","#meteored_share","#social_follow",".article-sharer",".community__social-desc"],frellwitSwedish:[t("YVtocmVmKj0iY2FzaW5vcHJvLnNlIl1bdGFyZ2V0PSJfYmxhbmsiXQ=="),t("YVtocmVmKj0iZG9rdG9yLXNlLm9uZWxpbmsubWUiXQ=="),"article.category-samarbete",t("ZGl2LmhvbGlkQWRz"),"ul.adsmodern"],greekAdBlock:[t("QVtocmVmKj0iYWRtYW4ub3RlbmV0LmdyL2NsaWNrPyJd"),t("QVtocmVmKj0iaHR0cDovL2F4aWFiYW5uZXJzLmV4b2R1cy5nci8iXQ=="),t("QVtocmVmKj0iaHR0cDovL2ludGVyYWN0aXZlLmZvcnRobmV0LmdyL2NsaWNrPyJd"),"DIV.agores300","TABLE.advright"],hungarian:["#cemp_doboz",".optimonk-iframe-container",t("LmFkX19tYWlu"),t("W2NsYXNzKj0iR29vZ2xlQWRzIl0="),"#hirdetesek_box"],iDontCareAboutCookies:['.alert-info[data-block-track*="CookieNotice"]',".ModuleTemplateCookieIndicator",".o--cookies--container","#cookies-policy-sticky","#stickyCookieBar"],icelandicAbp:[t("QVtocmVmXj0iL2ZyYW1ld29yay9yZXNvdXJjZXMvZm9ybXMvYWRzLmFzcHgiXQ==")],latvian:[t("YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiAxMjBweDsgaGVpZ2h0OiA0MHB4OyBvdmVyZmxvdzogaGlkZGVuOyBwb3NpdGlvbjogcmVsYXRpdmU7Il0="),t("YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiA4OHB4OyBoZWlnaHQ6IDMxcHg7IG92ZXJmbG93OiBoaWRkZW47IHBvc2l0aW9uOiByZWxhdGl2ZTsiXQ==")],listKr:[t("YVtocmVmKj0iLy9hZC5wbGFuYnBsdXMuY28ua3IvIl0="),t("I2xpdmVyZUFkV3JhcHBlcg=="),t("YVtocmVmKj0iLy9hZHYuaW1hZHJlcC5jby5rci8iXQ=="),t("aW5zLmZhc3R2aWV3LWFk"),".revenue_unit_item.dable"],listeAr:[t("LmdlbWluaUxCMUFk"),".right-and-left-sponsers",t("YVtocmVmKj0iLmFmbGFtLmluZm8iXQ=="),t("YVtocmVmKj0iYm9vcmFxLm9yZyJd"),t("YVtocmVmKj0iZHViaXp6bGUuY29tL2FyLz91dG1fc291cmNlPSJd")],listeFr:[t("YVtocmVmXj0iaHR0cDovL3Byb21vLnZhZG9yLmNvbS8iXQ=="),t("I2FkY29udGFpbmVyX3JlY2hlcmNoZQ=="),t("YVtocmVmKj0id2Vib3JhbWEuZnIvZmNnaS1iaW4vIl0="),".site-pub-interstitiel",'div[id^="crt-"][data-criteo-id]'],officialPolish:["#ceneo-placeholder-ceneo-12",t("W2hyZWZePSJodHRwczovL2FmZi5zZW5kaHViLnBsLyJd"),t("YVtocmVmXj0iaHR0cDovL2Fkdm1hbmFnZXIudGVjaGZ1bi5wbC9yZWRpcmVjdC8iXQ=="),t("YVtocmVmXj0iaHR0cDovL3d3dy50cml6ZXIucGwvP3V0bV9zb3VyY2UiXQ=="),t("ZGl2I3NrYXBpZWNfYWQ=")],ro:[t("YVtocmVmXj0iLy9hZmZ0cmsuYWx0ZXgucm8vQ291bnRlci9DbGljayJd"),t("YVtocmVmXj0iaHR0cHM6Ly9ibGFja2ZyaWRheXNhbGVzLnJvL3Ryay9zaG9wLyJd"),t("YVtocmVmXj0iaHR0cHM6Ly9ldmVudC4ycGVyZm9ybWFudC5jb20vZXZlbnRzL2NsaWNrIl0="),t("YVtocmVmXj0iaHR0cHM6Ly9sLnByb2ZpdHNoYXJlLnJvLyJd"),'a[href^="/url/"]'],ruAd:[t("YVtocmVmKj0iLy9mZWJyYXJlLnJ1LyJd"),t("YVtocmVmKj0iLy91dGltZy5ydS8iXQ=="),t("YVtocmVmKj0iOi8vY2hpa2lkaWtpLnJ1Il0="),"#pgeldiz",".yandex-rtb-block"],thaiAds:["a[href*=macau-uta-popup]",t("I2Fkcy1nb29nbGUtbWlkZGxlX3JlY3RhbmdsZS1ncm91cA=="),t("LmFkczMwMHM="),".bumq",".img-kosana"],webAnnoyancesUltralist:["#mod-social-share-2","#social-tools",t("LmN0cGwtZnVsbGJhbm5lcg=="),".zergnet-recommend",".yt.btn-link.btn-md.btn"]}}(),o=Object.keys(e),i=[].concat(...o.map((t=>e[t]))),r=await async function(t){var e;const o=document,i=o.createElement("div"),r=new Array(t.length),a={};it(i);for(let n=0;n{const n=e[t];return l(n.map((t=>r[t])))>.6*n.length}));return a.sort(),a},fontPreferences:function(){return function(t,e=4e3){return T(((n,o)=>{const i=o.document,r=i.body,a=r.style;a.width=`${e}px`,a.webkitTextSizeAdjust=a.textSizeAdjust="none",F()?r.style.zoom=""+1/o.devicePixelRatio:G()&&(r.style.zoom="reset");const c=i.createElement("div");return c.textContent=[...Array(e/20|0)].map((()=>"word")).join(" "),r.appendChild(c),t(i,r,o)}),'')}(((t,e,n)=>{const o={},i={};for(const a of Object.keys(pt)){const[n={},i=ft]=pt[a],r=t.createElement("span");r.textContent=i,r.style.whiteSpace="nowrap";for(const t of Object.keys(n)){const e=n[t];void 0!==e&&(r.style[t]=e)}o[a]=r,e.append(t.createElement("br"),r)}const r=F()&&X();for(const a of Object.keys(pt)){const t=o[a].getBoundingClientRect().width;i[a]=r?ht(t*n.devicePixelRatio):t}return i}))},audio:function(){return G()&&j()&&C()||F()&&A()&&function(){const t=window,{URLPattern:e}=t;return l(["union"in Set.prototype,"Iterator"in t,e&&"hasRegExpGroups"in e.prototype,"RGB8"in WebGLRenderingContext.prototype])>=3}()?-4:N()},screenFrame:function(){const t=G()&&j()&&C(),e=Y()&&P();if(t||e)return()=>Promise.resolve(void 0);const n=tt();return async()=>{const t=await n(),e=t=>null===t?null:d(t,10);return[e(t[0]),e(t[1]),e(t[2]),e(t[3])]}},canvas:function(){return O(function(){const t=G()&&j()&&C(),e=Y()&&function(){const t=window,e=navigator,{CSS:n}=t;return l(["userActivation"in e,n.supports("color","light-dark(#000, #fff)"),n.supports("height","1lh"),"globalPrivacyControl"in e])>=3}();return t||e}())},osCpu:function(){return navigator.oscpu},languages:function(){const t=navigator,e=[],n=t.language||t.userLanguage||t.browserLanguage||t.systemLanguage;if(void 0!==n&&e.push([n]),Array.isArray(t.languages))F()&&function(){const t=window;return l([!("MediaSettingsRange"in t),"RTCEncodedAudioFrame"in t,""+t.Intl=="[object Intl]",""+t.Reflect=="[object Reflect]"])>=3}()||e.push(t.languages);else if("string"==typeof t.languages){const n=t.languages;n&&e.push(n.split(","))}return e},colorDepth:function(){return window.screen.colorDepth},deviceMemory:function(){return u(s(navigator.deviceMemory),void 0)},screenResolution:function(){if(!(G()&&j()&&C()))return U()},hardwareConcurrency:function(){const t=ot();return void 0!==t&&Y()&&P()?t>=8?8:4:t},timezone:function(){var t;const e=null===(t=window.Intl)||void 0===t?void 0:t.DateTimeFormat;if(e){const t=(new e).resolvedOptions().timeZone;if(t)return t}const n=-function(){const t=(new Date).getFullYear();return Math.max(s(new Date(t,0,1).getTimezoneOffset()),s(new Date(t,6,1).getTimezoneOffset()))}();return`UTC${n>=0?"+":""}${n}`},sessionStorage:function(){try{return!!window.sessionStorage}catch(t){return!0}},localStorage:function(){try{return!!window.localStorage}catch(t){return!0}},indexedDB:function(){if(!M()&&!R())try{return!!window.indexedDB}catch(t){return!0}},openDatabase:function(){return!!window.openDatabase},cpuClass:function(){return navigator.cpuClass},platform:function(){const{platform:t}=navigator;return"MacIntel"===t&&G()&&!I()?function(){if("iPad"===navigator.platform)return!0;const t=screen,e=t.width/t.height;return l(["MediaSource"in window,!!Element.prototype.webkitRequestFullscreen,e>.65&&e<1.53])>=2}()?"iPad":"iPhone":t},plugins:function(){const t=navigator.plugins;if(!t)return;const e=[];for(let n=0;ndt.log(t+dt.sqrt(t*t+1)))(1),atanh:i(.5),atanhPf:(t=>dt.log((1+t)/(1-t))/2)(.5),atan:r(.5),sin:a(-1e300),sinh:c(1),sinhPf:(t=>dt.exp(t)-1/dt.exp(t)/2)(1),cos:s(10.000000000123),cosh:u(1),coshPf:(t=>(dt.exp(t)+1/dt.exp(t))/2)(1),tan:l(-1e300),tanh:d(1),tanhPf:(t=>(dt.exp(2*t)-1)/(dt.exp(2*t)+1))(1),exp:m(1),expm1:f(1),expm1Pf:(t=>dt.exp(t)-1)(1),log1p:p(10),log1pPf:(t=>dt.log(1+t))(10),powPI:(t=>dt.pow(dt.PI,t))(-100)};var h},pdfViewerEnabled:function(){return navigator.pdfViewerEnabled},architecture:function(){const t=new Float32Array(1),e=new Uint8Array(t.buffer);return t[0]=1/0,t[0]=t[0]-t[0],e[3]},applePay:function(){const{ApplePaySession:t}=window;if("function"!=typeof(null==t?void 0:t.canMakePayments))return-1;if(bt())return-3;try{return t.canMakePayments()?1:0}catch(e){return function(t){if(t instanceof Error&&"InvalidAccessError"===t.name&&/\bfrom\b.*\binsecure\b/i.test(t.message))return-2;throw t}(e)}},privateClickMeasurement:function(){var t;const e=document.createElement("a"),n=null!==(t=e.attributionSourceId)&&void 0!==t?t:e.attributionsourceid;return void 0===n?void 0:String(n)},audioBaseLatency:function(){if(!(H()||G()))return-2;if(!window.AudioContext)return-1;const t=(new AudioContext).baseLatency;return null==t?-1:isFinite(t)?t:-3},dateTimeLocale:function(){if(!window.Intl)return-1;const t=window.Intl.DateTimeFormat;if(!t)return-2;const e=t().resolvedOptions().locale;return e||""===e?e:-3},webGlBasics:function({cache:t}){var e,n,o,i,r,a;const c=kt(t);if(!c)return-1;if(!Zt(c))return-2;const s=xt()?null:c.getExtension(Lt);return{version:(null===(e=c.getParameter(c.VERSION))||void 0===e?void 0:e.toString())||"",vendor:(null===(n=c.getParameter(c.VENDOR))||void 0===n?void 0:n.toString())||"",vendorUnmasked:s?null===(o=c.getParameter(s.UNMASKED_VENDOR_WEBGL))||void 0===o?void 0:o.toString():"",renderer:(null===(i=c.getParameter(c.RENDERER))||void 0===i?void 0:i.toString())||"",rendererUnmasked:s?null===(r=c.getParameter(s.UNMASKED_RENDERER_WEBGL))||void 0===r?void 0:r.toString():"",shadingLanguageVersion:(null===(a=c.getParameter(c.SHADING_LANGUAGE_VERSION))||void 0===a?void 0:a.toString())||""}},webGlExtensions:function({cache:t}){const e=kt(t);if(!e)return-1;if(!Zt(e))return-2;const n=e.getSupportedExtensions(),o=e.getContextAttributes(),i=[],r=[],a=[],c=[],s=[];if(o)for(const l of Object.keys(o))r.push(`${l}=${o[l]}`);const u=St(e);for(const l of u){const t=e[l];a.push(`${l}=${t}${yt.has(t)?`=${e.getParameter(t)}`:""}`)}if(n)for(const l of n){if(l===Lt&&xt()||"WEBGL_polygon_mode"===l&&(F()||G()))continue;const t=e.getExtension(l);if(t)for(const n of St(t)){const o=t[n];c.push(`${n}=${o}${gt.has(o)?`=${e.getParameter(o)}`:""}`)}else i.push(l)}for(const l of vt)for(const t of wt){const n=Vt(e,l,t);s.push(`${l}.${t}=${n.join(",")}`)}return c.sort(),a.sort(),{contextAttributes:r,parameters:a,shaderPrecisions:s,extensions:n,extensionParameters:c,unsupportedExtensions:i}}};const Rt="$ if upgrade to Pro: https://fingerprint.com/github/?utm_source=oss&utm_medium=referral&utm_campaign=confidence_score";function Ft(t){const e=function(t){if(H())return.4;if(G())return!I()||j()&&C()?.3:.5;const e="value"in t.platform?t.platform.value:"";if(/^Win/.test(e))return.6;if(/^Mac/.test(e))return.5;return.7}(t),n=function(t){return d(.99+.01*t,1e-4)}(e);return{score:e,comment:Rt.replace(/\$/g,`${n}`)}}function Gt(t){return JSON.stringify(t,((t,e)=>{return e instanceof Error?{name:(n=e).name,message:n.message,stack:null===(o=n.stack)||void 0===o?void 0:o.split("\n"),...n}:e;var n,o}),2)}function It(t){return W(function(t){let e="";for(const n of Object.keys(t).sort()){const o=t[n],i="error"in o?"error":JSON.stringify(o.value);e+=`${e?"|":""}${n.replace(/([:|\\])/g,"\\$1")}:${i}`}return e}(t))}function Ct(t=50){return function(t,e=1/0){const{requestIdleCallback:o}=window;return o?new Promise((t=>o.call(window,(()=>t()),{timeout:e}))):n(Math.min(t,e))}(t,2*t)}function Yt(t,n){const o=Date.now();return{async get(i){const r=Date.now(),a=await t(),c=function(t){let n;const o=Ft(t);return{get visitorId(){return void 0===n&&(n=It(this.components)),n},set visitorId(t){n=t},confidence:o,components:t,version:e}}(a);return(n||(null==i?void 0:i.debug))&&console.log(`Copy the text below to get the debug data:\n\n\`\`\`\nversion: ${c.version}\nuserAgent: ${navigator.userAgent}\ntimeBetweenLoadAndGet: ${r-o}\nvisitorId: ${c.visitorId}\ncomponents: ${Gt(a)}\n\`\`\``),c}}}async function Pt(t={}){const{delayFallback:n,debug:o,monitoring:i=!0}=t;i&&function(){if(!(window.__fpjs_d_m||Math.random()>=.001))try{const t=new XMLHttpRequest;t.open("get",`https://m1.openfpcdn.io/fingerprintjs/v${e}/npm-monitoring`,!0),t.send()}catch(t){console.error(t)}}(),await Ct(n);const r=function(t){return Z(Mt,t,[])}({cache:{},debug:o});return Yt(r,o)}var Xt={load:Pt,hashComponents:It,componentsToDebugString:Gt};const jt=W;t.componentsToDebugString=Gt,t.default=Xt,t.getFullscreenElement=E,t.getUnstableAudioFingerprint=N,t.getUnstableCanvasFingerprint=O,t.getUnstableHardwareConcurrency=ot,t.getUnstableScreenFrame=tt,t.getUnstableScreenResolution=U,t.getWebGLContext=kt,t.hashComponents=It,t.isAndroid=H,t.isChromium=F,t.isDesktopWebKit=I,t.isEdgeHTML=R,t.isGecko=Y,t.isSamsungInternet=A,t.isTrident=M,t.isWebKit=G,t.load=Pt,t.loadSources=Z,t.murmurX64Hash128=jt,t.prepareForSources=Ct,t.sources=Mt,t.transformSource=function(t,e){const n=t=>x(t)?e(t):()=>{const n=t();return o(n)?n.then(e):e(n)};return e=>{const i=t(e);return o(i)?i.then(n):n(i)}},t.withIframe=T,Object.defineProperty(t,"__esModule",{value:!0})}));