diff options
Diffstat (limited to 'src/cdp/endpoint.ts')
| -rw-r--r-- | src/cdp/endpoint.ts | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/src/cdp/endpoint.ts b/src/cdp/endpoint.ts new file mode 100644 index 0000000..1be5322 --- /dev/null +++ b/src/cdp/endpoint.ts @@ -0,0 +1,84 @@ +/** + * Which browser this harness should drive. + * + * Upstream only ever drives a browser on localhost. This fork adds a remote + * target so pi can drive a shared Chrome on another host (see FORK.md). + * + * Resolution is pure — no I/O — so the status command can report the configured + * intent without touching the network, and the daemon can resolve the same way + * in its own process. + */ + +/** Default remote CDP endpoint: castle's Chrome, exposed on the wire network. */ +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"]); + +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 kind: "local"; readonly source: "env" } + | { readonly kind: "invalid"; readonly raw: string; readonly reason: string }; + +/** + * 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 + * + * 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. + */ +export const resolveCdpTarget = (env: NodeJS.ProcessEnv = process.env): CdpTarget => { + const raw = env["BU_CDP_HTTP"]?.trim(); + + if (raw === undefined || raw === "") { + const parsed = parseHostPort(DEFAULT_REMOTE_CDP); + // Unreachable in practice: the constant above is validated by its own test. + if (!parsed) return { kind: "local", source: "env" }; + return { kind: "remote", host: parsed.host, port: parsed.port, source: "default" }; + } + + if (LOCAL_ALIASES.has(raw.toLowerCase())) return { kind: "local", source: "env" }; + + const parsed = parseHostPort(raw); + if (!parsed) { + return { + kind: "invalid", + raw, + reason: `expected "host:port" or one of ${[...LOCAL_ALIASES].join(", ")}`, + }; + } + return { kind: "remote", host: parsed.host, port: parsed.port, source: "env" }; +}; + +const parseHostPort = (value: string): { host: string; port: number } | null => { + const sep = value.lastIndexOf(":"); + if (sep === -1) return null; + const host = value.slice(0, sep).trim(); + const port = Number(value.slice(sep + 1).trim()); + if (!host) return null; + if (!Number.isInteger(port) || port <= 0 || port >= 65536) return null; + return { host, port }; +}; + +/** 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)"); + case "local": + return "this machine's own browser (BU_CDP_HTTP=local)"; + case "invalid": + return `invalid BU_CDP_HTTP "${target.raw}" — ${target.reason}`; + } +}; |
