undetected-puppeteer
Version:
1:1 (sorta) replacement for Puppeteer, but undetected
154 lines (132 loc) • 5.48 kB
JavaScript
const puppeteer = require('puppeteer');
const { execFile } = require('child_process');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const os = require('os');
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function launch(options = {}) {
const {
headless = false,
args = [],
defaultViewport = { width: 800, height: 600 },
executablePath,
userDataDir,
...otherOptions
} = options;
const tempUserDataDir = !userDataDir ? fs.mkdtempSync(
path.join(os.tmpdir(), 'puppeteer_dev_chrome_profile-')
) : null;
try {
const debugPort = options.port || Math.floor(Math.random() * (9999 - 9000) + 9000);
// trying to replicate puppeteer chrome launch arguments is hell
const chromeArgs = [
`--remote-debugging-port=${debugPort}`,
`--user-data-dir=${userDataDir || tempUserDataDir}`,
'--disable-background-networking',
'--enable-features=NetworkService,NetworkServiceInProcess',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-extensions-with-background-pages',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-extensions',
'--disable-features=Translate',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-popup-blocking',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-sync',
'--metrics-recording-only',
'--no-first-run',
'--no-default-browser-check',
'--password-store=basic',
'--use-mock-keychain',
'--allow-pre-commit-input',
'--disable-domain-reliability',
'--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process',
'--disable-component-update',
'--no-service-autorun',
// idk if ts needed maybe delete?
'--disable-blink-features=AutomationControlled'
];
const userArgs = args.filter(arg =>
!arg.startsWith('--remote-debugging-port=') &&
!arg.startsWith('--user-data-dir='));
chromeArgs.push(...userArgs);
if (headless) {
chromeArgs.push('--headless=new');
if (!args.some(arg => arg.startsWith('--user-agent='))) {
chromeArgs.push(`--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36`);
}
if (!args.some(arg => arg.includes('--sandbox'))) {
chromeArgs.push('--no-sandbox', '--disable-setuid-sandbox');
}
}
if (!args.some(arg => arg.startsWith('--window-size')) && defaultViewport) {
chromeArgs.push(`--window-size=${defaultViewport.width},${defaultViewport.height}`);
}
const chromeProcess = execFile((executablePath || puppeteer.executablePath()), chromeArgs);
let webSocketUrl;
let attempts = 0;
const maxAttempts = 15;
while (!webSocketUrl && attempts < maxAttempts) {
try {
await delay(500);
const response = await axios.get(`http://127.0.0.1:${debugPort}/json/version`, {
timeout: 1000
});
webSocketUrl = response.data.webSocketDebuggerUrl;
} catch {
attempts++;
}
}
if (!webSocketUrl) {
throw new Error(`Failed to connect to Chrome on port ${debugPort}`);
}
const browser = await puppeteer.connect({
browserWSEndpoint: webSocketUrl,
defaultViewport,
...otherOptions
});
const cleanup = async (fromClose = false) => {
try {
if (!fromClose) {
try { await browser.disconnect(); } catch {}
}
chromeProcess.kill('SIGKILL');
if (tempUserDataDir) {
fs.rm(tempUserDataDir, { recursive: true, force: true }, () => {});
}
} catch {}
};
const old = browser.close.bind(browser);
browser.close = async () => {
await old();
await cleanup(true);
};
browser.on('disconnected', () => cleanup());
process.once('exit', () => cleanup());
process.once('SIGINT', () => {
cleanup();
process.exit(130);
});
process.once('SIGTERM', () => {
cleanup();
process.exit(143);
});
return browser;
} catch (error) {
if (tempUserDataDir) {
fs.rm(tempUserDataDir, { recursive: true, force: true }, () => {});
}
throw error;
}
}
module.exports = {
...puppeteer,
launch
};