/** * Shared browser-launch and profile helpers for web-automation scripts. * * Centralises the three reusable primitives that every command entry point * needs: * - getProfilePath() — resolve the persistent CloakBrowser profile dir * - launchBrowser() — launch a CloakBrowser persistent context * - getPage() — get a ready Page + BrowserContext pair * * All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts) * import from here instead of duplicating these bodies. */ import { launchPersistentContext } from 'cloakbrowser'; import { existsSync, mkdirSync } from 'fs'; import { homedir } from 'os'; import { join } from 'path'; import type { BrowserContext, Page } from 'playwright-core'; /** * Return the path to the persistent CloakBrowser profile directory. * * Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to * `~/.cloakbrowser-profile/` and creates it if it does not exist. */ export function getProfilePath(): string { const customPath = process.env.CLOAKBROWSER_PROFILE_PATH; if (customPath) return customPath; const profileDir = join(homedir(), '.cloakbrowser-profile'); if (!existsSync(profileDir)) { mkdirSync(profileDir, { recursive: true }); } return profileDir; } /** * Launch a CloakBrowser persistent context with the shared profile. * * Headless mode is resolved in order: * 1. `options.headless` (explicit caller preference) * 2. `CLOAKBROWSER_HEADLESS` env var * 3. `true` (safe default) */ export async function launchBrowser(options: { headless?: boolean; }): Promise { const profilePath = getProfilePath(); const envHeadless = process.env.CLOAKBROWSER_HEADLESS; const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true); console.log(`Using profile: ${profilePath}`); console.log(`Headless mode: ${headless}`); const context = await launchPersistentContext({ userDataDir: profilePath, headless, humanize: true, }); return context; } /** * Return a ready `{ page, browser }` pair using the shared persistent profile. * * Re-uses the first existing page or opens a new one if the context is empty. */ export async function getPage(options?: { headless?: boolean; }): Promise<{ page: Page; browser: BrowserContext }> { const browser = await launchBrowser({ headless: options?.headless }); const page = browser.pages()[0] || (await browser.newPage()); return { page, browser }; }