@orkestrel/browser 0.0.14 → 0.0.16
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/README.md +16 -20
- package/dist/src/core/index.cjs +2624 -2374
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1588 -762
- package/dist/src/core/index.d.ts +1588 -762
- package/dist/src/core/index.js +2594 -2341
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +283 -193
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +278 -131
- package/dist/src/server/index.d.ts +278 -131
- package/dist/src/server/index.js +277 -184
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -19
package/dist/src/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_WAIT_POLL_INTERVAL_MS, BrowserContext, BrowserError, CDPClient, validateBrowserContextOptions } from "../core/index.js";
|
|
1
|
+
import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_WAIT_POLL_INTERVAL_MS, BrowserContext, BrowserError, BrowserTransition, CDPClient, validateBrowserContextOptions } from "../core/index.js";
|
|
2
2
|
import { attempt, isArray, isError, isInstance, isInteger, isRecord, isString } from "@orkestrel/contract";
|
|
3
3
|
import { existsSync, globSync } from "node:fs";
|
|
4
4
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
@@ -13,35 +13,75 @@ import { Emitter } from "@orkestrel/emitter";
|
|
|
13
13
|
import { WEBSOCKET_READY_CLOSED, WEBSOCKET_READY_OPEN, WEBSOCKET_VERSION, computeWebSocketAccept, createNodeWebSocket } from "@orkestrel/websocket";
|
|
14
14
|
import { addAbortListener, once } from "node:events";
|
|
15
15
|
//#region src/server/constants.ts
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Sets the default CDP port probed for an existing browser and used for launches, `9222`.
|
|
18
|
+
*/
|
|
17
19
|
var BROWSER_DEFAULT_CDP_PORT = 9222;
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Sets the default host probed for an existing browser and used for launches, `'127.0.0.1'`,
|
|
22
|
+
* which avoids `localhost` resolving to `::1` when Chromium binds `127.0.0.1`.
|
|
23
|
+
*/
|
|
19
24
|
var BROWSER_DEFAULT_HOST = "127.0.0.1";
|
|
20
|
-
/**
|
|
25
|
+
/** Names the protocol prefix for CDP discovery requests, `'http'`. */
|
|
21
26
|
var BROWSER_CDP_PROTOCOL = "http";
|
|
22
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Names the path appended to the CDP host to fetch version metadata, `'/json/version'`, which
|
|
29
|
+
* is where endpoint discovery reads.
|
|
30
|
+
*/
|
|
23
31
|
var BROWSER_CDP_VERSION_PATH = "/json/version";
|
|
24
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Names the path appended to the CDP host to list open targets — pages, workers, and every
|
|
34
|
+
* other target category Chromium reports — `'/json/list'`.
|
|
35
|
+
*/
|
|
25
36
|
var BROWSER_CDP_LIST_PATH = "/json/list";
|
|
26
|
-
/**
|
|
37
|
+
/** Lists the flags always passed to a launched browser process, alongside the caller's own. */
|
|
27
38
|
var BROWSER_LAUNCH_ARGS = Object.freeze(["--no-first-run", "--no-default-browser-check"]);
|
|
28
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Names the flag that enables headless mode on a launched browser process, `'--headless=new'`.
|
|
41
|
+
*/
|
|
29
42
|
var BROWSER_HEADLESS_ARG = "--headless=new";
|
|
30
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Names the prefix for isolated browser profiles created beneath the operating-system temp
|
|
45
|
+
* directory, `'orkestrel-browser-'`.
|
|
46
|
+
*/
|
|
31
47
|
var BROWSER_PROFILE_PREFIX = "orkestrel-browser-";
|
|
32
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Bounds each launched-process exit window during TERM-to-KILL teardown at `3_000`
|
|
50
|
+
* milliseconds.
|
|
51
|
+
*/
|
|
33
52
|
var BROWSER_KILL_GRACE_MS = 3e3;
|
|
34
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Bounds the `discover: false` port-occupancy probe before launching at `200` milliseconds,
|
|
55
|
+
* which is short because the probe only needs to detect an already-listening CDP endpoint
|
|
56
|
+
* rather than perform full discovery.
|
|
57
|
+
*/
|
|
35
58
|
var BROWSER_PORT_PROBE_TIMEOUT_MS = 200;
|
|
36
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Defers once for `50` milliseconds when a transport loss is observed on an owned process,
|
|
61
|
+
* giving a near-simultaneous process-exit event, which libuv may reap slightly later than the
|
|
62
|
+
* socket close, first say over the diagnosis.
|
|
63
|
+
*/
|
|
37
64
|
var BROWSER_TRANSPORT_LOSS_DEFER_MS = 50;
|
|
38
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Names the machine-readable error-context cause for an owned browser process exiting,
|
|
67
|
+
* `'process-exit'`.
|
|
68
|
+
*/
|
|
39
69
|
var BROWSER_PROCESS_EXIT_CAUSE = "process-exit";
|
|
40
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Names the machine-readable error-context cause for a CDP transport disconnecting while its
|
|
72
|
+
* browser remains alive, `'transport-loss'`.
|
|
73
|
+
*/
|
|
41
74
|
var BROWSER_TRANSPORT_LOSS_CAUSE = "transport-loss";
|
|
42
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Lists the environment variables checked, in order, for an explicit browser executable path
|
|
77
|
+
* override: `PLAYWRIGHT_EXECUTABLE_PATH`, then `CHROME_PATH`.
|
|
78
|
+
*/
|
|
43
79
|
var BROWSER_ENV_PATH_KEYS = Object.freeze(["PLAYWRIGHT_EXECUTABLE_PATH", "CHROME_PATH"]);
|
|
44
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Lists the well-known Chrome/Chromium/Edge executable paths with no platform-specific root,
|
|
82
|
+
* keyed by `process.platform`, leaving `win32` empty because its roots come from
|
|
83
|
+
* `BROWSER_WINDOWS_SUFFIXES`.
|
|
84
|
+
*/
|
|
45
85
|
var BROWSER_EXECUTABLE_PATHS = Object.freeze({
|
|
46
86
|
linux: Object.freeze([
|
|
47
87
|
"/usr/bin/google-chrome",
|
|
@@ -61,18 +101,21 @@ var BROWSER_EXECUTABLE_PATHS = Object.freeze({
|
|
|
61
101
|
]),
|
|
62
102
|
win32: Object.freeze([])
|
|
63
103
|
});
|
|
64
|
-
/** Windows install-root-relative suffixes for Chrome/Edge/Chromium, joined against each candidate root (`PROGRAMFILES`, `PROGRAMFILES(X86)`, `LOCALAPPDATA`). */
|
|
104
|
+
/** Lists the Windows install-root-relative suffixes for Chrome/Edge/Chromium, joined against each candidate root (`PROGRAMFILES`, `PROGRAMFILES(X86)`, `LOCALAPPDATA`). */
|
|
65
105
|
var BROWSER_WINDOWS_SUFFIXES = Object.freeze([
|
|
66
106
|
"Google\\Chrome\\Application\\chrome.exe",
|
|
67
107
|
"Microsoft\\Edge\\Application\\msedge.exe",
|
|
68
108
|
"Chromium\\Application\\chrome.exe"
|
|
69
109
|
]);
|
|
70
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Lists the fallback Windows install roots used when `PROGRAMFILES`, `PROGRAMFILES(X86)`, or
|
|
112
|
+
* `LOCALAPPDATA` is absent.
|
|
113
|
+
*/
|
|
71
114
|
var BROWSER_WINDOWS_ROOT_FALLBACKS = Object.freeze({
|
|
72
115
|
PROGRAMFILES: "C:\\Program Files",
|
|
73
116
|
"PROGRAMFILES(X86)": "C:\\Program Files (x86)"
|
|
74
117
|
});
|
|
75
|
-
/**
|
|
118
|
+
/** Lists the command names probed on PATH when no well-known executable path exists. */
|
|
76
119
|
var BROWSER_EXECUTABLE_NAMES = Object.freeze([
|
|
77
120
|
"google-chrome",
|
|
78
121
|
"google-chrome-stable",
|
|
@@ -82,19 +125,28 @@ var BROWSER_EXECUTABLE_NAMES = Object.freeze([
|
|
|
82
125
|
"chromium-browser",
|
|
83
126
|
"chrome"
|
|
84
127
|
]);
|
|
85
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Names the environment variable that carries an additional Playwright browser store base
|
|
130
|
+
* directory, `'PLAYWRIGHT_BROWSERS_PATH'`.
|
|
131
|
+
*/
|
|
86
132
|
var BROWSER_STORE_ENV_KEY = "PLAYWRIGHT_BROWSERS_PATH";
|
|
87
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Lists the well-known Playwright browser store base directories checked in addition to
|
|
135
|
+
* `PLAYWRIGHT_BROWSERS_PATH`, starting with `/opt/pw-browsers`.
|
|
136
|
+
*/
|
|
88
137
|
var BROWSER_STORE_DEFAULT_DIRS = Object.freeze(["/opt/pw-browsers"]);
|
|
89
|
-
/**
|
|
138
|
+
/** Names the per-OS default Playwright browser cache directory, relative to the home directory (win32 uses `LOCALAPPDATA` directly). */
|
|
90
139
|
var BROWSER_STORE_CACHE_DIRS = Object.freeze({
|
|
91
140
|
linux: ".cache/ms-playwright",
|
|
92
141
|
darwin: "Library/Caches/ms-playwright"
|
|
93
142
|
});
|
|
94
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Names the top-level Chromium symlink or binary Playwright maintains inside a browser store
|
|
145
|
+
* base, `'chromium'`.
|
|
146
|
+
*/
|
|
95
147
|
var BROWSER_STORE_LINK_NAME = "chromium";
|
|
96
148
|
/**
|
|
97
|
-
*
|
|
149
|
+
* Lists the case-insensitive substrings identifying an executable path/name's browser
|
|
98
150
|
* engine, checked by `parseBrowserEngine` in the order `edge` → `chromium` → `chrome`.
|
|
99
151
|
*/
|
|
100
152
|
var BROWSER_ENGINE_HINTS = Object.freeze({
|
|
@@ -118,7 +170,7 @@ var BROWSER_ENGINE_HINTS = Object.freeze({
|
|
|
118
170
|
"chrome"
|
|
119
171
|
])
|
|
120
172
|
});
|
|
121
|
-
/**
|
|
173
|
+
/** Names the glob pattern (relative to a store base) matching a versioned Chromium binary, keyed by `process.platform`. */
|
|
122
174
|
var BROWSER_STORE_GLOBS = Object.freeze({
|
|
123
175
|
linux: "chromium-*/chrome-linux*/chrome",
|
|
124
176
|
darwin: "chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium",
|
|
@@ -127,7 +179,8 @@ var BROWSER_STORE_GLOBS = Object.freeze({
|
|
|
127
179
|
//#endregion
|
|
128
180
|
//#region src/server/errors.ts
|
|
129
181
|
/**
|
|
130
|
-
*
|
|
182
|
+
* Reports that a CDP connection, discovery, or launch attempt failed, under the code
|
|
183
|
+
* `BROWSER_CONNECTION_ERROR`.
|
|
131
184
|
*/
|
|
132
185
|
var BrowserConnectionError = class extends BrowserError {
|
|
133
186
|
constructor(message, context) {
|
|
@@ -136,7 +189,8 @@ var BrowserConnectionError = class extends BrowserError {
|
|
|
136
189
|
}
|
|
137
190
|
};
|
|
138
191
|
/**
|
|
139
|
-
*
|
|
192
|
+
* Reports that an operation requiring an active connection was attempted while disconnected,
|
|
193
|
+
* under the code `BROWSER_NOT_CONNECTED_ERROR`.
|
|
140
194
|
*/
|
|
141
195
|
var BrowserNotConnectedError = class extends BrowserError {
|
|
142
196
|
constructor(context) {
|
|
@@ -145,7 +199,8 @@ var BrowserNotConnectedError = class extends BrowserError {
|
|
|
145
199
|
}
|
|
146
200
|
};
|
|
147
201
|
/**
|
|
148
|
-
*
|
|
202
|
+
* Reports that an operation was attempted after the browser wrapper was destroyed, under the
|
|
203
|
+
* code `BROWSER_DESTROYED_ERROR`.
|
|
149
204
|
*/
|
|
150
205
|
var BrowserDestroyedError = class extends BrowserError {
|
|
151
206
|
constructor(context) {
|
|
@@ -154,28 +209,28 @@ var BrowserDestroyedError = class extends BrowserError {
|
|
|
154
209
|
}
|
|
155
210
|
};
|
|
156
211
|
/**
|
|
157
|
-
*
|
|
212
|
+
* Narrows an unknown value to a `BrowserConnectionError`.
|
|
158
213
|
*
|
|
159
214
|
* @param value - Value to check
|
|
160
|
-
* @returns True
|
|
215
|
+
* @returns True if value is a BrowserConnectionError instance; false otherwise
|
|
161
216
|
*/
|
|
162
217
|
function isBrowserConnectionError(value) {
|
|
163
218
|
return isInstance(value, BrowserConnectionError);
|
|
164
219
|
}
|
|
165
220
|
/**
|
|
166
|
-
*
|
|
221
|
+
* Narrows an unknown value to a `BrowserNotConnectedError`.
|
|
167
222
|
*
|
|
168
223
|
* @param value - Value to check
|
|
169
|
-
* @returns True
|
|
224
|
+
* @returns True if value is a BrowserNotConnectedError instance; false otherwise
|
|
170
225
|
*/
|
|
171
226
|
function isBrowserNotConnectedError(value) {
|
|
172
227
|
return isInstance(value, BrowserNotConnectedError);
|
|
173
228
|
}
|
|
174
229
|
/**
|
|
175
|
-
*
|
|
230
|
+
* Narrows an unknown value to a `BrowserDestroyedError`.
|
|
176
231
|
*
|
|
177
232
|
* @param value - Value to check
|
|
178
|
-
* @returns True
|
|
233
|
+
* @returns True if value is a BrowserDestroyedError instance; false otherwise
|
|
179
234
|
*/
|
|
180
235
|
function isBrowserDestroyedError(value) {
|
|
181
236
|
return isInstance(value, BrowserDestroyedError);
|
|
@@ -183,7 +238,7 @@ function isBrowserDestroyedError(value) {
|
|
|
183
238
|
//#endregion
|
|
184
239
|
//#region src/server/helpers.ts
|
|
185
240
|
/**
|
|
186
|
-
*
|
|
241
|
+
* Enumerates every Chrome/Chromium/Edge executable discoverable on this
|
|
187
242
|
* machine, deduplicated by normalized absolute path.
|
|
188
243
|
*
|
|
189
244
|
* @remarks
|
|
@@ -197,7 +252,7 @@ function isBrowserDestroyedError(value) {
|
|
|
197
252
|
* and the per-OS Playwright cache directory) — the top-level `chromium`
|
|
198
253
|
* link/binary, then the highest-revision `chromium-*` install found
|
|
199
254
|
*
|
|
200
|
-
* Each candidate is classified into a {@link BrowserEngine}
|
|
255
|
+
* Each candidate is classified into a {@link BrowserEngine} through
|
|
201
256
|
* `parseBrowserEngine` (unclassifiable candidates default to `'chromium'`),
|
|
202
257
|
* and `options.engine` (when given) narrows the result to that engine.
|
|
203
258
|
*
|
|
@@ -208,13 +263,13 @@ function findSystemBrowsers(options) {
|
|
|
208
263
|
const platform = process.platform;
|
|
209
264
|
const env = options?.env ?? process.env;
|
|
210
265
|
const candidates = [];
|
|
211
|
-
candidates.push(...
|
|
212
|
-
const paths = options?.paths ??
|
|
213
|
-
candidates.push(...
|
|
266
|
+
candidates.push(...findEnvOverrides(env));
|
|
267
|
+
const paths = options?.paths ?? buildInstallPaths(platform, env);
|
|
268
|
+
candidates.push(...findInstallPaths(paths));
|
|
214
269
|
const names = options?.names ?? BROWSER_EXECUTABLE_NAMES;
|
|
215
|
-
candidates.push(...
|
|
216
|
-
const stores = options?.stores ??
|
|
217
|
-
for (const store of stores) candidates.push(...
|
|
270
|
+
candidates.push(...probePathNames(names, platform));
|
|
271
|
+
const stores = options?.stores ?? buildStoreBases(env, platform);
|
|
272
|
+
for (const store of stores) candidates.push(...findStorePaths(store, platform));
|
|
218
273
|
const seen = /* @__PURE__ */ new Set();
|
|
219
274
|
const browsers = [];
|
|
220
275
|
for (const executable of candidates) {
|
|
@@ -231,7 +286,7 @@ function findSystemBrowsers(options) {
|
|
|
231
286
|
return browsers;
|
|
232
287
|
}
|
|
233
288
|
/**
|
|
234
|
-
*
|
|
289
|
+
* Locates a Chrome/Chromium/Edge executable on this machine — the first entry
|
|
235
290
|
* of {@link findSystemBrowsers}.
|
|
236
291
|
*
|
|
237
292
|
* @param options - Overrides for the candidate sources; see {@link SystemBrowserOptions}
|
|
@@ -241,7 +296,7 @@ function findSystemBrowser(options) {
|
|
|
241
296
|
return findSystemBrowsers(options)[0];
|
|
242
297
|
}
|
|
243
298
|
/**
|
|
244
|
-
*
|
|
299
|
+
* Classifies an executable path/name into a {@link BrowserEngine} by
|
|
245
300
|
* case-insensitive hint, checked in the order edge → chromium → chrome.
|
|
246
301
|
*
|
|
247
302
|
* @param executable - Executable path or command name to classify
|
|
@@ -255,19 +310,19 @@ function parseBrowserEngine(executable) {
|
|
|
255
310
|
"chrome"
|
|
256
311
|
]) if (BROWSER_ENGINE_HINTS[engine].some((hint) => lower.includes(hint))) return engine;
|
|
257
312
|
}
|
|
258
|
-
/**
|
|
313
|
+
/** Normalizes an executable path for cross-source deduplication (case-insensitive on Windows). */
|
|
259
314
|
function normalizeExecutablePath(path, platform) {
|
|
260
315
|
const normalized = platform === "win32" ? win32.normalize(path) : posix.normalize(path);
|
|
261
316
|
return platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
262
317
|
}
|
|
263
|
-
/**
|
|
318
|
+
/** Classifies a `/json/version` `Browser` string into a {@link BrowserEngine} (`Edg/` → edge, `Chrome/` → chrome, else chromium). */
|
|
264
319
|
function browserToEngine(browser) {
|
|
265
320
|
if (browser?.includes("Edg/") === true) return "edge";
|
|
266
321
|
if (browser?.includes("Chrome/") === true) return "chrome";
|
|
267
322
|
return "chromium";
|
|
268
323
|
}
|
|
269
324
|
/**
|
|
270
|
-
*
|
|
325
|
+
* Resolves a persistent caller profile or creates an isolated temporary one.
|
|
271
326
|
*
|
|
272
327
|
* @param path - Optional caller-owned persistent user-data directory
|
|
273
328
|
* @returns The resolved profile and whether the library owns its lifecycle
|
|
@@ -283,7 +338,7 @@ async function createBrowserProfile(path) {
|
|
|
283
338
|
};
|
|
284
339
|
}
|
|
285
340
|
/**
|
|
286
|
-
*
|
|
341
|
+
* Removes a library-owned isolated browser profile.
|
|
287
342
|
*
|
|
288
343
|
* @remarks
|
|
289
344
|
* The path must remain a direct child of the operating-system temp directory
|
|
@@ -296,7 +351,7 @@ async function removeBrowserProfile(profile) {
|
|
|
296
351
|
if (!profile.temporary) return;
|
|
297
352
|
const path = resolve(profile.path);
|
|
298
353
|
const temporary = resolve(tmpdir());
|
|
299
|
-
if (dirname(path) !== temporary || !basename(path).startsWith("orkestrel-browser-")) throw new
|
|
354
|
+
if (dirname(path) !== temporary || !basename(path).startsWith("orkestrel-browser-")) throw new BrowserError("Refusing to remove an unsafe browser profile path", void 0, { path });
|
|
300
355
|
await rm(path, {
|
|
301
356
|
recursive: true,
|
|
302
357
|
force: true,
|
|
@@ -304,12 +359,8 @@ async function removeBrowserProfile(profile) {
|
|
|
304
359
|
retryDelay: 100
|
|
305
360
|
});
|
|
306
361
|
}
|
|
307
|
-
/**
|
|
308
|
-
function
|
|
309
|
-
return findAllEnvOverrides(env)[0];
|
|
310
|
-
}
|
|
311
|
-
/** Check the env-override keys (`PLAYWRIGHT_EXECUTABLE_PATH`, `CHROME_PATH`) in order, returning every one that exists. */
|
|
312
|
-
function findAllEnvOverrides(env) {
|
|
362
|
+
/** Checks the env-override keys (`PLAYWRIGHT_EXECUTABLE_PATH`, `CHROME_PATH`) in order and returns every one that exists. */
|
|
363
|
+
function findEnvOverrides(env) {
|
|
313
364
|
const found = [];
|
|
314
365
|
for (const key of BROWSER_ENV_PATH_KEYS) {
|
|
315
366
|
const value = env[key];
|
|
@@ -317,36 +368,28 @@ function findAllEnvOverrides(env) {
|
|
|
317
368
|
}
|
|
318
369
|
return found;
|
|
319
370
|
}
|
|
320
|
-
/**
|
|
321
|
-
function
|
|
371
|
+
/** Builds the default well-known install-path candidates for a platform, deriving Windows roots from env vars. */
|
|
372
|
+
function buildInstallPaths(platform, env) {
|
|
322
373
|
if (platform !== "win32") return BROWSER_EXECUTABLE_PATHS[platform] ?? [];
|
|
323
|
-
const roots =
|
|
374
|
+
const roots = buildWindowsRoots(env);
|
|
324
375
|
const paths = [];
|
|
325
376
|
for (const root of roots) for (const suffix of BROWSER_WINDOWS_SUFFIXES) paths.push(win32.join(root, suffix));
|
|
326
377
|
return paths;
|
|
327
378
|
}
|
|
328
|
-
/**
|
|
329
|
-
function
|
|
379
|
+
/** Derives Windows install roots from env vars, falling back to well-known literals when absent. */
|
|
380
|
+
function buildWindowsRoots(env) {
|
|
330
381
|
return [
|
|
331
382
|
env["PROGRAMFILES"] ?? BROWSER_WINDOWS_ROOT_FALLBACKS["PROGRAMFILES"],
|
|
332
383
|
env["PROGRAMFILES(X86)"] ?? BROWSER_WINDOWS_ROOT_FALLBACKS["PROGRAMFILES(X86)"],
|
|
333
384
|
env["LOCALAPPDATA"]
|
|
334
385
|
].filter((root) => root !== void 0);
|
|
335
386
|
}
|
|
336
|
-
/**
|
|
337
|
-
function
|
|
338
|
-
return findAllInstallPaths(paths)[0];
|
|
339
|
-
}
|
|
340
|
-
/** Return every candidate path that exists on disk, in the given order. */
|
|
341
|
-
function findAllInstallPaths(paths) {
|
|
387
|
+
/** Returns every candidate path that exists on disk, in the given order. */
|
|
388
|
+
function findInstallPaths(paths) {
|
|
342
389
|
return paths.filter((path) => existsSync(path));
|
|
343
390
|
}
|
|
344
|
-
/**
|
|
391
|
+
/** Probes PATH (`which`/`where`) for every resolvable command name, in the given order. */
|
|
345
392
|
function probePathNames(names, platform) {
|
|
346
|
-
return probeAllPathNames(names, platform)[0];
|
|
347
|
-
}
|
|
348
|
-
/** Probe PATH (`which`/`where`) for every resolvable command name, in the given order. */
|
|
349
|
-
function probeAllPathNames(names, platform) {
|
|
350
393
|
const finder = platform === "win32" ? "where" : "which";
|
|
351
394
|
const found = [];
|
|
352
395
|
for (const name of names) {
|
|
@@ -381,8 +424,8 @@ function readFirstLine(output) {
|
|
|
381
424
|
if (text.length > 0) return text;
|
|
382
425
|
}
|
|
383
426
|
}
|
|
384
|
-
/**
|
|
385
|
-
function
|
|
427
|
+
/** Builds the default Playwright browser store base directories to search for a managed Chromium. */
|
|
428
|
+
function buildStoreBases(env, platform) {
|
|
386
429
|
const bases = [];
|
|
387
430
|
const envBase = env[BROWSER_STORE_ENV_KEY];
|
|
388
431
|
if (envBase !== void 0 && envBase.length > 0) bases.push(envBase);
|
|
@@ -397,12 +440,8 @@ function defaultStoreBases(env, platform) {
|
|
|
397
440
|
}
|
|
398
441
|
return bases;
|
|
399
442
|
}
|
|
400
|
-
/**
|
|
401
|
-
function
|
|
402
|
-
return findAllInStore(base, platform)[0];
|
|
403
|
-
}
|
|
404
|
-
/** Search one store base for the top-level `chromium` link and every `chromium-*` install, highest revision first. */
|
|
405
|
-
function findAllInStore(base, platform) {
|
|
443
|
+
/** Searches one store base for the top-level `chromium` link and every `chromium-*` install, highest revision first. */
|
|
444
|
+
function findStorePaths(base, platform) {
|
|
406
445
|
const joiner = platform === "win32" ? win32.join : posix.join;
|
|
407
446
|
const found = [];
|
|
408
447
|
const linkPath = joiner(base, BROWSER_STORE_LINK_NAME);
|
|
@@ -416,7 +455,7 @@ function findAllInStore(base, platform) {
|
|
|
416
455
|
return found;
|
|
417
456
|
}
|
|
418
457
|
/**
|
|
419
|
-
*
|
|
458
|
+
* Launches a browser process with raw-CDP debugging flags.
|
|
420
459
|
*
|
|
421
460
|
* @remarks
|
|
422
461
|
* POSIX launches own an isolated process group so lifecycle teardown can
|
|
@@ -446,11 +485,12 @@ function launchBrowserProcess(executable, port, headless, profile, extra) {
|
|
|
446
485
|
});
|
|
447
486
|
}
|
|
448
487
|
/**
|
|
449
|
-
*
|
|
488
|
+
* Polls a browser's CDP version endpoint until it responds or the timeout elapses.
|
|
450
489
|
*
|
|
451
490
|
* @param port - Port the browser exposes its CDP endpoint on
|
|
452
491
|
* @param timeout - Maximum time to wait in milliseconds
|
|
453
492
|
* @param host - Host the browser exposes its CDP endpoint on (default `127.0.0.1`)
|
|
493
|
+
* @param signal - Optional external abort; an abort while waiting rethrows rather than resolving
|
|
454
494
|
* @returns The browser's WebSocket debugger URL
|
|
455
495
|
*
|
|
456
496
|
* @throws When the endpoint does not become ready before the timeout
|
|
@@ -474,15 +514,34 @@ async function waitForCDPReady(port, timeout, host = BROWSER_DEFAULT_HOST, signa
|
|
|
474
514
|
const delay = Math.min(BROWSER_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now()));
|
|
475
515
|
if (delay > 0) await setTimeout$1(delay, void 0, { signal });
|
|
476
516
|
}
|
|
477
|
-
throw new
|
|
517
|
+
throw new BrowserConnectionError(`CDP endpoint on port ${port} did not become ready within ${timeout}ms`, {
|
|
518
|
+
port,
|
|
519
|
+
timeout
|
|
520
|
+
});
|
|
478
521
|
}
|
|
479
522
|
/**
|
|
480
|
-
*
|
|
523
|
+
* Fetches the current CDP target list from a browser's `/json/list` endpoint, as a `Result`
|
|
524
|
+
* carrying either the targets or a coded `BrowserConnectionError`.
|
|
525
|
+
*
|
|
526
|
+
* @remarks
|
|
527
|
+
* The endpoint is a network boundary, so an unreachable host, a non-2xx
|
|
528
|
+
* response, and a body that is not a JSON array each come back as a failed
|
|
529
|
+
* `Result` carrying a coded `BrowserConnectionError`. An entry missing a
|
|
530
|
+
* required string field is skipped rather than failing the whole list.
|
|
481
531
|
*
|
|
482
532
|
* @param port - Port the browser exposes its CDP endpoint on
|
|
483
533
|
* @param timeout - Request timeout in milliseconds
|
|
484
534
|
* @param host - Host the browser exposes its CDP endpoint on (default `127.0.0.1`)
|
|
485
|
-
* @returns
|
|
535
|
+
* @returns The normalized CDP targets, or the fault that stopped the request
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```ts
|
|
539
|
+
* import { fetchCDPTargets } from '@orkestrel/browser/server'
|
|
540
|
+
*
|
|
541
|
+
* const result = await fetchCDPTargets(9222, 2000)
|
|
542
|
+
* if (result.success) log(result.value.length)
|
|
543
|
+
* else log(result.error.code)
|
|
544
|
+
* ```
|
|
486
545
|
*/
|
|
487
546
|
async function fetchCDPTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
488
547
|
const url = `${BROWSER_CDP_PROTOCOL}://${host}:${port}${BROWSER_CDP_LIST_PATH}`;
|
|
@@ -490,23 +549,41 @@ async function fetchCDPTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
|
490
549
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
491
550
|
try {
|
|
492
551
|
const response = await fetch(url, { signal: controller.signal });
|
|
493
|
-
if (!response.ok) return
|
|
552
|
+
if (!response.ok) return {
|
|
553
|
+
success: false,
|
|
554
|
+
error: new BrowserConnectionError("CDP target list request was refused", {
|
|
555
|
+
url,
|
|
556
|
+
status: response.status
|
|
557
|
+
})
|
|
558
|
+
};
|
|
494
559
|
const list = await response.json();
|
|
495
|
-
if (!isArray(list)) return
|
|
560
|
+
if (!isArray(list)) return {
|
|
561
|
+
success: false,
|
|
562
|
+
error: new BrowserConnectionError("CDP target list is not a JSON array", { url })
|
|
563
|
+
};
|
|
496
564
|
const targets = [];
|
|
497
565
|
for (const entry of list) {
|
|
498
566
|
if (!isRecord(entry)) continue;
|
|
499
567
|
if (!isString(entry["id"]) || !isString(entry["type"]) || !isString(entry["title"]) || !isString(entry["url"])) continue;
|
|
500
568
|
targets.push({
|
|
501
569
|
id: entry["id"],
|
|
502
|
-
|
|
570
|
+
category: entry["type"],
|
|
503
571
|
title: entry["title"],
|
|
504
572
|
url: entry["url"]
|
|
505
573
|
});
|
|
506
574
|
}
|
|
507
|
-
return
|
|
508
|
-
|
|
509
|
-
|
|
575
|
+
return {
|
|
576
|
+
success: true,
|
|
577
|
+
value: targets
|
|
578
|
+
};
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return {
|
|
581
|
+
success: false,
|
|
582
|
+
error: new BrowserConnectionError("CDP target list is unreachable", {
|
|
583
|
+
url,
|
|
584
|
+
error
|
|
585
|
+
})
|
|
586
|
+
};
|
|
510
587
|
} finally {
|
|
511
588
|
clearTimeout(timer);
|
|
512
589
|
}
|
|
@@ -514,27 +591,36 @@ async function fetchCDPTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
|
514
591
|
//#endregion
|
|
515
592
|
//#region src/server/transports/WebSocketCDPTransport.ts
|
|
516
593
|
/**
|
|
517
|
-
*
|
|
594
|
+
* Provides a raw CDP text transport backed by `@orkestrel/websocket`.
|
|
518
595
|
*
|
|
519
596
|
* @remarks
|
|
520
597
|
* Performs and validates the RFC 6455 HTTP upgrade, then delegates frame
|
|
521
598
|
* handling to `@orkestrel/websocket`. Concurrent and repeated `start()` /
|
|
522
599
|
* `close()` calls share their active transition, and a later `start()` opens a
|
|
523
600
|
* fresh socket after the prior one closes.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```ts
|
|
604
|
+
* import { WebSocketCDPTransport } from '@orkestrel/browser/server'
|
|
605
|
+
*
|
|
606
|
+
* const transport = new WebSocketCDPTransport({ url: 'ws://127.0.0.1:9222/devtools/browser/abc' })
|
|
607
|
+
* await transport.start()
|
|
608
|
+
* await transport.close()
|
|
609
|
+
* ```
|
|
524
610
|
*/
|
|
525
611
|
var WebSocketCDPTransport = class {
|
|
526
612
|
#emitter;
|
|
527
613
|
#url;
|
|
528
614
|
#timeout;
|
|
529
615
|
#socket;
|
|
530
|
-
#starting;
|
|
531
|
-
#closing;
|
|
616
|
+
#starting = new BrowserTransition();
|
|
617
|
+
#closing = new BrowserTransition();
|
|
532
618
|
#request;
|
|
533
619
|
#resolve;
|
|
534
620
|
#reject;
|
|
535
621
|
#timer;
|
|
536
622
|
#closeResolve;
|
|
537
|
-
#
|
|
623
|
+
#socketCloseHandler = this.#handleSocketClose.bind(this);
|
|
538
624
|
constructor(options) {
|
|
539
625
|
this.#emitter = new Emitter({
|
|
540
626
|
...options.on !== void 0 ? { on: options.on } : {},
|
|
@@ -547,43 +633,27 @@ var WebSocketCDPTransport = class {
|
|
|
547
633
|
return this.#emitter;
|
|
548
634
|
}
|
|
549
635
|
async start() {
|
|
550
|
-
|
|
636
|
+
const closing = this.#closing.pending;
|
|
637
|
+
if (closing !== void 0) await closing;
|
|
551
638
|
if (this.#socket?.readyState === WEBSOCKET_READY_OPEN) return;
|
|
552
|
-
const active = this.#starting;
|
|
639
|
+
const active = this.#starting.pending;
|
|
553
640
|
if (active !== void 0) {
|
|
554
641
|
await active;
|
|
555
642
|
return;
|
|
556
643
|
}
|
|
557
644
|
this.#socket?.destroy();
|
|
558
645
|
this.#socket = void 0;
|
|
559
|
-
|
|
560
|
-
this.#starting = transition;
|
|
561
|
-
try {
|
|
562
|
-
await transition;
|
|
563
|
-
} finally {
|
|
564
|
-
if (this.#starting === transition) this.#starting = void 0;
|
|
565
|
-
}
|
|
646
|
+
await this.#starting.execute(() => this.#start());
|
|
566
647
|
}
|
|
567
648
|
async send(data) {
|
|
568
649
|
const socket = this.#socket;
|
|
569
|
-
if (socket === void 0 || socket.readyState !== WEBSOCKET_READY_OPEN) throw new
|
|
650
|
+
if (socket === void 0 || socket.readyState !== WEBSOCKET_READY_OPEN) throw new BrowserConnectionError("WebSocket CDP transport is not open", { url: this.#url });
|
|
570
651
|
socket.send(data);
|
|
571
652
|
}
|
|
572
653
|
async close() {
|
|
573
|
-
|
|
574
|
-
if (active !== void 0) {
|
|
575
|
-
await active;
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
const transition = this.#stop();
|
|
579
|
-
this.#closing = transition;
|
|
580
|
-
try {
|
|
581
|
-
await transition;
|
|
582
|
-
} finally {
|
|
583
|
-
if (this.#closing === transition) this.#closing = void 0;
|
|
584
|
-
}
|
|
654
|
+
await this.#closing.execute(() => this.#close());
|
|
585
655
|
}
|
|
586
|
-
async #
|
|
656
|
+
async #start() {
|
|
587
657
|
const parsed = attempt(() => new URL(this.#url));
|
|
588
658
|
if (!parsed.success) throw new BrowserConnectionError(`WebSocket CDP URL is invalid: ${this.#url}`, {
|
|
589
659
|
url: this.#url,
|
|
@@ -619,19 +689,19 @@ var WebSocketCDPTransport = class {
|
|
|
619
689
|
this.#socket = socket;
|
|
620
690
|
this.#bind(socket);
|
|
621
691
|
}
|
|
622
|
-
async #
|
|
692
|
+
async #close() {
|
|
623
693
|
const request = this.#request;
|
|
624
694
|
if (request !== void 0) {
|
|
625
695
|
this.#rejectRequest(request, new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} was closed before it finished connecting`, { url: this.#url }));
|
|
626
696
|
request.destroy();
|
|
627
697
|
}
|
|
628
|
-
await this.#starting?.catch(() => void 0);
|
|
698
|
+
await this.#starting.pending?.catch(() => void 0);
|
|
629
699
|
const socket = this.#socket;
|
|
630
700
|
this.#socket = void 0;
|
|
631
701
|
if (socket === void 0 || socket.readyState === WEBSOCKET_READY_CLOSED) return;
|
|
632
702
|
const closed = Promise.withResolvers();
|
|
633
703
|
this.#closeResolve = closed.resolve;
|
|
634
|
-
socket.emitter.on("close", this.#
|
|
704
|
+
socket.emitter.on("close", this.#socketCloseHandler);
|
|
635
705
|
try {
|
|
636
706
|
socket.close();
|
|
637
707
|
if (socket.readyState !== WEBSOCKET_READY_CLOSED) await closed.promise;
|
|
@@ -639,7 +709,7 @@ var WebSocketCDPTransport = class {
|
|
|
639
709
|
socket.destroy();
|
|
640
710
|
if (socket.readyState !== WEBSOCKET_READY_CLOSED) await closed.promise;
|
|
641
711
|
} finally {
|
|
642
|
-
socket.emitter.off("close", this.#
|
|
712
|
+
socket.emitter.off("close", this.#socketCloseHandler);
|
|
643
713
|
this.#closeResolve = void 0;
|
|
644
714
|
}
|
|
645
715
|
}
|
|
@@ -714,26 +784,61 @@ var WebSocketCDPTransport = class {
|
|
|
714
784
|
}
|
|
715
785
|
};
|
|
716
786
|
//#endregion
|
|
787
|
+
//#region src/server/writers/FileBrowserWriter.ts
|
|
788
|
+
/**
|
|
789
|
+
* Persists captured browser bytes to the filesystem through `node:fs/promises`.
|
|
790
|
+
*
|
|
791
|
+
* @remarks
|
|
792
|
+
* Creates every missing parent directory of the target path before writing, so
|
|
793
|
+
* a caller may name a nested destination that does not exist yet.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* ```ts
|
|
797
|
+
* import { FileBrowserWriter } from '@orkestrel/browser/server'
|
|
798
|
+
*
|
|
799
|
+
* const writer = new FileBrowserWriter()
|
|
800
|
+
* await writer.write('shots/hero.png', new Uint8Array([137, 80, 78, 71]))
|
|
801
|
+
* ```
|
|
802
|
+
*/
|
|
803
|
+
var FileBrowserWriter = class {
|
|
804
|
+
/**
|
|
805
|
+
* Writes the captured bytes to a path, creating its parent directories.
|
|
806
|
+
*
|
|
807
|
+
* @param path - Destination file path
|
|
808
|
+
* @param data - Captured bytes to persist
|
|
809
|
+
* @returns A promise resolving once the bytes are on disk
|
|
810
|
+
*/
|
|
811
|
+
async write(path, data) {
|
|
812
|
+
await mkdir(dirname(path), { recursive: true });
|
|
813
|
+
await writeFile(path, data);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
//#endregion
|
|
717
817
|
//#region src/server/factories.ts
|
|
718
818
|
/**
|
|
719
|
-
*
|
|
819
|
+
* Creates a raw-CDP `BrowserInterface` façade with discovery, connection, and lifecycle
|
|
820
|
+
* management.
|
|
720
821
|
*
|
|
721
822
|
* @param options - Connection, launch, and viewport configuration
|
|
722
823
|
* @returns A {@link BrowserInterface}
|
|
723
824
|
*
|
|
724
|
-
* @example
|
|
825
|
+
* @example Connect to a browser and drive a page
|
|
725
826
|
* ```ts
|
|
726
|
-
* import { createBrowser } from '@
|
|
827
|
+
* import { createBrowser } from '@orkestrel/browser/server'
|
|
727
828
|
*
|
|
728
|
-
* const browser = createBrowser()
|
|
729
|
-
* await browser.connect()
|
|
829
|
+
* const browser = createBrowser({ headless: true })
|
|
830
|
+
* await browser.connect() // CDP endpoint discovery → connect, else launch
|
|
831
|
+
* const page = await browser.create({ url: 'https://example.com' })
|
|
832
|
+
* await page.click('#accept')
|
|
833
|
+
* const shot = await page.screenshot({ path: './out.png' })
|
|
834
|
+
* await browser.destroy()
|
|
730
835
|
* ```
|
|
731
836
|
*/
|
|
732
837
|
function createBrowser(options) {
|
|
733
838
|
return new Browser(options);
|
|
734
839
|
}
|
|
735
840
|
/**
|
|
736
|
-
*
|
|
841
|
+
* Creates a Node `WebSocket`-backed `CDPTransportInterface` for the given CDP debugger URL.
|
|
737
842
|
*
|
|
738
843
|
* @param options - The CDP WebSocket debugger URL (and optional timeout)
|
|
739
844
|
* @returns A {@link CDPTransportInterface}
|
|
@@ -742,20 +847,28 @@ function createCDPTransport(options) {
|
|
|
742
847
|
return new WebSocketCDPTransport(options);
|
|
743
848
|
}
|
|
744
849
|
/**
|
|
745
|
-
*
|
|
850
|
+
* Creates a filesystem-backed `BrowserWriterInterface` that persists bytes through
|
|
851
|
+
* `node:fs/promises`.
|
|
746
852
|
*
|
|
747
|
-
* @returns A {@link
|
|
853
|
+
* @returns A {@link BrowserWriterInterface} that persists bytes through `node:fs/promises`
|
|
748
854
|
*/
|
|
749
|
-
function
|
|
750
|
-
return
|
|
751
|
-
await mkdir(dirname(path), { recursive: true });
|
|
752
|
-
await writeFile(path, data);
|
|
753
|
-
} };
|
|
855
|
+
function createBrowserWriter() {
|
|
856
|
+
return new FileBrowserWriter();
|
|
754
857
|
}
|
|
755
858
|
//#endregion
|
|
756
859
|
//#region src/server/Browser.ts
|
|
757
860
|
/**
|
|
758
861
|
* Discovers, launches, connects to, and owns Chromium-family browser sessions.
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```ts
|
|
865
|
+
* import { Browser } from '@orkestrel/browser/server'
|
|
866
|
+
*
|
|
867
|
+
* const browser = new Browser({ headless: true })
|
|
868
|
+
* await browser.connect()
|
|
869
|
+
* const page = await browser.create({ url: 'https://example.com' })
|
|
870
|
+
* await browser.destroy()
|
|
871
|
+
* ```
|
|
759
872
|
*/
|
|
760
873
|
var Browser = class {
|
|
761
874
|
#emitter;
|
|
@@ -774,14 +887,14 @@ var Browser = class {
|
|
|
774
887
|
#profile;
|
|
775
888
|
#contexts = [];
|
|
776
889
|
#destroyed = false;
|
|
777
|
-
#connecting;
|
|
778
|
-
#disconnecting;
|
|
890
|
+
#connecting = new BrowserTransition();
|
|
891
|
+
#disconnecting = new BrowserTransition();
|
|
779
892
|
#exitCleanup;
|
|
780
893
|
#shutdown;
|
|
781
894
|
#transport;
|
|
782
|
-
#
|
|
783
|
-
#
|
|
784
|
-
#
|
|
895
|
+
#transportCloseHandler = this.#handleTransportLoss.bind(this);
|
|
896
|
+
#transportErrorHandler = this.#handleTransportLoss.bind(this);
|
|
897
|
+
#processExitHandler = this.#handleProcessExit.bind(this);
|
|
785
898
|
constructor(options) {
|
|
786
899
|
this.#emitter = new Emitter({
|
|
787
900
|
...options?.on !== void 0 ? { on: options.on } : {},
|
|
@@ -808,9 +921,6 @@ var Browser = class {
|
|
|
808
921
|
get owned() {
|
|
809
922
|
return this.#owned;
|
|
810
923
|
}
|
|
811
|
-
get connected() {
|
|
812
|
-
return this.#status === "connected";
|
|
813
|
-
}
|
|
814
924
|
get pid() {
|
|
815
925
|
return this.#servingPid ?? this.#process?.pid;
|
|
816
926
|
}
|
|
@@ -821,19 +931,13 @@ var Browser = class {
|
|
|
821
931
|
}
|
|
822
932
|
async connect() {
|
|
823
933
|
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
824
|
-
const active = this.#connecting;
|
|
934
|
+
const active = this.#connecting.pending;
|
|
825
935
|
if (active !== void 0) {
|
|
826
936
|
await active;
|
|
827
937
|
return;
|
|
828
938
|
}
|
|
829
939
|
if (this.#status === "connected") return;
|
|
830
|
-
|
|
831
|
-
this.#connecting = attempt;
|
|
832
|
-
try {
|
|
833
|
-
await attempt;
|
|
834
|
-
} finally {
|
|
835
|
-
if (this.#connecting === attempt) this.#connecting = void 0;
|
|
836
|
-
}
|
|
940
|
+
await this.#connecting.execute(() => this.#establish());
|
|
837
941
|
}
|
|
838
942
|
adopt() {
|
|
839
943
|
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
@@ -842,19 +946,13 @@ var Browser = class {
|
|
|
842
946
|
}
|
|
843
947
|
async disconnect() {
|
|
844
948
|
if (this.#destroyed) return;
|
|
845
|
-
const disconnecting = this.#disconnecting;
|
|
846
|
-
const connecting = this.#connecting;
|
|
949
|
+
const disconnecting = this.#disconnecting.pending;
|
|
950
|
+
const connecting = this.#connecting.pending;
|
|
847
951
|
if (disconnecting !== void 0) await disconnecting;
|
|
848
952
|
if (connecting !== void 0) await connecting.catch(() => void 0);
|
|
849
953
|
if (this.#destroyed) return;
|
|
850
954
|
if (this.#status !== "connected" || this.#client === void 0) return;
|
|
851
|
-
|
|
852
|
-
this.#disconnecting = attempt;
|
|
853
|
-
try {
|
|
854
|
-
await attempt;
|
|
855
|
-
} finally {
|
|
856
|
-
if (this.#disconnecting === attempt) this.#disconnecting = void 0;
|
|
857
|
-
}
|
|
955
|
+
await this.#disconnecting.execute(() => this.#detach());
|
|
858
956
|
}
|
|
859
957
|
context(index) {
|
|
860
958
|
const i = index ?? 0;
|
|
@@ -878,7 +976,7 @@ var Browser = class {
|
|
|
878
976
|
const result = await client.send("Target.createBrowserContext", params);
|
|
879
977
|
if (!isRecord(result) || !isString(result["browserContextId"])) throw new BrowserConnectionError("Failed to create isolated browser context");
|
|
880
978
|
const id = result["browserContextId"];
|
|
881
|
-
const context = new BrowserContext(client, id, options?.emulation?.viewport ?? this.#options.viewport,
|
|
979
|
+
const context = new BrowserContext(client, id, options?.emulation?.viewport ?? this.#options.viewport, createBrowserWriter(), options?.emulation, options?.downloads, options);
|
|
882
980
|
try {
|
|
883
981
|
if (options?.downloads !== void 0) await client.send("Browser.setDownloadBehavior", {
|
|
884
982
|
behavior: options.downloads.named === true ? "allowAndName" : "allow",
|
|
@@ -899,7 +997,7 @@ var Browser = class {
|
|
|
899
997
|
if (this.#status !== "connected" || client === void 0) throw new BrowserNotConnectedError();
|
|
900
998
|
let context = this.#contexts[0];
|
|
901
999
|
if (context === void 0) {
|
|
902
|
-
context = new BrowserContext(client, void 0, this.#options.viewport,
|
|
1000
|
+
context = new BrowserContext(client, void 0, this.#options.viewport, createBrowserWriter());
|
|
903
1001
|
this.#registerContext(context);
|
|
904
1002
|
}
|
|
905
1003
|
const page = await context.create(options);
|
|
@@ -927,7 +1025,8 @@ var Browser = class {
|
|
|
927
1025
|
return shutdown;
|
|
928
1026
|
}
|
|
929
1027
|
async #establish() {
|
|
930
|
-
|
|
1028
|
+
const disconnecting = this.#disconnecting.pending;
|
|
1029
|
+
if (disconnecting !== void 0) await disconnecting;
|
|
931
1030
|
await this.#settleExit();
|
|
932
1031
|
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
933
1032
|
if (this.#status === "connected") return;
|
|
@@ -945,7 +1044,7 @@ var Browser = class {
|
|
|
945
1044
|
}
|
|
946
1045
|
if (this.#options.cdp?.discover ?? true) {
|
|
947
1046
|
const discovery = await this.#raceAbort(this.#discoverCDP(this.#signal()));
|
|
948
|
-
if (discovery.
|
|
1047
|
+
if (discovery.endpoint !== void 0) {
|
|
949
1048
|
this.#engine = browserToEngine(discovery.browser);
|
|
950
1049
|
await this.#connectCDP(discovery.endpoint);
|
|
951
1050
|
return;
|
|
@@ -1063,24 +1162,24 @@ var Browser = class {
|
|
|
1063
1162
|
#bindTransport(transport) {
|
|
1064
1163
|
this.#unbindTransport();
|
|
1065
1164
|
this.#transport = transport;
|
|
1066
|
-
transport.emitter.on("close", this.#
|
|
1067
|
-
transport.emitter.on("error", this.#
|
|
1165
|
+
transport.emitter.on("close", this.#transportCloseHandler);
|
|
1166
|
+
transport.emitter.on("error", this.#transportErrorHandler);
|
|
1068
1167
|
}
|
|
1069
1168
|
#unbindTransport() {
|
|
1070
1169
|
const transport = this.#transport;
|
|
1071
1170
|
if (transport === void 0) return;
|
|
1072
|
-
transport.emitter.off("close", this.#
|
|
1073
|
-
transport.emitter.off("error", this.#
|
|
1171
|
+
transport.emitter.off("close", this.#transportCloseHandler);
|
|
1172
|
+
transport.emitter.off("error", this.#transportErrorHandler);
|
|
1074
1173
|
this.#transport = void 0;
|
|
1075
1174
|
}
|
|
1076
1175
|
#bindProcess(process) {
|
|
1077
1176
|
this.#unbindProcess();
|
|
1078
1177
|
if (this.#servingPid !== void 0) return;
|
|
1079
|
-
process.once("exit", this.#
|
|
1080
|
-
if (process.exitCode !== null || process.signalCode !== null) queueMicrotask(this.#
|
|
1178
|
+
process.once("exit", this.#processExitHandler);
|
|
1179
|
+
if (process.exitCode !== null || process.signalCode !== null) queueMicrotask(this.#processExitHandler);
|
|
1081
1180
|
}
|
|
1082
1181
|
#unbindProcess() {
|
|
1083
|
-
this.#process?.off("exit", this.#
|
|
1182
|
+
this.#process?.off("exit", this.#processExitHandler);
|
|
1084
1183
|
}
|
|
1085
1184
|
#timeout() {
|
|
1086
1185
|
return this.#options.timeout ?? BROWSER_DEFAULT_TIMEOUT_MS;
|
|
@@ -1093,13 +1192,9 @@ var Browser = class {
|
|
|
1093
1192
|
if (!response.ok) return this.#notFound();
|
|
1094
1193
|
const info = await response.json();
|
|
1095
1194
|
if (!isRecord(info)) return this.#notFound();
|
|
1096
|
-
const endpoint = isString(info["webSocketDebuggerUrl"]) ? info["webSocketDebuggerUrl"] : void 0;
|
|
1097
|
-
const browser = isString(info["Browser"]) ? info["Browser"] : void 0;
|
|
1098
1195
|
return {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
browser,
|
|
1102
|
-
connection: endpoint !== void 0 ? "cdp" : void 0
|
|
1196
|
+
endpoint: isString(info["webSocketDebuggerUrl"]) ? info["webSocketDebuggerUrl"] : void 0,
|
|
1197
|
+
browser: isString(info["Browser"]) ? info["Browser"] : void 0
|
|
1103
1198
|
};
|
|
1104
1199
|
} catch (error) {
|
|
1105
1200
|
if (signal?.aborted === true) throw error;
|
|
@@ -1108,10 +1203,8 @@ var Browser = class {
|
|
|
1108
1203
|
}
|
|
1109
1204
|
#notFound() {
|
|
1110
1205
|
return {
|
|
1111
|
-
found: false,
|
|
1112
1206
|
endpoint: void 0,
|
|
1113
|
-
browser: void 0
|
|
1114
|
-
connection: void 0
|
|
1207
|
+
browser: void 0
|
|
1115
1208
|
};
|
|
1116
1209
|
}
|
|
1117
1210
|
async #assertPortFree() {
|
|
@@ -1312,13 +1405,13 @@ var Browser = class {
|
|
|
1312
1405
|
if (!isRecord(target) || !isString(target["targetId"]) || target["type"] !== "page" || !isString(target["title"]) || !isString(target["url"])) continue;
|
|
1313
1406
|
pages.push({
|
|
1314
1407
|
id: target["targetId"],
|
|
1315
|
-
|
|
1408
|
+
category: target["type"],
|
|
1316
1409
|
title: target["title"],
|
|
1317
1410
|
url: target["url"]
|
|
1318
1411
|
});
|
|
1319
1412
|
}
|
|
1320
1413
|
if (pages.length === 0 || this.#contexts.length > 0) return;
|
|
1321
|
-
const context = new BrowserContext(client, void 0, this.#options.viewport,
|
|
1414
|
+
const context = new BrowserContext(client, void 0, this.#options.viewport, createBrowserWriter());
|
|
1322
1415
|
await context.sync(pages);
|
|
1323
1416
|
this.#registerContext(context);
|
|
1324
1417
|
}
|
|
@@ -1382,8 +1475,8 @@ var Browser = class {
|
|
|
1382
1475
|
}
|
|
1383
1476
|
}
|
|
1384
1477
|
async #settle() {
|
|
1385
|
-
await this.#connecting?.catch(() => void 0);
|
|
1386
|
-
await this.#disconnecting?.catch(() => void 0);
|
|
1478
|
+
await this.#connecting.pending?.catch(() => void 0);
|
|
1479
|
+
await this.#disconnecting.pending?.catch(() => void 0);
|
|
1387
1480
|
await this.#settleExit();
|
|
1388
1481
|
}
|
|
1389
1482
|
async #settleExit() {
|
|
@@ -1549,6 +1642,6 @@ var Browser = class {
|
|
|
1549
1642
|
}
|
|
1550
1643
|
};
|
|
1551
1644
|
//#endregion
|
|
1552
|
-
export { BROWSER_CDP_LIST_PATH, BROWSER_CDP_PROTOCOL, BROWSER_CDP_VERSION_PATH, BROWSER_DEFAULT_CDP_PORT, BROWSER_DEFAULT_HOST, BROWSER_ENGINE_HINTS, BROWSER_ENV_PATH_KEYS, BROWSER_EXECUTABLE_NAMES, BROWSER_EXECUTABLE_PATHS, BROWSER_HEADLESS_ARG, BROWSER_KILL_GRACE_MS, BROWSER_LAUNCH_ARGS, BROWSER_PORT_PROBE_TIMEOUT_MS, BROWSER_PROCESS_EXIT_CAUSE, BROWSER_PROFILE_PREFIX, BROWSER_STORE_CACHE_DIRS, BROWSER_STORE_DEFAULT_DIRS, BROWSER_STORE_ENV_KEY, BROWSER_STORE_GLOBS, BROWSER_STORE_LINK_NAME, BROWSER_TRANSPORT_LOSS_CAUSE, BROWSER_TRANSPORT_LOSS_DEFER_MS, BROWSER_WINDOWS_ROOT_FALLBACKS, BROWSER_WINDOWS_SUFFIXES, Browser, BrowserConnectionError, BrowserDestroyedError, BrowserNotConnectedError, WebSocketCDPTransport, browserToEngine,
|
|
1645
|
+
export { BROWSER_CDP_LIST_PATH, BROWSER_CDP_PROTOCOL, BROWSER_CDP_VERSION_PATH, BROWSER_DEFAULT_CDP_PORT, BROWSER_DEFAULT_HOST, BROWSER_ENGINE_HINTS, BROWSER_ENV_PATH_KEYS, BROWSER_EXECUTABLE_NAMES, BROWSER_EXECUTABLE_PATHS, BROWSER_HEADLESS_ARG, BROWSER_KILL_GRACE_MS, BROWSER_LAUNCH_ARGS, BROWSER_PORT_PROBE_TIMEOUT_MS, BROWSER_PROCESS_EXIT_CAUSE, BROWSER_PROFILE_PREFIX, BROWSER_STORE_CACHE_DIRS, BROWSER_STORE_DEFAULT_DIRS, BROWSER_STORE_ENV_KEY, BROWSER_STORE_GLOBS, BROWSER_STORE_LINK_NAME, BROWSER_TRANSPORT_LOSS_CAUSE, BROWSER_TRANSPORT_LOSS_DEFER_MS, BROWSER_WINDOWS_ROOT_FALLBACKS, BROWSER_WINDOWS_SUFFIXES, Browser, BrowserConnectionError, BrowserDestroyedError, BrowserNotConnectedError, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserProfile, createBrowserWriter, createCDPTransport, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowser, findSystemBrowsers, isBrowserConnectionError, isBrowserDestroyedError, isBrowserNotConnectedError, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, probePathNames, readFirstLine, removeBrowserProfile, waitForCDPReady };
|
|
1553
1646
|
|
|
1554
1647
|
//# sourceMappingURL=index.js.map
|