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