diff --git a/main.js b/main.js index fab455d7..f1971306 100644 --- a/main.js +++ b/main.js @@ -807,6 +807,8 @@ async function applyFingerprint(wc) { if (settings.hideMediaDevices) { src += `try{const md=navigator.mediaDevices;if(md&&md.enumerateDevices){const o=md.enumerateDevices.bind(md);md.enumerateDevices=async()=>{let l=[];try{l=await o()}catch(e){}const ks=[...new Set(l.map(d=>d.kind))];return ks.map(kind=>({deviceId:'',kind:kind,label:'',groupId:'',toJSON(){return{deviceId:'',kind:kind,label:'',groupId:''}}}))};}}catch(e){}`; } + // Always on: Chrome-shaped window.chrome (see CHROME_SHIM_SRC). + src += CHROME_SHIM_SRC; if (src) { const res = await wc.debugger.sendCommand("Page.addScriptToEvaluateOnNewDocument", { source: src }); wc._langScript = res.identifier; @@ -875,6 +877,18 @@ function stockChromeUA() { // still said "Chromium" only. DataDome ("AI Threats Detection") blocked on // exactly that header/JS mismatch (estore.asus.com, 2026-09-20), so the same // metadata is now also installed in every tab via Emulation.setUserAgentOverride. +// Electron hands every page an EMPTY window.chrome. Real Chrome's carries +// app / csi / loadTimes / runtime, and bot checks — Google sign-in's among +// them — look for exactly those to tell Chrome from an embedded Chromium. +// Same shapes and return types as Chrome; nothing here is reachable by the +// site beyond what a stock Chrome would also give it. +const CHROME_SHIM_SRC = `try{(()=>{const c=window.chrome;if(!c||typeof c!=="object"||Object.keys(c).length)return; +const t=()=>performance.timing; +c.app={isInstalled:false,InstallState:{DISABLED:"disabled",INSTALLED:"installed",NOT_INSTALLED:"not_installed"},RunningState:{CANNOT_RUN:"cannot_run",READY_TO_RUN:"ready_to_run",RUNNING:"running"},getDetails(){return null},getIsInstalled(){return false},runningState(){return "cannot_run"}}; +c.csi=function(){const p=t();return{startE:p.navigationStart,onloadT:p.domContentLoadedEventEnd,pageT:performance.now(),tran:15}}; +c.loadTimes=function(){const p=t();return{requestTime:p.navigationStart/1000,startLoadTime:p.navigationStart/1000,commitLoadTime:p.responseStart/1000,finishDocumentLoadTime:p.domContentLoadedEventEnd/1000,finishLoadTime:p.loadEventEnd/1000,firstPaintTime:p.responseEnd/1000,firstPaintAfterLoadTime:0,navigationType:"Other",wasFetchedViaSpdy:true,wasNpnNegotiated:true,npnNegotiatedProtocol:"h2",wasAlternateProtocolAvailable:false,connectionInfo:"h2"}}; +c.runtime={OnInstalledReason:{CHROME_UPDATE:"chrome_update",INSTALL:"install",SHARED_MODULE_UPDATE:"shared_module_update",UPDATE:"update"},OnRestartRequiredReason:{APP_UPDATE:"app_update",OS_UPDATE:"os_update",PERIODIC:"periodic"},PlatformArch:{ARM:"arm",ARM64:"arm64",MIPS:"mips",MIPS64:"mips64",X86_32:"x86-32",X86_64:"x86-64"},PlatformNaclArch:{ARM:"arm",MIPS:"mips",MIPS64:"mips64",X86_32:"x86-32",X86_64:"x86-64"},PlatformOs:{ANDROID:"android",CROS:"cros",LINUX:"linux",MAC:"mac",OPENBSD:"openbsd",WIN:"win"},RequestUpdateCheckStatus:{NO_UPDATE:"no_update",THROTTLED:"throttled",UPDATE_AVAILABLE:"update_available"},id:undefined,connect(){throw new TypeError("chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument.")},sendMessage(){throw new TypeError("chrome.runtime.sendMessage() called from a webpage must specify an Extension ID (string) for its first argument.")}}; +})()}catch(e){}`; function chromeBrandMetadata() { const full = CHROME_VERSION_CLAIMED; const major = parseInt(full.split(".")[0], 10) || 130;