mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
feat(js): export composable launch helpers (#244)
Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
// Launch functions (Playwright API)
|
||||
export { launch, launchContext, launchPersistentContext } from "./playwright.js";
|
||||
export { launch, launchContext, launchPersistentContext, buildLaunchOptions, humanizeBrowser } from "./playwright.js";
|
||||
|
||||
// Binary management
|
||||
export { ensureBinary, clearCache, binaryInfo, checkForUpdate } from "./download.js";
|
||||
|
||||
+49
-31
@@ -3,7 +3,7 @@
|
||||
* Mirrors Python cloakbrowser/browser.py.
|
||||
*/
|
||||
|
||||
import type { Browser, BrowserContext, BrowserContextOptions } from "playwright-core";
|
||||
import type { Browser, BrowserContext, BrowserContextOptions, LaunchOptions as PlaywrightLaunchOptions } from "playwright-core";
|
||||
import type { LaunchOptions, LaunchContextOptions, LaunchPersistentContextOptions } from "./types.js";
|
||||
import { DEFAULT_VIEWPORT, IGNORE_DEFAULT_ARGS } from "./config.js";
|
||||
import { buildArgs } from "./args.js";
|
||||
@@ -44,6 +44,52 @@ function filterStealthCtxOptions(ctx?: BrowserContextOptions): Partial<BrowserCo
|
||||
return rest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Playwright launch options for CloakBrowser without starting Chromium.
|
||||
*
|
||||
* Useful when integrating CloakBrowser with a custom Playwright build or another
|
||||
* wrapper that needs to call `chromium.launch()` itself.
|
||||
*/
|
||||
export async function buildLaunchOptions(
|
||||
options: LaunchOptions = {}
|
||||
): Promise<PlaywrightLaunchOptions> {
|
||||
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
|
||||
const { exitIp, ...resolved } = await maybeResolveGeoip(options);
|
||||
const { proxyOption, proxyArgs } = resolveProxyConfig(options.proxy);
|
||||
let resolvedArgs = await resolveWebrtcArgs(options);
|
||||
if (exitIp && !(resolvedArgs ?? []).some(a => a.startsWith("--fingerprint-webrtc-ip"))) {
|
||||
resolvedArgs = [...(resolvedArgs ?? []), `--fingerprint-webrtc-ip=${exitIp}`];
|
||||
}
|
||||
const args = buildArgs({ ...options, ...resolved, args: [...(resolvedArgs ?? []), ...proxyArgs] });
|
||||
|
||||
return {
|
||||
executablePath: binaryPath,
|
||||
headless: options.headless ?? true,
|
||||
args,
|
||||
ignoreDefaultArgs: IGNORE_DEFAULT_ARGS,
|
||||
...(proxyOption ? { proxy: proxyOption } : {}),
|
||||
...options.launchOptions,
|
||||
} as PlaywrightLaunchOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply CloakBrowser's human-like behavioral layer to an existing Playwright browser.
|
||||
*/
|
||||
export async function humanizeBrowser(
|
||||
browser: Browser,
|
||||
options: LaunchOptions = {}
|
||||
): Promise<void> {
|
||||
if (!options.humanize) return;
|
||||
|
||||
const { patchBrowser } = await import('./human/index.js');
|
||||
const { resolveConfig } = await import('./human/config.js');
|
||||
const cfg = resolveConfig(
|
||||
options.humanPreset ?? 'default',
|
||||
options.humanConfig,
|
||||
);
|
||||
patchBrowser(browser, cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch stealth Chromium browser via Playwright.
|
||||
*
|
||||
@@ -59,36 +105,8 @@ function filterStealthCtxOptions(ctx?: BrowserContextOptions): Partial<BrowserCo
|
||||
*/
|
||||
export async function launch(options: LaunchOptions = {}): Promise<Browser> {
|
||||
const { chromium } = await import("playwright-core");
|
||||
|
||||
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
|
||||
const { exitIp, ...resolved } = await maybeResolveGeoip(options);
|
||||
const { proxyOption, proxyArgs } = resolveProxyConfig(options.proxy);
|
||||
let resolvedArgs = await resolveWebrtcArgs(options);
|
||||
if (exitIp && !(resolvedArgs ?? []).some(a => a.startsWith("--fingerprint-webrtc-ip"))) {
|
||||
resolvedArgs = [...(resolvedArgs ?? []), `--fingerprint-webrtc-ip=${exitIp}`];
|
||||
}
|
||||
const args = buildArgs({ ...options, ...resolved, args: [...(resolvedArgs ?? []), ...proxyArgs] });
|
||||
|
||||
const browser = await chromium.launch({
|
||||
executablePath: binaryPath,
|
||||
headless: options.headless ?? true,
|
||||
args,
|
||||
ignoreDefaultArgs: IGNORE_DEFAULT_ARGS,
|
||||
...(proxyOption ? { proxy: proxyOption } : {}),
|
||||
...options.launchOptions,
|
||||
});
|
||||
|
||||
// Human-like behavioral patching
|
||||
if (options.humanize) {
|
||||
const { patchBrowser } = await import('./human/index.js');
|
||||
const { resolveConfig } = await import('./human/config.js');
|
||||
const cfg = resolveConfig(
|
||||
options.humanPreset ?? 'default',
|
||||
options.humanConfig,
|
||||
);
|
||||
patchBrowser(browser, cfg);
|
||||
}
|
||||
|
||||
const browser = await chromium.launch(await buildLaunchOptions(options));
|
||||
await humanizeBrowser(browser, options);
|
||||
return browser;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,70 @@ describe("binaryInfo", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("composable Playwright launch helpers", () => {
|
||||
const origBinaryPath = process.env.CLOAKBROWSER_BINARY_PATH;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.CLOAKBROWSER_BINARY_PATH = "/fake/chrome";
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
if (origBinaryPath) {
|
||||
process.env.CLOAKBROWSER_BINARY_PATH = origBinaryPath;
|
||||
} else {
|
||||
delete process.env.CLOAKBROWSER_BINARY_PATH;
|
||||
}
|
||||
});
|
||||
|
||||
it("exports buildLaunchOptions and humanizeBrowser from the package entrypoint", async () => {
|
||||
const entry = await import("../src/index.js");
|
||||
|
||||
expect(entry.buildLaunchOptions).toBeTypeOf("function");
|
||||
expect(entry.humanizeBrowser).toBeTypeOf("function");
|
||||
});
|
||||
|
||||
it("buildLaunchOptions returns Playwright options without launching a browser", async () => {
|
||||
const { buildLaunchOptions } = await import("../src/index.js");
|
||||
|
||||
const options = await buildLaunchOptions({
|
||||
headless: false,
|
||||
proxy: "http://user:pass@proxy.example:8080",
|
||||
args: ["--custom-flag"],
|
||||
launchOptions: { timeout: 1234 },
|
||||
});
|
||||
|
||||
expect(options.executablePath).toBe("/fake/chrome");
|
||||
expect(options.headless).toBe(false);
|
||||
expect(options.args).toContain("--custom-flag");
|
||||
expect(options.ignoreDefaultArgs).toContain("--enable-automation");
|
||||
expect(options.proxy).toEqual({
|
||||
server: "http://proxy.example:8080",
|
||||
username: "user",
|
||||
password: "pass",
|
||||
});
|
||||
expect(options.timeout).toBe(1234);
|
||||
});
|
||||
|
||||
it("humanizeBrowser patches an existing browser only when requested", async () => {
|
||||
const { humanizeBrowser } = await import("../src/index.js");
|
||||
const browser = {
|
||||
contexts: () => [],
|
||||
newContext: vi.fn(async () => ({})),
|
||||
newPage: vi.fn(async () => ({ context: () => ({}) })),
|
||||
};
|
||||
const originalNewContext = browser.newContext;
|
||||
|
||||
await humanizeBrowser(browser as any, { humanize: false });
|
||||
expect(browser.newContext).toBe(originalNewContext);
|
||||
|
||||
await humanizeBrowser(browser as any, { humanize: true });
|
||||
expect(browser.newContext).not.toBe(originalNewContext);
|
||||
});
|
||||
});
|
||||
|
||||
// Integration tests require the binary — run with:
|
||||
// CLOAKBROWSER_BINARY_PATH=/path/to/chrome npm test
|
||||
describe.skipIf(!process.env.CLOAKBROWSER_BINARY_PATH)(
|
||||
|
||||
Reference in New Issue
Block a user