UNPKG

fakebrowser

Version:

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

328 lines • 25.1 kB
"use strict"; // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FakeBrowser = exports.kDefaultLaunchArgs = exports.kDefaultWindowsDD = void 0; const path = __importStar(require("path")); const assert_1 = require("assert"); const UserAgentHelper_1 = require("./UserAgentHelper"); const PptrToolkit_1 = require("./PptrToolkit"); const PptrPatcher_1 = require("./PptrPatcher"); const FakeUserAction_1 = require("./FakeUserAction"); const BrowserLauncher_1 = require("./BrowserLauncher"); const BrowserBuilder_1 = require("./BrowserBuilder"); const TouchScreen_1 = require("./TouchScreen"); exports.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 exports.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 class FakeBrowser { constructor(driverParams, vanillaBrowser, pptrExtra, bindingTime, uuid) { this.driverParams = driverParams; this.vanillaBrowser = vanillaBrowser; this.pptrExtra = pptrExtra; this.bindingTime = bindingTime; this.uuid = uuid; (0, assert_1.strict)(driverParams.deviceDesc && driverParams.deviceDesc.navigator && driverParams.deviceDesc.navigator.userAgent); this.isMobileBrowser = UserAgentHelper_1.UserAgentHelper.isMobile(driverParams.deviceDesc.navigator.userAgent); this.uuid = uuid; this.userAction = new FakeUserAction_1.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() { (0, assert_1.strict)(this.driverParams.launchOptions); return this.driverParams; } get connectParams() { (0, assert_1.strict)(this.driverParams.connectOptions); return this.driverParams; } async beforeShutdown() { } async shutdown() { if (!this._zombie) { await this.beforeShutdown(); this._zombie = true; await BrowserLauncher_1.BrowserLauncher._forceShutdown(this); } else { // console.warn('This instance has been shutdown and turned into a zombie.') } } async getActivePage() { const result = await PptrToolkit_1.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) { (0, assert_1.strict)(!!worker); const injectJs = await PptrPatcher_1.PptrPatcher.evasionsCode(this); await worker.evaluate(injectJs); } async interceptTarget(target, client) { (0, assert_1.strict)(!!client); // FIXME: Worker & SharedWorker does not work with this way // console.log('intercept', target.url()) const injectJs = await PptrPatcher_1.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; (0, assert_1.strict)(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_1.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_1.UserAgentHelper.chromeMajorVersion(ua); const os = UserAgentHelper_1.UserAgentHelper.os(fakeDD.navigator.userAgent); (0, assert_1.strict)(chromeMajorVersion); (0, assert_1.strict)(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_1.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_1.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; }, }), }); } } exports.FakeBrowser = FakeBrowser; FakeBrowser.Builder = BrowserBuilder_1.BrowserBuilder; FakeBrowser.globalConfig = { defaultBrowserMaxSurvivalTime: kBrowserMaxSurvivalTime, defaultReferers: kDefaultReferers, internalHttpServerPort: kInternalHttpServerPort, defaultLaunchArgs: exports.kDefaultLaunchArgs, }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRmFrZUJyb3dzZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29yZS9GYWtlQnJvd3Nlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMERBQTBEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRTFELDJDQUE0QjtBQUM1QixtQ0FBdUM7QUFLdkMsdURBQWlEO0FBQ2pELCtDQUF5QztBQUd6QywrQ0FBeUM7QUFDekMscURBQStDO0FBQy9DLHVEQUFpRDtBQUNqRCxxREFBK0M7QUFDL0MsK0NBQXlDO0FBRTVCLFFBQUEsaUJBQWlCLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLG9DQUFvQyxDQUFDLENBQUMsQ0FBQTtBQUV2RyxNQUFNLHVCQUF1QixHQUFHLEVBQUUsR0FBRyxJQUFJLEdBQUcsRUFBRSxDQUFBO0FBQzlDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyx3QkFBd0IsRUFBRSxzQkFBc0IsQ0FBQyxDQUFBO0FBQzNFLE1BQU0sdUJBQXVCLEdBQUcsS0FBSyxDQUFBO0FBRXJDLDhCQUE4QjtBQUM5QiwrREFBK0Q7QUFDL0QsaUZBQWlGO0FBQ2pGLGlFQUFpRTtBQUNwRCxRQUFBLGtCQUFrQixHQUFHO0lBQzlCLGNBQWM7SUFDZCxZQUFZO0lBQ1osYUFBYTtJQUNiLGNBQWM7SUFDZCxnQkFBZ0I7SUFDaEIsNEJBQTRCO0lBQzVCLCtCQUErQjtJQUMvQix3QkFBd0I7SUFDeEIsMEJBQTBCO0lBQzFCLDBCQUEwQjtJQUMxQiw2Q0FBNkM7SUFDN0MscUNBQXFDO0lBQ3JDLHlCQUF5QjtJQUN6Qix1QkFBdUI7SUFDdkIsd0JBQXdCO0lBQ3hCLDZCQUE2QjtJQUM3Qix3QkFBd0I7SUFDeEIsb0JBQW9CO0lBQ3BCLCtCQUErQjtJQUMvQix3QkFBd0I7SUFDeEIsZ0JBQWdCO0lBQ2hCLG1DQUFtQztJQUNuQyx3QkFBd0I7SUFDeEIsNkJBQTZCO0lBQzdCLHVDQUF1QztJQUN2QyxpQ0FBaUM7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