UNPKG

fakebrowser

Version:

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

220 lines • 16.2 kB
"use strict"; 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; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.kDefaultLaunchOptions = exports.kDefaultTimeout = void 0; const assert_1 = require("assert"); const fs = __importStar(require("fs-extra")); const puppeteer_extra_1 = require("puppeteer-extra"); const DeviceDescriptor_js_1 = __importDefault(require("./DeviceDescriptor.js")); const UserAgentHelper_js_1 = require("./UserAgentHelper.js"); const PptrPatcher_1 = require("./PptrPatcher"); exports.kDefaultTimeout = 15 * 1000; exports.kDefaultLaunchOptions = { headless: true, devtools: false, timeout: exports.kDefaultTimeout, }; class Driver { static checkParamsLegal(params) { // deviceDesc must be set const dd = params.deviceDesc; (0, assert_1.strict)(dd, 'deviceDesc must be set'); DeviceDescriptor_js_1.default.checkLegal(dd); // user data dir // The userDataDir in launchParameters must be set (0, assert_1.strict)(params.userDataDir, 'userDataDir must be set'); } /** * Connect to browser * @param uuid * @param params */ static async connect(uuid, params) { // Different instances with different puppeteer configurations const pptr = (0, puppeteer_extra_1.addExtra)(require('puppeteer')); // patch with evasions if (!params.doNotHook) { await PptrPatcher_1.PptrPatcher.patch(uuid, pptr, params); } const fakeDD = params.fakeDeviceDesc; (0, assert_1.strict)(!!fakeDD); const browser = await pptr.connect(params.connectOptions); await this.patchUAFromLaunchedBrowser(browser, fakeDD); return { vanillaBrowser: browser, pptrExtra: pptr, }; } /** * Launch browser * @param uuid * @param defaultLaunchArgs * @param params */ static async launch(uuid, defaultLaunchArgs, params) { this.checkParamsLegal(params); if (!params.launchOptions || Object.keys(params.launchOptions).length === 0) { params.launchOptions = exports.kDefaultLaunchOptions; } this.patchLaunchArgs(defaultLaunchArgs, params); // Different instances with different puppeteer configurations const pptr = (0, puppeteer_extra_1.addExtra)(require('puppeteer')); // patch with evasions if (!params.doNotHook) { await PptrPatcher_1.PptrPatcher.patch(uuid, pptr, params); } const fakeDD = params.fakeDeviceDesc; (0, assert_1.strict)(!!fakeDD); const browser = await pptr.launch(params.launchOptions); await this.patchUAFromLaunchedBrowser(browser, fakeDD); return { vanillaBrowser: browser, pptrExtra: pptr, }; } static async patchUAFromLaunchedBrowser(browser, fakeDD) { // read major version from the launched browser and replace dd.userAgent const orgUA = await browser.userAgent(); const orgVersion = UserAgentHelper_js_1.UserAgentHelper.chromeVersion(orgUA); const fakeVersion = UserAgentHelper_js_1.UserAgentHelper.chromeVersion(fakeDD.navigator.userAgent); (0, assert_1.strict)(orgVersion); (0, assert_1.strict)(fakeVersion); fakeDD.navigator.userAgent = fakeDD.navigator.userAgent.replace(fakeVersion, orgVersion); fakeDD.navigator.appVersion = fakeDD.navigator.appVersion.replace(fakeVersion, orgVersion); } static patchLaunchArgs(defaultLaunchArgs, launchParams) { // args // noinspection SuspiciousTypeOfGuard (0, assert_1.strict)(defaultLaunchArgs instanceof Array); const args = [ ...defaultLaunchArgs, ...(launchParams.launchOptions.args || []), ]; const fakeDD = launchParams.fakeDeviceDesc; (0, assert_1.strict)(!!fakeDD); // Modify default options launchParams.launchOptions = { ignoreHTTPSErrors: true, ignoreDefaultArgs: [ '--enable-automation', '--enable-blink-features=IdleDetection', ], handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false, pipe: true, defaultViewport: { width: fakeDD.window.innerWidth, height: fakeDD.window.innerHeight, deviceScaleFactor: fakeDD.window.devicePixelRatio, isMobile: UserAgentHelper_js_1.UserAgentHelper.isMobile(fakeDD.navigator.userAgent), hasTouch: fakeDD.navigator.maxTouchPoints > 0, isLandscape: false, }, ...launchParams.launchOptions, args, }; // headless let headless = launchParams.launchOptions.headless; if ('undefined' === typeof headless) { headless = true; } if (launchParams.launchOptions.devtools) { headless = false; } // proxy if (launchParams.proxy) { args.push(`--proxy-server=${launchParams.proxy.proxy}`); } // browser language (0, assert_1.strict)(fakeDD.acceptLanguage); args.push(`--lang=${fakeDD.acceptLanguage}`); const userDataDir = launchParams.userDataDir; (0, assert_1.strict)(userDataDir); fs.mkdirSync(userDataDir, { recursive: true }); // throw exception args.push(`--user-data-dir=${userDataDir}`); // window position & window size let { screenX, screenY, innerWidth, innerHeight, outerWidth, outerHeight, } = fakeDD.window; outerWidth = outerWidth || innerWidth; outerHeight = outerHeight || (innerHeight + 85); args.push(`--window-position=${screenX},${screenY}`, `--window-size=${outerWidth},${outerHeight}`); // Some options can only be used in headless. // If you use them again in headful, you will see a plain white browser window without any content. if (headless) { args.push('--in-process-gpu', // https://source.chromium.org/search?q=lang:cpp+symbol:kInProcessGPU&ss=chromium '--disable-canvas-aa', // Disable antialiasing on 2d canvas '--disable-2d-canvas-clip-aa', // Disable antialiasing on 2d canvas clips '--disable-gl-drawing-for-tests'); } } static async getPids(pid) { if ('string' === typeof (pid)) { pid = parseInt(pid); } try { const pidtree = require('pidtree'); const pids = await pidtree(pid); return pids.includes(pid) ? pids : [...pids, pid]; } catch (ignored) { return [pid]; } } /** * Shutdown browser * @param browser */ static async shutdown(browser) { try { const pages = await browser.pages(); for (const page of pages) { await page.close(); } } catch (ignored) { } const browserProcess = browser.process(); if (browserProcess) { const pid = browserProcess.pid; if (pid) { const pids = await this.getPids(pid); pids.forEach(pid => { try { process.kill(pid, 'SIGKILL'); } catch (ignored) { } }); } } try { await browser.close(); } catch (ignored) { } } } exports.default = Driver; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRHJpdmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvcmUvRHJpdmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxtQ0FBeUM7QUFDekMsNkNBQThCO0FBRTlCLHFEQUFnRjtBQUdoRixnRkFBc0c7QUFDdEcsNkRBQXNEO0FBQ3RELCtDQUEyQztBQW1DOUIsUUFBQSxlQUFlLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQTtBQUUzQixRQUFBLHFCQUFxQixHQUFHO0lBQ2pDLFFBQVEsRUFBRSxJQUFJO0lBQ2QsUUFBUSxFQUFFLEtBQUs7SUFDZixPQUFPLEVBQUUsdUJBQWU7Q0FDM0IsQ0FBQTtBQUVELE1BQXFCLE1BQU07SUFFZixNQUFNLENBQUMsZ0JBQWdCLENBQUMsTUFBd0I7UUFDcEQseUJBQXlCO1FBQ3pCLE1BQU0sRUFBRSxHQUFxQixNQUFNLENBQUMsVUFBVSxDQUFBO1FBQzlDLElBQUEsZUFBTSxFQUFDLEVBQUUsRUFBRSx3QkFBd0IsQ0FBQyxDQUFBO1FBRXBDLDZCQUFzQixDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQTtRQUVyQyxnQkFBZ0I7UUFDaEIsa0RBQWtEO1FBQ2xELElBQUEsZUFBTSxFQUFDLE1BQU0sQ0FBQyxXQUFXLEVBQUUseUJBQXlCLENBQUMsQ0FBQTtJQUN6RCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILE1BQU0sQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUNoQixJQUFZLEVBQ1osTUFBeUI7UUFLekIsOERBQThEO1FBQzlELE1BQU0sSUFBSSxHQUFHLElBQUEsMEJBQVEsRUFBQyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQTtRQUUzQyxzQkFBc0I7UUFDdEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7WUFDbkIsTUFBTSx5QkFBVyxDQUFDLEtBQUssQ0FDbkIsSUFBSSxFQUNKLElBQUksRUFDSixNQUFNLENBQ1QsQ0FB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