2026-02-23 19:57:19 +01:00
|
|
|
/**
|
|
|
|
|
* Puppeteer launch wrapper for cloakbrowser.
|
|
|
|
|
* Alternative to the Playwright wrapper for users who prefer Puppeteer.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import type { Browser } from "puppeteer-core";
|
|
|
|
|
import type { LaunchOptions } from "./types.js";
|
|
|
|
|
import { getDefaultStealthArgs } from "./config.js";
|
|
|
|
|
import { ensureBinary } from "./download.js";
|
2026-02-24 19:00:12 +01:00
|
|
|
import { parseProxyUrl } from "./proxy.js";
|
2026-02-23 19:57:19 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Launch stealth Chromium browser via Puppeteer.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```ts
|
|
|
|
|
* import { launch } from 'cloakbrowser/puppeteer';
|
|
|
|
|
* const browser = await launch();
|
|
|
|
|
* const page = await browser.newPage();
|
|
|
|
|
* await page.goto('https://bot.incolumitas.com');
|
|
|
|
|
* console.log(await page.title());
|
|
|
|
|
* await browser.close();
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export async function launch(options: LaunchOptions = {}): Promise<Browser> {
|
|
|
|
|
const puppeteer = await import("puppeteer-core");
|
|
|
|
|
|
|
|
|
|
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
|
2026-03-01 01:07:11 +01:00
|
|
|
const resolved = await maybeResolveGeoip(options);
|
|
|
|
|
const args = buildArgs({ ...options, ...resolved });
|
2026-02-23 19:57:19 +01:00
|
|
|
|
2026-02-24 19:00:12 +01:00
|
|
|
// Puppeteer handles proxy via CLI args, not a separate option.
|
|
|
|
|
// Chromium's --proxy-server does NOT support inline credentials,
|
|
|
|
|
// so we strip them and use page.authenticate() instead.
|
|
|
|
|
let proxyAuth: { username: string; password: string } | undefined;
|
2026-02-23 19:57:19 +01:00
|
|
|
if (options.proxy) {
|
2026-02-24 19:00:12 +01:00
|
|
|
const { server, username, password } = parseProxyUrl(options.proxy);
|
|
|
|
|
args.push(`--proxy-server=${server}`);
|
|
|
|
|
if (username) {
|
|
|
|
|
proxyAuth = { username, password: password || "" };
|
|
|
|
|
}
|
2026-02-23 19:57:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const browser = await puppeteer.default.launch({
|
|
|
|
|
executablePath: binaryPath,
|
|
|
|
|
headless: options.headless ?? true,
|
|
|
|
|
args,
|
|
|
|
|
ignoreDefaultArgs: ["--enable-automation"],
|
|
|
|
|
...options.launchOptions,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 19:00:12 +01:00
|
|
|
// Monkey-patch newPage() to auto-authenticate proxy credentials
|
|
|
|
|
if (proxyAuth) {
|
|
|
|
|
const origNewPage = browser.newPage.bind(browser);
|
|
|
|
|
const auth = proxyAuth;
|
|
|
|
|
browser.newPage = async (...pageArgs: Parameters<typeof origNewPage>) => {
|
|
|
|
|
const page = await origNewPage(...pageArgs);
|
|
|
|
|
await page.authenticate(auth);
|
|
|
|
|
return page;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 19:57:19 +01:00
|
|
|
return browser;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Internal
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-01 01:07:11 +01:00
|
|
|
async function maybeResolveGeoip(
|
|
|
|
|
options: LaunchOptions
|
|
|
|
|
): Promise<{ timezone?: string; locale?: string }> {
|
|
|
|
|
if (!options.geoip || !options.proxy) return { timezone: options.timezone, locale: options.locale };
|
|
|
|
|
if (options.timezone && options.locale) return { timezone: options.timezone, locale: options.locale };
|
|
|
|
|
|
|
|
|
|
const { resolveProxyGeo } = await import("./geoip.js");
|
|
|
|
|
const { timezone: geoTz, locale: geoLocale } = await resolveProxyGeo(options.proxy);
|
|
|
|
|
return {
|
|
|
|
|
timezone: options.timezone ?? geoTz ?? undefined,
|
|
|
|
|
locale: options.locale ?? geoLocale ?? undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 19:57:19 +01:00
|
|
|
function buildArgs(options: LaunchOptions): string[] {
|
|
|
|
|
const args: string[] = [];
|
|
|
|
|
if (options.stealthArgs !== false) {
|
|
|
|
|
args.push(...getDefaultStealthArgs());
|
|
|
|
|
}
|
|
|
|
|
if (options.args) {
|
|
|
|
|
args.push(...options.args);
|
|
|
|
|
}
|
2026-03-01 01:07:11 +01:00
|
|
|
if (options.timezone) {
|
2026-03-02 18:20:57 +01:00
|
|
|
args.push(`--fingerprint-timezone=${options.timezone}`);
|
2026-03-01 01:07:11 +01:00
|
|
|
}
|
|
|
|
|
if (options.locale) {
|
|
|
|
|
args.push(`--lang=${options.locale}`);
|
|
|
|
|
}
|
2026-02-23 19:57:19 +01:00
|
|
|
return args;
|
|
|
|
|
}
|