diff options
| author | Igor Soarez <igor@soarez.org> | 2026-07-28 22:08:44 +0100 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-10-14 12:17:59 +0100 |
| commit | 95ff5e8eaefb35d79109f7ddfa1de5bf45a8daff (patch) | |
| tree | 397c6cde867cb5028fcc5d00c1eb84d8a949da50 /src/cdp/discovery.ts | |
| parent | bfb533fc9a829819b9dd425607101d40c676d45c (diff) | |
feat(cdp): default to the shared browser, and say so in /browser-status
Two follow-ups to BU_CDP_HTTP.
Default the endpoint. The remote host is now a built-in default
(DEFAULT_REMOTE_CDP), so nothing has to export BU_CDP_HTTP just to get the
usual setup. The variable becomes an override rather than a requirement.
Precedence is deliberately asymmetric, in src/cdp/endpoint.ts:
BU_CDP_HTTP=host:port that endpoint; unreachable is a hard error
BU_CDP_HTTP=local|off upstream behaviour, this machine's own browser
unset the default, falling back to local when unreachable
An explicitly configured endpoint is an instruction, so failing to reach it must
not quietly automate a different browser - that would drive the operator's own
signed-in Chrome instead of the shared one. The built-in default is only a
preference, so a laptop away from the network still gets the local browser the
extension was originally written for.
Resolution is pure, with 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.
/browser-status no longer reports a bare "Disconnected". The connection is lazy
- nothing dials the browser until the first browser_* tool runs - so a fresh
session always showed a red light that reads as a fault. It now distinguishes
"not connected yet, connects on first use" from "target unreachable", and names
the target with a reachability check, so the common confusion answers itself.
26 tests for the resolution table, covering the aliases, malformed values that
must be rejected rather than coerced, and that the default constant is itself
well-formed - if it ever stopped parsing, every machine would silently fall
back to local.
Diffstat (limited to 'src/cdp/discovery.ts')
| -rw-r--r-- | src/cdp/discovery.ts | 49 |
1 files changed, 27 insertions, 22 deletions
diff --git a/src/cdp/discovery.ts b/src/cdp/discovery.ts index 41bcfe5..53d4ad2 100644 --- a/src/cdp/discovery.ts +++ b/src/cdp/discovery.ts @@ -6,6 +6,7 @@ import { type Result, err, ok } from "../util/result"; import { userDataDirCandidates } from "../profile/paths"; import { type CdpError, cdpError } from "./errors"; import { asString, errnoCode, isRecord } from "../util/guards"; +import { describeCdpTarget, resolveCdpTarget } from "./endpoint"; const PORT_PROBE_DEADLINE_MS = 30_000; const PORT_PROBE_INTERVAL_MS = 1_000; @@ -63,7 +64,7 @@ const waitForPort = async (port: number): Promise<Result<void, CdpError>> => { const isPortLive = async (port: number): Promise<boolean> => (await probePort(port)).success; // /json/version is authoritative: a stale DevToolsActivePort file may name a browser that has since exited while another now owns the port. -const queryLiveWsUrl = (port: number, timeoutMs = 1_500, host = "127.0.0.1"): Promise<string | null> => +export const queryLiveWsUrl = (port: number, timeoutMs = 1_500, host = "127.0.0.1"): Promise<string | null> => new Promise<string | null>((resolve) => { const req = httpRequest( { host, port, path: "/json/version", method: "GET", timeout: timeoutMs }, @@ -114,33 +115,37 @@ export const discoverEndpoint = async (): Promise<Result<CdpEndpoint, CdpError>> const override = endpointFromEnv(); if (override) return ok(override); - // LOCAL PATCH: direct remote browser support. + // FORK: drive a browser on another host. See src/cdp/endpoint.ts for the + // precedence rules and FORK.md for why this exists at all. // - // BU_CDP_HTTP="host:port" points at a CDP *HTTP* endpoint on another machine. - // We ask it for its canonical webSocketDebuggerUrl on every discovery call, - // so the browser UUID is re-read after the remote Chrome restarts. That is - // the difference from the stock BU_CDP_WS (checked just above, so an explicit - // WebSocket URL still wins), which pins a UUID that Chrome re-mints on every - // launch. + // The remote is asked for its canonical webSocketDebuggerUrl on every + // discovery call, so the browser UUID is re-read after the remote browser + // restarts. That is the difference from upstream's BU_CDP_WS (checked just + // above, so an explicit WebSocket URL still wins), which pins a UUID that + // Chrome re-mints on every launch. // - // Returns no userDataDir: the profile registry lives on the remote host, so - // profile enumeration correctly degrades to the pre-profile behaviour. - const remote = process.env["BU_CDP_HTTP"]?.trim(); - if (remote) { - const sep = remote.lastIndexOf(":"); - const host = sep === -1 ? remote : remote.slice(0, sep); - const port = sep === -1 ? 9222 : Number(remote.slice(sep + 1)); - if (!host || !Number.isInteger(port) || port <= 0 || port >= 65536) { - return err(cdpError("discovery_failed", `BU_CDP_HTTP is not a valid host:port — got "${remote}"`)); - } - const ws = await queryLiveWsUrl(port, 5_000, host); - if (!ws) { + // No userDataDir is returned: the profile registry lives on the remote host, + // so profile enumeration degrades to the pre-profile behaviour rather than + // reading the wrong machine's Local State. + const target = resolveCdpTarget(); + if (target.kind === "invalid") { + return err(cdpError("discovery_failed", describeCdpTarget(target))); + } + if (target.kind === "remote") { + const ws = await queryLiveWsUrl(target.port, 5_000, target.host); + if (ws) return ok({ wsUrl: ws }); + + // An explicitly configured endpoint is an instruction: fail rather than + // quietly automate a different browser than the one that was asked for. + if (target.source === "env") { return err(cdpError( "discovery_failed", - `No CDP endpoint answered at http://${host}:${port}/json/version (BU_CDP_HTTP). Is the remote browser running and reachable?`, + `No CDP endpoint answered at http://${target.host}:${target.port}/json/version (BU_CDP_HTTP). ` + + `Is the remote browser running and reachable? Set BU_CDP_HTTP=local to use this machine's browser instead.`, )); } - return ok({ wsUrl: ws }); + // The built-in default is only a preference — off the wire network, fall + // through to the local browser the extension was originally written for. } const dirs = userDataDirCandidates(); |
