@monoes/monobrowse 1.0.25 → 1.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/__tests__/browser-launch.test.d.ts +3 -2
- package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
- package/dist/src/__tests__/browser-launch.test.js +71 -6
- package/dist/src/__tests__/browser-launch.test.js.map +1 -1
- package/dist/src/__tests__/launch-close-settle.test.d.ts +6 -0
- package/dist/src/__tests__/launch-close-settle.test.d.ts.map +1 -1
- package/dist/src/__tests__/launch-close-settle.test.js +29 -8
- package/dist/src/__tests__/launch-close-settle.test.js.map +1 -1
- package/dist/src/browser/actions-eval.d.ts +8 -0
- package/dist/src/browser/actions-eval.d.ts.map +1 -0
- package/dist/src/browser/actions-eval.js +72 -0
- package/dist/src/browser/actions-eval.js.map +1 -0
- package/dist/src/browser/actions-form.d.ts +10 -0
- package/dist/src/browser/actions-form.d.ts.map +1 -0
- package/dist/src/browser/actions-form.js +133 -0
- package/dist/src/browser/actions-form.js.map +1 -0
- package/dist/src/browser/actions-keyboard.d.ts +7 -0
- package/dist/src/browser/actions-keyboard.d.ts.map +1 -0
- package/dist/src/browser/actions-keyboard.js +131 -0
- package/dist/src/browser/actions-keyboard.js.map +1 -0
- package/dist/src/browser/actions-mouse.d.ts +11 -0
- package/dist/src/browser/actions-mouse.d.ts.map +1 -0
- package/dist/src/browser/actions-mouse.js +105 -0
- package/dist/src/browser/actions-mouse.js.map +1 -0
- package/dist/src/browser/actions.d.ts +12 -28
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +12 -434
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/browser-discovery.d.ts +26 -0
- package/dist/src/browser/browser-discovery.d.ts.map +1 -0
- package/dist/src/browser/browser-discovery.js +113 -0
- package/dist/src/browser/browser-discovery.js.map +1 -0
- package/dist/src/browser/browser-launch.d.ts +3 -0
- package/dist/src/browser/browser-launch.d.ts.map +1 -0
- package/dist/src/browser/browser-launch.js +324 -0
- package/dist/src/browser/browser-launch.js.map +1 -0
- package/dist/src/browser/browser-lifecycle.d.ts +52 -0
- package/dist/src/browser/browser-lifecycle.d.ts.map +1 -0
- package/dist/src/browser/browser-lifecycle.js +233 -0
- package/dist/src/browser/browser-lifecycle.js.map +1 -0
- package/dist/src/browser/browser-page.d.ts +13 -0
- package/dist/src/browser/browser-page.d.ts.map +1 -0
- package/dist/src/browser/browser-page.js +179 -0
- package/dist/src/browser/browser-page.js.map +1 -0
- package/dist/src/browser/browser-state.d.ts +7 -0
- package/dist/src/browser/browser-state.d.ts.map +1 -0
- package/dist/src/browser/browser-state.js +19 -0
- package/dist/src/browser/browser-state.js.map +1 -0
- package/dist/src/browser/browser.d.ts +5 -68
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +9 -794
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/browser-launch.test.ts +78 -6
- package/src/__tests__/launch-close-settle.test.ts +32 -10
- package/src/browser/actions-eval.ts +118 -0
- package/src/browser/actions-form.ts +218 -0
- package/src/browser/actions-keyboard.ts +171 -0
- package/src/browser/actions-mouse.ts +212 -0
- package/src/browser/actions.ts +37 -712
- package/src/browser/browser-discovery.ts +116 -0
- package/src/browser/browser-launch.ts +358 -0
- package/src/browser/browser-lifecycle.ts +238 -0
- package/src/browser/browser-page.ts +213 -0
- package/src/browser/browser-state.ts +21 -0
- package/src/browser/browser.ts +17 -859
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// Split out of browser.ts (file-size sweep). Pure move: no behaviour change.
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { rmSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { browserIdOf, chromeIdentity, fetchBrowserWebSocketUrl, findChrome, isChromeIdentity, isPortOpen, isTcpPortOpen, readDevToolsActivePort, } from './browser-discovery.js';
|
|
8
|
+
import { reapIdleLaunchedBrowser } from './browser-lifecycle.js';
|
|
9
|
+
import { launchedPids, launchedUserDataDirs } from './browser-state.js';
|
|
10
|
+
const DEFAULT_PORT = 9222;
|
|
11
|
+
const LAUNCH_TIMEOUT = 10_000;
|
|
12
|
+
const POLL_INTERVAL = 200;
|
|
13
|
+
/** Ports scanned when the requested one is occupied by a non-Chrome process
|
|
14
|
+
* (e.g. another local tool that happens to reuse Chrome's conventional CDP
|
|
15
|
+
* default, 9222 — mirrors the auto-increment convention this monorepo's own
|
|
16
|
+
* dashboard server uses in bindServer/server.mjs). Only occupied-by-a-
|
|
17
|
+
* DIFFERENT-process is worked around; an already-attachable Chrome on the
|
|
18
|
+
* requested port is still returned as-is (existing "attach, don't relaunch"
|
|
19
|
+
* behavior). */
|
|
20
|
+
const LAUNCH_PORT_SCAN_TRIES = 10;
|
|
21
|
+
export async function launchBrowser(config = {}) {
|
|
22
|
+
const rawPort = config.port ?? DEFAULT_PORT;
|
|
23
|
+
// Port 0 means "let Chrome bind a free port and tell us which" — see
|
|
24
|
+
// launchOnFreePort. Otherwise validate port is in a safe range for
|
|
25
|
+
// localhost CDP debugging.
|
|
26
|
+
if (rawPort === 0) {
|
|
27
|
+
if (!config.userDataDir) {
|
|
28
|
+
throw new Error('port 0 requires a dedicated userDataDir (Chrome reports the port it bound inside it).');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (!Number.isInteger(rawPort) || rawPort < 1024 || rawPort > 65535) {
|
|
32
|
+
throw new Error(`Invalid port: ${rawPort}. Must be 0 or an integer between 1024 and 65535.`);
|
|
33
|
+
}
|
|
34
|
+
// Every launch/attach is this tool's only chance to notice that a previous
|
|
35
|
+
// session abandoned a browser on a CDP port and never gave it back — there
|
|
36
|
+
// is no daemon to do it on a timer. Bounded and non-throwing; a stale
|
|
37
|
+
// instance on the port we are about to use is freed before we probe it.
|
|
38
|
+
await reapIdleLaunchedBrowser();
|
|
39
|
+
if (rawPort === 0)
|
|
40
|
+
return launchOnFreePort(config, 0);
|
|
41
|
+
// strictPort: fail fast on the exact requested port, matching the old
|
|
42
|
+
// behavior (Vite has the same escape hatch for the same reason) — for
|
|
43
|
+
// callers that treat the error as a signal ("this port is taken by
|
|
44
|
+
// something else, bail") rather than consuming the returned port.
|
|
45
|
+
if (config.strictPort) {
|
|
46
|
+
if (await isTcpPortOpen(rawPort)) {
|
|
47
|
+
if (await isChromeIdentity(rawPort))
|
|
48
|
+
return rawPort;
|
|
49
|
+
throw new Error(`Port ${rawPort} is occupied by a process that does not identify as Chrome/Chromium. ` +
|
|
50
|
+
`Refusing to attach — pass a different port or free port ${rawPort}.`);
|
|
51
|
+
}
|
|
52
|
+
return launchOnFreePort(config, rawPort);
|
|
53
|
+
}
|
|
54
|
+
const candidates = [];
|
|
55
|
+
for (let i = 0; i < LAUNCH_PORT_SCAN_TRIES && rawPort + i <= 65535; i++)
|
|
56
|
+
candidates.push(rawPort + i);
|
|
57
|
+
for (const candidate of candidates) {
|
|
58
|
+
// TCP-level check for "is anything at all listening" — isPortOpen()
|
|
59
|
+
// does a full CDP /json fetch, which returns false BOTH for a genuinely
|
|
60
|
+
// free port and for one occupied by a non-CDP process (that ambiguity is
|
|
61
|
+
// exactly what the post-spawn isTcpPortOpen fallback below exists to
|
|
62
|
+
// resolve, the hard way, after a 10s launch timeout). Checking the raw
|
|
63
|
+
// socket first tells free and occupied apart up front, so the scan can
|
|
64
|
+
// skip an occupied candidate instead of trying to spawn Chrome on top of
|
|
65
|
+
// it and only discovering the conflict after a timeout.
|
|
66
|
+
if (await isTcpPortOpen(candidate)) {
|
|
67
|
+
// Attach-if-already-Chrome only applies to the EXACT requested port —
|
|
68
|
+
// the original, deliberate, single-port risk ("don't silently take
|
|
69
|
+
// over an unrelated real browser that happens to be on this port").
|
|
70
|
+
// Scanning past an occupied default must not let that same shortcut
|
|
71
|
+
// attach to a DIFFERENT Chrome instance the caller never named;
|
|
72
|
+
// forward candidates are launch-only (skip if anything is there,
|
|
73
|
+
// Chrome or not).
|
|
74
|
+
if (candidate === rawPort && (await isChromeIdentity(candidate)))
|
|
75
|
+
return candidate;
|
|
76
|
+
// Occupied (by anything — not just non-Chrome, per the note above) —
|
|
77
|
+
// try the next candidate instead of failing outright, same as a
|
|
78
|
+
// normal EADDRINUSE retry would.
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return await launchOnFreePort(config, candidate);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const isLastCandidate = candidate === candidates[candidates.length - 1];
|
|
86
|
+
// isTcpPortOpen() above is a connect() probe, not an atomic claim: two
|
|
87
|
+
// concurrent launchBrowser() calls with no explicit --port can both
|
|
88
|
+
// observe the same candidate as free and both spawn Chrome on it
|
|
89
|
+
// before either binds. One wins; the other's Chrome exits before its
|
|
90
|
+
// CDP endpoint opens ("Chrome exited before the CDP endpoint opened
|
|
91
|
+
// ... code=21"). Something is listening on the candidate NOW that
|
|
92
|
+
// was not a moment ago — a losing race, not a broken Chrome install —
|
|
93
|
+
// so try the next candidate exactly like an already-occupied one,
|
|
94
|
+
// instead of failing the whole launch outright. A candidate that
|
|
95
|
+
// fails with nothing now listening (a real launch failure — bad
|
|
96
|
+
// executable, sandbox refusal, etc.) is not a race: retrying the
|
|
97
|
+
// next candidate would only fail the same way, so surface it as-is.
|
|
98
|
+
if (!isLastCandidate && (await isTcpPortOpen(candidate)))
|
|
99
|
+
continue;
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`Ports ${candidates[0]}-${candidates[candidates.length - 1]} are all occupied and port ${candidates[0]} ` +
|
|
104
|
+
`isn't a Chrome/Chromium instance to attach to. Pass a different --port.`);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Spawn Chrome and wait for its CDP endpoint.
|
|
108
|
+
*
|
|
109
|
+
* With a fixed `port`, "our Chrome is up" is inferred from *some* Chrome
|
|
110
|
+
* answering on 127.0.0.1:<port> — which is not necessarily ours. When the port
|
|
111
|
+
* is already bound on 127.0.0.1 (a concurrent launch picked the same "free"
|
|
112
|
+
* port between its probe and Chrome's bind), Chrome does not fail: it logs
|
|
113
|
+
* `bind() failed: Address already in use` and listens on [::1]:<port>
|
|
114
|
+
* instead. The poll then accepts the OTHER launcher's browser, both callers
|
|
115
|
+
* drive one Chrome, and whichever closes first kills the other's connections
|
|
116
|
+
* ("CDP connection closed") while this launch's own Chrome is orphaned.
|
|
117
|
+
*
|
|
118
|
+
* `port: 0` removes the race instead of narrowing it: Chrome asks the kernel
|
|
119
|
+
* for a free port at bind time — atomic, nothing to collide on — and writes
|
|
120
|
+
* the port it got to DevToolsActivePort in its own (caller-dedicated) profile
|
|
121
|
+
* directory, which is proof the endpoint is the process we spawned.
|
|
122
|
+
*
|
|
123
|
+
* A fixed port needs its own proof, since Chrome writes DevToolsActivePort
|
|
124
|
+
* only for port 0. Once listening, Chrome prints `DevTools listening on
|
|
125
|
+
* ws://…/devtools/browser/<id>` to stderr, and `<id>` is unique to that
|
|
126
|
+
* browser process, the same one `/json/version` reports. An endpoint is
|
|
127
|
+
* adopted only when it reports the id our own child printed. Without that
|
|
128
|
+
* check, a launch whose Chrome was still booting found a concurrent
|
|
129
|
+
* launch's Chrome on the shared port and returned it, recording its own
|
|
130
|
+
* doomed pid there. It also covers Chrome's [::1] fallback described above.
|
|
131
|
+
*/
|
|
132
|
+
async function launchOnFreePort(config, port) {
|
|
133
|
+
const chromePath = findChrome(config.executablePath);
|
|
134
|
+
// Unique per launch, not just per port and pid: two concurrent launches
|
|
135
|
+
// that both probe the same candidate port as free (the race this function
|
|
136
|
+
// exists to survive — see the retry-on-collision caller above) would
|
|
137
|
+
// otherwise share one profile directory (lock files, preferences, the
|
|
138
|
+
// DevToolsActivePort file itself) — including two launches from the same
|
|
139
|
+
// process, where a pid suffix alone is identical.
|
|
140
|
+
const userDataDir = config.userDataDir ??
|
|
141
|
+
join(tmpdir(), `monomind-browser-${port}-${process.pid}-${randomUUID().slice(0, 8)}`);
|
|
142
|
+
const activePortFile = join(userDataDir, 'DevToolsActivePort');
|
|
143
|
+
// A reused profile dir may hold a previous run's file naming a port some
|
|
144
|
+
// other process now owns.
|
|
145
|
+
if (port === 0)
|
|
146
|
+
rmSync(activePortFile, { force: true });
|
|
147
|
+
const defaultArgs = [
|
|
148
|
+
`--remote-debugging-port=${port}`,
|
|
149
|
+
`--user-data-dir=${userDataDir}`,
|
|
150
|
+
'--no-first-run',
|
|
151
|
+
'--no-default-browser-check',
|
|
152
|
+
'--disable-background-networking',
|
|
153
|
+
'--disable-client-side-phishing-detection',
|
|
154
|
+
'--disable-default-apps',
|
|
155
|
+
'--disable-extensions',
|
|
156
|
+
'--disable-hang-monitor',
|
|
157
|
+
'--disable-popup-blocking',
|
|
158
|
+
'--disable-prompt-on-repost',
|
|
159
|
+
'--disable-sync',
|
|
160
|
+
'--disable-translate',
|
|
161
|
+
'--metrics-recording-only',
|
|
162
|
+
'--safebrowsing-disable-auto-update',
|
|
163
|
+
'--password-store=basic',
|
|
164
|
+
'--use-mock-keychain',
|
|
165
|
+
];
|
|
166
|
+
if (config.headless !== false) {
|
|
167
|
+
defaultArgs.push('--headless=new');
|
|
168
|
+
}
|
|
169
|
+
// Chrome's setuid sandbox cannot initialise on most CI runners and
|
|
170
|
+
// containers, so the process dies during startup and the CDP endpoint never
|
|
171
|
+
// opens. The launch below then burns its whole timeout before reporting a
|
|
172
|
+
// generic failure, which reads as a hang rather than "Chrome could not
|
|
173
|
+
// start". Disabling the sandbox is the standard remedy and is scoped to CI
|
|
174
|
+
// so a real user's browser keeps it. --disable-dev-shm-usage goes with it:
|
|
175
|
+
// a container's default /dev/shm is 64MB, which Chrome exhausts and then
|
|
176
|
+
// crashes the same way.
|
|
177
|
+
if (process.env.CI) {
|
|
178
|
+
defaultArgs.push('--no-sandbox', '--disable-dev-shm-usage');
|
|
179
|
+
}
|
|
180
|
+
// Cap caller-supplied args to prevent memory exhaustion via huge argument arrays
|
|
181
|
+
const callerArgs = (config.args ?? []).slice(0, 50);
|
|
182
|
+
const args = [...defaultArgs, ...callerArgs];
|
|
183
|
+
const child = spawn(chromePath, args, {
|
|
184
|
+
detached: true,
|
|
185
|
+
// stderr carries the fixed-port ownership proof (see above). The pipe is
|
|
186
|
+
// closed as soon as this launch settles, so a Chrome that outlives us
|
|
187
|
+
// never blocks writing to it.
|
|
188
|
+
stdio: port === 0 ? 'ignore' : ['ignore', 'ignore', 'pipe'],
|
|
189
|
+
});
|
|
190
|
+
let announcedBrowserId = null;
|
|
191
|
+
if (child.stderr) {
|
|
192
|
+
let stderrTail = '';
|
|
193
|
+
child.stderr.setEncoding('utf8');
|
|
194
|
+
child.stderr.on('data', (chunk) => {
|
|
195
|
+
if (announcedBrowserId)
|
|
196
|
+
return;
|
|
197
|
+
stderrTail = (stderrTail + chunk).slice(-4096);
|
|
198
|
+
announcedBrowserId = browserIdOf(/DevTools listening on (ws:\/\/\S+)\r?\n/.exec(stderrTail)?.[1]);
|
|
199
|
+
});
|
|
200
|
+
child.stderr.on('error', () => {
|
|
201
|
+
/* closed under us — nothing to read */
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Without this, a spawn failure (e.g. EACCES/ENOENT — chromePath exists per
|
|
205
|
+
// findChrome()'s existsSync check but isn't actually executable, or is
|
|
206
|
+
// removed between the check and exec) fires Node's 'error' event on the
|
|
207
|
+
// next tick. With no listener, Node throws it as an uncaught exception and
|
|
208
|
+
// the whole process goes down — which strands this function's promise
|
|
209
|
+
// forever pending rather than rejecting it (issue #314: node:test reports
|
|
210
|
+
// that as "cancelledByParent" / "still pending" because nothing here ever
|
|
211
|
+
// got the chance to settle it). Recording the failure and having the poll
|
|
212
|
+
// loop below notice it turns that crash into a normal rejection. 'exit'
|
|
213
|
+
// covers the twin case: Chrome execs fine but the process itself dies
|
|
214
|
+
// immediately (missing shared libraries, a container's sandbox refusing
|
|
215
|
+
// it, etc.) — that fires 'exit', not 'error', and without this the poll
|
|
216
|
+
// loop would just burn its whole timeout probing a port nothing will ever
|
|
217
|
+
// open on.
|
|
218
|
+
let earlyFailure = null;
|
|
219
|
+
child.on('error', (err) => {
|
|
220
|
+
earlyFailure ??= new Error(`Chrome failed to start on port ${port}: ${err.message}`);
|
|
221
|
+
});
|
|
222
|
+
child.on('exit', (code, signal) => {
|
|
223
|
+
earlyFailure ??= new Error(`Chrome exited before the CDP endpoint opened on port ${port} ` +
|
|
224
|
+
`(code=${code ?? 'null'}, signal=${signal ?? 'null'})`);
|
|
225
|
+
});
|
|
226
|
+
child.unref();
|
|
227
|
+
const track = (boundPort) => {
|
|
228
|
+
if (!child.pid)
|
|
229
|
+
return;
|
|
230
|
+
launchedPids.set(boundPort, child.pid);
|
|
231
|
+
launchedUserDataDirs.set(boundPort, userDataDir);
|
|
232
|
+
};
|
|
233
|
+
// Tracked only once Chrome is CONFIRMED up on `boundPort`, not right after
|
|
234
|
+
// spawn(). For port !== 0 this used to track(port) unconditionally the
|
|
235
|
+
// moment the child was spawned, before knowing whether it would actually
|
|
236
|
+
// win the port — when a racing launchBrowser() call retries a candidate
|
|
237
|
+
// that a concurrent process also spawned Chrome on (see the retry loop in
|
|
238
|
+
// launchBrowser), BOTH child processes called track() for the SAME
|
|
239
|
+
// candidate, and whichever call's spawn happened to run last clobbered the
|
|
240
|
+
// map entry with its own pid — including the LOSING, about-to-exit
|
|
241
|
+
// process's pid overwriting the real winner's, corrupting the very map
|
|
242
|
+
// closeBrowser()'s kill fallback depends on.
|
|
243
|
+
// A refused or timed-out launch is not tracked under any port, so no
|
|
244
|
+
// closeBrowser() could ever reach it — stop it rather than leave it
|
|
245
|
+
// running.
|
|
246
|
+
const killUntracked = () => {
|
|
247
|
+
try {
|
|
248
|
+
if (child.pid)
|
|
249
|
+
process.kill(child.pid, 'SIGKILL');
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
/* already gone */
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
// Our own Chrome, not just some Chrome, is serving `boundPort`. For port 0
|
|
256
|
+
// the port came from our own profile's DevToolsActivePort, so it is.
|
|
257
|
+
const isOwnEndpoint = async (boundPort) => {
|
|
258
|
+
if (port === 0)
|
|
259
|
+
return true;
|
|
260
|
+
if (!announcedBrowserId)
|
|
261
|
+
return false;
|
|
262
|
+
const wsUrl = await fetchBrowserWebSocketUrl(boundPort).catch(() => null);
|
|
263
|
+
return browserIdOf(wsUrl ?? undefined) === announcedBrowserId;
|
|
264
|
+
};
|
|
265
|
+
const launchTimeout = config.launchTimeoutMs ?? LAUNCH_TIMEOUT;
|
|
266
|
+
const deadline = Date.now() + launchTimeout;
|
|
267
|
+
try {
|
|
268
|
+
while (Date.now() < deadline) {
|
|
269
|
+
if (earlyFailure)
|
|
270
|
+
throw earlyFailure;
|
|
271
|
+
await sleep(POLL_INTERVAL);
|
|
272
|
+
if (earlyFailure)
|
|
273
|
+
throw earlyFailure;
|
|
274
|
+
const boundPort = port === 0 ? readDevToolsActivePort(activePortFile) : port;
|
|
275
|
+
if (boundPort !== null && (await isPortOpen(boundPort))) {
|
|
276
|
+
const identity = await chromeIdentity(boundPort);
|
|
277
|
+
if (identity === 'chrome') {
|
|
278
|
+
if (await isOwnEndpoint(boundPort)) {
|
|
279
|
+
track(boundPort);
|
|
280
|
+
return boundPort;
|
|
281
|
+
}
|
|
282
|
+
// Some Chrome answers on the port, but not the one we started: a
|
|
283
|
+
// concurrent launch's, or ours before its stderr line arrived. Our
|
|
284
|
+
// child exiting (it lost the bind) or the deadline decides.
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
// A Chrome that has only just opened its endpoint, on a loaded
|
|
288
|
+
// machine, can answer /json/list (no timeout) yet miss the identity
|
|
289
|
+
// check's 1 s timeout on /json/version. That says nothing about who is
|
|
290
|
+
// listening — keep polling rather than refuse our own Chrome.
|
|
291
|
+
if (identity === 'unknown')
|
|
292
|
+
continue;
|
|
293
|
+
killUntracked();
|
|
294
|
+
throw new Error(`Port ${boundPort} is occupied by a CDP-speaking process that does not identify as Chrome/Chromium. ` +
|
|
295
|
+
`Refusing to attach — pass a different port or free port ${boundPort}.`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (earlyFailure)
|
|
299
|
+
throw earlyFailure;
|
|
300
|
+
killUntracked();
|
|
301
|
+
if (port === 0) {
|
|
302
|
+
throw new Error(`Chrome did not report a CDP port in ${activePortFile} within ${launchTimeout}ms`);
|
|
303
|
+
}
|
|
304
|
+
// Timed out waiting for our Chrome to come up on the port. Distinguish
|
|
305
|
+
// "another Chrome holds it", "nothing is listening" (real launch failure)
|
|
306
|
+
// and "something non-CDP is squatting the port" (confusing generic
|
|
307
|
+
// timeout otherwise).
|
|
308
|
+
if (await isChromeIdentity(port)) {
|
|
309
|
+
throw new Error(`Port ${port} is held by a Chrome/Chromium this launch did not start (a concurrent launch took it).`);
|
|
310
|
+
}
|
|
311
|
+
if (await isTcpPortOpen(port)) {
|
|
312
|
+
throw new Error(`Port ${port} is occupied by a non-Chrome process (TCP connection succeeds but no CDP response within ${launchTimeout}ms). ` +
|
|
313
|
+
`Free the port or pass a different one.`);
|
|
314
|
+
}
|
|
315
|
+
throw new Error(`Chrome failed to start on port ${port} within ${launchTimeout}ms`);
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
child.stderr?.destroy();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function sleep(ms) {
|
|
322
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=browser-launch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-launch.js","sourceRoot":"","sources":["../../../src/browser/browser-launch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CdpClient } from './cdp.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cleanly terminate a Chrome/Chromium instance we launched on `port`.
|
|
4
|
+
* Sends the `Browser.close` CDP command (the correct graceful shutdown —
|
|
5
|
+
* closes all tabs and exits the process cleanly) over `client`'s connection.
|
|
6
|
+
* If that command fails, times out, or the connection is already gone,
|
|
7
|
+
* falls back to killing the tracked PID directly so a headed/interactive
|
|
8
|
+
* browser window (e.g. one spawned for a login/CAPTCHA flow) never lingers
|
|
9
|
+
* as a visible, authenticated, still-debuggable orphan process.
|
|
10
|
+
*
|
|
11
|
+
* On the graceful path this resolves only once the process is actually gone,
|
|
12
|
+
* or after force-killing it when it outlasts PROCESS_EXIT_TIMEOUT_MS. Chrome
|
|
13
|
+
* acknowledges `Browser.close` well before it exits, and this used to return
|
|
14
|
+
* on the ack with an unref'd 1s timer as the only force-kill — so it resolved
|
|
15
|
+
* while Chrome was still running and holding its port and its profile's
|
|
16
|
+
* singleton lock, and if the caller's process exited first the kill never
|
|
17
|
+
* happened at all. Callers reasonably read a resolved close() as "that
|
|
18
|
+
* browser is gone"; on the graceful path it now means that.
|
|
19
|
+
*/
|
|
20
|
+
export declare function closeBrowser(client: CdpClient, port: number): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Give back the CDP port of a browser we launched that nothing is using any
|
|
23
|
+
* more. A launched Chrome is deliberately detached and unref'd so it outlives
|
|
24
|
+
* the command that spawned it and later commands can reattach — but nothing
|
|
25
|
+
* reaped it afterwards, so an abandoned session kept listening on its port
|
|
26
|
+
* (9222 by convention, which other local tools want) indefinitely. A foreign
|
|
27
|
+
* CDP server *answers* there rather than refusing the connection, so those
|
|
28
|
+
* tools retry the wrong port and report misleading errors instead of moving on.
|
|
29
|
+
*
|
|
30
|
+
* Nothing long-lived exists to run this on a timer — every CLI command is its
|
|
31
|
+
* own short-lived process — so it runs at the next launch/attach instead
|
|
32
|
+
* (see launchBrowser). A browser is reaped only when ALL of these hold:
|
|
33
|
+
* - the persisted session says WE launched it — `connect` records
|
|
34
|
+
* launched:false for a browser that belongs to the user, never touched;
|
|
35
|
+
* - that session is older than IDLE_REAP_AFTER_MS;
|
|
36
|
+
* - the CDP endpoint still answers and reports ZERO targets. An open tab,
|
|
37
|
+
* or a client attached to one (Chrome drops webSocketDebuggerUrl from an
|
|
38
|
+
* attached target but still lists it), means the session is live and
|
|
39
|
+
* reattach-across-commands must keep working.
|
|
40
|
+
*
|
|
41
|
+
* Shutdown goes through closeBrowser() on the browser-level websocket — the
|
|
42
|
+
* only connection left once every target is gone — so a recycled PID can
|
|
43
|
+
* never be signaled by mistake: graceful `Browser.close` reaches whatever is
|
|
44
|
+
* actually listening on that port, at any age, and the persisted PID is only
|
|
45
|
+
* ever used for closeBrowser's own age-bounded force-kill.
|
|
46
|
+
*
|
|
47
|
+
* Never throws: a failed reap must not stop a launch. Returns the reaped
|
|
48
|
+
* port, or null when there was nothing to reap. Sessions are per-port
|
|
49
|
+
* (#318), so every recorded one is considered, not just the newest.
|
|
50
|
+
*/
|
|
51
|
+
export declare function reapIdleLaunchedBrowser(): Promise<number | null>;
|
|
52
|
+
//# sourceMappingURL=browser-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/browser/browser-lifecycle.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAgB,MAAM,UAAU,CAAC;AAsBnD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8EjF;AAgCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOtE"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Split out of browser.ts (file-size sweep). Pure move: no behaviour change.
|
|
2
|
+
import { fetchBrowserWebSocketUrl } from './browser-discovery.js';
|
|
3
|
+
import { launchedPids, launchedUserDataDirs } from './browser-state.js';
|
|
4
|
+
import { CdpClient, fetchTargets } from './cdp.js';
|
|
5
|
+
import { listSessionRecords, loadSessionRecord, removeSessionRecord, } from './ref-cache.js';
|
|
6
|
+
const BROWSER_CLOSE_TIMEOUT_MS = 3000;
|
|
7
|
+
// How long closeBrowser() waits for the process to actually disappear before
|
|
8
|
+
// forcing it, and again after forcing. Bounded so a wedged Chrome cannot hang
|
|
9
|
+
// a caller indefinitely.
|
|
10
|
+
const PROCESS_EXIT_TIMEOUT_MS = 5000;
|
|
11
|
+
const PROCESS_EXIT_POLL_MS = 50;
|
|
12
|
+
// A cross-process persisted PID older than this is not trusted for the
|
|
13
|
+
// SIGKILL fallback in closeBrowser() — see the call site for why.
|
|
14
|
+
const PERSISTED_PID_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
// A browser WE launched that has had no targets for longer than this is
|
|
16
|
+
// reaped at the next launch/attach — see reapIdleLaunchedBrowser().
|
|
17
|
+
const IDLE_REAP_AFTER_MS = 30 * 60 * 1000;
|
|
18
|
+
const REAP_CONNECT_TIMEOUT_MS = 3000;
|
|
19
|
+
/**
|
|
20
|
+
* Cleanly terminate a Chrome/Chromium instance we launched on `port`.
|
|
21
|
+
* Sends the `Browser.close` CDP command (the correct graceful shutdown —
|
|
22
|
+
* closes all tabs and exits the process cleanly) over `client`'s connection.
|
|
23
|
+
* If that command fails, times out, or the connection is already gone,
|
|
24
|
+
* falls back to killing the tracked PID directly so a headed/interactive
|
|
25
|
+
* browser window (e.g. one spawned for a login/CAPTCHA flow) never lingers
|
|
26
|
+
* as a visible, authenticated, still-debuggable orphan process.
|
|
27
|
+
*
|
|
28
|
+
* On the graceful path this resolves only once the process is actually gone,
|
|
29
|
+
* or after force-killing it when it outlasts PROCESS_EXIT_TIMEOUT_MS. Chrome
|
|
30
|
+
* acknowledges `Browser.close` well before it exits, and this used to return
|
|
31
|
+
* on the ack with an unref'd 1s timer as the only force-kill — so it resolved
|
|
32
|
+
* while Chrome was still running and holding its port and its profile's
|
|
33
|
+
* singleton lock, and if the caller's process exited first the kill never
|
|
34
|
+
* happened at all. Callers reasonably read a resolved close() as "that
|
|
35
|
+
* browser is gone"; on the graceful path it now means that.
|
|
36
|
+
*/
|
|
37
|
+
export async function closeBrowser(client, port) {
|
|
38
|
+
let gracefullyClosed = false;
|
|
39
|
+
let closeTimer;
|
|
40
|
+
try {
|
|
41
|
+
await Promise.race([
|
|
42
|
+
client.send('Browser.close', {}),
|
|
43
|
+
// Deliberately NOT unref'd, for the same reason waitForProcessExit()'s
|
|
44
|
+
// poll below is not. This timer is the only thing that settles the race
|
|
45
|
+
// when Chrome's socket dies without ever acknowledging the close, and
|
|
46
|
+
// closeBrowser() is actively awaited by callers. An unref'd timer does
|
|
47
|
+
// not hold the event loop open, so Node would consider the loop drained
|
|
48
|
+
// and exit — status 0, before the caller's verdict was ever printed.
|
|
49
|
+
// Cleared in the finally so a close that IS acknowledged does not pin
|
|
50
|
+
// the loop for the rest of BROWSER_CLOSE_TIMEOUT_MS.
|
|
51
|
+
new Promise((_, reject) => {
|
|
52
|
+
closeTimer = setTimeout(() => reject(new Error('Browser.close timed out')), BROWSER_CLOSE_TIMEOUT_MS);
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
55
|
+
gracefullyClosed = true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// fall through to PID-kill fallback below
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(closeTimer);
|
|
62
|
+
}
|
|
63
|
+
let pid = launchedPids.get(port);
|
|
64
|
+
if (pid === undefined) {
|
|
65
|
+
// Fresh process (each CLI invocation is its own node process — see
|
|
66
|
+
// module header) — launchedPids is per-process and empty here even
|
|
67
|
+
// though a prior `open` in a DIFFERENT process launched this Chrome.
|
|
68
|
+
// Fall back to the PID that process persisted via saveSessionRecord().
|
|
69
|
+
try {
|
|
70
|
+
const persisted = await loadSessionRecord(port);
|
|
71
|
+
if (persisted?.launched && persisted.pid) {
|
|
72
|
+
// Bound how far we trust a cross-process PID: if the Chrome we
|
|
73
|
+
// launched died outside this flow (OOM-killed, host slept, etc.)
|
|
74
|
+
// without its session record ever being cleared, the OS can recycle
|
|
75
|
+
// that PID for an unrelated process. There is no cmdline/start-time
|
|
76
|
+
// check available portably, so a coarse age bound is the practical
|
|
77
|
+
// guard — a PID persisted more than a day ago is far more likely to
|
|
78
|
+
// be stale/reused than to still be the same live Chrome.
|
|
79
|
+
const age = persisted.savedAt !== undefined ? Date.now() - persisted.savedAt : undefined;
|
|
80
|
+
if (age === undefined || age <= PERSISTED_PID_MAX_AGE_MS) {
|
|
81
|
+
pid = persisted.pid;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// best-effort — fall through to "nothing to kill" below
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (pid === undefined)
|
|
90
|
+
return; // not a process we launched, or too stale to trust — nothing to kill
|
|
91
|
+
launchedPids.delete(port);
|
|
92
|
+
// Liveness-check before every SIGKILL (not just the safety-net path below)
|
|
93
|
+
// — process.kill(pid, 0) throws ESRCH if the PID is no longer running,
|
|
94
|
+
// which also catches the case where the PID has already been recycled by
|
|
95
|
+
// an unrelated process that happens to have exited too.
|
|
96
|
+
if (!gracefullyClosed) {
|
|
97
|
+
try {
|
|
98
|
+
process.kill(pid, 0);
|
|
99
|
+
process.kill(pid, 'SIGKILL');
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* already exited, or never was — nothing to do */
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Browser.close was acknowledged — wait for the process to actually go away,
|
|
107
|
+
// then force-kill as a safety net in case it hung.
|
|
108
|
+
if (await waitForProcessExit(pid))
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
process.kill(pid, 'SIGKILL');
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* raced us to exit — expected */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Poll until `pid` is gone or PROCESS_EXIT_TIMEOUT_MS elapses. Returns
|
|
118
|
+
* whether it exited. `process.kill(pid, 0)` throws once the process is no
|
|
119
|
+
* longer there, on Windows as well as POSIX.
|
|
120
|
+
*
|
|
121
|
+
* This timer is deliberately NOT unref'd. closeBrowser() awaits this poll
|
|
122
|
+
* as part of its own return value — a caller that awaits closeBrowser() is
|
|
123
|
+
* actively blocked on it, not doing other work in the background. Once
|
|
124
|
+
* Chrome's CDP websocket closes (which happens right around here, as part
|
|
125
|
+
* of it shutting down), nothing else may be left holding the event loop
|
|
126
|
+
* open; an unref'd timer at that point never fires because Node considers
|
|
127
|
+
* the loop drained and exits without running it, leaving this promise (and
|
|
128
|
+
* closeBrowser()'s) pending forever — reproduced locally by closing a real
|
|
129
|
+
* launched browser with nothing else scheduled. Bounded by
|
|
130
|
+
* PROCESS_EXIT_TIMEOUT_MS (5s) either way, so keeping it ref'd only ever
|
|
131
|
+
* costs a caller a few seconds, never a hang. */
|
|
132
|
+
async function waitForProcessExit(pid) {
|
|
133
|
+
const deadline = Date.now() + PROCESS_EXIT_TIMEOUT_MS;
|
|
134
|
+
for (;;) {
|
|
135
|
+
try {
|
|
136
|
+
process.kill(pid, 0);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (Date.now() >= deadline)
|
|
142
|
+
return false;
|
|
143
|
+
await new Promise((resolve) => {
|
|
144
|
+
setTimeout(resolve, PROCESS_EXIT_POLL_MS);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Give back the CDP port of a browser we launched that nothing is using any
|
|
150
|
+
* more. A launched Chrome is deliberately detached and unref'd so it outlives
|
|
151
|
+
* the command that spawned it and later commands can reattach — but nothing
|
|
152
|
+
* reaped it afterwards, so an abandoned session kept listening on its port
|
|
153
|
+
* (9222 by convention, which other local tools want) indefinitely. A foreign
|
|
154
|
+
* CDP server *answers* there rather than refusing the connection, so those
|
|
155
|
+
* tools retry the wrong port and report misleading errors instead of moving on.
|
|
156
|
+
*
|
|
157
|
+
* Nothing long-lived exists to run this on a timer — every CLI command is its
|
|
158
|
+
* own short-lived process — so it runs at the next launch/attach instead
|
|
159
|
+
* (see launchBrowser). A browser is reaped only when ALL of these hold:
|
|
160
|
+
* - the persisted session says WE launched it — `connect` records
|
|
161
|
+
* launched:false for a browser that belongs to the user, never touched;
|
|
162
|
+
* - that session is older than IDLE_REAP_AFTER_MS;
|
|
163
|
+
* - the CDP endpoint still answers and reports ZERO targets. An open tab,
|
|
164
|
+
* or a client attached to one (Chrome drops webSocketDebuggerUrl from an
|
|
165
|
+
* attached target but still lists it), means the session is live and
|
|
166
|
+
* reattach-across-commands must keep working.
|
|
167
|
+
*
|
|
168
|
+
* Shutdown goes through closeBrowser() on the browser-level websocket — the
|
|
169
|
+
* only connection left once every target is gone — so a recycled PID can
|
|
170
|
+
* never be signaled by mistake: graceful `Browser.close` reaches whatever is
|
|
171
|
+
* actually listening on that port, at any age, and the persisted PID is only
|
|
172
|
+
* ever used for closeBrowser's own age-bounded force-kill.
|
|
173
|
+
*
|
|
174
|
+
* Never throws: a failed reap must not stop a launch. Returns the reaped
|
|
175
|
+
* port, or null when there was nothing to reap. Sessions are per-port
|
|
176
|
+
* (#318), so every recorded one is considered, not just the newest.
|
|
177
|
+
*/
|
|
178
|
+
export async function reapIdleLaunchedBrowser() {
|
|
179
|
+
let reaped = null;
|
|
180
|
+
for (const session of await listSessionRecords().catch(() => [])) {
|
|
181
|
+
const port = await reapSession(session);
|
|
182
|
+
reaped ??= port;
|
|
183
|
+
}
|
|
184
|
+
return reaped;
|
|
185
|
+
}
|
|
186
|
+
async function reapSession(session) {
|
|
187
|
+
try {
|
|
188
|
+
if (!session.launched || session.savedAt === undefined)
|
|
189
|
+
return null;
|
|
190
|
+
if (Date.now() - session.savedAt < IDLE_REAP_AFTER_MS)
|
|
191
|
+
return null;
|
|
192
|
+
const wsUrl = await fetchBrowserWebSocketUrl(session.port);
|
|
193
|
+
if (!wsUrl)
|
|
194
|
+
return null;
|
|
195
|
+
if ((await fetchTargets(session.port)).length > 0)
|
|
196
|
+
return null;
|
|
197
|
+
const client = new CdpClient();
|
|
198
|
+
let reapTimer;
|
|
199
|
+
try {
|
|
200
|
+
// CdpClient.connect() has no timeout of its own — a socket that never
|
|
201
|
+
// opens (and never errors) would hang the launch this reap is running
|
|
202
|
+
// inside of, which is strictly worse than leaving the port squatted.
|
|
203
|
+
// Not unref'd (see closeBrowser): a socket that never opens and never
|
|
204
|
+
// errors leaves this timer as the only thing that can settle the race,
|
|
205
|
+
// and the reap is awaited inside launch/attach. Cleared in the finally
|
|
206
|
+
// below so a connect that succeeds does not pin the event loop.
|
|
207
|
+
await Promise.race([
|
|
208
|
+
client.connect(wsUrl),
|
|
209
|
+
new Promise((_, reject) => {
|
|
210
|
+
reapTimer = setTimeout(() => reject(new Error('Reap: CDP connect timed out')), REAP_CONNECT_TIMEOUT_MS);
|
|
211
|
+
}),
|
|
212
|
+
]);
|
|
213
|
+
await closeBrowser(client, session.port);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
clearTimeout(reapTimer);
|
|
217
|
+
try {
|
|
218
|
+
client.close();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
/* already gone */
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
launchedPids.delete(session.port);
|
|
225
|
+
launchedUserDataDirs.delete(session.port);
|
|
226
|
+
await removeSessionRecord(session.port);
|
|
227
|
+
return session.port;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=browser-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-lifecycle.js","sourceRoot":"","sources":["../../../src/browser/browser-lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CdpClient } from './cdp.js';
|
|
2
|
+
import type { CdpTarget } from './types.js';
|
|
3
|
+
export declare function enableSessionDomains(client: CdpClient, sessionId: string): Promise<void>;
|
|
4
|
+
export declare function connectToTarget(port: number, targetId?: string): Promise<{
|
|
5
|
+
client: CdpClient;
|
|
6
|
+
target: CdpTarget;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function openUrl(client: CdpClient, sessionId: string, url: string): Promise<void>;
|
|
10
|
+
export declare function waitForLoad(client: CdpClient, sessionId: string, condition?: 'load' | 'networkidle' | 'domcontentloaded', timeout?: number): Promise<void>;
|
|
11
|
+
export declare function getCurrentUrl(client: CdpClient, sessionId: string): Promise<string>;
|
|
12
|
+
export declare function getCurrentTitle(client: CdpClient, sessionId: string): Promise<string>;
|
|
13
|
+
//# sourceMappingURL=browser-page.d.ts.map
|