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