diff options
Diffstat (limited to 'src/cdp/endpoint.ts')
| -rw-r--r-- | src/cdp/endpoint.ts | 76 |
1 files changed, 56 insertions, 20 deletions
diff --git a/src/cdp/endpoint.ts b/src/cdp/endpoint.ts index 1be5322..96cf2ea 100644 --- a/src/cdp/endpoint.ts +++ b/src/cdp/endpoint.ts @@ -15,31 +15,53 @@ export const DEFAULT_REMOTE_CDP = "10.88.0.25:9223"; /** Values of BU_CDP_HTTP that mean "ignore the default, use this machine". */ const LOCAL_ALIASES = new Set(["local", "localhost", "off", "none", "0", "no"]); +/** + * Where a target came from. "env" is BU_CDP_HTTP, "stored" is /browser-target, + * "default" is this fork's built-in. Only "default" is allowed to fall back. + */ +export type CdpTargetSource = "env" | "stored" | "default"; + export type CdpTarget = | { readonly kind: "remote"; readonly host: string; readonly port: number; - /** "env" was asked for explicitly; "default" is this fork's built-in. */ - readonly source: "env" | "default"; + readonly source: CdpTargetSource; } - | { readonly kind: "local"; readonly source: "env" } - | { readonly kind: "invalid"; readonly raw: string; readonly reason: string }; + | { readonly kind: "local"; readonly source: Exclude<CdpTargetSource, "default"> } + | { readonly kind: "invalid"; readonly raw: string; readonly reason: string; readonly source: CdpTargetSource }; /** - * Precedence: - * BU_CDP_HTTP="host:port" -> that remote, and a failure to reach it is fatal - * BU_CDP_HTTP=local|off|… -> upstream behaviour, this machine's own browser - * unset -> DEFAULT_REMOTE_CDP, falling back to local when - * it isn't reachable + * Precedence: BU_CDP_HTTP, then the /browser-target choice, then the built-in + * default. + * + * "host:port" -> that remote, and a failure to reach it is fatal + * local|off|… -> upstream behaviour, this machine's own browser + * neither set -> DEFAULT_REMOTE_CDP, falling back to local when unreachable + * + * The environment wins over the stored choice so that a one-off + * `BU_CDP_HTTP=… pi` is never silently overridden by a setting made days ago. + * + * The fallback asymmetry is deliberate. An explicit endpoint — from either + * source — is an instruction, so silently driving a different browser instead + * would be wrong: it would automate the operator's own signed-in Chrome rather + * than the shared one. The built-in default is only a preference, so a laptop + * away from the wire network still gets the local browser the extension was + * originally written for. * - * The asymmetry is deliberate. An explicit endpoint is an instruction, so - * silently driving a different browser instead would be wrong. The built-in - * default is only a preference, so a laptop away from the wire network still - * gets the local browser the extension was originally written for. + * `stored` is passed in rather than read here so this stays pure and + * synchronous: /browser-status can describe the target without I/O, and the + * daemon can resolve identically in its own process. */ -export const resolveCdpTarget = (env: NodeJS.ProcessEnv = process.env): CdpTarget => { - const raw = env["BU_CDP_HTTP"]?.trim(); +export const resolveCdpTarget = ( + env: NodeJS.ProcessEnv = process.env, + stored: string | null = null, +): CdpTarget => { + const fromEnv = env["BU_CDP_HTTP"]?.trim(); + const fromStore = stored?.trim(); + + const raw = fromEnv !== undefined && fromEnv !== "" ? fromEnv : fromStore; + const source: CdpTargetSource = fromEnv !== undefined && fromEnv !== "" ? "env" : "stored"; if (raw === undefined || raw === "") { const parsed = parseHostPort(DEFAULT_REMOTE_CDP); @@ -48,17 +70,28 @@ export const resolveCdpTarget = (env: NodeJS.ProcessEnv = process.env): CdpTarge return { kind: "remote", host: parsed.host, port: parsed.port, source: "default" }; } - if (LOCAL_ALIASES.has(raw.toLowerCase())) return { kind: "local", source: "env" }; + if (LOCAL_ALIASES.has(raw.toLowerCase())) return { kind: "local", source }; const parsed = parseHostPort(raw); if (!parsed) { return { kind: "invalid", raw, + source, reason: `expected "host:port" or one of ${[...LOCAL_ALIASES].join(", ")}`, }; } - return { kind: "remote", host: parsed.host, port: parsed.port, source: "env" }; + return { kind: "remote", host: parsed.host, port: parsed.port, source }; +}; + +/** The vocabulary /browser-target accepts, for help text and validation. */ +export const localAliases = (): ReadonlyArray<string> => [...LOCAL_ALIASES]; + +/** True when `value` is something resolveCdpTarget can make sense of. */ +export const isValidTargetValue = (value: string): boolean => { + const v = value.trim(); + if (v === "") return false; + return LOCAL_ALIASES.has(v.toLowerCase()) || parseHostPort(v) !== null; }; const parseHostPort = (value: string): { host: string; port: number } | null => { @@ -71,14 +104,17 @@ const parseHostPort = (value: string): { host: string; port: number } | null => return { host, port }; }; +const sourceLabel = (source: CdpTargetSource): string => + source === "env" ? "BU_CDP_HTTP" : source === "stored" ? "/browser-target" : "built-in default"; + /** One-line description for /browser-status and error messages. */ export const describeCdpTarget = (target: CdpTarget): string => { switch (target.kind) { case "remote": - return `${target.host}:${target.port}` + (target.source === "default" ? " (built-in default)" : " (BU_CDP_HTTP)"); + return `${target.host}:${target.port} (${sourceLabel(target.source)})`; case "local": - return "this machine's own browser (BU_CDP_HTTP=local)"; + return `this machine's own browser (${sourceLabel(target.source)})`; case "invalid": - return `invalid BU_CDP_HTTP "${target.raw}" — ${target.reason}`; + return `invalid target "${target.raw}" from ${sourceLabel(target.source)} — ${target.reason}`; } }; |
