From d33313f9e47d960ecff8599b82a2282ae96d444e Mon Sep 17 00:00:00 2001 From: Hai Chang Date: Fri, 24 Apr 2026 00:55:51 +1000 Subject: [PATCH] Add Chrome channel persistent context launch - Enable launchPersistentContext when BROWSE_CHANNEL is set - Support macOS SSO by using temporary profile directory - Maintain headless and sandbox handling for other browsers --- browse/src/browser-manager.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index 32f5ab769..917f15d6d 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -287,6 +287,7 @@ export class BrowserManager { // BROWSE_EXTENSIONS_DIR points to an unpacked Chrome extension directory. // Extensions only work in headed mode, so we use an off-screen window. const extensionsDir = process.env.BROWSE_EXTENSIONS_DIR; + const channel = process.env.BROWSE_CHANNEL; const { STEALTH_LAUNCH_ARGS } = await import('./stealth'); const launchArgs: string[] = [...STEALTH_LAUNCH_ARGS]; let useHeadless = true; @@ -310,7 +311,7 @@ export class BrowserManager { console.log(`[browse] Extensions loaded from: ${extensionsDir}`); } - this.browser = await chromium.launch({ + const launchOpts = { headless: useHeadless, // On Windows, Chromium's sandbox fails when the server is spawned through // the Bun→Node process chain (GitHub #276). Disable it — local daemon @@ -320,7 +321,24 @@ export class BrowserManager { chromiumSandbox: shouldEnableChromiumSandbox(), ...(launchArgs.length > 0 ? { args: launchArgs } : {}), ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}), - }); + }; + + const contextOpts: BrowserContextOptions = { + viewport: this.currentViewport, + deviceScaleFactor: this.deviceScaleFactor, + ...(this.customUserAgent ? { userAgent: this.customUserAgent } : {}), + }; + + if (channel) { + // launchPersistentContext enables macOS platform SSO (newContext does not) + this.context = await chromium.launchPersistentContext('', { + ...launchOpts, ...contextOpts, channel, + }); + this.browser = this.context.browser(); + } else { + this.browser = await chromium.launch(launchOpts); + this.context = await this.browser.newContext(contextOpts); + } // Chromium disconnect → distinguish clean user-quit from crash. Both // events look identical to Playwright (one 'disconnected' fires), but @@ -335,15 +353,6 @@ export class BrowserManager { void handleChromiumDisconnect(this.browser); }); - const contextOptions: BrowserContextOptions = { - viewport: { width: this.currentViewport.width, height: this.currentViewport.height }, - deviceScaleFactor: this.deviceScaleFactor, - }; - if (this.customUserAgent) { - contextOptions.userAgent = this.customUserAgent; - } - this.context = await this.browser.newContext(contextOptions); - if (Object.keys(this.extraHeaders).length > 0) { await this.context.setExtraHTTPHeaders(this.extraHeaders); }