33 * Mirrors Python cloakbrowser/browser.py.
44 */
55
6- import type { Browser , BrowserContext , BrowserContextOptions , LaunchOptions as PlaywrightLaunchOptions } from "playwright-core" ;
6+ import type {
7+ Browser ,
8+ BrowserContext ,
9+ BrowserContextOptions ,
10+ BrowserType ,
11+ LaunchOptions as PlaywrightLaunchOptions
12+ } from "playwright-core" ;
713import type { LaunchOptions , LaunchContextOptions , LaunchPersistentContextOptions } from "./types.js" ;
814import { DEFAULT_VIEWPORT , IGNORE_DEFAULT_ARGS } from "./config.js" ;
915import { buildArgs } from "./args.js" ;
@@ -71,6 +77,7 @@ function effectiveHeadless(options: LaunchOptions): boolean {
7177export function buildContextOptions (
7278 options : LaunchContextOptions = { }
7379) : BrowserContextOptions {
80+ options = resolveTimezone ( options ) ;
7481 // Headed: viewport=null (no emulation) so the page tracks the real window and
7582 // outerWidth >= innerWidth stays coherent — CDP viewport emulation forces
7683 // inner > outer = a physically impossible window = bot tell. Headless has no
@@ -123,6 +130,27 @@ export async function buildLaunchOptions(
123130 } as PlaywrightLaunchOptions ;
124131}
125132
133+ /**
134+ * Build Playwright launchPersistentContextOptions options for CloakBrowser
135+ * without starting Chromium.
136+ *
137+ * Useful when integrating CloakBrowser with a custom Playwright build or another
138+ * wrapper that needs to call `chromium.launchPersistentContext()` itself.
139+ */
140+ export async function buildLaunchPersistentContextOptions (
141+ options : LaunchPersistentContextOptions
142+ ) : Promise < Parameters < BrowserType [ "launchPersistentContext" ] > [ 1 ] > {
143+ const launchOptions = await buildLaunchOptions ( options ) ;
144+ const contextOptions = buildContextOptions ( options ) ;
145+
146+ seedWidevineHint ( options . userDataDir , launchOptions . executablePath as string ) ;
147+
148+ return {
149+ ...launchOptions ,
150+ ...contextOptions ,
151+ }
152+ }
153+
126154/**
127155 * Apply CloakBrowser's human-like behavioral layer to an existing Playwright browser.
128156 */
@@ -141,6 +169,24 @@ export async function humanizeBrowser(
141169 patchBrowser ( browser , cfg ) ;
142170}
143171
172+ /**
173+ * Apply CloakBrowser's human-like behavioral layer to an existing Playwright context.
174+ */
175+ export async function humanizeContext (
176+ context : BrowserContext ,
177+ options : LaunchContextOptions = { }
178+ ) : Promise < void > {
179+ if ( ! options . humanize ) return ;
180+
181+ const { patchContext } = await import ( './human/index.js' ) ;
182+ const { resolveConfig } = await import ( './human/config.js' ) ;
183+ const cfg = resolveConfig (
184+ options . humanPreset ?? 'default' ,
185+ options . humanConfig ,
186+ ) ;
187+ patchContext ( context , cfg ) ;
188+ }
189+
144190/**
145191 * Launch stealth Chromium browser via Playwright.
146192 *
@@ -203,20 +249,7 @@ function applyDefaultNoViewport(browser: Browser): void {
203249export async function launchContext (
204250 options : LaunchContextOptions = { }
205251) : Promise < BrowserContext > {
206- options = resolveTimezone ( options ) ;
207- // Resolve geoip BEFORE launch() to avoid double-resolution
208- const { exitIp, ...resolved } = await maybeResolveGeoip ( options ) ;
209- let launchArgs = await resolveWebrtcArgs ( options ) ;
210- // Inject geoip exit IP for WebRTC spoofing (free — no extra HTTP call)
211- if ( exitIp && ! ( launchArgs ?? [ ] ) . some ( a => a . startsWith ( "--fingerprint-webrtc-ip" ) ) ) {
212- launchArgs = [ ...( launchArgs ?? [ ] ) , `--fingerprint-webrtc-ip=${ exitIp } ` ] ;
213- }
214- // --fingerprint-timezone is process-wide (reads CommandLine in renderer),
215- // so it applies to ALL contexts, not just the default one.
216- // locale and timezone are set via binary flags only — no CDP emulation.
217- // humanize:false on the inner launch — patchContext below applies humanize
218- // exactly once (else launch()'s humanizeBrowser would patch it a second time).
219- const browser = await launch ( { ...options , ...resolved , args : launchArgs , geoip : false , humanize : false } ) ;
252+ const browser = await launch ( options ) ;
220253
221254 let context : BrowserContext ;
222255 try {
@@ -233,17 +266,7 @@ export async function launchContext(
233266 await browser . close ( ) ;
234267 } ;
235268
236- // Human-like behavioral patching
237- if ( options . humanize ) {
238- const { patchContext } = await import ( './human/index.js' ) ;
239- const { resolveConfig } = await import ( './human/config.js' ) ;
240- const cfg = resolveConfig (
241- options . humanPreset ?? 'default' ,
242- options . humanConfig ,
243- ) ;
244- patchContext ( context , cfg ) ;
245- }
246-
269+ await humanizeContext ( context , options ) ;
247270 return context ;
248271}
249272
@@ -271,45 +294,12 @@ export async function launchContext(
271294export async function launchPersistentContext (
272295 options : LaunchPersistentContextOptions
273296) : Promise < BrowserContext > {
274- options = resolveTimezone ( options ) ;
275297 const { chromium } = await import ( "playwright-core" ) ;
276-
277- const binaryPath =
278- process . env . CLOAKBROWSER_BINARY_PATH ||
279- ( await ensureBinary ( options . licenseKey , options . browserVersion ) ) ;
280- const { exitIp, ...resolved } = await maybeResolveGeoip ( options ) ;
281- const { proxyOption, proxyArgs } = resolveProxyConfig ( options . proxy , options . browserVersion ) ;
282- let resolvedArgs = await resolveWebrtcArgs ( options ) ;
283- if ( exitIp && ! ( resolvedArgs ?? [ ] ) . some ( a => a . startsWith ( "--fingerprint-webrtc-ip" ) ) ) {
284- resolvedArgs = [ ...( resolvedArgs ?? [ ] ) , `--fingerprint-webrtc-ip=${ exitIp } ` ] ;
285- }
286- const args = buildArgs ( { ...options , ...resolved , args : [ ...( resolvedArgs ?? [ ] ) , ...proxyArgs ] } ) ;
287-
288- seedWidevineHint ( options . userDataDir , binaryPath ) ;
289-
290- // locale and timezone are set via binary flags (--lang, --fingerprint-timezone)
291- // — NOT via Playwright context kwargs which use detectable CDP emulation.
292- const context = await chromium . launchPersistentContext ( options . userDataDir , {
293- executablePath : binaryPath ,
294- headless : options . headless ?? true ,
295- args,
296- ignoreDefaultArgs : IGNORE_DEFAULT_ARGS ,
297- ...( proxyOption ? { proxy : proxyOption } : { } ) ,
298- ...buildContextOptions ( options ) ,
299- ...options . launchOptions ,
300- } ) ;
301-
302- // Human-like behavioral patching
303- if ( options . humanize ) {
304- const { patchContext } = await import ( './human/index.js' ) ;
305- const { resolveConfig } = await import ( './human/config.js' ) ;
306- const cfg = resolveConfig (
307- options . humanPreset ?? 'default' ,
308- options . humanConfig ,
309- ) ;
310- patchContext ( context , cfg ) ;
311- }
312-
298+ const context = await chromium . launchPersistentContext (
299+ options . userDataDir ,
300+ await buildLaunchPersistentContextOptions ( options ) ,
301+ ) ;
302+ await humanizeContext ( context , options ) ;
313303 return context ;
314304}
315305
0 commit comments