1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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}`;
}
};
|