aboutsummaryrefslogtreecommitdiff
path: root/src/cdp/endpoint.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/cdp/endpoint.ts')
-rw-r--r--src/cdp/endpoint.ts84
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}`;
+ }
+};