UNPKG

fakebrowser

Version:

🤖 Fake fingerprints to bypass anti-bot systems. Simulate mouse and keyboard operations to make behavior like a real person.

305 lines • 24 kB
// noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols import * as path from 'path'; import { strict as assert } from 'assert'; import { UserAgentHelper } from './UserAgentHelper'; import { PptrToolkit } from './PptrToolkit'; import { PptrPatcher } from './PptrPatcher'; import { FakeUserAction } from './FakeUserAction'; import { BrowserLauncher } from './BrowserLauncher'; import { BrowserBuilder } from './BrowserBuilder'; import { Touchscreen } from './TouchScreen'; export const kDefaultWindowsDD = require(path.resolve(__dirname, '../../device-hub-demo/Windows.json')); const kBrowserMaxSurvivalTime = 60 * 1000 * 15; const kDefaultReferers = ['https://www.google.com', 'https://www.bing.com']; const kInternalHttpServerPort = 17311; // chromium startup parameters // https://peter.sh/experiments/chromium-command-line-switches/ // https://www.scrapehero.com/how-to-increase-web-scraping-speed-using-puppeteer/ // noinspection TypeScriptValidateJSTypes,SpellCheckingInspection export const kDefaultLaunchArgs = [ '--no-sandbox', '--no-pings', '--no-zygote', '--mute-audio', '--no-first-run', '--no-default-browser-check', '--disable-software-rasterizer', '--disable-cloud-import', '--disable-gesture-typing', '--disable-setuid-sandbox', '--disable-offer-store-unmasked-wallet-cards', '--disable-offer-upload-credit-cards', '--disable-print-preview', '--disable-voice-input', '--disable-wake-on-wifi', '--disable-cookie-encryption', '--ignore-gpu-blocklist', '--enable-async-dns', '--enable-simple-cache-backend', '--enable-tcp-fast-open', '--enable-webgl', '--prerender-from-omnibox=disabled', '--enable-web-bluetooth', '--ignore-certificate-errors', '--ignore-certificate-errors-spki-list', '--disable-site-isolation-trials', '--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process,TranslateUI,BlinkGenPropertyTrees', '--aggressive-cache-discard', '--disable-extensions', '--disable-blink-features', '--disable-blink-features=AutomationControlled', '--disable-ipc-flooding-protection', '--enable-features=NetworkService,NetworkServiceInProcess,TrustTokens,TrustTokensAlwaysAllowIssuance', '--disable-component-extensions-with-background-pages', '--disable-default-apps', '--disable-breakpad', '--disable-component-update', '--disable-domain-reliability', '--disable-sync', '--disable-client-side-phishing-detection', '--disable-hang-monitor', '--disable-popup-blocking', '--disable-prompt-on-repost', '--metrics-recording-only', '--safebrowsing-disable-auto-update', '--password-store=basic', '--autoplay-policy=no-user-gesture-required', '--use-mock-keychain', '--force-webrtc-ip-handling-policy=default_public_interface_only', '--disable-session-crashed-bubble', '--disable-crash-reporter', '--disable-dev-shm-usage', '--force-color-profile=srgb', '--disable-accelerated-2d-canvas', '--disable-translate', '--disable-background-networking', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-infobars', '--hide-scrollbars', '--disable-renderer-backgrounding', '--font-render-hinting=none', '--disable-logging', '--use-gl=swiftshader', // optimze fps '--enable-surface-synchronization', '--run-all-compositor-stages-before-draw', '--disable-threaded-animation', '--disable-threaded-scrolling', '--disable-checker-imaging', '--disable-new-content-rendering-timeout', '--disable-image-animation-resync', '--disable-partial-raster', '--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4', // '--deterministic-mode', // Some friends commented that with this parameter mouse movement is stuck, so let's comment it out // '--disable-web-security', // '--disable-cache', // cache // '--disable-application-cache', // '--disable-offline-load-stale-cache', // '--disable-gpu-shader-disk-cache', // '--media-cache-size=0', // '--disk-cache-size=0', // '--enable-experimental-web-platform-features', // Make Chrome for Linux support Bluetooth. eg: navigator.bluetooth, window.BluetoothUUID // '--disable-gpu', // Cannot be disabled: otherwise webgl will not work // '--disable-speech-api', // Cannot be disabled: some websites use speech-api as fingerprint // '--no-startup-window', // Cannot be enabled: Chrome won't open the window and puppeteer thinks it's not connected // '--disable-webgl', // Requires webgl fingerprint // '--disable-webgl2', // '--disable-notifications', // Cannot be disabled: notification-api not available, fingerprints will be dirty ]; // if (helper.inLinux()) { // kDefaultLaunchArgs.push(...[ // '--single-process', // Chrome does not run with single process in windows / macos, but it runs very well in linux (from Anton bro). // ]) // } // Is there a friend class similar to C++ ? // friend class BrowserLauncher export class FakeBrowser { constructor(driverParams, vanillaBrowser, pptrExtra, bindingTime, uuid) { this.driverParams = driverParams; this.vanillaBrowser = vanillaBrowser; this.pptrExtra = pptrExtra; this.bindingTime = bindingTime; this.uuid = uuid; assert(driverParams.deviceDesc && driverParams.deviceDesc.navigator && driverParams.deviceDesc.navigator.userAgent); this.isMobileBrowser = UserAgentHelper.isMobile(driverParams.deviceDesc.navigator.userAgent); this.uuid = uuid; this.userAction = new FakeUserAction(this); this._zombie = false; // this._workerUrls = [] vanillaBrowser.on('disconnected', this.onDisconnected.bind(this)); if (!driverParams.doNotHook) { vanillaBrowser.on('targetcreated', this.onTargetCreated.bind(this)); } } // private readonly _workerUrls: string[] get launchParams() { assert(this.driverParams.launchOptions); return this.driverParams; } get connectParams() { assert(this.driverParams.connectOptions); return this.driverParams; } async beforeShutdown() { } async shutdown() { if (!this._zombie) { await this.beforeShutdown(); this._zombie = true; await BrowserLauncher._forceShutdown(this); } else { // console.warn('This instance has been shutdown and turned into a zombie.') } } async getActivePage() { const result = await PptrToolkit.getActivePage(this.vanillaBrowser); return result; } onDisconnected() { return this.shutdown(); } async onTargetCreated(target) { // console.log('targetcreated type:', target.type(), target.url()) const targetType = target.type(); const worker = await target.worker(); if (0 && worker) { await this.interceptWorker(worker); } else if (targetType === 'service_worker' || targetType === 'other' && (target.url().startsWith('http'))) { const cdpSession = await target.createCDPSession(); await this.interceptTarget(target, cdpSession); } else if (targetType === 'page') { await this.interceptPage((await target.page())); } } async interceptWorker(worker) { assert(!!worker); const injectJs = await PptrPatcher.evasionsCode(this); await worker.evaluate(injectJs); } async interceptTarget(target, client) { assert(!!client); // FIXME: Worker & SharedWorker does not work with this way // console.log('intercept', target.url()) const injectJs = await PptrPatcher.evasionsCode(this); await client.send('Runtime.evaluate', { expression: injectJs, }); } async interceptPage(page) { // console.log('inject page') let cdpSession = null; const fakeDD = this.driverParams.fakeDeviceDesc; assert(fakeDD); // if there is an account password that proxy needs to log in if (this.driverParams.proxy && this.driverParams.proxy.username && this.driverParams.proxy.password) { await page.authenticate({ username: this.driverParams.proxy.username, password: this.driverParams.proxy.password, }); } // cdp try { await page['_client'].send('ServiceWorker.setForceUpdateOnPageLoad', { forceUpdateOnPageLoad: true }); } catch (ex) { console.warn('CDP ServiceWorker.setForceUpdateOnPageLoad exception', ex); } // touch if (this.isMobileBrowser) { try { await page['_client'].send('Emulation.setEmitTouchEventsForMouse', { enabled: true, }); } catch (ex) { console.warn('CDP Emulation.setEmitTouchEventsForMouse exception', ex); } Object.defineProperty(page, '_patchTouchscreen', { value: new Touchscreen(page['_client'], page.keyboard), }); } // intercept worker // const target = page.target() // cdpSession = await target.createCDPSession() // await this.interceptWorker(target, cdpSession); // // page.on('workercreated', (worker: WebWorker) => { // console.log(`worker created ${worker.url()}`) // this._workerUrls.push(worker.url()) // }) // // page.on('workerdestroyed', async (worker: WebWorker) => { // console.log(`worker destroyed ${worker.url()}`) // }) // set additional request headers // read version from the launched browser const ua = await this.vanillaBrowser.userAgent(); const chromeMajorVersion = UserAgentHelper.chromeMajorVersion(ua); const os = UserAgentHelper.os(fakeDD.navigator.userAgent); assert(chromeMajorVersion); assert(os); const extraHTTPHeaders = { // MUST NOT SET ACCEPT-LANGUAGE!!!! : https://github.com/puppeteer/puppeteer/issues/1984 // 'Accept-Language': UserAgentHelper.buildAcceptLanguage(fakeDD), // FIXME: error occurs after the referer is set // 'referer': FakeBrowser.globalConfig.defaultReferers[sh.rd(0, referers.length - 1)], 'sec-ch-ua': UserAgentHelper.browserType(ua) === 'Edge' ? `"Microsoft Edge";v="${chromeMajorVersion}", "Chromium";v="${chromeMajorVersion}", ";Not A Brand";v="99"` : `"Google Chrome";v="${chromeMajorVersion}", "Chromium";v="${chromeMajorVersion}", ";Not A Brand";v="99"`, 'sec-ch-ua-mobile': '?0', // 'sec-fetch-site': 'cross-site', }; if (chromeMajorVersion >= 93) { extraHTTPHeaders['sec-ch-ua-platform'] = `"${os}"`; } await page.setExtraHTTPHeaders(extraHTTPHeaders); await page.setUserAgent(fakeDD.navigator.userAgent); await page.setViewport({ width: fakeDD.window.innerWidth, height: fakeDD.window.innerHeight, isMobile: UserAgentHelper.isMobile(fakeDD.navigator.userAgent), hasTouch: fakeDD.navigator.maxTouchPoints > 0, deviceScaleFactor: fakeDD.window.devicePixelRatio, }); return { page, cdpSession }; } async _patchPages0Bug() { // pages[0] keeps failing to hook effectively // But I can't close it, because in windows, closing this page will cause the whole browser to close // So I can only make it inaccessible to users const abandonedPageTargetIds = []; const pages = await this.vanillaBrowser.pages(); if (pages.length > 0) { abandonedPageTargetIds.push(...pages.map(e => e.target()['_targetId'])); } const pagesFn = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this.vanillaBrowser), 'pages').value.bind(this.vanillaBrowser); Object.defineProperty(Object.getPrototypeOf(this.vanillaBrowser), 'pages', { value: new Proxy(this.vanillaBrowser.pages, { async apply(target, thisArg, args) { let pages = await pagesFn(); // Maybe browser is created based on connect, with different instances // so can only compare TargetId pages = pages.filter(e => !abandonedPageTargetIds.includes(e.target()['_targetId'])); return pages; }, }), }); } } FakeBrowser.Builder = BrowserBuilder; FakeBrowser.globalConfig = { defaultBrowserMaxSurvivalTime: kBrowserMaxSurvivalTime, defaultReferers: kDefaultReferers, internalHttpServerPort: kInternalHttpServerPort, defaultLaunchArgs: kDefaultLaunchArgs, }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRmFrZUJyb3dzZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29yZS9GYWtlQnJvd3Nlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwwREFBMEQ7QUFFMUQsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFDNUIsT0FBTyxFQUFDLE1BQU0sSUFBSSxNQUFNLEVBQUMsTUFBTSxRQUFRLENBQUE7QUFLdkMsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLG1CQUFtQixDQUFBO0FBQ2pELE9BQU8sRUFBQyxXQUFXLEVBQUMsTUFBTSxlQUFlLENBQUE7QUFHekMsT0FBTyxFQUFDLFdBQVcsRUFBQyxNQUFNLGVBQWUsQ0FBQTtBQUN6QyxPQUFPLEVBQUMsY0FBYyxFQUFDLE1BQU0sa0JBQWtCLENBQUE7QUFDL0MsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLG1CQUFtQixDQUFBO0FBQ2pELE9BQU8sRUFBQyxjQUFjLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQTtBQUMvQyxPQUFPLEVBQUMsV0FBVyxFQUFDLE1BQU0sZUFBZSxDQUFBO0FBRXpDLE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxvQ0FBb0MsQ0FBQyxDQUFDLENBQUE7QUFFdkcsTUFBTSx1QkFBdUIsR0FBRyxFQUFFLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQTtBQUM5QyxNQUFNLGdCQUFnQixHQUFHLENBQUMsd0JBQXdCLEVBQUUsc0JBQXNCLENBQUMsQ0FBQTtBQUMzRSxNQUFNLHVCQUF1QixHQUFHLEtBQUssQ0FBQTtBQUVyQyw4QkFBOEI7QUFDOUIsK0RBQStEO0FBQy9ELGlGQUFpRjtBQUNqRixpRUFBaUU7QUFDakUsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUc7SUFDOUIsY0FBYztJQUNkLFlBQVk7SUFDWixhQUFhO0lBQ2IsY0FBYztJQUNkLGdCQUFnQjtJQUNoQiw0QkFB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