@orkestrel/browser 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/index.cjs +422 -162
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +11 -2
- package/dist/src/core/index.d.ts +11 -2
- package/dist/src/core/index.js +422 -162
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +588 -411
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +52 -53
- package/dist/src/server/index.d.ts +52 -53
- package/dist/src/server/index.js +586 -410
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -6
|
@@ -3,11 +3,14 @@ let _src_core = require("../core/index.cjs");
|
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
|
6
|
+
let node_timers_promises = require("node:timers/promises");
|
|
6
7
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
7
8
|
let node_crypto = require("node:crypto");
|
|
8
9
|
let node_http = require("node:http");
|
|
10
|
+
let node_https = require("node:https");
|
|
9
11
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
10
12
|
let _orkestrel_websocket = require("@orkestrel/websocket");
|
|
13
|
+
let node_events = require("node:events");
|
|
11
14
|
let node_fs_promises = require("node:fs/promises");
|
|
12
15
|
//#region src/server/constants.ts
|
|
13
16
|
/** Default CDP port probed for an existing browser and used for launches. */
|
|
@@ -20,13 +23,6 @@ var BROWSER_CDP_PROTOCOL = "http";
|
|
|
20
23
|
var BROWSER_CDP_VERSION_PATH = "/json/version";
|
|
21
24
|
/** Path appended to the CDP host to list open targets (pages, workers, etc). */
|
|
22
25
|
var BROWSER_CDP_LIST_PATH = "/json/list";
|
|
23
|
-
/** Sentinel result returned by discovery when no browser is reachable. */
|
|
24
|
-
var BROWSER_NOT_FOUND_RESULT = Object.freeze({
|
|
25
|
-
found: false,
|
|
26
|
-
endpoint: void 0,
|
|
27
|
-
browser: void 0,
|
|
28
|
-
connection: void 0
|
|
29
|
-
});
|
|
30
26
|
/** Flags always passed to a launched browser process, alongside the caller's own. */
|
|
31
27
|
var BROWSER_LAUNCH_ARGS = Object.freeze(["--no-first-run", "--no-default-browser-check"]);
|
|
32
28
|
/** Flag enabling headless mode on a launched browser process. */
|
|
@@ -37,6 +33,10 @@ var BROWSER_KILL_GRACE_MS = 3e3;
|
|
|
37
33
|
var BROWSER_PORT_PROBE_TIMEOUT_MS = 200;
|
|
38
34
|
/** Brief defer applied once when a transport loss is observed on an owned process, giving a near-simultaneous process-exit event (which libuv may reap slightly later than the socket close) first say over the diagnosis. */
|
|
39
35
|
var BROWSER_TRANSPORT_LOSS_DEFER_MS = 50;
|
|
36
|
+
/** Machine-readable error-context cause for an owned browser process exiting. */
|
|
37
|
+
var BROWSER_PROCESS_EXIT_CAUSE = "process-exit";
|
|
38
|
+
/** Machine-readable error-context cause for a CDP transport disconnecting while its browser remains alive. */
|
|
39
|
+
var BROWSER_TRANSPORT_LOSS_CAUSE = "transport-loss";
|
|
40
40
|
/** Environment variables checked (in order) for an explicit browser executable path override. */
|
|
41
41
|
var BROWSER_ENV_PATH_KEYS = Object.freeze(["PLAYWRIGHT_EXECUTABLE_PATH", "CHROME_PATH"]);
|
|
42
42
|
/** Well-known Chrome/Chromium/Edge executable paths with no platform-specific root, keyed by `process.platform`. */
|
|
@@ -260,8 +260,8 @@ function normalizeExecutablePath(path, platform) {
|
|
|
260
260
|
}
|
|
261
261
|
/** Classify a `/json/version` `Browser` string into a {@link BrowserEngine} (`Edg/` → edge, `Chrome/` → chrome, else chromium). */
|
|
262
262
|
function browserToEngine(browser) {
|
|
263
|
-
if (browser
|
|
264
|
-
if (browser
|
|
263
|
+
if (browser?.includes("Edg/") === true) return "edge";
|
|
264
|
+
if (browser?.includes("Chrome/") === true) return "chrome";
|
|
265
265
|
return "chromium";
|
|
266
266
|
}
|
|
267
267
|
/** Check the env-override keys (`PLAYWRIGHT_EXECUTABLE_PATH`, `CHROME_PATH`) in order for an existing file. */
|
|
@@ -347,7 +347,7 @@ function findAllInStore(base, platform) {
|
|
|
347
347
|
const glob = BROWSER_STORE_GLOBS[platform];
|
|
348
348
|
if (glob === void 0) return found;
|
|
349
349
|
const matches = (0, node_fs.globSync)(`${base.replaceAll("\\", "/")}/${glob}`).filter((match) => (0, node_fs.existsSync)(match));
|
|
350
|
-
matches.sort((a, b) => a
|
|
350
|
+
matches.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
351
351
|
found.push(...matches);
|
|
352
352
|
return found;
|
|
353
353
|
}
|
|
@@ -379,23 +379,24 @@ function launchBrowserProcess(executable, port, headless, profile, extra) {
|
|
|
379
379
|
*
|
|
380
380
|
* @throws When the endpoint does not become ready before the timeout
|
|
381
381
|
*/
|
|
382
|
-
async function
|
|
382
|
+
async function waitForCDPReady(port, timeout, host = BROWSER_DEFAULT_HOST, signal) {
|
|
383
383
|
const url = `${BROWSER_CDP_PROTOCOL}://${host}:${port}${BROWSER_CDP_VERSION_PATH}`;
|
|
384
384
|
const deadline = Date.now() + timeout;
|
|
385
385
|
while (Date.now() < deadline) {
|
|
386
|
+
signal?.throwIfAborted();
|
|
386
387
|
const remaining = Math.max(0, deadline - Date.now());
|
|
387
|
-
const
|
|
388
|
-
const timer = setTimeout(() => controller.abort(), remaining);
|
|
388
|
+
const requestSignal = signal === void 0 ? AbortSignal.timeout(remaining) : AbortSignal.any([signal, AbortSignal.timeout(remaining)]);
|
|
389
389
|
try {
|
|
390
|
-
const response = await fetch(url, { signal:
|
|
390
|
+
const response = await fetch(url, { signal: requestSignal });
|
|
391
391
|
if (response.ok) {
|
|
392
392
|
const info = await response.json();
|
|
393
393
|
if ((0, _orkestrel_contract.isRecord)(info) && (0, _orkestrel_contract.isString)(info["webSocketDebuggerUrl"])) return info["webSocketDebuggerUrl"];
|
|
394
394
|
}
|
|
395
|
-
} catch
|
|
396
|
-
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (signal?.aborted === true) throw error;
|
|
397
397
|
}
|
|
398
|
-
|
|
398
|
+
const delay = Math.min(_src_core.BROWSER_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now()));
|
|
399
|
+
if (delay > 0) await (0, node_timers_promises.setTimeout)(delay, void 0, { signal });
|
|
399
400
|
}
|
|
400
401
|
throw new Error(`CDP endpoint on port ${port} did not become ready within ${timeout}ms`);
|
|
401
402
|
}
|
|
@@ -407,7 +408,7 @@ async function waitForCdpReady(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
|
407
408
|
* @param host - Host the browser exposes its CDP endpoint on (default `127.0.0.1`)
|
|
408
409
|
* @returns Normalized CDP targets
|
|
409
410
|
*/
|
|
410
|
-
async function
|
|
411
|
+
async function fetchCDPTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
411
412
|
const url = `${BROWSER_CDP_PROTOCOL}://${host}:${port}${BROWSER_CDP_LIST_PATH}`;
|
|
412
413
|
const controller = new AbortController();
|
|
413
414
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
@@ -419,12 +420,12 @@ async function fetchCdpTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
|
419
420
|
const targets = [];
|
|
420
421
|
for (const entry of list) {
|
|
421
422
|
if (!(0, _orkestrel_contract.isRecord)(entry)) continue;
|
|
422
|
-
if (!(0, _orkestrel_contract.isString)(entry["id"]) || !(0, _orkestrel_contract.isString)(entry["type"])) continue;
|
|
423
|
+
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;
|
|
423
424
|
targets.push({
|
|
424
425
|
id: entry["id"],
|
|
425
426
|
type: entry["type"],
|
|
426
|
-
title:
|
|
427
|
-
url:
|
|
427
|
+
title: entry["title"],
|
|
428
|
+
url: entry["url"]
|
|
428
429
|
});
|
|
429
430
|
}
|
|
430
431
|
return targets;
|
|
@@ -437,33 +438,32 @@ async function fetchCdpTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
|
437
438
|
//#endregion
|
|
438
439
|
//#region src/server/transports/WebSocketCDPTransport.ts
|
|
439
440
|
/**
|
|
440
|
-
* `@orkestrel/websocket`-backed
|
|
441
|
-
* pipe a {@link import('@src/core').CDPClientInterface} sends and receives
|
|
442
|
-
* JSON-RPC frames over.
|
|
441
|
+
* `@orkestrel/websocket`-backed raw CDP text transport.
|
|
443
442
|
*
|
|
444
443
|
* @remarks
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* `
|
|
448
|
-
*
|
|
449
|
-
* — a mismatch (or a connection/request error, or a timeout) rejects `start()`
|
|
450
|
-
* with a coded {@link BrowserConnectionError} and cleans up the socket. On
|
|
451
|
-
* success it wraps the raw upgraded socket in `createNodeWebSocket({ socket,
|
|
452
|
-
* head })` (CLIENT mode — no `key` — so outgoing frames are masked per RFC
|
|
453
|
-
* 6455 §5.3) and bridges its `message`/`close`/`error` events onto this
|
|
454
|
-
* transport's emitter. `start()` opens a fresh socket each call (safe to call
|
|
455
|
-
* again after `close()` — a new request/socket/NodeWebSocket triple is
|
|
456
|
-
* created every time, which is what `CDPClient.reconnect()` depends on). CDP
|
|
457
|
-
* frames are text-only, so `message` always carries a `string`.
|
|
444
|
+
* Performs and validates the RFC 6455 HTTP upgrade, then delegates frame
|
|
445
|
+
* handling to `@orkestrel/websocket`. Concurrent and repeated `start()` /
|
|
446
|
+
* `close()` calls share their active transition, and a later `start()` opens a
|
|
447
|
+
* fresh socket after the prior one closes.
|
|
458
448
|
*/
|
|
459
449
|
var WebSocketCDPTransport = class {
|
|
460
450
|
#emitter;
|
|
461
451
|
#url;
|
|
462
452
|
#timeout;
|
|
463
453
|
#socket;
|
|
464
|
-
#
|
|
454
|
+
#starting;
|
|
455
|
+
#closing;
|
|
456
|
+
#request;
|
|
457
|
+
#resolve;
|
|
458
|
+
#reject;
|
|
459
|
+
#timer;
|
|
460
|
+
#closeResolve;
|
|
461
|
+
#onSocketClose = () => this.#closeResolve?.();
|
|
465
462
|
constructor(options) {
|
|
466
|
-
this.#emitter = new _orkestrel_emitter.Emitter(
|
|
463
|
+
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
464
|
+
on: options.on,
|
|
465
|
+
error: options.error
|
|
466
|
+
});
|
|
467
467
|
this.#url = options.url;
|
|
468
468
|
this.#timeout = options.timeout ?? _src_core.BROWSER_DEFAULT_TIMEOUT_MS;
|
|
469
469
|
}
|
|
@@ -471,62 +471,22 @@ var WebSocketCDPTransport = class {
|
|
|
471
471
|
return this.#emitter;
|
|
472
472
|
}
|
|
473
473
|
async start() {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
port: url.port.length > 0 ? Number(url.port) : 80,
|
|
491
|
-
path: `${url.pathname}${url.search}`,
|
|
492
|
-
headers: {
|
|
493
|
-
Connection: "Upgrade",
|
|
494
|
-
Upgrade: "websocket",
|
|
495
|
-
"Sec-WebSocket-Key": key,
|
|
496
|
-
"Sec-WebSocket-Version": _orkestrel_websocket.WEBSOCKET_VERSION
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
request.on("upgrade", (response, socket, head) => {
|
|
500
|
-
clearTimeout(timer);
|
|
501
|
-
const accept = response.headers["sec-websocket-accept"];
|
|
502
|
-
if (!(0, _orkestrel_contract.isString)(accept) || accept !== (0, _orkestrel_websocket.computeWebSocketAccept)(key)) {
|
|
503
|
-
socket.destroy();
|
|
504
|
-
settle(() => reject(new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} failed: Sec-WebSocket-Accept mismatch`, { url: this.#url })));
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
settle(() => resolve((0, _orkestrel_websocket.createNodeWebSocket)({
|
|
508
|
-
socket,
|
|
509
|
-
head
|
|
510
|
-
})));
|
|
511
|
-
});
|
|
512
|
-
request.on("response", (response) => {
|
|
513
|
-
clearTimeout(timer);
|
|
514
|
-
response.resume();
|
|
515
|
-
settle(() => reject(new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} failed: upgrade declined with status ${response.statusCode ?? 0}`, { url: this.#url })));
|
|
516
|
-
});
|
|
517
|
-
request.on("error", (error) => {
|
|
518
|
-
clearTimeout(timer);
|
|
519
|
-
settle(() => reject(new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} failed: ${error.message}`, { url: this.#url })));
|
|
520
|
-
});
|
|
521
|
-
this.#connecting = {
|
|
522
|
-
abort: () => request.destroy(),
|
|
523
|
-
timer,
|
|
524
|
-
reject: (error) => settle(() => reject(error))
|
|
525
|
-
};
|
|
526
|
-
request.end();
|
|
527
|
-
});
|
|
528
|
-
this.#socket = ws;
|
|
529
|
-
this.#bind(ws);
|
|
474
|
+
if (this.#closing !== void 0) await this.#closing;
|
|
475
|
+
if (this.#socket?.readyState === _orkestrel_websocket.WEBSOCKET_READY_OPEN) return;
|
|
476
|
+
const active = this.#starting;
|
|
477
|
+
if (active !== void 0) {
|
|
478
|
+
await active;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
this.#socket?.destroy();
|
|
482
|
+
this.#socket = void 0;
|
|
483
|
+
const attempt = this.#open();
|
|
484
|
+
this.#starting = attempt;
|
|
485
|
+
try {
|
|
486
|
+
await attempt;
|
|
487
|
+
} finally {
|
|
488
|
+
if (this.#starting === attempt) this.#starting = void 0;
|
|
489
|
+
}
|
|
530
490
|
}
|
|
531
491
|
async send(data) {
|
|
532
492
|
const socket = this.#socket;
|
|
@@ -534,32 +494,139 @@ var WebSocketCDPTransport = class {
|
|
|
534
494
|
socket.send(data);
|
|
535
495
|
}
|
|
536
496
|
async close() {
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
this.#connecting = void 0;
|
|
541
|
-
connecting.abort();
|
|
542
|
-
connecting.reject(new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} was closed before it finished connecting`, { url: this.#url }));
|
|
497
|
+
const active = this.#closing;
|
|
498
|
+
if (active !== void 0) {
|
|
499
|
+
await active;
|
|
543
500
|
return;
|
|
544
501
|
}
|
|
502
|
+
const attempt = this.#stop();
|
|
503
|
+
this.#closing = attempt;
|
|
504
|
+
try {
|
|
505
|
+
await attempt;
|
|
506
|
+
} finally {
|
|
507
|
+
if (this.#closing === attempt) this.#closing = void 0;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async #open() {
|
|
511
|
+
const url = new URL(this.#url);
|
|
512
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") throw new BrowserConnectionError(`WebSocket CDP connection requires a ws: or wss: URL: ${this.#url}`, { url: this.#url });
|
|
513
|
+
const key = (0, node_crypto.randomBytes)(16).toString("base64");
|
|
514
|
+
const deferred = Promise.withResolvers();
|
|
515
|
+
const options = {
|
|
516
|
+
hostname: url.hostname,
|
|
517
|
+
port: url.port.length > 0 ? Number(url.port) : url.protocol === "wss:" ? 443 : 80,
|
|
518
|
+
path: `${url.pathname}${url.search}`,
|
|
519
|
+
headers: {
|
|
520
|
+
Connection: "Upgrade",
|
|
521
|
+
Upgrade: "websocket",
|
|
522
|
+
"Sec-WebSocket-Key": key,
|
|
523
|
+
"Sec-WebSocket-Version": _orkestrel_websocket.WEBSOCKET_VERSION
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const request = url.protocol === "wss:" ? (0, node_https.request)(options) : (0, node_http.request)(options);
|
|
527
|
+
this.#request = request;
|
|
528
|
+
this.#resolve = deferred.resolve;
|
|
529
|
+
this.#reject = deferred.reject;
|
|
530
|
+
this.#timer = setTimeout(() => this.#expire(request), this.#timeout);
|
|
531
|
+
request.on("upgrade", (response, socket, head) => {
|
|
532
|
+
this.#upgrade(request, key, response, socket, head);
|
|
533
|
+
});
|
|
534
|
+
request.on("response", (response) => this.#decline(request, response));
|
|
535
|
+
request.on("error", (error) => this.#fail(request, error));
|
|
536
|
+
request.end();
|
|
537
|
+
const socket = await deferred.promise;
|
|
538
|
+
this.#socket = socket;
|
|
539
|
+
this.#bind(socket);
|
|
540
|
+
}
|
|
541
|
+
async #stop() {
|
|
542
|
+
const request = this.#request;
|
|
543
|
+
if (request !== void 0) {
|
|
544
|
+
this.#rejectRequest(request, new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} was closed before it finished connecting`, { url: this.#url }));
|
|
545
|
+
request.destroy();
|
|
546
|
+
}
|
|
547
|
+
await this.#starting?.catch(() => void 0);
|
|
545
548
|
const socket = this.#socket;
|
|
546
|
-
if (socket === void 0) return;
|
|
547
549
|
this.#socket = void 0;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
+
if (socket === void 0 || socket.readyState === _orkestrel_websocket.WEBSOCKET_READY_CLOSED) return;
|
|
551
|
+
const closed = Promise.withResolvers();
|
|
552
|
+
this.#closeResolve = closed.resolve;
|
|
553
|
+
socket.emitter.on("close", this.#onSocketClose);
|
|
554
|
+
try {
|
|
550
555
|
socket.close();
|
|
551
|
-
|
|
556
|
+
if (socket.readyState !== _orkestrel_websocket.WEBSOCKET_READY_CLOSED) await closed.promise;
|
|
557
|
+
} catch {
|
|
558
|
+
socket.destroy();
|
|
559
|
+
if (socket.readyState !== _orkestrel_websocket.WEBSOCKET_READY_CLOSED) await closed.promise;
|
|
560
|
+
} finally {
|
|
561
|
+
socket.emitter.off("close", this.#onSocketClose);
|
|
562
|
+
this.#closeResolve = void 0;
|
|
563
|
+
}
|
|
552
564
|
}
|
|
553
|
-
#
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
565
|
+
#upgrade(request, key, response, socket, head) {
|
|
566
|
+
if (this.#request !== request) {
|
|
567
|
+
socket.destroy();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const accept = response.headers["sec-websocket-accept"];
|
|
571
|
+
if (!(0, _orkestrel_contract.isString)(accept) || accept !== (0, _orkestrel_websocket.computeWebSocketAccept)(key)) {
|
|
572
|
+
socket.destroy();
|
|
573
|
+
this.#rejectRequest(request, new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} failed: Sec-WebSocket-Accept mismatch`, { url: this.#url }));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
this.#resolveRequest(request, (0, _orkestrel_websocket.createNodeWebSocket)({
|
|
578
|
+
socket,
|
|
579
|
+
head
|
|
580
|
+
}));
|
|
581
|
+
} catch (error) {
|
|
582
|
+
socket.destroy();
|
|
583
|
+
this.#rejectRequest(request, error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
#decline(request, response) {
|
|
587
|
+
response.resume();
|
|
588
|
+
const reason = response.statusCode === void 0 ? "upgrade was declined without a status code" : `upgrade declined with status ${response.statusCode}`;
|
|
589
|
+
this.#rejectRequest(request, new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} failed: ${reason}`, { url: this.#url }));
|
|
590
|
+
}
|
|
591
|
+
#fail(request, error) {
|
|
592
|
+
this.#rejectRequest(request, new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} failed: ${error.message}`, { url: this.#url }));
|
|
593
|
+
}
|
|
594
|
+
#expire(request) {
|
|
595
|
+
this.#rejectRequest(request, new BrowserConnectionError(`WebSocket CDP connection to ${this.#url} timed out after ${this.#timeout}ms`, {
|
|
596
|
+
url: this.#url,
|
|
597
|
+
timeout: this.#timeout
|
|
598
|
+
}));
|
|
599
|
+
request.destroy();
|
|
600
|
+
}
|
|
601
|
+
#resolveRequest(request, socket) {
|
|
602
|
+
if (this.#request !== request) {
|
|
603
|
+
socket.destroy();
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const resolve = this.#resolve;
|
|
607
|
+
this.#clearRequest();
|
|
608
|
+
resolve?.(socket);
|
|
609
|
+
}
|
|
610
|
+
#rejectRequest(request, error) {
|
|
611
|
+
if (this.#request !== request) return;
|
|
612
|
+
const reject = this.#reject;
|
|
613
|
+
this.#clearRequest();
|
|
614
|
+
reject?.(error);
|
|
615
|
+
}
|
|
616
|
+
#clearRequest() {
|
|
617
|
+
if (this.#timer !== void 0) clearTimeout(this.#timer);
|
|
618
|
+
this.#timer = void 0;
|
|
619
|
+
this.#request = void 0;
|
|
620
|
+
this.#resolve = void 0;
|
|
621
|
+
this.#reject = void 0;
|
|
622
|
+
}
|
|
623
|
+
#bind(socket) {
|
|
624
|
+
socket.emitter.on("message", (data) => this.#emitter.emit("message", data));
|
|
625
|
+
socket.emitter.on("close", () => {
|
|
626
|
+
if (this.#socket === socket) this.#socket = void 0;
|
|
558
627
|
this.#emitter.emit("close");
|
|
559
628
|
});
|
|
560
|
-
|
|
561
|
-
this.#emitter.emit("error", error);
|
|
562
|
-
});
|
|
629
|
+
socket.emitter.on("error", (error) => this.#emitter.emit("error", error));
|
|
563
630
|
}
|
|
564
631
|
};
|
|
565
632
|
//#endregion
|
|
@@ -606,20 +673,30 @@ function createScreenshotWriter() {
|
|
|
606
673
|
var Browser = class {
|
|
607
674
|
#emitter;
|
|
608
675
|
#options;
|
|
676
|
+
#abort = new AbortController();
|
|
677
|
+
#cdpPort;
|
|
678
|
+
#cdpHost;
|
|
609
679
|
#engine;
|
|
610
680
|
#status = "idle";
|
|
611
681
|
#connection;
|
|
682
|
+
#owned;
|
|
683
|
+
#endpoint;
|
|
612
684
|
#client;
|
|
613
685
|
#process;
|
|
614
|
-
#cdpPort;
|
|
615
|
-
#cdpHost;
|
|
616
686
|
#contexts = [];
|
|
617
687
|
#destroyed = false;
|
|
618
|
-
#
|
|
619
|
-
#
|
|
620
|
-
#
|
|
688
|
+
#connecting;
|
|
689
|
+
#disconnecting;
|
|
690
|
+
#shutdown;
|
|
691
|
+
#transport;
|
|
692
|
+
#onTransportClose = () => this.#handleTransportLoss();
|
|
693
|
+
#onTransportError = () => this.#handleTransportLoss();
|
|
694
|
+
#onProcessExit = () => this.#handleProcessExit();
|
|
621
695
|
constructor(options) {
|
|
622
|
-
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
696
|
+
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
697
|
+
on: options?.on,
|
|
698
|
+
error: options?.error
|
|
699
|
+
});
|
|
623
700
|
this.#options = options ?? {};
|
|
624
701
|
this.#engine = this.#options.engine ?? (this.#options.executable !== void 0 ? parseBrowserEngine(this.#options.executable) ?? "chromium" : "chromium");
|
|
625
702
|
this.#cdpPort = this.#options.cdp?.port ?? 9222;
|
|
@@ -638,63 +715,62 @@ var Browser = class {
|
|
|
638
715
|
get connection() {
|
|
639
716
|
return this.#connection;
|
|
640
717
|
}
|
|
718
|
+
get owned() {
|
|
719
|
+
return this.#owned;
|
|
720
|
+
}
|
|
641
721
|
get connected() {
|
|
642
722
|
return this.#status === "connected";
|
|
643
723
|
}
|
|
644
724
|
get pid() {
|
|
645
|
-
return this.#process?.pid
|
|
725
|
+
return this.#process?.pid;
|
|
646
726
|
}
|
|
647
727
|
async discover() {
|
|
648
|
-
const result = await this.#
|
|
728
|
+
const result = await this.#discoverCDP();
|
|
649
729
|
this.#emitter.emit("discover", result);
|
|
650
730
|
return result;
|
|
651
731
|
}
|
|
652
732
|
async connect() {
|
|
733
|
+
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
734
|
+
if (this.#disconnecting !== void 0) await this.#disconnecting;
|
|
653
735
|
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
654
736
|
if (this.#status === "connected") return;
|
|
655
|
-
this.#
|
|
656
|
-
|
|
737
|
+
const active = this.#connecting;
|
|
738
|
+
if (active !== void 0) {
|
|
739
|
+
await active;
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const attempt = this.#establish();
|
|
743
|
+
this.#connecting = attempt;
|
|
657
744
|
try {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
if (this.#options.cdp?.discover ?? true) {
|
|
664
|
-
this.#assertNotAborted();
|
|
665
|
-
const discovery = await this.#raceAbort(this.#discoverCdp());
|
|
666
|
-
if (discovery.found && discovery.endpoint !== void 0) {
|
|
667
|
-
this.#engine = browserToEngine(discovery.browser ?? "");
|
|
668
|
-
await this.#connectCdp(discovery.endpoint);
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
} else {
|
|
672
|
-
this.#assertNotAborted();
|
|
673
|
-
await this.#raceAbort(this.#assertPortFree());
|
|
674
|
-
}
|
|
675
|
-
this.#assertNotAborted();
|
|
676
|
-
await this.#launch();
|
|
677
|
-
} catch (thrown) {
|
|
678
|
-
this.#status = "error";
|
|
679
|
-
this.#emitter.emit("error", thrown);
|
|
680
|
-
if (thrown instanceof BrowserConnectionError) throw thrown;
|
|
681
|
-
throw new BrowserConnectionError(thrown instanceof Error ? thrown.message : String(thrown), { executable: this.#options.executable });
|
|
745
|
+
await attempt;
|
|
746
|
+
} finally {
|
|
747
|
+
if (this.#connecting === attempt) this.#connecting = void 0;
|
|
682
748
|
}
|
|
683
749
|
}
|
|
750
|
+
adopt() {
|
|
751
|
+
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
752
|
+
if (this.#status !== "connected" || this.#client === void 0 || this.#endpoint === void 0) throw new BrowserNotConnectedError();
|
|
753
|
+
this.#owned = true;
|
|
754
|
+
}
|
|
684
755
|
async disconnect() {
|
|
685
|
-
if (this.#destroyed
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
this.#
|
|
696
|
-
this.#
|
|
697
|
-
this.#
|
|
756
|
+
if (this.#destroyed) return;
|
|
757
|
+
const active = this.#disconnecting;
|
|
758
|
+
if (active !== void 0) {
|
|
759
|
+
await active;
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (this.#connecting !== void 0) {
|
|
763
|
+
await this.#connecting.catch(() => void 0);
|
|
764
|
+
if (this.#destroyed) return;
|
|
765
|
+
}
|
|
766
|
+
if (this.#status !== "connected" || this.#client === void 0) return;
|
|
767
|
+
const attempt = this.#detach();
|
|
768
|
+
this.#disconnecting = attempt;
|
|
769
|
+
try {
|
|
770
|
+
await attempt;
|
|
771
|
+
} finally {
|
|
772
|
+
if (this.#disconnecting === attempt) this.#disconnecting = void 0;
|
|
773
|
+
}
|
|
698
774
|
}
|
|
699
775
|
context(index) {
|
|
700
776
|
const i = index ?? 0;
|
|
@@ -706,237 +782,218 @@ var Browser = class {
|
|
|
706
782
|
}
|
|
707
783
|
async create(options) {
|
|
708
784
|
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
this.#
|
|
785
|
+
const client = this.#client;
|
|
786
|
+
if (this.#status !== "connected" || client === void 0) throw new BrowserNotConnectedError();
|
|
787
|
+
let context = this.#contexts[0];
|
|
788
|
+
if (context === void 0) {
|
|
789
|
+
context = new _src_core.BrowserContext(client, void 0, this.#options.viewport, createScreenshotWriter());
|
|
790
|
+
this.#contexts.push(context);
|
|
714
791
|
}
|
|
715
|
-
const page = await
|
|
792
|
+
const page = await context.create(options);
|
|
716
793
|
this.#emitter.emit("page", page);
|
|
717
794
|
return page;
|
|
718
795
|
}
|
|
719
|
-
|
|
720
|
-
|
|
796
|
+
destroy() {
|
|
797
|
+
const active = this.#shutdown;
|
|
798
|
+
if (active !== void 0) return active;
|
|
799
|
+
if (this.#destroyed) return Promise.resolve();
|
|
721
800
|
this.#destroyed = true;
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
await ctx.close();
|
|
727
|
-
} catch {}
|
|
728
|
-
this.#contexts = [];
|
|
729
|
-
await this.#terminate(this.#process);
|
|
730
|
-
this.#process = void 0;
|
|
731
|
-
if (this.#client !== void 0) {
|
|
732
|
-
try {
|
|
733
|
-
await this.#client.close();
|
|
734
|
-
} catch {}
|
|
735
|
-
this.#client = void 0;
|
|
736
|
-
}
|
|
737
|
-
} finally {
|
|
738
|
-
this.#status = "disconnected";
|
|
739
|
-
this.#connection = void 0;
|
|
740
|
-
this.#lastPid = void 0;
|
|
741
|
-
this.#emitter.emit("destroy");
|
|
742
|
-
this.#emitter.destroy();
|
|
743
|
-
}
|
|
801
|
+
this.#abort.abort();
|
|
802
|
+
const shutdown = this.#destroyResources();
|
|
803
|
+
this.#shutdown = shutdown;
|
|
804
|
+
return shutdown;
|
|
744
805
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
* @remarks
|
|
750
|
-
* Sends CDP `Browser.close` best-effort; on an owned browser also awaits
|
|
751
|
-
* the process's exit (bounded by the kill-escalation grace period,
|
|
752
|
-
* escalating to a kill only if it does not exit in time). Ends in the
|
|
753
|
-
* same local-cleanup state as `destroy()`.
|
|
754
|
-
*/
|
|
755
|
-
async close() {
|
|
756
|
-
if (this.#destroyed) return;
|
|
806
|
+
close() {
|
|
807
|
+
const active = this.#shutdown;
|
|
808
|
+
if (active !== void 0) return active;
|
|
809
|
+
if (this.#destroyed) return Promise.resolve();
|
|
757
810
|
this.#destroyed = true;
|
|
811
|
+
this.#abort.abort();
|
|
812
|
+
const shutdown = this.#closeResources();
|
|
813
|
+
this.#shutdown = shutdown;
|
|
814
|
+
return shutdown;
|
|
815
|
+
}
|
|
816
|
+
async #establish() {
|
|
817
|
+
this.#assertNotAborted();
|
|
818
|
+
this.#status = "connecting";
|
|
758
819
|
try {
|
|
759
|
-
this.#
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (client !== void 0) try {
|
|
763
|
-
await client.send("Browser.close");
|
|
764
|
-
} catch {}
|
|
765
|
-
const process = this.#process;
|
|
766
|
-
if (process !== void 0) {
|
|
767
|
-
const exited = new Promise((resolve) => {
|
|
768
|
-
process.once("exit", () => resolve());
|
|
769
|
-
});
|
|
770
|
-
let timer;
|
|
771
|
-
const timedOut = await Promise.race([exited.then(() => false), new Promise((resolve) => {
|
|
772
|
-
timer = setTimeout(() => resolve(true), BROWSER_KILL_GRACE_MS);
|
|
773
|
-
})]);
|
|
774
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
775
|
-
if (timedOut) await this.#terminate(process);
|
|
776
|
-
this.#process = void 0;
|
|
820
|
+
if (this.#owned === true && this.#endpoint !== void 0) {
|
|
821
|
+
await this.#connectCDP(this.#endpoint);
|
|
822
|
+
return;
|
|
777
823
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (client !== void 0) {
|
|
783
|
-
try {
|
|
784
|
-
await client.close();
|
|
785
|
-
} catch {}
|
|
786
|
-
this.#client = void 0;
|
|
824
|
+
const endpoint = this.#options.cdp?.endpoint;
|
|
825
|
+
if (endpoint !== void 0) {
|
|
826
|
+
await this.#connectCDP(endpoint);
|
|
827
|
+
return;
|
|
787
828
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
829
|
+
if (this.#options.cdp?.discover ?? true) {
|
|
830
|
+
const discovery = await this.#raceAbort(this.#discoverCDP(this.#signal()));
|
|
831
|
+
if (discovery.found && discovery.endpoint !== void 0) {
|
|
832
|
+
this.#engine = browserToEngine(discovery.browser);
|
|
833
|
+
await this.#connectCDP(discovery.endpoint);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
} else await this.#raceAbort(this.#assertPortFree());
|
|
837
|
+
this.#assertNotAborted();
|
|
838
|
+
await this.#launch();
|
|
839
|
+
} catch (error) {
|
|
840
|
+
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
841
|
+
this.#status = "error";
|
|
842
|
+
this.#emitter.emit("error", error);
|
|
843
|
+
if (error instanceof BrowserConnectionError) throw error;
|
|
844
|
+
throw new BrowserConnectionError(error instanceof Error ? error.message : String(error), { executable: this.#options.executable });
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
async #detach() {
|
|
848
|
+
this.#status = "disconnected";
|
|
849
|
+
this.#connection = void 0;
|
|
850
|
+
this.#unbindTransport();
|
|
851
|
+
const client = this.#client;
|
|
852
|
+
this.#client = void 0;
|
|
853
|
+
await this.#destroyContexts();
|
|
854
|
+
this.#contexts = [];
|
|
855
|
+
if (this.#owned !== true) {
|
|
856
|
+
this.#owned = void 0;
|
|
857
|
+
this.#endpoint = void 0;
|
|
794
858
|
}
|
|
859
|
+
await this.#closeClient(client);
|
|
860
|
+
if (this.#destroyed) return;
|
|
861
|
+
this.#emitter.emit("disconnect");
|
|
862
|
+
this.#emitter.emit("idle");
|
|
795
863
|
}
|
|
796
864
|
#assertNotAborted() {
|
|
797
|
-
if (this.#
|
|
865
|
+
if (this.#signal().aborted) throw new BrowserConnectionError("Connection aborted");
|
|
866
|
+
}
|
|
867
|
+
#signal() {
|
|
868
|
+
const external = this.#options.signal;
|
|
869
|
+
return external === void 0 ? this.#abort.signal : AbortSignal.any([this.#abort.signal, external]);
|
|
798
870
|
}
|
|
799
|
-
/**
|
|
800
|
-
* Race a promise against the connection's external AbortSignal (if any),
|
|
801
|
-
* rejecting promptly with a coded BrowserConnectionError when it fires.
|
|
802
|
-
*/
|
|
803
871
|
async #raceAbort(promise) {
|
|
804
|
-
const signal = this.#
|
|
805
|
-
if (signal === void 0) return promise;
|
|
872
|
+
const signal = this.#signal();
|
|
806
873
|
if (signal.aborted) throw new BrowserConnectionError("Connection aborted");
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
abortWon = true;
|
|
811
|
-
cleanup();
|
|
812
|
-
reject(new BrowserConnectionError("Connection aborted"));
|
|
813
|
-
};
|
|
814
|
-
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
815
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
816
|
-
promise.then((value) => {
|
|
817
|
-
cleanup();
|
|
818
|
-
resolve(value);
|
|
819
|
-
}, (error) => {
|
|
820
|
-
cleanup();
|
|
821
|
-
reject(error);
|
|
822
|
-
});
|
|
823
|
-
}).catch((error) => {
|
|
824
|
-
if (abortWon) promise.catch(() => void 0);
|
|
825
|
-
throw error;
|
|
874
|
+
const aborted = Promise.withResolvers();
|
|
875
|
+
const listener = (0, node_events.addAbortListener)(signal, () => {
|
|
876
|
+
aborted.reject(new BrowserConnectionError("Connection aborted"));
|
|
826
877
|
});
|
|
878
|
+
try {
|
|
879
|
+
return await Promise.race([promise, aborted.promise]);
|
|
880
|
+
} finally {
|
|
881
|
+
listener[Symbol.dispose]();
|
|
882
|
+
promise.catch(() => void 0);
|
|
883
|
+
}
|
|
827
884
|
}
|
|
828
|
-
#reset() {
|
|
829
|
-
this.#contexts = [];
|
|
830
|
-
this.#status = "disconnected";
|
|
831
|
-
this.#connection = void 0;
|
|
832
|
-
this.#client = void 0;
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* Handle the owned process exiting on its own (not via destroy()/close()).
|
|
836
|
-
* The process is already gone — no kill is attempted. Cleans up orphaned
|
|
837
|
-
* state so the next connect() call can start fresh, and emits a coded
|
|
838
|
-
* `error` (cause: process exit) before `disconnect`.
|
|
839
|
-
*/
|
|
840
885
|
#handleProcessExit() {
|
|
841
886
|
if (this.#destroyed) return;
|
|
842
|
-
|
|
887
|
+
const connected = this.#status === "connected";
|
|
888
|
+
const client = this.#client;
|
|
889
|
+
const contexts = this.#contexts;
|
|
843
890
|
this.#unbindTransport();
|
|
844
891
|
this.#unbindProcess();
|
|
845
|
-
const client = this.#client;
|
|
846
892
|
this.#process = void 0;
|
|
847
|
-
this.#
|
|
848
|
-
this.#
|
|
849
|
-
|
|
850
|
-
this.#
|
|
851
|
-
this.#
|
|
852
|
-
this.#
|
|
893
|
+
this.#client = void 0;
|
|
894
|
+
this.#contexts = [];
|
|
895
|
+
this.#connection = void 0;
|
|
896
|
+
this.#owned = void 0;
|
|
897
|
+
this.#endpoint = void 0;
|
|
898
|
+
if (connected) this.#status = "disconnected";
|
|
899
|
+
this.#destroyContextList(contexts);
|
|
900
|
+
this.#closeClient(client);
|
|
901
|
+
this.#emitter.emit("error", new BrowserConnectionError("The browser process exited unexpectedly", { cause: BROWSER_PROCESS_EXIT_CAUSE }));
|
|
902
|
+
if (connected) {
|
|
903
|
+
this.#emitter.emit("disconnect");
|
|
904
|
+
this.#emitter.emit("idle");
|
|
905
|
+
}
|
|
853
906
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
* still alive, or the connection is CDP-attached. Never kills the
|
|
857
|
-
* process — only the local client is reset, so the SAME instance can
|
|
858
|
-
* reconnect (e.g. rediscover on the port and reattach). Emits a coded
|
|
859
|
-
* `error` (cause: connection loss) before `disconnect`.
|
|
860
|
-
*/
|
|
861
|
-
#handleTransportLoss(deferred = false) {
|
|
862
|
-
if (this.#destroyed) return;
|
|
863
|
-
if (this.#status !== "connected") return;
|
|
907
|
+
#handleTransportLoss() {
|
|
908
|
+
if (this.#destroyed || this.#status !== "connected") return;
|
|
864
909
|
const process = this.#process;
|
|
865
910
|
if (process !== void 0 && (process.exitCode !== null || process.signalCode !== null)) {
|
|
866
911
|
this.#handleProcessExit();
|
|
867
912
|
return;
|
|
868
913
|
}
|
|
869
|
-
if (process !== void 0
|
|
870
|
-
setTimeout(() => this.#
|
|
914
|
+
if (process !== void 0) {
|
|
915
|
+
setTimeout(() => this.#confirmTransportLoss(), 50);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
this.#confirmTransportLoss();
|
|
919
|
+
}
|
|
920
|
+
#confirmTransportLoss() {
|
|
921
|
+
if (this.#destroyed || this.#status !== "connected") return;
|
|
922
|
+
const process = this.#process;
|
|
923
|
+
if (process !== void 0 && (process.exitCode !== null || process.signalCode !== null)) {
|
|
924
|
+
this.#handleProcessExit();
|
|
871
925
|
return;
|
|
872
926
|
}
|
|
873
|
-
if (process
|
|
927
|
+
if (process?.pid !== void 0) try {
|
|
874
928
|
process.kill(0);
|
|
875
929
|
} catch {
|
|
876
930
|
this.#handleProcessExit();
|
|
877
931
|
return;
|
|
878
932
|
}
|
|
879
|
-
this.#unbindTransport();
|
|
880
933
|
const client = this.#client;
|
|
881
|
-
this.#
|
|
882
|
-
|
|
883
|
-
this.#
|
|
934
|
+
const owned = this.#owned === true;
|
|
935
|
+
const contexts = this.#contexts;
|
|
936
|
+
this.#unbindTransport();
|
|
937
|
+
this.#client = void 0;
|
|
938
|
+
this.#contexts = [];
|
|
939
|
+
this.#connection = void 0;
|
|
940
|
+
this.#status = "disconnected";
|
|
941
|
+
if (!owned) {
|
|
942
|
+
this.#owned = void 0;
|
|
943
|
+
this.#endpoint = void 0;
|
|
944
|
+
}
|
|
945
|
+
this.#destroyContextList(contexts);
|
|
946
|
+
this.#closeClient(client);
|
|
947
|
+
this.#emitter.emit("error", new BrowserConnectionError("The CDP transport connection was lost", { cause: BROWSER_TRANSPORT_LOSS_CAUSE }));
|
|
884
948
|
this.#emitter.emit("disconnect");
|
|
885
949
|
this.#emitter.emit("idle");
|
|
886
950
|
}
|
|
887
|
-
/** Subscribe to the CDP transport's close/error events for deterministic transport-loss detection. */
|
|
888
951
|
#bindTransport(transport) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
transport.emitter.on("close",
|
|
892
|
-
transport.emitter.on("error",
|
|
893
|
-
this.#transportUnbind = () => {
|
|
894
|
-
transport.emitter.off("close", onClose);
|
|
895
|
-
transport.emitter.off("error", onError);
|
|
896
|
-
};
|
|
952
|
+
this.#unbindTransport();
|
|
953
|
+
this.#transport = transport;
|
|
954
|
+
transport.emitter.on("close", this.#onTransportClose);
|
|
955
|
+
transport.emitter.on("error", this.#onTransportError);
|
|
897
956
|
}
|
|
898
957
|
#unbindTransport() {
|
|
899
|
-
this.#
|
|
900
|
-
|
|
958
|
+
const transport = this.#transport;
|
|
959
|
+
if (transport === void 0) return;
|
|
960
|
+
transport.emitter.off("close", this.#onTransportClose);
|
|
961
|
+
transport.emitter.off("error", this.#onTransportError);
|
|
962
|
+
this.#transport = void 0;
|
|
901
963
|
}
|
|
902
|
-
/** Subscribe to a launched process's exit event for deterministic process-exit detection. */
|
|
903
964
|
#bindProcess(process) {
|
|
904
|
-
|
|
905
|
-
process.once("exit",
|
|
906
|
-
|
|
965
|
+
this.#unbindProcess();
|
|
966
|
+
process.once("exit", this.#onProcessExit);
|
|
967
|
+
if (process.exitCode !== null || process.signalCode !== null) queueMicrotask(this.#onProcessExit);
|
|
907
968
|
}
|
|
908
969
|
#unbindProcess() {
|
|
909
|
-
this.#
|
|
910
|
-
this.#processUnbind = void 0;
|
|
970
|
+
this.#process?.off("exit", this.#onProcessExit);
|
|
911
971
|
}
|
|
912
972
|
#timeout() {
|
|
913
973
|
return this.#options.timeout ?? _src_core.BROWSER_DEFAULT_TIMEOUT_MS;
|
|
914
974
|
}
|
|
915
|
-
async #
|
|
916
|
-
const
|
|
917
|
-
const
|
|
918
|
-
const controller = new AbortController();
|
|
919
|
-
const timer = setTimeout(() => controller.abort(), this.#timeout());
|
|
975
|
+
async #discoverCDP(signal) {
|
|
976
|
+
const url = `${BROWSER_CDP_PROTOCOL}://${this.#cdpHost}:${this.#cdpPort}${BROWSER_CDP_VERSION_PATH}`;
|
|
977
|
+
const requestSignal = signal === void 0 ? AbortSignal.timeout(this.#timeout()) : AbortSignal.any([signal, AbortSignal.timeout(this.#timeout())]);
|
|
920
978
|
try {
|
|
921
|
-
const response = await fetch(url, { signal:
|
|
922
|
-
if (!response.ok) return this.#
|
|
979
|
+
const response = await fetch(url, { signal: requestSignal });
|
|
980
|
+
if (!response.ok) return this.#notFound();
|
|
923
981
|
const info = await response.json();
|
|
924
|
-
if (!(0, _orkestrel_contract.isRecord)(info)) return this.#
|
|
982
|
+
if (!(0, _orkestrel_contract.isRecord)(info)) return this.#notFound();
|
|
925
983
|
const endpoint = (0, _orkestrel_contract.isString)(info["webSocketDebuggerUrl"]) ? info["webSocketDebuggerUrl"] : void 0;
|
|
926
|
-
const
|
|
984
|
+
const browser = (0, _orkestrel_contract.isString)(info["Browser"]) ? info["Browser"] : void 0;
|
|
927
985
|
return {
|
|
928
986
|
found: endpoint !== void 0,
|
|
929
987
|
endpoint,
|
|
930
|
-
browser
|
|
988
|
+
browser,
|
|
931
989
|
connection: endpoint !== void 0 ? "cdp" : void 0
|
|
932
990
|
};
|
|
933
|
-
} catch {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
clearTimeout(timer);
|
|
991
|
+
} catch (error) {
|
|
992
|
+
if (signal?.aborted === true) throw error;
|
|
993
|
+
return this.#notFound();
|
|
937
994
|
}
|
|
938
995
|
}
|
|
939
|
-
#
|
|
996
|
+
#notFound() {
|
|
940
997
|
return {
|
|
941
998
|
found: false,
|
|
942
999
|
endpoint: void 0,
|
|
@@ -944,31 +1001,21 @@ var Browser = class {
|
|
|
944
1001
|
connection: void 0
|
|
945
1002
|
};
|
|
946
1003
|
}
|
|
947
|
-
/**
|
|
948
|
-
* Reject when something is already listening on the configured CDP port —
|
|
949
|
-
* used ahead of a `discover: false` launch, so a caller demanding a fresh
|
|
950
|
-
* browser never silently attaches to a stranger already on that port.
|
|
951
|
-
*/
|
|
952
1004
|
async #assertPortFree() {
|
|
953
1005
|
if (await this.#probePort()) throw new BrowserConnectionError(`Port ${this.#cdpPort} on ${this.#cdpHost} is already occupied by another CDP endpoint`, {
|
|
954
1006
|
port: this.#cdpPort,
|
|
955
1007
|
host: this.#cdpHost
|
|
956
1008
|
});
|
|
957
1009
|
}
|
|
958
|
-
/** Very short probe of the CDP version endpoint — just enough to detect an occupied port, not full discovery. */
|
|
959
1010
|
async #probePort() {
|
|
960
1011
|
const url = `${BROWSER_CDP_PROTOCOL}://${this.#cdpHost}:${this.#cdpPort}${BROWSER_CDP_VERSION_PATH}`;
|
|
961
|
-
const controller = new AbortController();
|
|
962
|
-
const timer = setTimeout(() => controller.abort(), 200);
|
|
963
1012
|
try {
|
|
964
|
-
return (await fetch(url, { signal:
|
|
1013
|
+
return (await fetch(url, { signal: AbortSignal.timeout(200) })).ok;
|
|
965
1014
|
} catch {
|
|
966
1015
|
return false;
|
|
967
|
-
} finally {
|
|
968
|
-
clearTimeout(timer);
|
|
969
1016
|
}
|
|
970
1017
|
}
|
|
971
|
-
async #
|
|
1018
|
+
async #connectCDP(endpoint) {
|
|
972
1019
|
const transport = createCDPTransport({
|
|
973
1020
|
url: endpoint,
|
|
974
1021
|
timeout: this.#timeout()
|
|
@@ -977,18 +1024,27 @@ var Browser = class {
|
|
|
977
1024
|
transport,
|
|
978
1025
|
timeout: this.#timeout()
|
|
979
1026
|
});
|
|
1027
|
+
const retained = this.#owned === true && this.#endpoint === endpoint;
|
|
980
1028
|
try {
|
|
981
1029
|
await this.#raceAbort(client.connect());
|
|
982
1030
|
this.#client = client;
|
|
983
|
-
this.#
|
|
1031
|
+
this.#endpoint = endpoint;
|
|
1032
|
+
this.#connection = retained ? this.#retainedConnection() : "cdp";
|
|
1033
|
+
this.#owned = retained;
|
|
984
1034
|
this.#status = "connected";
|
|
985
1035
|
this.#bindTransport(transport);
|
|
986
1036
|
await this.#syncContexts();
|
|
987
|
-
this.#emitter.emit("connect",
|
|
1037
|
+
this.#emitter.emit("connect", this.#connection);
|
|
988
1038
|
} catch (error) {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1039
|
+
this.#unbindTransport();
|
|
1040
|
+
if (this.#client === client) this.#client = void 0;
|
|
1041
|
+
this.#contexts = [];
|
|
1042
|
+
this.#connection = void 0;
|
|
1043
|
+
if (!retained) {
|
|
1044
|
+
this.#owned = void 0;
|
|
1045
|
+
this.#endpoint = void 0;
|
|
1046
|
+
}
|
|
1047
|
+
await this.#closeClient(client);
|
|
992
1048
|
throw error;
|
|
993
1049
|
}
|
|
994
1050
|
}
|
|
@@ -1006,18 +1062,16 @@ var Browser = class {
|
|
|
1006
1062
|
executable = found?.executable;
|
|
1007
1063
|
resolvedEngine = found?.engine;
|
|
1008
1064
|
}
|
|
1009
|
-
if (executable === void 0) throw new BrowserConnectionError("No Chromium browser found. Install Chrome, Edge, or Chromium.", requestedEngine
|
|
1065
|
+
if (executable === void 0) throw new BrowserConnectionError("No Chromium browser found. Install Chrome, Edge, or Chromium.", requestedEngine === void 0 ? void 0 : { engine: requestedEngine });
|
|
1010
1066
|
this.#engine = resolvedEngine ?? "chromium";
|
|
1011
|
-
const headless = this.#options.headless ?? true;
|
|
1012
1067
|
const profile = this.#options.profile;
|
|
1013
|
-
const
|
|
1014
|
-
const process = launchBrowserProcess(executable, this.#cdpPort, headless, profile, extra);
|
|
1068
|
+
const process = launchBrowserProcess(executable, this.#cdpPort, this.#options.headless ?? true, profile, this.#options.args);
|
|
1015
1069
|
this.#process = process;
|
|
1016
|
-
let transport;
|
|
1017
1070
|
let client;
|
|
1018
1071
|
try {
|
|
1019
|
-
|
|
1020
|
-
|
|
1072
|
+
const endpoint = await this.#waitForLaunch(process, executable, this.#options.args);
|
|
1073
|
+
const transport = createCDPTransport({
|
|
1074
|
+
url: endpoint,
|
|
1021
1075
|
timeout: this.#timeout()
|
|
1022
1076
|
});
|
|
1023
1077
|
client = new _src_core.CDPClient({
|
|
@@ -1025,20 +1079,26 @@ var Browser = class {
|
|
|
1025
1079
|
timeout: this.#timeout()
|
|
1026
1080
|
});
|
|
1027
1081
|
await this.#raceAbort(client.connect());
|
|
1082
|
+
const connection = profile === void 0 ? "launch" : "persistent";
|
|
1028
1083
|
this.#client = client;
|
|
1029
|
-
this.#
|
|
1084
|
+
this.#endpoint = endpoint;
|
|
1085
|
+
this.#connection = connection;
|
|
1086
|
+
this.#owned = true;
|
|
1030
1087
|
this.#status = "connected";
|
|
1031
1088
|
this.#bindTransport(transport);
|
|
1032
1089
|
this.#bindProcess(process);
|
|
1033
1090
|
await this.#syncContexts();
|
|
1034
1091
|
this.#emitter.emit("launch", this.#engine);
|
|
1035
|
-
this.#emitter.emit("connect",
|
|
1092
|
+
this.#emitter.emit("connect", connection);
|
|
1036
1093
|
} catch (error) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1094
|
+
this.#unbindTransport();
|
|
1095
|
+
this.#unbindProcess();
|
|
1096
|
+
await this.#closeClient(client);
|
|
1040
1097
|
await this.#terminate(process);
|
|
1041
1098
|
this.#process = void 0;
|
|
1099
|
+
this.#client = void 0;
|
|
1100
|
+
this.#endpoint = void 0;
|
|
1101
|
+
this.#owned = void 0;
|
|
1042
1102
|
throw error;
|
|
1043
1103
|
}
|
|
1044
1104
|
}
|
|
@@ -1047,72 +1107,188 @@ var Browser = class {
|
|
|
1047
1107
|
executable,
|
|
1048
1108
|
args
|
|
1049
1109
|
};
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
callback();
|
|
1058
|
-
};
|
|
1059
|
-
const onError = (error) => {
|
|
1060
|
-
finish(() => reject(new BrowserConnectionError(error.message, context)));
|
|
1061
|
-
};
|
|
1062
|
-
const onExit = (code, signal) => {
|
|
1063
|
-
finish(() => reject(new BrowserConnectionError(this.#formatLaunchExit(code, signal), context)));
|
|
1064
|
-
};
|
|
1065
|
-
process.once("error", onError);
|
|
1066
|
-
process.once("exit", onExit);
|
|
1067
|
-
waitForCdpReady(this.#cdpPort, this.#timeout(), this.#cdpHost).then((wsUrl) => {
|
|
1068
|
-
finish(() => resolve(wsUrl));
|
|
1069
|
-
}, (error) => {
|
|
1070
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1071
|
-
finish(() => reject(new BrowserConnectionError(message, context)));
|
|
1072
|
-
});
|
|
1110
|
+
const controller = new AbortController();
|
|
1111
|
+
const signal = AbortSignal.any([controller.signal, this.#signal()]);
|
|
1112
|
+
const ready = waitForCDPReady(this.#cdpPort, this.#timeout(), this.#cdpHost, signal);
|
|
1113
|
+
const exited = (0, node_events.once)(process, "exit", { signal }).then((values) => {
|
|
1114
|
+
const code = typeof values[0] === "number" ? values[0] : null;
|
|
1115
|
+
const exitSignal = typeof values[1] === "string" ? values[1] : null;
|
|
1116
|
+
throw new BrowserConnectionError(this.#formatLaunchExit(code, exitSignal), context);
|
|
1073
1117
|
});
|
|
1118
|
+
try {
|
|
1119
|
+
return await Promise.race([ready, exited]);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
if (error instanceof BrowserConnectionError) throw error;
|
|
1122
|
+
if (this.#signal().aborted) throw new BrowserConnectionError("Connection aborted", context);
|
|
1123
|
+
throw new BrowserConnectionError(error instanceof Error ? error.message : String(error), context);
|
|
1124
|
+
} finally {
|
|
1125
|
+
controller.abort();
|
|
1126
|
+
ready.catch(() => void 0);
|
|
1127
|
+
exited.catch(() => void 0);
|
|
1128
|
+
}
|
|
1074
1129
|
}
|
|
1075
1130
|
#formatLaunchExit(code, signal) {
|
|
1076
1131
|
if (signal !== null) return `Browser process exited before CDP became ready (signal: ${signal})`;
|
|
1077
1132
|
if (code !== null) return `Browser process exited before CDP became ready (code: ${code})`;
|
|
1078
1133
|
return "Browser process exited before CDP became ready";
|
|
1079
1134
|
}
|
|
1135
|
+
#retainedConnection() {
|
|
1136
|
+
if (this.#process === void 0) return "cdp";
|
|
1137
|
+
return this.#options.profile === void 0 ? "launch" : "persistent";
|
|
1138
|
+
}
|
|
1080
1139
|
async #syncContexts() {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
await
|
|
1086
|
-
|
|
1140
|
+
const client = this.#client;
|
|
1141
|
+
if (client === void 0) return;
|
|
1142
|
+
let result;
|
|
1143
|
+
try {
|
|
1144
|
+
result = await client.send("Target.getTargets");
|
|
1145
|
+
} catch {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (!(0, _orkestrel_contract.isRecord)(result) || !Array.isArray(result["targetInfos"])) return;
|
|
1149
|
+
const pages = [];
|
|
1150
|
+
for (const target of result["targetInfos"]) {
|
|
1151
|
+
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;
|
|
1152
|
+
pages.push({
|
|
1153
|
+
id: target["targetId"],
|
|
1154
|
+
type: target["type"],
|
|
1155
|
+
title: target["title"],
|
|
1156
|
+
url: target["url"]
|
|
1157
|
+
});
|
|
1087
1158
|
}
|
|
1159
|
+
if (pages.length === 0 || this.#contexts.length > 0) return;
|
|
1160
|
+
const context = new _src_core.BrowserContext(client, void 0, this.#options.viewport, createScreenshotWriter());
|
|
1161
|
+
await context.sync(pages);
|
|
1162
|
+
this.#contexts.push(context);
|
|
1088
1163
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1164
|
+
async #destroyResources() {
|
|
1165
|
+
try {
|
|
1166
|
+
await this.#settle();
|
|
1167
|
+
this.#unbindTransport();
|
|
1168
|
+
this.#unbindProcess();
|
|
1169
|
+
const client = this.#client;
|
|
1170
|
+
if (this.#owned === true) {
|
|
1171
|
+
await this.#closeContexts();
|
|
1172
|
+
if (this.#process === void 0) await this.#closeRemote(client);
|
|
1173
|
+
} else await this.#destroyContexts();
|
|
1174
|
+
this.#contexts = [];
|
|
1175
|
+
await this.#terminate(this.#process);
|
|
1176
|
+
this.#process = void 0;
|
|
1177
|
+
await this.#closeClient(client);
|
|
1178
|
+
this.#client = void 0;
|
|
1179
|
+
} finally {
|
|
1180
|
+
this.#finish();
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
async #closeResources() {
|
|
1184
|
+
try {
|
|
1185
|
+
await this.#settle();
|
|
1186
|
+
this.#unbindTransport();
|
|
1187
|
+
this.#unbindProcess();
|
|
1188
|
+
const client = this.#client;
|
|
1189
|
+
await this.#closeContexts();
|
|
1190
|
+
this.#contexts = [];
|
|
1191
|
+
await this.#closeRemote(client);
|
|
1192
|
+
const process = this.#process;
|
|
1193
|
+
if (process !== void 0) {
|
|
1194
|
+
if (!await this.#waitForProcessWithin(process, 3e3)) await this.#terminate(process);
|
|
1195
|
+
}
|
|
1196
|
+
this.#process = void 0;
|
|
1197
|
+
await this.#closeClient(client);
|
|
1198
|
+
this.#client = void 0;
|
|
1199
|
+
} finally {
|
|
1200
|
+
this.#finish();
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
async #settle() {
|
|
1204
|
+
await this.#connecting?.catch(() => void 0);
|
|
1205
|
+
await this.#disconnecting?.catch(() => void 0);
|
|
1206
|
+
}
|
|
1207
|
+
async #closeRemote(client) {
|
|
1208
|
+
let remote = client;
|
|
1209
|
+
let temporary = false;
|
|
1210
|
+
if (remote === void 0 && this.#owned === true && this.#endpoint !== void 0) {
|
|
1211
|
+
remote = new _src_core.CDPClient({
|
|
1212
|
+
transport: createCDPTransport({
|
|
1213
|
+
url: this.#endpoint,
|
|
1214
|
+
timeout: this.#timeout()
|
|
1215
|
+
}),
|
|
1216
|
+
timeout: this.#timeout()
|
|
1217
|
+
});
|
|
1218
|
+
try {
|
|
1219
|
+
await remote.connect();
|
|
1220
|
+
temporary = true;
|
|
1221
|
+
} catch {
|
|
1222
|
+
await this.#closeClient(remote);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (remote !== void 0) try {
|
|
1227
|
+
await remote.send("Browser.close");
|
|
1228
|
+
} catch {}
|
|
1229
|
+
if (temporary) await this.#closeClient(remote);
|
|
1230
|
+
}
|
|
1231
|
+
async #closeContexts() {
|
|
1232
|
+
for (const context of this.#contexts) try {
|
|
1233
|
+
await context.close();
|
|
1234
|
+
} catch {}
|
|
1235
|
+
}
|
|
1236
|
+
async #destroyContexts() {
|
|
1237
|
+
await this.#destroyContextList(this.#contexts);
|
|
1238
|
+
}
|
|
1239
|
+
async #destroyContextList(contexts) {
|
|
1240
|
+
for (const context of contexts) try {
|
|
1241
|
+
await context.destroy();
|
|
1242
|
+
} catch {}
|
|
1243
|
+
}
|
|
1244
|
+
async #closeClient(client) {
|
|
1245
|
+
if (client === void 0) return;
|
|
1246
|
+
try {
|
|
1247
|
+
await client.close();
|
|
1248
|
+
} catch {}
|
|
1249
|
+
}
|
|
1250
|
+
async #waitForProcess(process) {
|
|
1096
1251
|
if (process.exitCode !== null || process.signalCode !== null) return;
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1252
|
+
await (0, node_events.once)(process, "exit");
|
|
1253
|
+
}
|
|
1254
|
+
async #waitForProcessWithin(process, timeout) {
|
|
1255
|
+
if (process.exitCode !== null || process.signalCode !== null) return true;
|
|
1256
|
+
const signal = AbortSignal.timeout(timeout);
|
|
1257
|
+
try {
|
|
1258
|
+
await (0, node_events.once)(process, "exit", { signal });
|
|
1259
|
+
return true;
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (signal.aborted) return process.exitCode !== null || process.signalCode !== null;
|
|
1262
|
+
throw error;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
async #terminate(process) {
|
|
1266
|
+
if (process === void 0 || process.exitCode !== null || process.signalCode !== null) return;
|
|
1100
1267
|
try {
|
|
1101
1268
|
process.kill("SIGTERM");
|
|
1102
1269
|
} catch {
|
|
1103
1270
|
return;
|
|
1104
1271
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (timedOut) {
|
|
1111
|
-
try {
|
|
1112
|
-
process.kill("SIGKILL");
|
|
1113
|
-
} catch {}
|
|
1114
|
-
await exited;
|
|
1272
|
+
if (await this.#waitForProcessWithin(process, 3e3)) return;
|
|
1273
|
+
try {
|
|
1274
|
+
process.kill("SIGKILL");
|
|
1275
|
+
} catch {
|
|
1276
|
+
return;
|
|
1115
1277
|
}
|
|
1278
|
+
await this.#waitForProcess(process);
|
|
1279
|
+
}
|
|
1280
|
+
#finish() {
|
|
1281
|
+
this.#unbindTransport();
|
|
1282
|
+
this.#unbindProcess();
|
|
1283
|
+
this.#status = "disconnected";
|
|
1284
|
+
this.#connection = void 0;
|
|
1285
|
+
this.#owned = void 0;
|
|
1286
|
+
this.#endpoint = void 0;
|
|
1287
|
+
this.#client = void 0;
|
|
1288
|
+
this.#process = void 0;
|
|
1289
|
+
this.#contexts = [];
|
|
1290
|
+
this.#emitter.emit("destroy");
|
|
1291
|
+
this.#emitter.destroy();
|
|
1116
1292
|
}
|
|
1117
1293
|
};
|
|
1118
1294
|
//#endregion
|
|
@@ -1128,13 +1304,14 @@ exports.BROWSER_EXECUTABLE_PATHS = BROWSER_EXECUTABLE_PATHS;
|
|
|
1128
1304
|
exports.BROWSER_HEADLESS_ARG = BROWSER_HEADLESS_ARG;
|
|
1129
1305
|
exports.BROWSER_KILL_GRACE_MS = BROWSER_KILL_GRACE_MS;
|
|
1130
1306
|
exports.BROWSER_LAUNCH_ARGS = BROWSER_LAUNCH_ARGS;
|
|
1131
|
-
exports.BROWSER_NOT_FOUND_RESULT = BROWSER_NOT_FOUND_RESULT;
|
|
1132
1307
|
exports.BROWSER_PORT_PROBE_TIMEOUT_MS = BROWSER_PORT_PROBE_TIMEOUT_MS;
|
|
1308
|
+
exports.BROWSER_PROCESS_EXIT_CAUSE = BROWSER_PROCESS_EXIT_CAUSE;
|
|
1133
1309
|
exports.BROWSER_STORE_CACHE_DIRS = BROWSER_STORE_CACHE_DIRS;
|
|
1134
1310
|
exports.BROWSER_STORE_DEFAULT_DIRS = BROWSER_STORE_DEFAULT_DIRS;
|
|
1135
1311
|
exports.BROWSER_STORE_ENV_KEY = BROWSER_STORE_ENV_KEY;
|
|
1136
1312
|
exports.BROWSER_STORE_GLOBS = BROWSER_STORE_GLOBS;
|
|
1137
1313
|
exports.BROWSER_STORE_LINK_NAME = BROWSER_STORE_LINK_NAME;
|
|
1314
|
+
exports.BROWSER_TRANSPORT_LOSS_CAUSE = BROWSER_TRANSPORT_LOSS_CAUSE;
|
|
1138
1315
|
exports.BROWSER_TRANSPORT_LOSS_DEFER_MS = BROWSER_TRANSPORT_LOSS_DEFER_MS;
|
|
1139
1316
|
exports.BROWSER_WINDOWS_ROOT_FALLBACKS = BROWSER_WINDOWS_ROOT_FALLBACKS;
|
|
1140
1317
|
exports.BROWSER_WINDOWS_SUFFIXES = BROWSER_WINDOWS_SUFFIXES;
|
|
@@ -1149,7 +1326,7 @@ exports.createCDPTransport = createCDPTransport;
|
|
|
1149
1326
|
exports.createScreenshotWriter = createScreenshotWriter;
|
|
1150
1327
|
exports.defaultInstallPaths = defaultInstallPaths;
|
|
1151
1328
|
exports.defaultStoreBases = defaultStoreBases;
|
|
1152
|
-
exports.
|
|
1329
|
+
exports.fetchCDPTargets = fetchCDPTargets;
|
|
1153
1330
|
exports.findAllEnvOverrides = findAllEnvOverrides;
|
|
1154
1331
|
exports.findAllInStore = findAllInStore;
|
|
1155
1332
|
exports.findAllInstallPaths = findAllInstallPaths;
|
|
@@ -1166,7 +1343,7 @@ exports.normalizeExecutablePath = normalizeExecutablePath;
|
|
|
1166
1343
|
exports.parseBrowserEngine = parseBrowserEngine;
|
|
1167
1344
|
exports.probeAllPathNames = probeAllPathNames;
|
|
1168
1345
|
exports.probePathNames = probePathNames;
|
|
1169
|
-
exports.
|
|
1346
|
+
exports.waitForCDPReady = waitForCDPReady;
|
|
1170
1347
|
exports.windowsRoots = windowsRoots;
|
|
1171
1348
|
|
|
1172
1349
|
//# sourceMappingURL=index.cjs.map
|