Merge pull request #22 from evelaa123/feat/launch-persistent-context

feat: add launchPersistentContext() to avoid incognito detection
This commit is contained in:
Cloak-HQ
2026-03-04 19:10:23 +01:00
committed by GitHub
5 changed files with 253 additions and 4 deletions
+2 -2
View File
@@ -16,7 +16,7 @@
*/
// Launch functions (Playwright API)
export { launch, launchContext } from "./playwright.js";
export { launch, launchContext, launchPersistentContext } from "./playwright.js";
// Binary management
export { ensureBinary, clearCache, binaryInfo, checkForUpdate } from "./download.js";
@@ -25,4 +25,4 @@ export { ensureBinary, clearCache, binaryInfo, checkForUpdate } from "./download
export { CHROMIUM_VERSION, getDefaultStealthArgs } from "./config.js";
// Types
export type { LaunchOptions, LaunchContextOptions, BinaryInfo } from "./types.js";
export type { LaunchOptions, LaunchContextOptions, LaunchPersistentContextOptions, BinaryInfo } from "./types.js";
+48 -1
View File
@@ -4,7 +4,7 @@
*/
import type { Browser, BrowserContext } from "playwright-core";
import type { LaunchOptions, LaunchContextOptions } from "./types.js";
import type { LaunchOptions, LaunchContextOptions, LaunchPersistentContextOptions } from "./types.js";
import { DEFAULT_VIEWPORT, getDefaultStealthArgs } from "./config.js";
import { ensureBinary } from "./download.js";
import { parseProxyUrl } from "./proxy.js";
@@ -88,6 +88,53 @@ export async function launchContext(
return context;
}
/**
* Launch stealth browser with a persistent user profile (non-incognito).
* Uses Playwright's chromium.launchPersistentContext() under the hood.
*
* This avoids incognito detection by services like BrowserScan (-10% penalty)
* and enables session persistence (cookies, localStorage) across launches.
*
* @example
* ```ts
* import { launchPersistentContext } from 'cloakbrowser';
* const context = await launchPersistentContext({
* userDataDir: './chrome-profile',
* headless: false,
* proxy: 'http://user:pass@host:port',
* geoip: true,
* });
* const page = context.pages()[0] || await context.newPage();
* await page.goto('https://example.com');
* await context.close();
* ```
*/
export async function launchPersistentContext(
options: LaunchPersistentContextOptions
): Promise<BrowserContext> {
const { chromium } = await import("playwright-core");
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
const resolved = await maybeResolveGeoip(options);
const args = buildArgs({ ...options, ...resolved });
const context = await chromium.launchPersistentContext(options.userDataDir, {
executablePath: binaryPath,
headless: options.headless ?? true,
args,
ignoreDefaultArgs: ["--enable-automation"],
...(options.proxy ? { proxy: parseProxyUrl(options.proxy) } : {}),
...(options.userAgent ? { userAgent: options.userAgent } : {}),
viewport: options.viewport ?? DEFAULT_VIEWPORT,
...(resolved.locale ? { locale: resolved.locale } : {}),
...(resolved.timezone ? { timezoneId: resolved.timezone } : {}),
...(options.colorScheme ? { colorScheme: options.colorScheme } : {}),
...options.launchOptions,
});
return context;
}
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
+5
View File
@@ -34,6 +34,11 @@ export interface LaunchContextOptions extends LaunchOptions {
colorScheme?: "light" | "dark" | "no-preference";
}
export interface LaunchPersistentContextOptions extends LaunchContextOptions {
/** Path to user data directory for persistent profile. */
userDataDir: string;
}
export interface BinaryInfo {
version: string;
platform: string;