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
JavaScript
"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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