Last active
August 14, 2025 20:30
-
-
Save a904guy/068e4f980907a5a3270291c554308712 to your computer and use it in GitHub Desktop.
All-in-one stealth JS patch for anti-fingerprinting.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*! | |
| * Ava Stealth FP Patch - Combined Edition | |
| * Version: 2025-08-13 | |
| * Injection: document-start in the main world. Idempotent guards prevent double install. | |
| * Purpose: Reduce fingerprint surface area and stabilize identity for embedded browsers or webviews. | |
| * | |
| * Coverage summary: | |
| * 1) Navigator and UA identity | |
| * - navigator.userAgent, userAgentData (brands, platform, mobile) | |
| * - platform, language, languages, vendor, appName, appVersion, productSub, vendorSub | |
| * - doNotTrack, deviceMemory, hardwareConcurrency, maxTouchPoints, cookieEnabled, pdfViewerEnabled | |
| * - webdriver set to undefined | |
| * - window.chrome stub (runtime, app, webstore), legacy chrome.loadTimes and chrome.csi stubs | |
| * | |
| * 2) Permissions and notification surfaces | |
| * - navigator.permissions.query normalization for notifications | |
| * - Notification.permission forced stable, requestPermission returns "default" | |
| * | |
| * 3) Battery and network info | |
| * - navigator.getBattery() stable values | |
| * - NetworkInformation: effectiveType, downlink, rtt, saveData stabilized | |
| * | |
| * 4) Storage and quota | |
| * - navigator.storage.estimate() returns rounded usage and quota | |
| * | |
| * 5) Plugins and mimeTypes | |
| * - Stable PluginArray and MimeTypeArray shapes with PDF viewer entries | |
| * | |
| * 6) Screen and window geometry | |
| * - screen.width, height, availWidth, availHeight, colorDepth | |
| * - devicePixelRatio | |
| * - outerWidth, outerHeight best effort | |
| * - screenX, screenY, availTop, availLeft | |
| * - screen.orientation angle and type | |
| * - visualViewport rounding for width, height, offsets, scale | |
| * | |
| * 7) Timezone and Intl | |
| * - Intl.DateTimeFormat.resolvedOptions().timeZone | |
| * - Intl.NumberFormat, PluralRules, Collator normalized fields | |
| * | |
| * 8) CSS and media queries | |
| * - window.matchMedia for prefers-color-scheme, prefers-reduced-motion, contrast, color-gamut, dynamic-range | |
| * - CSS.supports filter for high entropy color and HDR features | |
| * | |
| * 9) Geometry probes | |
| * - getBoundingClientRect optional tiny jitter | |
| * - getClientRects rounding or tiny jitter | |
| * | |
| * 10) Canvas and text metrics | |
| * - 2D canvas getImageData noise injection | |
| * - toDataURL and toBlob side effects to ensure noise is sampled | |
| * - measureText width fuzzing | |
| * - OffscreenCanvas 2D path parity | |
| * | |
| * 11) WebGL identity and caps | |
| * - WebGL vendor and renderer spoof | |
| * - Hide WEBGL_debug_renderer_info | |
| * - Normalize supported extensions list | |
| * - Normalize shader precision and key capability limits | |
| * | |
| * 12) Audio fingerprinting | |
| * - AudioContext sampleRate guard | |
| * - AnalyserNode.getByteFrequencyData shaping | |
| * - OfflineAudioContext.startRendering output noise | |
| * | |
| * 13) WebRTC leak reduction | |
| * - Sanitize SDP to prefer relay and strip host candidates | |
| * - Filter onicecandidate events to relay only | |
| * | |
| * 14) Media devices | |
| * - mediaDevices.enumerateDevices fallback shape | |
| * - getUserMedia strips exact deviceId hints before permission | |
| * | |
| * 15) Speech and input devices | |
| * - speechSynthesis.getVoices stable fallback | |
| * - navigator.getGamepads stable array shape | |
| * - navigator.keyboard.getLayoutMap returns stable QWERTY map | |
| * | |
| * 16) WebGPU | |
| * - navigator.gpu.requestAdapter returns null to reduce entropy | |
| * | |
| * 17) Performance surfaces | |
| * - performance.now jitter or rounding | |
| * - performance.timeOrigin rounded | |
| * - performance.memory stabilized | |
| * - getEntries*, getEntriesByType, getEntriesByName rounding and size field removal | |
| * - PerformanceObserver.observe filters resource entries | |
| * - requestIdleCallback wrapper with stable timeRemaining | |
| * | |
| * 18) History and referrer | |
| * - history.length floor to plausible value | |
| * - document.referrer empty | |
| * | |
| * 19) SVG and path metrics | |
| * - SVGPathElement.getTotalLength tiny deterministic offset | |
| * | |
| * 20) Media capabilities and codecs | |
| * - HTMLMediaElement.canPlayType returns common Chrome style answers | |
| * - navigator.mediaCapabilities.decodingInfo optimistic, stable | |
| * - EME requestMediaKeySystemAccess reduces to Widevine or rejects uncommon systems | |
| * | |
| * 21) Clipboard API | |
| * - readText and writeText guarded to avoid leaking environment via errors | |
| * | |
| * 22) Document encoding | |
| * - document.characterSet and document.charset set to UTF-8 | |
| * | |
| * Configuration: | |
| * - CFG object controls identity and toggles | |
| * ua, platform, brands, lang, timezone, doNotTrack, maxTouchPoints, hardwareConcurrency, deviceMemory, | |
| * colorDepth, dpr, screen.{width,height,availWidth,availHeight}, webglVendor, webglRenderer, | |
| * enableCanvasNoise, enableAudioNoise, enablePerfJitter, stablePerOrigin | |
| * - ENABLE flags gate optional modules | |
| * cssMedia, geometryJitter, fonts, offscreenCanvas, webglCaps, mediaCodecs, | |
| * perfEntries, keyboardLayout, legacyNavFields, screenExtras | |
| * | |
| * Usage: | |
| * - Inject at document-start before any site script. For Qt use QWebEngineScript.DocumentCreation. For CEF use a preload or extension. | |
| * - Keep CFG stable per persona to avoid linkability across visits. | |
| * - Modules are guarded and safe to include multiple times in the same context. | |
| * | |
| * Host level hardening recommended outside JS: | |
| * - Engine flags: force WebRTC to public interface only or relay only | |
| * - Normalize HTTP headers and UA hints: Accept-Language, DNT, Sec-CH-UA family | |
| * - Lock TLS profile, ALPN order, cipher suites per persona via proxy or engine config | |
| * - Fix OS font list by bundling a known font pack | |
| * - Route DNS and all traffic through a fixed proxy or gateway | |
| * | |
| * Caveats: | |
| * - Overriding too much can break feature detection or playback on some sites. Toggle modules as needed. | |
| * - Stability matters. Frequent identity changes can increase linkability risk. | |
| */ | |
| (() => { | |
| if (globalThis.__ava_stealth_installed) return; | |
| Object.defineProperty(globalThis, "__ava_stealth_installed", { value: true }); | |
| // -------- Config -------- | |
| const CFG = { | |
| ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", | |
| platform: "Win32", | |
| brands: [ | |
| { brand: "Chromium", version: "123" }, | |
| { brand: "Not.A/Brand", version: "24" }, | |
| { brand: "Google Chrome", version: "123" } | |
| ], | |
| lang: ["en-US", "en"], | |
| timezone: "America/New_York", | |
| doNotTrack: "1", | |
| maxTouchPoints: 0, | |
| hardwareConcurrency: 8, | |
| deviceMemory: 8, | |
| colorDepth: 24, | |
| dpr: 1, | |
| screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1040 }, | |
| webglVendor: "Intel Inc.", | |
| webglRenderer: "Intel Iris OpenGL Engine", | |
| enableCanvasNoise: true, | |
| enableAudioNoise: true, | |
| enablePerfJitter: true, | |
| stablePerOrigin: true, | |
| // module toggles | |
| cssMedia: true, | |
| geometryJitter: false, // set true if you accept tiny layout noise | |
| fonts: true, | |
| offscreenCanvas: true, | |
| webglCaps: true, | |
| mediaCodecs: true, | |
| perfEntries: true, | |
| keyboardLayout: true, | |
| legacyNavFields: true, | |
| screenExtras: true | |
| }; | |
| // Map toggles to ENABLE for code that expects it | |
| const ENABLE = { | |
| cssMedia: CFG.cssMedia, | |
| geometryJitter: CFG.geometryJitter, | |
| fonts: CFG.fonts, | |
| offscreenCanvas: CFG.offscreenCanvas, | |
| webglCaps: CFG.webglCaps, | |
| mediaCodecs: CFG.mediaCodecs, | |
| perfEntries: CFG.perfEntries, | |
| keyboardLayout: CFG.keyboardLayout, | |
| legacyNavFields: CFG.legacyNavFields, | |
| screenExtras: CFG.screenExtras | |
| }; | |
| // -------- Utilities -------- | |
| const Rand = (() => { | |
| const seedStr = CFG.stablePerOrigin | |
| ? (location.origin || location.host || "default") + "|" + CFG.ua | |
| : (Math.random() + "|" + Date.now()); | |
| let h = 2166136261 >>> 0; | |
| for (let i = 0; i < seedStr.length; i++) { h ^= seedStr.charCodeAt(i); h = Math.imul(h, 16777619); } | |
| const next = () => { | |
| h ^= h << 13; h >>>= 0; | |
| h ^= h >>> 17; h >>>= 0; | |
| h ^= h << 5; h >>>= 0; | |
| return (h >>> 0) / 4294967296; | |
| }; | |
| return { | |
| float: next, | |
| int: (min, max) => Math.floor(next() * (max - min + 1)) + min, | |
| smallNoise: (scale = 1e-5) => (next() - 0.5) * 2 * scale | |
| }; | |
| })(); | |
| const defineGetter = (obj, prop, getter) => { | |
| try { | |
| const desc = Object.getOwnPropertyDescriptor(obj, prop); | |
| if (desc && !desc.configurable) return; | |
| Object.defineProperty(obj, prop, { configurable: true, enumerable: desc ? desc.enumerable : true, get: getter }); | |
| } catch {} | |
| }; | |
| const defineValue = (obj, prop, value) => { | |
| try { | |
| const desc = Object.getOwnPropertyDescriptor(obj, prop); | |
| if (desc && !desc.configurable) return; | |
| Object.defineProperty(obj, prop, { configurable: true, enumerable: desc ? desc.enumerable : true, value }); | |
| } catch {} | |
| }; | |
| const override = (obj, prop, fnFactory) => { | |
| try { | |
| const orig = obj[prop]; | |
| const wrapped = fnFactory(orig); | |
| defineValue(obj, prop, wrapped); | |
| return { orig, wrapped }; | |
| } catch { return null; } | |
| }; | |
| // -------- Navigator base props -------- | |
| try { | |
| const navProto = Navigator.prototype; | |
| defineGetter(navProto, "userAgent", () => CFG.ua); | |
| defineGetter(navProto, "platform", () => CFG.platform); | |
| defineGetter(navProto, "language", () => CFG.lang[0]); | |
| defineGetter(navProto, "languages", () => CFG.lang.slice()); | |
| defineGetter(navProto, "hardwareConcurrency", () => CFG.hardwareConcurrency); | |
| defineGetter(navProto, "deviceMemory", () => CFG.deviceMemory); | |
| defineGetter(navProto, "maxTouchPoints", () => CFG.maxTouchPoints); | |
| defineGetter(navProto, "doNotTrack", () => CFG.doNotTrack); | |
| defineGetter(navProto, "webdriver", () => undefined); | |
| const uaData = { | |
| brands: CFG.brands.map(b => ({ brand: b.brand, version: String(b.version) })), | |
| mobile: false, | |
| platform: CFG.platform.includes("Win") ? "Windows" : (CFG.platform.includes("Mac") ? "macOS" : "Linux"), | |
| getHighEntropyValues: async (hints) => { | |
| const out = { | |
| architecture: "x86", | |
| bitness: "64", | |
| model: "", | |
| platform: CFG.platform.includes("Win") ? "Windows" : (CFG.platform.includes("Mac") ? "macOS" : "Linux"), | |
| platformVersion: "15.0.0", | |
| uaFullVersion: "123.0.0.0", | |
| fullVersionList: CFG.brands.map(b => ({ brand: b.brand, version: "123.0.0.0" })) | |
| }; | |
| if (!Array.isArray(hints)) return out; | |
| return Object.fromEntries(hints.map(k => [k, out[k]])); | |
| }, | |
| toJSON() { return { brands: this.brands, mobile: this.mobile, platform: this.platform }; } | |
| }; | |
| defineGetter(navProto, "userAgentData", () => uaData); | |
| if (!("chrome" in globalThis)) { | |
| defineValue(globalThis, "chrome", { runtime: {}, app: {}, webstore: {} }); | |
| } | |
| } catch {} | |
| // -------- Permissions API -------- | |
| try { | |
| const permissions = navigator.permissions; | |
| if (permissions && permissions.query) { | |
| override(permissions, "query", (orig) => function(p) { | |
| try { | |
| if (p && p.name === "notifications") { | |
| return Promise.resolve({ state: Notification.permission }); | |
| } | |
| } catch {} | |
| return orig.apply(this, arguments); | |
| }); | |
| } | |
| } catch {} | |
| // -------- Battery API -------- | |
| try { | |
| if (typeof navigator.getBattery === "function") { | |
| override(navigator, "getBattery", (_orig) => async function() { | |
| return { | |
| charging: true, | |
| chargingTime: 0, | |
| dischargingTime: Infinity, | |
| level: 1, | |
| onchargingchange: null, | |
| onlevelchange: null, | |
| addEventListener() {}, | |
| removeEventListener() {}, | |
| dispatchEvent() { return false; } | |
| }; | |
| }); | |
| } | |
| } catch {} | |
| // -------- Network Information API -------- | |
| try { | |
| const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection; | |
| if (conn) { | |
| const proto = Object.getPrototypeOf(conn) || conn; | |
| defineGetter(proto, "effectiveType", () => "4g"); | |
| defineGetter(proto, "downlink", () => 10); | |
| defineGetter(proto, "rtt", () => 50); | |
| defineGetter(proto, "saveData", () => false); | |
| } | |
| } catch {} | |
| // -------- Storage API estimate -------- | |
| try { | |
| if (navigator.storage && navigator.storage.estimate) { | |
| override(navigator.storage, "estimate", (orig) => async function() { | |
| const e = await orig.call(this); | |
| const quota = e && e.quota ? e.quota : 1024 * 1024 * 1024; | |
| return { usage: Math.floor(quota * 0.05), quota }; | |
| }); | |
| } | |
| } catch {} | |
| // -------- Plugins and MimeTypes -------- | |
| try { | |
| const makeArrayLike = (arr, name) => { | |
| Object.defineProperty(arr, "item", { value: (i) => arr[i] || null }); | |
| Object.defineProperty(arr, "namedItem", { value: (n) => arr.find(x => x.name === n) || null }); | |
| Object.defineProperty(arr, "refresh", { value: () => {} }); | |
| Object.defineProperty(arr, "toString", { value: () => `[object ${name}]` }); | |
| return arr; | |
| }; | |
| const mimeTypes = makeArrayLike([], "MimeTypeArray"); | |
| const plugins = makeArrayLike([], "PluginArray"); | |
| const addPlugin = (name, filename, description, mimes) => { | |
| const p = { name, filename, description, length: mimes.length, 0: null }; | |
| mimes.forEach((mt, idx) => { | |
| const mtObj = { type: mt.type, suffixes: mt.suffixes || "", description: mt.description || "" }; | |
| mimeTypes.push(mtObj); | |
| p[idx] = mtObj; | |
| }); | |
| plugins.push(p); | |
| }; | |
| addPlugin("Chrome PDF Viewer", "internal-pdf-viewer", "Portable Document Format", [{ type: "application/pdf", suffixes: "pdf", description: "" }]); | |
| addPlugin("Chromium PDF Viewer", "internal-pdf-viewer", "Portable Document Format", [{ type: "application/pdf", suffixes: "pdf", description: "" }]); | |
| addPlugin("PDF Viewer", "internal-pdf-viewer", "Portable Document Format", [{ type: "application/pdf", suffixes: "pdf", description: "" }]); | |
| defineGetter(Navigator.prototype, "plugins", () => plugins); | |
| defineGetter(Navigator.prototype, "mimeTypes", () => mimeTypes); | |
| } catch {} | |
| // -------- Screen, DPR, window sizing -------- | |
| try { | |
| if (typeof Screen !== "undefined" && Screen.prototype) { | |
| const scr = Screen.prototype; | |
| defineGetter(scr, "width", () => CFG.screen.width); | |
| defineGetter(scr, "height", () => CFG.screen.height); | |
| defineGetter(scr, "availWidth", () => CFG.screen.availWidth); | |
| defineGetter(scr, "availHeight", () => CFG.screen.availHeight); | |
| defineGetter(scr, "colorDepth", () => CFG.colorDepth); | |
| } | |
| } catch {} | |
| try { | |
| defineGetter(globalThis, "devicePixelRatio", () => CFG.dpr); | |
| } catch {} | |
| try { | |
| defineGetter(globalThis, "outerWidth", () => innerWidth + 16); | |
| defineGetter(globalThis, "outerHeight", () => innerHeight + 88); | |
| } catch {} | |
| // -------- Timezone hints -------- | |
| try { | |
| const intlProto = Intl.DateTimeFormat.prototype; | |
| override(intlProto, "resolvedOptions", (orig) => function() { | |
| const r = orig.call(this); | |
| if (!r || typeof r !== "object") return r; | |
| return Object.assign({}, r, { timeZone: CFG.timezone }); | |
| }); | |
| } catch {} | |
| // -------- Canvas fingerprint noise -------- | |
| try { | |
| if (CFG.enableCanvasNoise) { | |
| const addNoise2D = (ctx) => { | |
| const _getImageData = ctx.getImageData; | |
| ctx.getImageData = function() { | |
| const data = _getImageData.apply(this, arguments); | |
| for (let i = 0; i < data.data.length; i += 4) { | |
| data.data[i] = Math.min(255, Math.max(0, data.data[i] + Rand.smallNoise(0.6) * 255)); | |
| } | |
| return data; | |
| }; | |
| const _toDataURL = HTMLCanvasElement.prototype.toDataURL; | |
| HTMLCanvasElement.prototype.toDataURL = function() { | |
| try { ctx.getImageData(0, 0, 1, 1); } catch {} | |
| return _toDataURL.apply(this, arguments); | |
| }; | |
| const _toBlob = HTMLCanvasElement.prototype.toBlob; | |
| if (_toBlob) { | |
| HTMLCanvasElement.prototype.toBlob = function() { | |
| try { ctx.getImageData(0, 0, 1, 1); } catch {} | |
| return _toBlob.apply(this, arguments); | |
| }; | |
| } | |
| const _measureText = ctx.measureText; | |
| ctx.measureText = function() { | |
| const m = _measureText.apply(this, arguments); | |
| try { | |
| const w = m.width + Rand.smallNoise(0.01); | |
| return new Proxy(m, { get: (t, p) => p === "width" ? w : t[p] }); | |
| } catch { return m; } | |
| }; | |
| }; | |
| const _getContext = HTMLCanvasElement.prototype.getContext; | |
| HTMLCanvasElement.prototype.getContext = function(type, opts) { | |
| const ctx = _getContext.call(this, type, opts); | |
| if (type === "2d" && ctx && !ctx.__ava_patched) { | |
| addNoise2D(ctx); | |
| defineValue(ctx, "__ava_patched", true); | |
| } | |
| return ctx; | |
| }; | |
| } | |
| } catch {} | |
| // -------- WebGL spoof -------- | |
| try { | |
| const patchGL = (proto) => { | |
| if (!proto) return; | |
| const PARAM_VENDOR = 0x1F00; | |
| const PARAM_RENDERER = 0x1F01; | |
| const UNMASKED_VENDOR_WEBGL = 0x9245; | |
| const UNMASKED_RENDERER_WEBGL = 0x9246; | |
| override(proto, "getParameter", (orig) => function(p) { | |
| if (p === PARAM_VENDOR || p === UNMASKED_VENDOR_WEBGL) return CFG.webglVendor; | |
| if (p === PARAM_RENDERER || p === UNMASKED_RENDERER_WEBGL) return CFG.webglRenderer; | |
| return orig.apply(this, arguments); | |
| }); | |
| override(proto, "getExtension", (orig) => function(name) { | |
| if (name === "WEBGL_debug_renderer_info") return null; | |
| return orig.apply(this, arguments); | |
| }); | |
| }; | |
| if (globalThis.WebGLRenderingContext) patchGL(WebGLRenderingContext.prototype); | |
| if (globalThis.WebGL2RenderingContext) patchGL(WebGL2RenderingContext.prototype); | |
| } catch {} | |
| // -------- Audio fingerprint noise -------- | |
| try { | |
| if (CFG.enableAudioNoise) { | |
| const addNoiseToBuffer = (buf) => { | |
| try { | |
| const len = buf.length; | |
| for (let ch = 0; ch < buf.numberOfChannels; ch++) { | |
| const data = buf.getChannelData(ch); | |
| for (let i = 0; i < len; i++) data[i] += Rand.smallNoise(1e-6); | |
| } | |
| } catch {} | |
| return buf; | |
| }; | |
| const wrapCtx = (proto) => { | |
| if (!proto) return; | |
| override(proto, "createAnalyser", (orig) => function() { | |
| const node = orig.apply(this, arguments); | |
| const _getByteFreqData = node.getByteFrequencyData; | |
| node.getByteFrequencyData = function(arr) { | |
| _getByteFreqData.call(this, arr); | |
| for (let i = 0; i < arr.length; i++) arr[i] = Math.min(255, Math.max(0, arr[i] + Rand.int(-1, 1))); | |
| }; | |
| return node; | |
| }); | |
| }; | |
| if (globalThis.AudioContext) wrapCtx(AudioContext.prototype); | |
| if (globalThis.webkitAudioContext) wrapCtx(webkitAudioContext.prototype); | |
| const OAC = globalThis.OfflineAudioContext || globalThis.webkitOfflineAudioContext; | |
| if (OAC && OAC.prototype && OAC.prototype.startRendering) { | |
| override(OAC.prototype, "startRendering", (orig) => async function() { | |
| const res = await orig.apply(this, arguments); | |
| try { addNoiseToBuffer(res); } catch {} | |
| return res; | |
| }); | |
| } | |
| } | |
| } catch {} | |
| // -------- WebRTC leak reduction -------- | |
| try { | |
| if ("RTCPeerConnection" in globalThis) { | |
| const sanitizeSDP = (sdp) => { | |
| if (typeof sdp !== "string") return sdp; | |
| return sdp | |
| .split("\n") | |
| .filter(line => !/^a=candidate:/.test(line) || / typ relay /.test(line)) | |
| .map(line => line.replace(/(c=IN IP4 )\d+\.\d+\.\d+\.\d+/, "$10.0.0.0")) | |
| .join("\n"); | |
| }; | |
| const PC = RTCPeerConnection; | |
| const pcProto = PC.prototype; | |
| override(pcProto, "createOffer", (orig) => function() { | |
| return orig.apply(this, arguments).then(desc => { | |
| desc.sdp = sanitizeSDP(desc.sdp); | |
| return desc; | |
| }); | |
| }); | |
| override(pcProto, "setLocalDescription", (orig) => function(desc) { | |
| if (desc && typeof desc.sdp === "string") desc.sdp = sanitizeSDP(desc.sdp); | |
| return orig.apply(this, arguments); | |
| }); | |
| const _addEventListener = pcProto.addEventListener; | |
| pcProto.addEventListener = function(type, listener, opts) { | |
| if (type === "icecandidate" && typeof listener === "function") { | |
| const wrapped = (e) => { | |
| if (e && e.candidate && e.candidate.candidate && !/ typ relay /.test(e.candidate.candidate)) return; | |
| listener(e); | |
| }; | |
| return _addEventListener.call(this, type, wrapped, opts); | |
| } | |
| return _addEventListener.call(this, type, listener, opts); | |
| }; | |
| const _onicecandidate = Object.getOwnPropertyDescriptor(pcProto, "onicecandidate"); | |
| if (_onicecandidate && _onicecandidate.configurable) { | |
| Object.defineProperty(pcProto, "onicecandidate", { | |
| configurable: true, | |
| get() { return this.__ava_onice || null; }, | |
| set(fn) { | |
| this.__ava_onice = typeof fn === "function" ? (e) => { | |
| if (e && e.candidate && e.candidate.candidate && !/ typ relay /.test(e.candidate.candidate)) return; | |
| fn(e); | |
| } : fn; | |
| } | |
| }); | |
| } | |
| } | |
| } catch {} | |
| // -------- MediaDevices and device enumeration -------- | |
| try { | |
| if (navigator.mediaDevices) { | |
| override(navigator.mediaDevices, "enumerateDevices", (orig) => async function() { | |
| const base = await orig.call(this); | |
| const fake = [ | |
| { deviceId: "default", kind: "audioinput", label: "", groupId: "default" }, | |
| { deviceId: "default", kind: "audiooutput", label: "", groupId: "default" }, | |
| { deviceId: "default", kind: "videoinput", label: "", groupId: "default" } | |
| ]; | |
| return Array.isArray(base) && base.length ? base : fake; | |
| }); | |
| if (navigator.mediaDevices.getUserMedia) { | |
| override(navigator.mediaDevices, "getUserMedia", (orig) => function(constraints) { | |
| const c = JSON.parse(JSON.stringify(constraints || {})); | |
| ["audio", "video"].forEach(k => { | |
| if (c[k] && typeof c[k] === "object") { | |
| if ("deviceId" in c[k]) delete c[k].deviceId; | |
| } | |
| }); | |
| return orig.call(this, c); | |
| }); | |
| } | |
| } | |
| } catch {} | |
| // -------- SpeechSynthesis voices -------- | |
| try { | |
| if ("speechSynthesis" in globalThis && speechSynthesis.getVoices) { | |
| override(speechSynthesis, "getVoices", (orig) => function() { | |
| const list = orig.call(this) || []; | |
| if (list.length) return list; | |
| return [ | |
| { name: "Microsoft David - English", lang: "en-US", localService: true, voiceURI: "Microsoft David - English", default: true } | |
| ]; | |
| }); | |
| } | |
| } catch {} | |
| // -------- Gamepads -------- | |
| try { | |
| if ("getGamepads" in navigator) { | |
| override(navigator, "getGamepads", (orig) => function() { | |
| const pads = orig.call(this); | |
| return pads && pads.length ? pads : [null, null, null, null]; | |
| }); | |
| } | |
| } catch {} | |
| // -------- WebGPU -------- | |
| try { | |
| if ("gpu" in navigator) { | |
| defineGetter(Navigator.prototype, "gpu", () => ({ | |
| requestAdapter: async () => null | |
| })); | |
| } | |
| } catch {} | |
| // -------- Performance jitter -------- | |
| try { | |
| if (CFG.enablePerfJitter && performance && performance.now) { | |
| override(performance, "now", (orig) => function() { | |
| return orig.call(this) + Rand.smallNoise(0.02); | |
| }); | |
| if (globalThis.Performance && Performance.prototype && Performance.prototype.now) { | |
| override(Performance.prototype, "now", (orig) => function() { | |
| return orig.call(this) + Rand.smallNoise(0.02); | |
| }); | |
| } | |
| } | |
| } catch {} | |
| // -------- Navigator connection toString shape -------- | |
| try { | |
| const toStringPatched = (proto, name) => { | |
| if (!proto) return; | |
| override(proto, "toString", () => function() { return `[object ${name}]`; }); | |
| }; | |
| if (navigator.connection) toStringPatched(Object.getPrototypeOf(navigator.connection), "NetworkInformation"); | |
| } catch {} | |
| // -------- Document focus and visibility edge cases -------- | |
| try { | |
| if (typeof document.hidden === "undefined") defineGetter(Document.prototype, "hidden", () => false); | |
| if (typeof document.visibilityState === "undefined") defineGetter(Document.prototype, "visibilityState", () => "visible"); | |
| } catch {} | |
| // -------- Misc small surfaces -------- | |
| try { | |
| if (!("mediaCapabilities" in navigator)) { | |
| defineValue(navigator, "mediaCapabilities", { | |
| decodingInfo: async () => ({ supported: true, powerEfficient: true, smooth: true }), | |
| encodingInfo: async () => ({ supported: true, powerEfficient: true, smooth: true }) | |
| }); | |
| } | |
| defineGetter(Navigator.prototype, "vendor", () => "Google Inc."); | |
| defineGetter(Navigator.prototype, "product", () => "Gecko"); | |
| } catch {} | |
| try { | |
| if (CFG.maxTouchPoints > 0 && !("ontouchstart" in globalThis)) { | |
| defineValue(globalThis, "ontouchstart", null); | |
| } | |
| } catch {} | |
| // --- CSS media queries normalization --- | |
| if (ENABLE.cssMedia && "matchMedia" in window) { | |
| const fixed = { | |
| "(prefers-color-scheme: dark)": false, | |
| "(prefers-contrast: more)": false, | |
| "(prefers-reduced-motion: reduce)": false, | |
| "(prefers-reduced-transparency: reduce)": false, | |
| "(color-gamut: srgb)": true, | |
| "(color-gamut: p3)": false, | |
| "(color-gamut: rec2020)": false, | |
| "(dynamic-range: standard)": true, | |
| "(dynamic-range: high)": false, | |
| "(forced-colors: active)": false, | |
| "(monochrome)": false | |
| }; | |
| const mm = window.matchMedia.bind(window); | |
| window.matchMedia = function(q) { | |
| const m = mm(q); | |
| if (q in fixed) { | |
| return new Proxy(m, { | |
| get(t, p) { | |
| if (p === "matches") return fixed[q]; | |
| if (p === "media") return q; | |
| return t[p]; | |
| } | |
| }); | |
| } | |
| return m; | |
| }; | |
| } | |
| // --- Geometry jitter --- | |
| if (ENABLE.geometryJitter) { | |
| const jit = (v) => v + (Math.random() - 0.5) * 0.02; | |
| const E = Element.prototype; | |
| const origRect = E.getBoundingClientRect; | |
| E.getBoundingClientRect = function() { | |
| const r = origRect.apply(this, arguments); | |
| return new DOMRectReadOnly(jit(r.x), jit(r.y), jit(r.width), jit(r.height)); | |
| }; | |
| } | |
| // --- Font probing normalization --- | |
| if (ENABLE.fonts) { | |
| if (document.fonts && document.fonts.check) { | |
| const baseCheck = document.fonts.check.bind(document.fonts); | |
| document.fonts.check = function(spec, text) { | |
| const ok = /(?:^|\s)(serif|sans-serif|monospace)(?:,|$)/i.test(spec); | |
| return ok ? true : baseCheck("12px sans-serif", text); | |
| }; | |
| } | |
| if (document.fonts) { | |
| const ffsProto = Object.getPrototypeOf(document.fonts); | |
| if (ffsProto && !ffsProto.status) { | |
| Object.defineProperty(ffsProto, "status", { get: () => "loaded" }); | |
| } | |
| } | |
| } | |
| // --- OffscreenCanvas parity --- | |
| if (ENABLE.offscreenCanvas && "OffscreenCanvas" in window) { | |
| try { | |
| const ocp = OffscreenCanvas.prototype; | |
| const _getContext = ocp.getContext; | |
| ocp.getContext = function(type, opts) { | |
| const ctx = _getContext.call(this, type, opts); | |
| if (type === "2d" && ctx && !ctx.__ava_patched) { | |
| const _getImageData = ctx.getImageData; | |
| ctx.getImageData = function() { | |
| const data = _getImageData.apply(this, arguments); | |
| for (let i = 0; i < data.data.length; i += 4) { | |
| data.data[i] = Math.min(255, Math.max(0, data.data[i] + (Math.random() - 0.5) * 0.6)); | |
| } | |
| return data; | |
| }; | |
| Object.defineProperty(ctx, "__ava_patched", { value: true }); | |
| } | |
| return ctx; | |
| }; | |
| } catch {} | |
| } | |
| // --- WebGL caps normalization --- | |
| if (ENABLE.webglCaps) { | |
| const normCaps = (gl) => { | |
| try { | |
| const ORIG = { | |
| getParameter: gl.getParameter.bind(gl), | |
| getSupportedExtensions: gl.getSupportedExtensions && gl.getSupportedExtensions.bind(gl), | |
| getShaderPrecisionFormat: gl.getShaderPrecisionFormat && gl.getShaderPrecisionFormat.bind(gl) | |
| }; | |
| gl.getSupportedExtensions = () => ["OES_standard_derivatives", "OES_vertex_array_object", "OES_element_index_uint"]; | |
| gl.getShaderPrecisionFormat = (_type, _prec) => ({ rangeMin: 127, rangeMax: 127, precision: 23 }); | |
| gl.getParameter = (p) => { | |
| const caps = { | |
| 0x0D33: 16384, // MAX_TEXTURE_SIZE | |
| 0x846D: 8, // MAX_VERTEX_ATTRIBS | |
| 0x8872: 16, // MAX_TEXTURE_IMAGE_UNITS | |
| 0x8C8A: 4096 // MAX_RENDERBUFFER_SIZE | |
| }; | |
| if (p in caps) return caps[p]; | |
| return ORIG.getParameter(p); | |
| }; | |
| } catch {} | |
| }; | |
| if (window.WebGLRenderingContext) { | |
| const _getCtx = HTMLCanvasElement.prototype.getContext; | |
| HTMLCanvasElement.prototype.getContext = function(type, opts) { | |
| const ctx = _getCtx.call(this, type, opts); | |
| if ((type === "webgl" || type === "experimental-webgl") && ctx) normCaps(ctx); | |
| return ctx; | |
| }; | |
| } | |
| } | |
| // --- Media codecs normalization --- | |
| if (ENABLE.mediaCodecs) { | |
| const H = HTMLMediaElement.prototype; | |
| const _cpt = H.canPlayType; | |
| H.canPlayType = function(t) { | |
| if (typeof t !== "string") return ""; | |
| if (/video\/mp4/i.test(t)) return "probably"; | |
| if (/audio\/mp4|audio\/aac/i.test(t)) return "probably"; | |
| if (/audio\/mpeg/i.test(t)) return "probably"; | |
| if (/video\/webm|audio\/webm/i.test(t)) return "maybe"; | |
| return ""; | |
| }; | |
| if (navigator.mediaCapabilities && navigator.mediaCapabilities.decodingInfo) { | |
| const _di = navigator.mediaCapabilities.decodingInfo.bind(navigator.mediaCapabilities); | |
| navigator.mediaCapabilities.decodingInfo = async (cfg) => { | |
| const base = await _di(cfg).catch(() => null); | |
| return base ? { ...base, powerEfficient: true, smooth: true, supported: true } | |
| : { supported: true, powerEfficient: true, smooth: true }; | |
| }; | |
| } | |
| } | |
| // --- Performance entries rounding --- | |
| if (ENABLE.perfEntries && performance) { | |
| const round = (x) => Math.round(x * 10) / 10; | |
| ["getEntries", "getEntriesByType", "getEntriesByName"].forEach(fn => { | |
| if (performance[fn]) { | |
| const orig = performance[fn].bind(performance); | |
| performance[fn] = function() { | |
| const entries = orig.apply(this, arguments) || []; | |
| return entries.map(e => { | |
| const clone = {}; | |
| for (const k of Object.keys(e)) { | |
| const v = e[k]; | |
| clone[k] = typeof v === "number" ? round(v) : v; | |
| } | |
| delete clone.serverTiming; | |
| delete clone.transferSize; | |
| delete clone.encodedBodySize; | |
| delete clone.decodedBodySize; | |
| return clone; | |
| }); | |
| }; | |
| } | |
| }); | |
| if ("timeOrigin" in performance) { | |
| Object.defineProperty(performance, "timeOrigin", { get: () => Math.floor(Date.now() / 1000) * 1000 }); | |
| } | |
| if (performance.memory) { | |
| try { | |
| const mem = performance.memory; | |
| Object.defineProperty(performance, "memory", { | |
| get: () => ({ jsHeapSizeLimit: mem.jsHeapSizeLimit, totalJSHeapSize: 200e6, usedJSHeapSize: 120e6 }) | |
| }); | |
| } catch {} | |
| } | |
| } | |
| // --- Keyboard layout normalization --- | |
| if (ENABLE.keyboardLayout && navigator.keyboard && navigator.keyboard.getLayoutMap) { | |
| const _glm = navigator.keyboard.getLayoutMap.bind(navigator.keyboard); | |
| navigator.keyboard.getLayoutMap = async () => { | |
| const m = await _glm().catch(() => null); | |
| if (!m) return new Map([["KeyQ", "q"], ["KeyW", "w"]]); | |
| const map = new Map(); | |
| ["KeyQ","KeyW","KeyE","KeyR","KeyT","KeyY","KeyU","KeyI","KeyO","KeyP", | |
| "KeyA","KeyS","KeyD","KeyF","KeyG","KeyH","KeyJ","KeyK","KeyL", | |
| "KeyZ","KeyX","KeyC","KeyV","KeyB","KeyN","KeyM","Space"].forEach(c => map.set(c, (c === "Space") ? " " : c.slice(-1).toLowerCase())); | |
| return map; | |
| }; | |
| } | |
| // --- Legacy navigator fields --- | |
| if (ENABLE.legacyNavFields) { | |
| const np = Navigator.prototype; | |
| Object.defineProperty(np, "appVersion", { get: () => "5.0 (" + (navigator.platform || "Win32") + ") AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" }); | |
| Object.defineProperty(np, "appName", { get: () => "Netscape" }); | |
| Object.defineProperty(np, "productSub", { get: () => "20030107" }); | |
| Object.defineProperty(np, "vendorSub", { get: () => "" }); | |
| Object.defineProperty(np, "pdfViewerEnabled", { get: () => true }); | |
| } | |
| // --- Screen extras and window positions --- | |
| if (ENABLE.screenExtras) { | |
| if (typeof Screen !== "undefined" && Screen.prototype) { | |
| const sp = Screen.prototype; | |
| if (!("availLeft" in screen)) Object.defineProperty(sp, "availLeft", { get: () => 0 }); | |
| if (!("availTop" in screen)) Object.defineProperty(sp, "availTop", { get: () => 0 }); | |
| if (screen.orientation) { | |
| const so = Object.getPrototypeOf(screen.orientation) || screen.orientation; | |
| try { Object.defineProperty(so, "angle", { get: () => 0 }); } catch {} | |
| try { Object.defineProperty(so, "type", { get: () => "landscape-primary" }); } catch {} | |
| } | |
| } | |
| if (!("screenX" in window)) Object.defineProperty(window, "screenX", { get: () => 0 }); | |
| if (!("screenY" in window)) Object.defineProperty(window, "screenY", { get: () => 0 }); | |
| } | |
| // ---- Utilities: native camouflage and rounding ---- | |
| const NativeSig = Symbol.for("__ava_native_sig__"); | |
| const makeNative = (fn, name = fn.name || "", length = fn.length) => { | |
| try { Object.defineProperty(fn, "name", { value: name, configurable: true }); } catch {} | |
| try { Object.defineProperty(fn, "length", { value: length, configurable: true }); } catch {} | |
| try { Object.defineProperty(fn, NativeSig, { value: true }); } catch {} | |
| return fn; | |
| }; | |
| try { | |
| const origToString = Function.prototype.toString; | |
| Function.prototype.toString = makeNative(function toString() { | |
| const fn = this; | |
| try { | |
| if (fn && fn[NativeSig]) { | |
| const name = fn.name || ""; | |
| return `function ${name}() { [native code] }`; | |
| } | |
| } catch {} | |
| return origToString.call(fn); | |
| }, "toString", 0); | |
| } catch {} | |
| try { | |
| const patchTag = /__ava_stealth/i; | |
| const _prepare = Error.prepareStackTrace; | |
| Error.prepareStackTrace = makeNative(function(err, frames) { | |
| try { | |
| frames = frames.filter(f => { | |
| const file = (f && typeof f.getFileName === "function") ? f.getFileName() : null; | |
| return !(file && patchTag.test(String(file))); | |
| }); | |
| } catch {} | |
| return _prepare ? _prepare(err, frames) : `${err}\n${frames.map(String).join("\n")}`; | |
| }, "prepareStackTrace", 2); | |
| } catch {} | |
| const round01 = (x) => Math.round(x * 10) / 10; | |
| // ---- CSS.supports and extended matchMedia hardening ---- | |
| try { | |
| if ("CSS" in globalThis && CSS.supports) { | |
| const orig = CSS.supports.bind(CSS); | |
| CSS.supports = makeNative(function(prop, value) { | |
| if (typeof prop === "string" && /(^|:)\s*(environment-blending|color-gamut|dynamic-range|display-p3)/i.test(prop)) { | |
| return false; | |
| } | |
| return orig(prop, value); | |
| }, "supports", 2); | |
| } | |
| if ("matchMedia" in window) { | |
| const mm = window.matchMedia.bind(window); | |
| const table = { | |
| "(prefers-color-scheme: dark)": false, | |
| "(prefers-color-scheme: light)": true, | |
| "(prefers-reduced-motion: reduce)": false, | |
| "(prefers-contrast: more)": false, | |
| "(color-gamut: srgb)": true, | |
| "(color-gamut: p3)": false, | |
| "(dynamic-range: high)": false, | |
| "(inverted-colors: inverted)": false, | |
| "(forced-colors: active)": false | |
| }; | |
| window.matchMedia = makeNative(function(q) { | |
| const m = mm(q); | |
| if (q in table) { | |
| return new Proxy(m, { | |
| get(t, p) { | |
| if (p === "matches") return table[q]; | |
| if (p === "media") return q; | |
| return t[p]; | |
| } | |
| }); | |
| } | |
| return m; | |
| }, "matchMedia", 1); | |
| } | |
| } catch {} | |
| // ---- Geometry extras ---- | |
| try { | |
| const EP = Element.prototype; | |
| if (EP.getClientRects) { | |
| const _gcr = EP.getClientRects; | |
| EP.getClientRects = makeNative(function() { | |
| const list = _gcr.apply(this, arguments); | |
| try { | |
| const arr = []; | |
| for (let i = 0; i < list.length; i++) { | |
| const r = list[i]; | |
| arr.push(new DOMRectReadOnly( | |
| Math.round(r.x * 10) / 10, Math.round(r.y * 10) / 10, | |
| Math.round(r.width * 10) / 10, Math.round(r.height * 10) / 10 | |
| )); | |
| } | |
| arr.item = (i) => arr[i] || null; | |
| return arr; | |
| } catch { return list; } | |
| }, "getClientRects", 0); | |
| } | |
| if (globalThis.visualViewport) { | |
| const vpProto = Object.getPrototypeOf(visualViewport); | |
| ["width","height","offsetLeft","offsetTop","pageLeft","pageTop","scale"].forEach(k => { | |
| try { | |
| const desc = Object.getOwnPropertyDescriptor(vpProto, k) || { configurable: true }; | |
| Object.defineProperty(vpProto, k, { | |
| configurable: true, | |
| get: makeNative(function() { | |
| const v = (desc.get ? desc.get.call(this) : visualViewport[k]); | |
| return typeof v === "number" ? round01(v) : v; | |
| }, `get ${k}`, 0) | |
| }); | |
| } catch {} | |
| }); | |
| } | |
| } catch {} | |
| // ---- Intl normalization ---- | |
| try { | |
| const fixResolved = (proto, fields) => { | |
| if (!proto || !proto.resolvedOptions) return; | |
| const orig = proto.resolvedOptions; | |
| proto.resolvedOptions = makeNative(function() { | |
| const r = orig.call(this); | |
| for (const [k, v] of Object.entries(fields)) { | |
| try { r[k] = v; } catch {} | |
| } | |
| return r; | |
| }, "resolvedOptions", 0); | |
| }; | |
| fixResolved(Intl.DateTimeFormat.prototype, { timeZone: CFG.timezone }); | |
| fixResolved(Intl.NumberFormat.prototype, { numberingSystem: "latn" }); | |
| fixResolved(Intl.PluralRules.prototype, { type: "cardinal" }); | |
| fixResolved(Intl.Collator.prototype, { usage: "sort", sensitivity: "variant" }); | |
| } catch {} | |
| // ---- Clipboard API stubs ---- | |
| try { | |
| if (navigator.clipboard) { | |
| const cp = navigator.clipboard; | |
| if (cp.readText) { | |
| const _rt = cp.readText.bind(cp); | |
| cp.readText = makeNative(function() { | |
| return _rt().catch(() => Promise.resolve("")); | |
| }, "readText", 0); | |
| } | |
| if (cp.writeText) { | |
| const _wt = cp.writeText.bind(cp); | |
| cp.writeText = makeNative(function(t) { | |
| return _wt(t).catch(() => Promise.resolve()); | |
| }, "writeText", 1); | |
| } | |
| } else { | |
| Object.defineProperty(navigator, "clipboard", { | |
| configurable: true, | |
| value: { readText: makeNative(async function(){ return ""; }, "readText", 0), | |
| writeText: makeNative(async function(){}, "writeText", 1) } | |
| }); | |
| } | |
| } catch {} | |
| // ---- Notification API consistency ---- | |
| try { | |
| if ("Notification" in globalThis) { | |
| const _perm = Object.getOwnPropertyDescriptor(Notification, "permission"); | |
| if (_perm && _perm.configurable) { | |
| Object.defineProperty(Notification, "permission", { get: makeNative(() => "default", "get permission", 0) }); | |
| } | |
| if (Notification.requestPermission) { | |
| const _rp = Notification.requestPermission.bind(Notification); | |
| Notification.requestPermission = makeNative(function(cb) { | |
| const p = Promise.resolve("default"); | |
| if (typeof cb === "function") p.then(cb); | |
| return p; | |
| }, "requestPermission", 1); | |
| } | |
| } | |
| } catch {} | |
| // ---- History and referrer ---- | |
| try { | |
| if ("length" in history) { | |
| const d = Object.getOwnPropertyDescriptor(History.prototype, "length"); | |
| if (d && d.configurable) { | |
| Object.defineProperty(History.prototype, "length", { get: makeNative(() => Math.max(2, window.history.length || 2), "get length", 0) }); | |
| } | |
| } | |
| if ("referrer" in Document.prototype) { | |
| const dr = Object.getOwnPropertyDescriptor(Document.prototype, "referrer"); | |
| if (dr && dr.configurable) { | |
| Object.defineProperty(Document.prototype, "referrer", { get: makeNative(() => "", "get referrer", 0) }); | |
| } | |
| } | |
| } catch {} | |
| // ---- Legacy navigator fields and booleans ---- | |
| try { | |
| const NP = Navigator.prototype; | |
| if (!("cookieEnabled" in NP)) { | |
| Object.defineProperty(NP, "cookieEnabled", { get: makeNative(() => true, "get cookieEnabled", 0) }); | |
| } | |
| if (!("pdfViewerEnabled" in NP)) { | |
| Object.defineProperty(NP, "pdfViewerEnabled", { get: makeNative(() => true, "get pdfViewerEnabled", 0) }); | |
| } | |
| if (!("vendor" in NP)) { | |
| Object.defineProperty(NP, "vendor", { get: makeNative(() => "Google Inc.", "get vendor", 0) }); | |
| } | |
| if (!("appName" in NP)) { | |
| Object.defineProperty(NP, "appName", { get: makeNative(() => "Netscape", "get appName", 0) }); | |
| } | |
| } catch {} | |
| // ---- Audio fingerprint extras ---- | |
| try { | |
| const AC = globalThis.AudioContext || globalThis.webkitAudioContext; | |
| if (AC && AC.prototype) { | |
| const ACP = AC.prototype; | |
| try { | |
| Object.defineProperty(ACP, "sampleRate", { get: makeNative(function() { return 48000; }, "get sampleRate", 0) }); | |
| } catch {} | |
| if (ACP.createAnalyser) { | |
| const _ca = ACP.createAnalyser; | |
| ACP.createAnalyser = makeNative(function() { | |
| const node = _ca.apply(this, arguments); | |
| try { | |
| node.fftSize = 2048; | |
| const _gbfd = node.getByteFrequencyData.bind(node); | |
| node.getByteFrequencyData = makeNative(function(arr) { | |
| _gbfd(arr); | |
| for (let i = 0; i < arr.length; i++) arr[i] = Math.min(255, Math.max(0, arr[i])); | |
| }, "getByteFrequencyData", 1); | |
| } catch {} | |
| return node; | |
| }, "createAnalyser", 0); | |
| } | |
| } | |
| } catch {} | |
| // ---- SVG metrics ---- | |
| try { | |
| if (globalThis.SVGPathElement && SVGPathElement.prototype && SVGPathElement.prototype.getTotalLength) { | |
| const _gtl = SVGPathElement.prototype.getTotalLength; | |
| SVGPathElement.prototype.getTotalLength = makeNative(function() { | |
| const v = _gtl.apply(this, arguments); | |
| return v + 0.0001; | |
| }, "getTotalLength", 0); | |
| } | |
| } catch {} | |
| // ---- Media EME flattening ---- | |
| try { | |
| if (navigator.requestMediaKeySystemAccess) { | |
| const _rmksa = navigator.requestMediaKeySystemAccess.bind(navigator); | |
| navigator.requestMediaKeySystemAccess = makeNative(async function(ks, cfg) { | |
| if (!/com\.widevine\.alpha/i.test(String(ks))) throw new DOMException("NotSupportedError", "NotSupportedError"); | |
| return _rmksa(ks, cfg); | |
| }, "requestMediaKeySystemAccess", 2); | |
| } | |
| } catch {} | |
| // ---- PerformanceObserver and entries rounding ---- | |
| try { | |
| if (globalThis.PerformanceObserver) { | |
| const POP = PerformanceObserver.prototype; | |
| const _observe = POP.observe; | |
| POP.observe = makeNative(function(opts) { | |
| if (opts && Array.isArray(opts.entryTypes)) { | |
| opts = { ...opts, entryTypes: opts.entryTypes.filter(t => t !== "resource") }; | |
| } | |
| return _observe.call(this, opts); | |
| }, "observe", 1); | |
| } | |
| if (performance) { | |
| ["getEntries", "getEntriesByType", "getEntriesByName"].forEach(fn => { | |
| if (performance[fn]) { | |
| const orig = performance[fn].bind(performance); | |
| performance[fn] = makeNative(function() { | |
| const arr = orig.apply(this, arguments) || []; | |
| return arr.map(e => { | |
| const o = {}; | |
| for (const k in e) { | |
| const v = e[k]; | |
| o[k] = typeof v === "number" ? round01(v) : v; | |
| } | |
| delete o.transferSize; | |
| delete o.encodedBodySize; | |
| delete o.decodedBodySize; | |
| return o; | |
| }); | |
| }, fn, performance[fn].length || 0); | |
| } | |
| }); | |
| if ("now" in performance) { | |
| const _now = performance.now.bind(performance); | |
| performance.now = makeNative(function() { return round01(_now()); }, "now", 0); | |
| } | |
| } | |
| if ("requestIdleCallback" in window) { | |
| const ric = window.requestIdleCallback.bind(window); | |
| window.requestIdleCallback = makeNative(function(cb, opts) { | |
| return ric(function(deadline) { | |
| const proxy = new Proxy(deadline, { | |
| get(t, p) { | |
| if (p === "timeRemaining") return makeNative(function(){ return 10; }, "timeRemaining", 0); | |
| return t[p]; | |
| } | |
| }); | |
| return cb(proxy); | |
| }, opts); | |
| }, "requestIdleCallback", 2); | |
| } | |
| } catch {} | |
| // ---- Window.chrome legacy stubs ---- | |
| try { | |
| if (!("chrome" in window)) { | |
| Object.defineProperty(window, "chrome", { configurable: true, value: { runtime: {}, app: {}, webstore: {} } }); | |
| } | |
| if (window.chrome) { | |
| window.chrome.loadTimes = makeNative(function(){ return {}; }, "loadTimes", 0); | |
| window.chrome.csi = makeNative(function(){ return {}; }, "csi", 0); | |
| } | |
| } catch {} | |
| // ---- Document charset ---- | |
| try { | |
| const DP = Document.prototype; | |
| const cs = Object.getOwnPropertyDescriptor(DP, "characterSet"); | |
| if (cs && cs.configurable) { | |
| Object.defineProperty(DP, "characterSet", { get: makeNative(() => "UTF-8", "get characterSet", 0) }); | |
| } | |
| const ce = Object.getOwnPropertyDescriptor(DP, "charset"); | |
| if (ce && ce.configurable) { | |
| Object.defineProperty(DP, "charset", { get: makeNative(() => "UTF-8", "get charset", 0) }); | |
| } | |
| } catch {} | |
| // ---- Window dimensions rounding ---- | |
| try { | |
| ["innerWidth","innerHeight","outerWidth","outerHeight","screenX","screenY"].forEach(k => { | |
| const desc = Object.getOwnPropertyDescriptor(window, k) || { configurable: true }; | |
| try { | |
| Object.defineProperty(window, k, { configurable: true, get: makeNative(function() { | |
| const v = desc.get ? desc.get.call(window) : window[k]; | |
| return typeof v === "number" ? Math.round(v) : v; | |
| }, `get ${k}`, 0) }); | |
| } catch {} | |
| }); | |
| } catch {} | |
| // console.debug("Ava stealth patch installed"); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment