import type { BrowserConfig } from "../config/config.js"; import { DEFAULT_CLAWD_BROWSER_COLOR, DEFAULT_CLAWD_BROWSER_CONTROL_URL, DEFAULT_CLAWD_BROWSER_ENABLED, } from "./constants.js"; export type ResolvedBrowserConfig = { enabled: boolean; controlUrl: string; controlHost: string; controlPort: number; cdpPort: number; color: string; headless: boolean; attachOnly: boolean; }; function isLoopbackHost(host: string) { const h = host.trim().toLowerCase(); return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1"; } function normalizeHexColor(raw: string | undefined) { const value = (raw ?? "").trim(); if (!value) return DEFAULT_CLAWD_BROWSER_COLOR; const normalized = value.startsWith("#") ? value : `#${value}`; if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) return DEFAULT_CLAWD_BROWSER_COLOR; return normalized.toUpperCase(); } export function resolveBrowserConfig( cfg: BrowserConfig | undefined, ): ResolvedBrowserConfig { const enabled = cfg?.enabled ?? DEFAULT_CLAWD_BROWSER_ENABLED; const controlUrl = ( cfg?.controlUrl ?? DEFAULT_CLAWD_BROWSER_CONTROL_URL ).trim(); const parsed = new URL(controlUrl); if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { throw new Error( `browser.controlUrl must be http(s), got: ${parsed.protocol.replace(":", "")}`, ); } const port = parsed.port && Number.parseInt(parsed.port, 10) > 0 ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80; if (Number.isNaN(port) || port <= 0 || port > 65535) { throw new Error(`browser.controlUrl has invalid port: ${parsed.port}`); } const cdpPort = port + 1; if (cdpPort > 65535) { throw new Error( `browser.controlUrl port (${port}) is too high; cannot derive CDP port (${cdpPort})`, ); } if (port === cdpPort) { throw new Error( `browser.controlUrl port (${port}) must not equal CDP port (${cdpPort})`, ); } const headless = cfg?.headless === true; const attachOnly = cfg?.attachOnly === true; return { enabled, controlUrl: parsed.toString().replace(/\/$/, ""), controlHost: parsed.hostname, controlPort: port, cdpPort, color: normalizeHexColor(cfg?.color), headless, attachOnly, }; } export function shouldStartLocalBrowserServer(resolved: ResolvedBrowserConfig) { return isLoopbackHost(resolved.controlHost); }