@orkestrel/browser 0.0.29 → 0.0.30
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/bin/main.js +1 -2
- package/dist/src/browser/index.d.ts +86 -6
- package/dist/src/browser/index.js +359 -271
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +2281 -1745
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +342 -156
- package/dist/src/core/index.d.ts +342 -156
- package/dist/src/core/index.js +2261 -1744
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +377 -344
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +47 -9
- package/dist/src/server/index.d.ts +47 -9
- package/dist/src/server/index.js +376 -344
- package/dist/src/server/index.js.map +1 -1
- package/package.json +27 -26
package/dist/src/server/index.js
CHANGED
|
@@ -11,11 +11,12 @@ import { request as request$1 } from "node:https";
|
|
|
11
11
|
import { Emitter } from "@orkestrel/emitter";
|
|
12
12
|
import { WEBSOCKET_READY_CLOSED, WEBSOCKET_READY_OPEN, WEBSOCKET_VERSION, computeWebSocketAccept, createNodeWebSocket } from "@orkestrel/websocket";
|
|
13
13
|
import { addAbortListener, once } from "node:events";
|
|
14
|
+
import { inspectProcess } from "@orkestrel/process/server";
|
|
14
15
|
import { JSONRPC_SERVER_ERROR, MCPError, createMCPLegacy, createMCPServer } from "@orkestrel/mcp";
|
|
15
16
|
import { createPool, isPoolError } from "@orkestrel/pool";
|
|
16
17
|
import { createStdioServer } from "@orkestrel/mcp/server";
|
|
17
18
|
import { createTool, createToolManager, toolToDefinition } from "@orkestrel/tool";
|
|
18
|
-
|
|
19
|
+
// src/server/constants.ts
|
|
19
20
|
/**
|
|
20
21
|
* Sets the default CDP port probed for an existing browser and used for launches, `9222`.
|
|
21
22
|
*/
|
|
@@ -331,8 +332,43 @@ var BROWSER_SERVER_COPY = Object.freeze({
|
|
|
331
332
|
annotations: Object.freeze({ pure: false })
|
|
332
333
|
})
|
|
333
334
|
});
|
|
334
|
-
|
|
335
|
-
|
|
335
|
+
// src/server/BrowserEndpoint.ts
|
|
336
|
+
/** Reads the announced CDP endpoint while continuing to drain the browser's standard error. */
|
|
337
|
+
var BrowserEndpoint = class {
|
|
338
|
+
#completion = Promise.withResolvers();
|
|
339
|
+
#pending = "";
|
|
340
|
+
#endpoint;
|
|
341
|
+
constructor(stream, signal) {
|
|
342
|
+
if (signal.aborted) this.#completion.reject(signal.reason);
|
|
343
|
+
signal.addEventListener("abort", () => this.#completion.reject(signal.reason), { once: true });
|
|
344
|
+
stream.on("error", this.#completion.reject);
|
|
345
|
+
stream.setEncoding("utf8");
|
|
346
|
+
stream.on("data", this.#handleChunk.bind(this));
|
|
347
|
+
stream.on("close", this.#handleClose.bind(this));
|
|
348
|
+
}
|
|
349
|
+
get endpoint() {
|
|
350
|
+
return this.#completion.promise;
|
|
351
|
+
}
|
|
352
|
+
#handleChunk(chunk) {
|
|
353
|
+
if (this.#endpoint !== void 0 || !isString(chunk)) return;
|
|
354
|
+
const lines = (this.#pending + chunk).split(/\r\n|\n/);
|
|
355
|
+
this.#pending = lines.pop() ?? "";
|
|
356
|
+
for (const line of lines) {
|
|
357
|
+
const endpoint = BROWSER_DEVTOOLS_PATTERN.exec(line)?.[1];
|
|
358
|
+
if (endpoint === void 0) continue;
|
|
359
|
+
this.#endpoint = endpoint;
|
|
360
|
+
this.#pending = "";
|
|
361
|
+
this.#completion.resolve(endpoint);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
#handleClose() {
|
|
366
|
+
const endpoint = this.#endpoint === void 0 ? BROWSER_DEVTOOLS_PATTERN.exec(this.#pending)?.[1] : void 0;
|
|
367
|
+
if (endpoint !== void 0) this.#completion.resolve(endpoint);
|
|
368
|
+
else this.#completion.reject(new BrowserError("CONNECTION", "Browser closed its standard error before reporting a CDP endpoint"));
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
// src/server/helpers.ts
|
|
336
372
|
/**
|
|
337
373
|
* Parses positive integer viewport dimensions in WIDTHxHEIGHT form.
|
|
338
374
|
* @param value - The viewport environment variable, or undefined when unset
|
|
@@ -353,21 +389,6 @@ function parseBrowserViewport(value) {
|
|
|
353
389
|
};
|
|
354
390
|
}
|
|
355
391
|
/**
|
|
356
|
-
* Probes whether a process has not been confirmed absent.
|
|
357
|
-
* @param pid - Process identifier to probe with signal zero
|
|
358
|
-
* @returns True if the host has not confirmed absence with ESRCH; false otherwise
|
|
359
|
-
* @example
|
|
360
|
-
* probeProcess(process.pid) // true
|
|
361
|
-
*/
|
|
362
|
-
function probeProcess(pid) {
|
|
363
|
-
try {
|
|
364
|
-
process.kill(pid, 0);
|
|
365
|
-
return true;
|
|
366
|
-
} catch (error) {
|
|
367
|
-
return !(isError(error) && "code" in error && error.code === "ESRCH");
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
392
|
* Parses a profile record naming a positive process identifier and a loopback DevTools endpoint.
|
|
372
393
|
* @param text - JSON record read from a browse profile
|
|
373
394
|
* @returns The record, or undefined when its shape or endpoint is refused
|
|
@@ -699,32 +720,7 @@ function launchBrowserProcess(executable, port, headless, profile, extra) {
|
|
|
699
720
|
* @throws When the stream closes without the line, or the signal aborts first
|
|
700
721
|
*/
|
|
701
722
|
function readBrowserEndpoint(stream, signal) {
|
|
702
|
-
|
|
703
|
-
let pending = "";
|
|
704
|
-
let found = false;
|
|
705
|
-
if (signal.aborted) read.reject(signal.reason);
|
|
706
|
-
signal.addEventListener("abort", () => read.reject(signal.reason), { once: true });
|
|
707
|
-
stream.on("error", read.reject);
|
|
708
|
-
stream.setEncoding("utf8");
|
|
709
|
-
stream.on("data", (chunk) => {
|
|
710
|
-
if (found || !isString(chunk)) return;
|
|
711
|
-
const lines = (pending + chunk).split(/\r\n|\n/);
|
|
712
|
-
pending = lines.pop() ?? "";
|
|
713
|
-
for (const line of lines) {
|
|
714
|
-
const endpoint = BROWSER_DEVTOOLS_PATTERN.exec(line)?.[1];
|
|
715
|
-
if (endpoint === void 0) continue;
|
|
716
|
-
found = true;
|
|
717
|
-
pending = "";
|
|
718
|
-
read.resolve(endpoint);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
stream.on("close", () => {
|
|
723
|
-
const endpoint = found ? void 0 : BROWSER_DEVTOOLS_PATTERN.exec(pending)?.[1];
|
|
724
|
-
if (endpoint !== void 0) read.resolve(endpoint);
|
|
725
|
-
else read.reject(new BrowserError("CONNECTION", "Browser closed its standard error before reporting a CDP endpoint"));
|
|
726
|
-
});
|
|
727
|
-
return read.promise;
|
|
723
|
+
return new BrowserEndpoint(stream, signal).endpoint;
|
|
728
724
|
}
|
|
729
725
|
/**
|
|
730
726
|
* Fetches the current CDP target list from a browser's `/json/list` endpoint, as a `Result`
|
|
@@ -795,8 +791,7 @@ async function fetchCDPTargets(port, timeout, host = BROWSER_DEFAULT_HOST) {
|
|
|
795
791
|
clearTimeout(timer);
|
|
796
792
|
}
|
|
797
793
|
}
|
|
798
|
-
|
|
799
|
-
//#region src/server/transports/WebSocketCDPTransport.ts
|
|
794
|
+
// src/server/transports/WebSocketCDPTransport.ts
|
|
800
795
|
/**
|
|
801
796
|
* Provides a raw CDP text transport backed by `@orkestrel/websocket`.
|
|
802
797
|
*
|
|
@@ -990,8 +985,7 @@ var WebSocketCDPTransport = class {
|
|
|
990
985
|
this.#closeResolve?.();
|
|
991
986
|
}
|
|
992
987
|
};
|
|
993
|
-
|
|
994
|
-
//#region src/server/writers/FileBrowserWriter.ts
|
|
988
|
+
// src/server/writers/FileBrowserWriter.ts
|
|
995
989
|
/**
|
|
996
990
|
* Persists captured browser bytes to the filesystem through `node:fs/promises`.
|
|
997
991
|
*
|
|
@@ -1020,11 +1014,35 @@ var FileBrowserWriter = class {
|
|
|
1020
1014
|
await writeFile(path, data);
|
|
1021
1015
|
}
|
|
1022
1016
|
};
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
var
|
|
1026
|
-
|
|
1027
|
-
|
|
1017
|
+
// src/server/BrowserProcessDrain.ts
|
|
1018
|
+
/** Waits for a process remainder whose host exposes no exit event. */
|
|
1019
|
+
var BrowserProcessDrain = class {
|
|
1020
|
+
#inspect;
|
|
1021
|
+
#deadline;
|
|
1022
|
+
#result;
|
|
1023
|
+
#confirmed = false;
|
|
1024
|
+
constructor(inspect, timeout) {
|
|
1025
|
+
this.#inspect = inspect;
|
|
1026
|
+
this.#deadline = performance.now() + timeout;
|
|
1027
|
+
this.#result = this.#wait();
|
|
1028
|
+
}
|
|
1029
|
+
get result() {
|
|
1030
|
+
return this.#result;
|
|
1031
|
+
}
|
|
1032
|
+
async #wait() {
|
|
1033
|
+
while (true) {
|
|
1034
|
+
const alive = this.#inspect();
|
|
1035
|
+
if (alive === false) return true;
|
|
1036
|
+
if (alive === true) this.#confirmed = true;
|
|
1037
|
+
const remaining = this.#deadline - performance.now();
|
|
1038
|
+
if (remaining <= 0) return this.#confirmed ? false : void 0;
|
|
1039
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(100, remaining)));
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
// package.json
|
|
1044
|
+
var version = "0.0.30";
|
|
1045
|
+
// src/server/BrowserMCPServer.ts
|
|
1028
1046
|
/**
|
|
1029
1047
|
* Implements `BrowserMCPServerInterface`: serves the browser vocabulary and the journey tools over
|
|
1030
1048
|
* the Model Context Protocol on stdio, and warms Chromium at server start.
|
|
@@ -1244,7 +1262,7 @@ var BrowserMCPServer = class {
|
|
|
1244
1262
|
for (const folder of this.#folders) {
|
|
1245
1263
|
try {
|
|
1246
1264
|
const record = parseBrowserProfileRecord(await readFile(join(folder, BROWSER_SERVER_RECORD), "utf8"));
|
|
1247
|
-
if (record === void 0 ||
|
|
1265
|
+
if (record === void 0 || inspectProcess(record.pid) !== false) continue;
|
|
1248
1266
|
} catch (error) {
|
|
1249
1267
|
if (!isError(error) || !("code" in error) || error.code !== "ENOENT") continue;
|
|
1250
1268
|
}
|
|
@@ -1402,16 +1420,34 @@ var BrowserMCPServer = class {
|
|
|
1402
1420
|
}
|
|
1403
1421
|
async #perform(holder, name, args, context) {
|
|
1404
1422
|
const signal = holder === this.#shared ? context.signal : AbortSignal.any([context.signal, holder.abort.signal]);
|
|
1405
|
-
|
|
1423
|
+
const lease = await this.#serveCall(holder, context, signal);
|
|
1424
|
+
const result = await this.#executeCall(lease, name, args, context, signal);
|
|
1425
|
+
context.signal.throwIfAborted();
|
|
1426
|
+
if (holder !== this.#shared && holder.abort.signal.aborted) throw new BrowserError(BROWSER_SERVER_UNRESOLVED, BROWSER_SERVER_UNRESOLVED + ": the holder ended during the call");
|
|
1427
|
+
if (!result.success) {
|
|
1428
|
+
if (this.#leases.get(holder) === lease && !context.signal.aborted) try {
|
|
1429
|
+
await this.#race(this.#ping(lease.token.value), context.signal);
|
|
1430
|
+
} catch {}
|
|
1431
|
+
context.signal.throwIfAborted();
|
|
1432
|
+
if (this.#leases.get(holder) !== lease) {
|
|
1433
|
+
const loss = this.#departures.get(lease);
|
|
1434
|
+
throw new BrowserError(BROWSER_SERVER_UNRESOLVED, this.#annotate(holder, describeBrowserServerLoss(BROWSER_SERVER_UNRESOLVED, loss?.cause, loss?.url), lease));
|
|
1435
|
+
}
|
|
1436
|
+
throw new BrowserError("PROTOCOL", boundBrowserText(this.#annotate(holder, result.error, lease), BROWSER_TOOL_LIMIT, BROWSER_TOOL_CUT_FOOTER));
|
|
1437
|
+
}
|
|
1438
|
+
return typeof result.value === "string" ? this.#annotate(holder, result.value, lease) : result.value;
|
|
1439
|
+
}
|
|
1440
|
+
async #serveCall(holder, context, signal) {
|
|
1406
1441
|
try {
|
|
1407
|
-
|
|
1442
|
+
return await this.#serve(holder, signal);
|
|
1408
1443
|
} catch (error) {
|
|
1409
1444
|
context.signal.throwIfAborted();
|
|
1410
1445
|
if (holder !== this.#shared && holder.abort.signal.aborted) throw new BrowserError(BROWSER_SERVER_UNRESOLVED, `${BROWSER_SERVER_UNRESOLVED}: the holder ended during the call`);
|
|
1411
1446
|
if (isBrowserError(error)) throw new BrowserError(error.code, this.#annotate(holder, `${error.code}: ${error.message}`));
|
|
1412
1447
|
throw error;
|
|
1413
1448
|
}
|
|
1414
|
-
|
|
1449
|
+
}
|
|
1450
|
+
async #executeCall(lease, name, args, context, signal) {
|
|
1415
1451
|
try {
|
|
1416
1452
|
const execution = lease.generation.toolset.tools.execute({
|
|
1417
1453
|
id: name,
|
|
@@ -1423,29 +1459,15 @@ var BrowserMCPServer = class {
|
|
|
1423
1459
|
});
|
|
1424
1460
|
const outcome = await (name === "replay" || name === "forget" ? execution : this.#race(execution, signal));
|
|
1425
1461
|
if (outcome === void 0) throw this.#ended();
|
|
1426
|
-
|
|
1462
|
+
return outcome;
|
|
1427
1463
|
} catch (error) {
|
|
1428
|
-
|
|
1464
|
+
return {
|
|
1429
1465
|
id: name,
|
|
1430
1466
|
name,
|
|
1431
1467
|
success: false,
|
|
1432
1468
|
error: isError(error) ? error.message : String(error)
|
|
1433
1469
|
};
|
|
1434
1470
|
}
|
|
1435
|
-
context.signal.throwIfAborted();
|
|
1436
|
-
if (holder !== this.#shared && holder.abort.signal.aborted) throw new BrowserError(BROWSER_SERVER_UNRESOLVED, BROWSER_SERVER_UNRESOLVED + ": the holder ended during the call");
|
|
1437
|
-
if (!result.success) {
|
|
1438
|
-
if (this.#leases.get(holder) === lease && !context.signal.aborted) try {
|
|
1439
|
-
await this.#race(this.#ping(lease.token.value), context.signal);
|
|
1440
|
-
} catch {}
|
|
1441
|
-
context.signal.throwIfAborted();
|
|
1442
|
-
if (this.#leases.get(holder) !== lease) {
|
|
1443
|
-
const loss = this.#departures.get(lease);
|
|
1444
|
-
throw new BrowserError(BROWSER_SERVER_UNRESOLVED, this.#annotate(holder, describeBrowserServerLoss(BROWSER_SERVER_UNRESOLVED, loss?.cause, loss?.url), lease));
|
|
1445
|
-
}
|
|
1446
|
-
throw new BrowserError("PROTOCOL", boundBrowserText(this.#annotate(holder, result.error, lease), BROWSER_TOOL_LIMIT, BROWSER_TOOL_CUT_FOOTER));
|
|
1447
|
-
}
|
|
1448
|
-
return typeof result.value === "string" ? this.#annotate(holder, result.value, lease) : result.value;
|
|
1449
1471
|
}
|
|
1450
1472
|
#annotate(holder, value, lease) {
|
|
1451
1473
|
const notes = [];
|
|
@@ -1484,23 +1506,35 @@ var BrowserMCPServer = class {
|
|
|
1484
1506
|
async #grant(holder, signal) {
|
|
1485
1507
|
const held = this.#leases.get(holder);
|
|
1486
1508
|
if (held !== void 0) return held;
|
|
1487
|
-
|
|
1488
|
-
if (granting === void 0) {
|
|
1489
|
-
granting = Promise.resolve().then(async () => {
|
|
1490
|
-
await Promise.allSettled(this.#drains.get(holder) ?? []);
|
|
1491
|
-
if (this.#retentions.has(holder)) throw this.#unavailable(this.#retentions.get(holder));
|
|
1492
|
-
const token = await this.#pool.acquire(holder.abort.signal);
|
|
1493
|
-
return this.#hold(holder, token);
|
|
1494
|
-
});
|
|
1495
|
-
this.#grants.set(holder, granting);
|
|
1496
|
-
granting.finally(() => {
|
|
1497
|
-
if (this.#grants.get(holder) === granting) this.#grants.delete(holder);
|
|
1498
|
-
}).catch(() => void 0);
|
|
1499
|
-
}
|
|
1509
|
+
const granting = this.#grants.get(holder) ?? this.#beginGrant(holder);
|
|
1500
1510
|
const lease = await this.#race(granting, signal);
|
|
1501
1511
|
if (lease === void 0) throw this.#ended();
|
|
1502
1512
|
return lease;
|
|
1503
1513
|
}
|
|
1514
|
+
async #prepareGeneration(slot, token) {
|
|
1515
|
+
const prepared = this.#prepared.get(slot);
|
|
1516
|
+
this.#prepared.delete(slot);
|
|
1517
|
+
try {
|
|
1518
|
+
return prepared ?? await this.#build(slot, token);
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
if (this.#losses.has(slot)) await this.#endings.get(slot)?.promise;
|
|
1521
|
+
else token.release();
|
|
1522
|
+
throw this.#unavailable(error);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
#beginGrant(holder) {
|
|
1526
|
+
const granting = Promise.resolve().then(async () => {
|
|
1527
|
+
await Promise.allSettled(this.#drains.get(holder) ?? []);
|
|
1528
|
+
if (this.#retentions.has(holder)) throw this.#unavailable(this.#retentions.get(holder));
|
|
1529
|
+
const token = await this.#pool.acquire(holder.abort.signal);
|
|
1530
|
+
return this.#hold(holder, token);
|
|
1531
|
+
});
|
|
1532
|
+
this.#grants.set(holder, granting);
|
|
1533
|
+
granting.finally(() => {
|
|
1534
|
+
if (this.#grants.get(holder) === granting) this.#grants.delete(holder);
|
|
1535
|
+
}).catch(() => void 0);
|
|
1536
|
+
return granting;
|
|
1537
|
+
}
|
|
1504
1538
|
async #hold(holder, token) {
|
|
1505
1539
|
const slot = token.value;
|
|
1506
1540
|
if (this.#closing !== void 0 || this.#holders.get(holder.id) !== holder || holder.abort.signal.aborted) {
|
|
@@ -1516,15 +1550,7 @@ var BrowserMCPServer = class {
|
|
|
1516
1550
|
token.release();
|
|
1517
1551
|
throw this.#unavailable(this.#losses.get(slot)?.cause);
|
|
1518
1552
|
}
|
|
1519
|
-
|
|
1520
|
-
this.#prepared.delete(slot);
|
|
1521
|
-
try {
|
|
1522
|
-
generation ??= await this.#build(slot, token);
|
|
1523
|
-
} catch (error) {
|
|
1524
|
-
if (this.#losses.has(slot)) await this.#endings.get(slot)?.promise;
|
|
1525
|
-
else token.release();
|
|
1526
|
-
throw this.#unavailable(error);
|
|
1527
|
-
}
|
|
1553
|
+
const generation = await this.#prepareGeneration(slot, token);
|
|
1528
1554
|
const lease = {
|
|
1529
1555
|
token,
|
|
1530
1556
|
generation
|
|
@@ -1700,9 +1726,9 @@ var BrowserMCPServer = class {
|
|
|
1700
1726
|
} else if (removed) lease.token.release();
|
|
1701
1727
|
}
|
|
1702
1728
|
#clean(slot, generation) {
|
|
1703
|
-
|
|
1704
|
-
if (
|
|
1705
|
-
cleaning = Promise.resolve().then(async () => {
|
|
1729
|
+
const pending = this.#cleaning.get(generation);
|
|
1730
|
+
if (pending !== void 0) return pending;
|
|
1731
|
+
const cleaning = Promise.resolve().then(async () => {
|
|
1706
1732
|
await generation.toolset.destroy().catch(this.#fault.bind(this));
|
|
1707
1733
|
await generation.context.close().catch((error) => {
|
|
1708
1734
|
if (!this.#losses.has(slot) && slot.browser.status === "connected") this.#fault(error);
|
|
@@ -1732,26 +1758,11 @@ var BrowserMCPServer = class {
|
|
|
1732
1758
|
}
|
|
1733
1759
|
async #construct(slot, token) {
|
|
1734
1760
|
const directory = join(slot.profile, "contexts", randomUUID(), "downloads");
|
|
1735
|
-
|
|
1736
|
-
let toolset;
|
|
1761
|
+
const cleanup = [];
|
|
1737
1762
|
try {
|
|
1738
1763
|
await mkdir(directory, { recursive: true });
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
context = await slot.browser.isolate({
|
|
1742
|
-
reference: this.#reference,
|
|
1743
|
-
downloads: { path: directory },
|
|
1744
|
-
...this.#viewport === void 0 ? {} : { emulation: { viewport: this.#viewport } }
|
|
1745
|
-
});
|
|
1746
|
-
page = await context.create();
|
|
1747
|
-
} catch (error) {
|
|
1748
|
-
if (token !== void 0) {
|
|
1749
|
-
if (![...this.#leases.values()].some((lease) => lease.token.value === slot) && (this.#building.get(slot)?.size ?? 0) === 1) token.release();
|
|
1750
|
-
this.#lose(slot, error);
|
|
1751
|
-
}
|
|
1752
|
-
throw error;
|
|
1753
|
-
}
|
|
1754
|
-
toolset = createBrowserToolset(page, {
|
|
1764
|
+
const { context, page } = await this.#openGeneration(slot, directory, cleanup, token);
|
|
1765
|
+
const toolset = createBrowserToolset(page, {
|
|
1755
1766
|
context,
|
|
1756
1767
|
notes: this.#notice.bind(this, context),
|
|
1757
1768
|
journeys: {
|
|
@@ -1760,6 +1771,9 @@ var BrowserMCPServer = class {
|
|
|
1760
1771
|
readonly: this.#readonly
|
|
1761
1772
|
}
|
|
1762
1773
|
});
|
|
1774
|
+
cleanup.push(async () => {
|
|
1775
|
+
await toolset.destroy().catch(this.#fault.bind(this));
|
|
1776
|
+
});
|
|
1763
1777
|
await toolset.start();
|
|
1764
1778
|
const generation = {
|
|
1765
1779
|
context,
|
|
@@ -1771,10 +1785,7 @@ var BrowserMCPServer = class {
|
|
|
1771
1785
|
this.#owned.set(slot, owned);
|
|
1772
1786
|
return generation;
|
|
1773
1787
|
} catch (error) {
|
|
1774
|
-
|
|
1775
|
-
await context?.close().catch((failure) => {
|
|
1776
|
-
if (!this.#losses.has(slot) && slot.browser.status === "connected" && this.#closing === void 0 && !(isBrowserError(failure) && failure.code === "DISCONNECTED")) this.#fault(failure);
|
|
1777
|
-
});
|
|
1788
|
+
for (const release of cleanup.toReversed()) await release();
|
|
1778
1789
|
await rm(directory, {
|
|
1779
1790
|
recursive: true,
|
|
1780
1791
|
force: true
|
|
@@ -1782,6 +1793,31 @@ var BrowserMCPServer = class {
|
|
|
1782
1793
|
throw error;
|
|
1783
1794
|
}
|
|
1784
1795
|
}
|
|
1796
|
+
async #openGeneration(slot, directory, cleanup, token) {
|
|
1797
|
+
try {
|
|
1798
|
+
const context = await slot.browser.isolate({
|
|
1799
|
+
reference: this.#reference,
|
|
1800
|
+
downloads: { path: directory },
|
|
1801
|
+
...this.#viewport === void 0 ? {} : { emulation: { viewport: this.#viewport } }
|
|
1802
|
+
});
|
|
1803
|
+
cleanup.push(this.#closeGenerationContext.bind(this, context, slot));
|
|
1804
|
+
return {
|
|
1805
|
+
context,
|
|
1806
|
+
page: await context.create()
|
|
1807
|
+
};
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
if (token !== void 0) {
|
|
1810
|
+
if (![...this.#leases.values()].some((lease) => lease.token.value === slot) && (this.#building.get(slot)?.size ?? 0) === 1) token.release();
|
|
1811
|
+
this.#lose(slot, error);
|
|
1812
|
+
}
|
|
1813
|
+
throw error;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
async #closeGenerationContext(context, slot) {
|
|
1817
|
+
await context.close().catch((failure) => {
|
|
1818
|
+
if (!this.#losses.has(slot) && slot.browser.status === "connected" && this.#closing === void 0 && !(isBrowserError(failure) && failure.code === "DISCONNECTED")) this.#fault(failure);
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1785
1821
|
async #destroyRecord(slot) {
|
|
1786
1822
|
const cause = this.#losses.get(slot)?.cause;
|
|
1787
1823
|
if (isBrowserError(cause) && cause.code === "TIMEOUT" && slot.browser.pid !== void 0) try {
|
|
@@ -1811,12 +1847,23 @@ var BrowserMCPServer = class {
|
|
|
1811
1847
|
if (this.#closing !== void 0) throw this.#ended();
|
|
1812
1848
|
if (this.#stranded !== void 0) throw new BrowserError(BROWSER_SERVER_TEARDOWN, isError(this.#stranded) ? this.#stranded.message : String(this.#stranded), isBrowserError(this.#stranded) ? this.#stranded.context : void 0);
|
|
1813
1849
|
const profile = join(this.#root, ".profiles", formatBrowserLockEntry(process.pid, randomUUID()));
|
|
1814
|
-
let browser;
|
|
1815
|
-
let slot;
|
|
1816
1850
|
try {
|
|
1817
1851
|
await mkdir(profile);
|
|
1818
1852
|
this.#folders.add(profile);
|
|
1819
|
-
browser = this.#
|
|
1853
|
+
const browser = await this.#launchWarm(profile);
|
|
1854
|
+
await this.#connectWarm(profile, browser);
|
|
1855
|
+
return await this.#prepareWarm({
|
|
1856
|
+
browser,
|
|
1857
|
+
profile
|
|
1858
|
+
});
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_LAUNCH, error)}`);
|
|
1861
|
+
throw error;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
async #launchWarm(profile) {
|
|
1865
|
+
try {
|
|
1866
|
+
return this.#launcher({
|
|
1820
1867
|
headless: this.#headless,
|
|
1821
1868
|
profile,
|
|
1822
1869
|
cdp: { discover: false },
|
|
@@ -1825,6 +1872,13 @@ var BrowserMCPServer = class {
|
|
|
1825
1872
|
...this.#viewport === void 0 ? {} : { viewport: this.#viewport },
|
|
1826
1873
|
...process.platform === "linux" && process.getuid?.() === 0 ? { args: ["--no-sandbox"] } : {}
|
|
1827
1874
|
});
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
await this.#discardWarm(profile, void 0);
|
|
1877
|
+
throw error;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
async #connectWarm(profile, browser) {
|
|
1881
|
+
try {
|
|
1828
1882
|
await browser.connect();
|
|
1829
1883
|
if (browser.pid !== void 0 && browser.endpoint !== void 0) {
|
|
1830
1884
|
const record = join(profile, BROWSER_SERVER_RECORD);
|
|
@@ -1834,10 +1888,13 @@ var BrowserMCPServer = class {
|
|
|
1834
1888
|
}));
|
|
1835
1889
|
await rename(`${record}.tmp`, record);
|
|
1836
1890
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
await this.#discardWarm(profile, browser);
|
|
1893
|
+
throw error;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
async #prepareWarm(slot) {
|
|
1897
|
+
try {
|
|
1841
1898
|
const ending = Promise.withResolvers();
|
|
1842
1899
|
ending.promise.catch(() => void 0);
|
|
1843
1900
|
this.#endings.set(slot, ending);
|
|
@@ -1846,13 +1903,15 @@ var BrowserMCPServer = class {
|
|
|
1846
1903
|
this.#observe(slot, generation);
|
|
1847
1904
|
return slot;
|
|
1848
1905
|
} catch (error) {
|
|
1849
|
-
|
|
1850
|
-
this.#stranded = failure;
|
|
1851
|
-
});
|
|
1852
|
-
if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_LAUNCH, error)}`);
|
|
1906
|
+
await this.#discardWarm(slot.profile, slot.browser, slot);
|
|
1853
1907
|
throw error;
|
|
1854
1908
|
}
|
|
1855
1909
|
}
|
|
1910
|
+
async #discardWarm(profile, browser, slot) {
|
|
1911
|
+
if (this.#folders.has(profile)) await (slot === void 0 ? this.#destroySlot(profile, browser) : this.#destroyRecord(slot)).catch((failure) => {
|
|
1912
|
+
this.#stranded = failure;
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1856
1915
|
async #destroySlot(profile, browser) {
|
|
1857
1916
|
try {
|
|
1858
1917
|
await browser?.destroy();
|
|
@@ -1875,13 +1934,13 @@ var BrowserMCPServer = class {
|
|
|
1875
1934
|
for (const entry of await readdir(profiles)) {
|
|
1876
1935
|
if (this.#closing !== void 0) return;
|
|
1877
1936
|
const pid = parseBrowserLockEntry(entry);
|
|
1878
|
-
if (pid === void 0 || pid === process.pid ||
|
|
1937
|
+
if (pid === void 0 || pid === process.pid || inspectProcess(pid) !== false) continue;
|
|
1879
1938
|
const folder = join(profiles, entry);
|
|
1880
1939
|
this.#folders.add(folder);
|
|
1881
1940
|
try {
|
|
1882
1941
|
const record = parseBrowserProfileRecord(await readFile(join(folder, BROWSER_SERVER_RECORD), "utf8"));
|
|
1883
1942
|
if (record === void 0) continue;
|
|
1884
|
-
if (
|
|
1943
|
+
if (inspectProcess(record.pid) !== false) {
|
|
1885
1944
|
const transport = createWebSocketCDPTransport({ url: record.endpoint });
|
|
1886
1945
|
const client = createCDPClient({ transport });
|
|
1887
1946
|
try {
|
|
@@ -1892,7 +1951,7 @@ var BrowserMCPServer = class {
|
|
|
1892
1951
|
await transport.close();
|
|
1893
1952
|
await client.close();
|
|
1894
1953
|
}
|
|
1895
|
-
if (
|
|
1954
|
+
if (inspectProcess(record.pid) !== false) continue;
|
|
1896
1955
|
}
|
|
1897
1956
|
} catch (error) {
|
|
1898
1957
|
if (!isError(error) || !("code" in error) || error.code !== "ENOENT") continue;
|
|
@@ -1959,8 +2018,7 @@ var BrowserMCPServer = class {
|
|
|
1959
2018
|
return new BrowserError("CLOSED", "the browser session ended", { subject: "toolset" });
|
|
1960
2019
|
}
|
|
1961
2020
|
};
|
|
1962
|
-
|
|
1963
|
-
//#region src/server/stores/FileBrowserStore.ts
|
|
2021
|
+
// src/server/stores/FileBrowserStore.ts
|
|
1964
2022
|
/**
|
|
1965
2023
|
* Shares confined filesystem operations between the journey and run stores.
|
|
1966
2024
|
* @remarks Requires an existing root directory. Links present during a component check are refused;
|
|
@@ -2005,9 +2063,11 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2005
2063
|
*/
|
|
2006
2064
|
async check(path, options) {
|
|
2007
2065
|
this.resolvePath(path);
|
|
2008
|
-
let current = this.#root;
|
|
2009
2066
|
const parts = relative(this.#root, path).split(sep).filter(Boolean);
|
|
2010
|
-
|
|
2067
|
+
return (await [void 0, ...parts].reduce(async (previous, part, index) => {
|
|
2068
|
+
const ancestor = await previous;
|
|
2069
|
+
if (!ancestor.exists) return ancestor;
|
|
2070
|
+
const current = part === void 0 ? ancestor.path : this.resolvePath(ancestor.path, part);
|
|
2011
2071
|
options?.signal?.throwIfAborted();
|
|
2012
2072
|
try {
|
|
2013
2073
|
const status = await lstat(current);
|
|
@@ -2016,13 +2076,20 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2016
2076
|
if (index < parts.length && !status.isDirectory()) throw new BrowserError("STORE_PATH", `Not a directory: ${current}`);
|
|
2017
2077
|
} catch (error) {
|
|
2018
2078
|
options?.signal?.throwIfAborted();
|
|
2019
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return
|
|
2079
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {
|
|
2080
|
+
path: current,
|
|
2081
|
+
exists: false
|
|
2082
|
+
};
|
|
2020
2083
|
throw this.translateError(current, error);
|
|
2021
2084
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2085
|
+
return {
|
|
2086
|
+
path: current,
|
|
2087
|
+
exists: true
|
|
2088
|
+
};
|
|
2089
|
+
}, Promise.resolve({
|
|
2090
|
+
path: this.#root,
|
|
2091
|
+
exists: true
|
|
2092
|
+
}))).exists;
|
|
2026
2093
|
}
|
|
2027
2094
|
/** Creates checked directories one component at a time. @param path - Directory @param options - Cancellation options */
|
|
2028
2095
|
async createDirectory(path, options) {
|
|
@@ -2057,35 +2124,45 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2057
2124
|
/** Atomically replaces a file and cleans its owned temporary file on failure. @param path - Destination @param value - Bytes or UTF-8 @param options - Cancellation options */
|
|
2058
2125
|
async write(path, value, options) {
|
|
2059
2126
|
const temporary = this.resolvePath(dirname(path), `.${randomUUID()}.tmp`);
|
|
2060
|
-
|
|
2127
|
+
const file = await this.#openTemporary(path, temporary, options);
|
|
2061
2128
|
try {
|
|
2062
|
-
await this
|
|
2063
|
-
|
|
2064
|
-
options?.signal?.throwIfAborted();
|
|
2065
|
-
const file = await open(temporary, "wx");
|
|
2066
|
-
owned = true;
|
|
2129
|
+
await this.#commitTemporary(file, path, temporary, value, options);
|
|
2130
|
+
} catch (error) {
|
|
2067
2131
|
try {
|
|
2068
2132
|
options?.signal?.throwIfAborted();
|
|
2069
|
-
|
|
2133
|
+
throw this.translateError(path, error);
|
|
2070
2134
|
} finally {
|
|
2071
|
-
await
|
|
2135
|
+
await this.remove(temporary);
|
|
2072
2136
|
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
async #openTemporary(path, temporary, options) {
|
|
2140
|
+
try {
|
|
2073
2141
|
await this.check(path, options);
|
|
2074
2142
|
await this.check(temporary, options);
|
|
2075
2143
|
options?.signal?.throwIfAborted();
|
|
2076
|
-
|
|
2077
|
-
await rename(temporary, path);
|
|
2078
|
-
} catch (error) {
|
|
2079
|
-
const target = await lstat(path).catch(() => void 0);
|
|
2080
|
-
if (error instanceof Error && "code" in error && (error.code === "EISDIR" || error.code === "EPERM") && target?.isDirectory()) throw new BrowserError("STORE_FILE", `Cannot replace directory: ${path}`, { path });
|
|
2081
|
-
throw error;
|
|
2082
|
-
}
|
|
2083
|
-
owned = false;
|
|
2144
|
+
return await open(temporary, "wx");
|
|
2084
2145
|
} catch (error) {
|
|
2085
2146
|
options?.signal?.throwIfAborted();
|
|
2086
2147
|
throw this.translateError(path, error);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
async #commitTemporary(file, path, temporary, value, options) {
|
|
2151
|
+
try {
|
|
2152
|
+
options?.signal?.throwIfAborted();
|
|
2153
|
+
await file.writeFile(value, { signal: options?.signal });
|
|
2087
2154
|
} finally {
|
|
2088
|
-
|
|
2155
|
+
await file.close();
|
|
2156
|
+
}
|
|
2157
|
+
await this.check(path, options);
|
|
2158
|
+
await this.check(temporary, options);
|
|
2159
|
+
options?.signal?.throwIfAborted();
|
|
2160
|
+
try {
|
|
2161
|
+
await rename(temporary, path);
|
|
2162
|
+
} catch (error) {
|
|
2163
|
+
const target = await lstat(path).catch(() => void 0);
|
|
2164
|
+
if (error instanceof Error && "code" in error && (error.code === "EISDIR" || error.code === "EPERM") && target?.isDirectory()) throw new BrowserError("STORE_FILE", `Cannot replace directory: ${path}`, { path });
|
|
2165
|
+
throw error;
|
|
2089
2166
|
}
|
|
2090
2167
|
}
|
|
2091
2168
|
/** Removes a checked file, accepting absence. @param path - File @param options - Cancellation options */
|
|
@@ -2125,43 +2202,43 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2125
2202
|
await this.createDirectory(dirname(path), options);
|
|
2126
2203
|
const name = formatBrowserLockEntry(process.pid, randomUUID());
|
|
2127
2204
|
const entry = this.resolvePath(path, name);
|
|
2128
|
-
for (
|
|
2129
|
-
await this.check(
|
|
2205
|
+
for (const lock of Array.from({ length: 8 }, () => path)) {
|
|
2206
|
+
await this.check(lock, options);
|
|
2130
2207
|
options?.signal?.throwIfAborted();
|
|
2131
2208
|
try {
|
|
2132
|
-
await mkdir(
|
|
2209
|
+
await mkdir(lock);
|
|
2133
2210
|
} catch (error) {
|
|
2134
|
-
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw this.translateError(
|
|
2135
|
-
const entries = await this.#readLock(
|
|
2211
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw this.translateError(lock, error);
|
|
2212
|
+
const entries = await this.#readLock(lock, options);
|
|
2136
2213
|
if (entries === void 0) continue;
|
|
2137
2214
|
if (entries.length > 1) break;
|
|
2138
2215
|
const holder = entries[0];
|
|
2139
2216
|
if (holder !== void 0) {
|
|
2140
2217
|
const pid = parseBrowserLockEntry(holder);
|
|
2141
|
-
if (pid === void 0 ||
|
|
2142
|
-
if (!await this.#unlinkLock(this.resolvePath(
|
|
2218
|
+
if (pid === void 0 || inspectProcess(pid) !== false) break;
|
|
2219
|
+
if (!await this.#unlinkLock(this.resolvePath(lock, holder))) continue;
|
|
2143
2220
|
}
|
|
2144
|
-
await this.#removeLock(
|
|
2221
|
+
await this.#removeLock(lock);
|
|
2145
2222
|
continue;
|
|
2146
2223
|
}
|
|
2147
|
-
|
|
2224
|
+
const cleanup = [];
|
|
2148
2225
|
try {
|
|
2149
2226
|
await this.check(entry, options);
|
|
2150
2227
|
try {
|
|
2151
2228
|
const file = await open(entry, "wx");
|
|
2152
|
-
|
|
2229
|
+
cleanup.push(this.#unlinkLock.bind(this, entry));
|
|
2153
2230
|
await file.close();
|
|
2154
2231
|
} catch (error) {
|
|
2155
2232
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
|
|
2156
2233
|
throw this.translateError(entry, error);
|
|
2157
2234
|
}
|
|
2158
|
-
const entries = await this.#readLock(
|
|
2235
|
+
const entries = await this.#readLock(lock, options);
|
|
2159
2236
|
if (entries?.length !== 1 || entries[0] !== name) break;
|
|
2160
2237
|
options?.signal?.throwIfAborted();
|
|
2161
2238
|
return await action();
|
|
2162
2239
|
} finally {
|
|
2163
|
-
|
|
2164
|
-
await this.#removeLock(
|
|
2240
|
+
for (const release of cleanup) await release();
|
|
2241
|
+
await this.#removeLock(lock);
|
|
2165
2242
|
}
|
|
2166
2243
|
}
|
|
2167
2244
|
throw new BrowserError("STORE_LOCKED", `Journey is locked: ${path}`);
|
|
@@ -2185,27 +2262,18 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2185
2262
|
faults,
|
|
2186
2263
|
truncated: false
|
|
2187
2264
|
};
|
|
2188
|
-
|
|
2189
|
-
try {
|
|
2190
|
-
options?.signal?.throwIfAborted();
|
|
2191
|
-
names = (await readdir(path)).sort();
|
|
2192
|
-
} catch (error) {
|
|
2193
|
-
options?.signal?.throwIfAborted();
|
|
2194
|
-
throw this.translateError(path, error);
|
|
2195
|
-
}
|
|
2196
|
-
let count = 0;
|
|
2265
|
+
const names = await this.#readNames(path, options);
|
|
2197
2266
|
for (const name of names) {
|
|
2198
2267
|
options?.signal?.throwIfAborted();
|
|
2199
2268
|
try {
|
|
2200
2269
|
const entry = await reader(name);
|
|
2201
2270
|
if (entry === void 0) continue;
|
|
2202
|
-
|
|
2203
|
-
if (entries.length
|
|
2204
|
-
entries,
|
|
2271
|
+
entries.push(entry);
|
|
2272
|
+
if (entries.length > offset + limit) return {
|
|
2273
|
+
entries: entries.filter((_entry, index) => index >= offset && index < offset + limit),
|
|
2205
2274
|
faults,
|
|
2206
2275
|
truncated: true
|
|
2207
2276
|
};
|
|
2208
|
-
entries.push(entry);
|
|
2209
2277
|
} catch (error) {
|
|
2210
2278
|
options?.signal?.throwIfAborted();
|
|
2211
2279
|
const reason = error instanceof BrowserError ? error.context?.["reason"] : void 0;
|
|
@@ -2216,11 +2284,20 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2216
2284
|
}
|
|
2217
2285
|
}
|
|
2218
2286
|
return {
|
|
2219
|
-
entries,
|
|
2287
|
+
entries: entries.filter((_entry, index) => index >= offset),
|
|
2220
2288
|
faults,
|
|
2221
2289
|
truncated: false
|
|
2222
2290
|
};
|
|
2223
2291
|
}
|
|
2292
|
+
async #readNames(path, options) {
|
|
2293
|
+
try {
|
|
2294
|
+
options?.signal?.throwIfAborted();
|
|
2295
|
+
return (await readdir(path)).sort();
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
options?.signal?.throwIfAborted();
|
|
2298
|
+
throw this.translateError(path, error);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2224
2301
|
/**
|
|
2225
2302
|
* Allocates a run directory exclusively, retrying colliding producer candidates.
|
|
2226
2303
|
* @param parent - Checked parent directory
|
|
@@ -2300,8 +2377,7 @@ var FileBrowserStore = class FileBrowserStore {
|
|
|
2300
2377
|
}
|
|
2301
2378
|
}
|
|
2302
2379
|
};
|
|
2303
|
-
|
|
2304
|
-
//#region src/server/stores/FileBrowserJourneyStore.ts
|
|
2380
|
+
// src/server/stores/FileBrowserJourneyStore.ts
|
|
2305
2381
|
/**
|
|
2306
2382
|
* Persists journeys with exclusive writes and revision counters retained across deletion.
|
|
2307
2383
|
* @remarks Requires an existing root. A live holder refuses immediately; dead or empty locks are reclaimed within a bounded attempt count.
|
|
@@ -2395,8 +2471,7 @@ var FileBrowserJourneyStore = class {
|
|
|
2395
2471
|
}, options);
|
|
2396
2472
|
}
|
|
2397
2473
|
};
|
|
2398
|
-
|
|
2399
|
-
//#region src/server/stores/FileBrowserRunStore.ts
|
|
2474
|
+
// src/server/stores/FileBrowserRunStore.ts
|
|
2400
2475
|
/**
|
|
2401
2476
|
* Persists runs and captures only in directories allocated by this instance.
|
|
2402
2477
|
* @remarks Requires an existing root. Reopened stores can read and delete saved runs.
|
|
@@ -2553,8 +2628,7 @@ var FileBrowserRunStore = class FileBrowserRunStore {
|
|
|
2553
2628
|
}
|
|
2554
2629
|
}
|
|
2555
2630
|
};
|
|
2556
|
-
|
|
2557
|
-
//#region src/server/factories.ts
|
|
2631
|
+
// src/server/factories.ts
|
|
2558
2632
|
/**
|
|
2559
2633
|
* Creates a raw-CDP `BrowserInterface` façade with discovery, connection, and lifecycle
|
|
2560
2634
|
* management.
|
|
@@ -2634,8 +2708,7 @@ function createFileBrowserRunStore(options) {
|
|
|
2634
2708
|
function createBrowserMCPServer(options) {
|
|
2635
2709
|
return new BrowserMCPServer(options);
|
|
2636
2710
|
}
|
|
2637
|
-
|
|
2638
|
-
//#region src/server/Browser.ts
|
|
2711
|
+
// src/server/Browser.ts
|
|
2639
2712
|
/**
|
|
2640
2713
|
* Discovers, launches, connects to, and owns Chromium-family browser sessions.
|
|
2641
2714
|
*
|
|
@@ -2752,30 +2825,30 @@ var Browser = class {
|
|
|
2752
2825
|
return [...this.#contexts];
|
|
2753
2826
|
}
|
|
2754
2827
|
async isolate(options) {
|
|
2755
|
-
const
|
|
2756
|
-
this.#isolating.add(
|
|
2828
|
+
const pending = this.#isolate(options);
|
|
2829
|
+
this.#isolating.add(pending);
|
|
2757
2830
|
try {
|
|
2758
|
-
return await
|
|
2831
|
+
return await pending;
|
|
2759
2832
|
} finally {
|
|
2760
|
-
this.#isolating.delete(
|
|
2833
|
+
this.#isolating.delete(pending);
|
|
2761
2834
|
}
|
|
2762
2835
|
}
|
|
2763
2836
|
async create(options) {
|
|
2764
2837
|
if (this.#destroyed) throw new BrowserError("CLOSED", "Browser has been destroyed", { subject: "browser" });
|
|
2765
2838
|
const client = this.#client;
|
|
2766
2839
|
if (this.#status !== "connected" || client === void 0) throw new BrowserError("DISCONNECTED", "Browser is not connected");
|
|
2767
|
-
|
|
2768
|
-
if (context === void 0) {
|
|
2769
|
-
context = new BrowserContext(client, {
|
|
2770
|
-
...this.#options.viewport === void 0 ? {} : { viewport: this.#options.viewport },
|
|
2771
|
-
writer: createFileBrowserWriter()
|
|
2772
|
-
});
|
|
2773
|
-
this.#registerContext(context);
|
|
2774
|
-
}
|
|
2775
|
-
const page = await context.create(options);
|
|
2840
|
+
const page = await (this.#contexts[0] ?? this.#defaultContext(client)).create(options);
|
|
2776
2841
|
this.#emitter.emit("page", page);
|
|
2777
2842
|
return page;
|
|
2778
2843
|
}
|
|
2844
|
+
#defaultContext(client) {
|
|
2845
|
+
const context = new BrowserContext(client, {
|
|
2846
|
+
...this.#options.viewport === void 0 ? {} : { viewport: this.#options.viewport },
|
|
2847
|
+
writer: createFileBrowserWriter()
|
|
2848
|
+
});
|
|
2849
|
+
this.#registerContext(context);
|
|
2850
|
+
return context;
|
|
2851
|
+
}
|
|
2779
2852
|
destroy() {
|
|
2780
2853
|
const active = this.#shutdown;
|
|
2781
2854
|
if (active !== void 0) return active;
|
|
@@ -3069,41 +3142,40 @@ var Browser = class {
|
|
|
3069
3142
|
async #launch() {
|
|
3070
3143
|
if (this.#process !== void 0) throw new BrowserError("CONNECTION", "A browser process is already active on this instance");
|
|
3071
3144
|
const requestedEngine = this.#options.browsers?.engine;
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
}
|
|
3145
|
+
const supplied = this.#options.executable;
|
|
3146
|
+
const selected = supplied === void 0 ? findSystemBrowsers({
|
|
3147
|
+
...this.#options.browsers,
|
|
3148
|
+
...requestedEngine !== void 0 ? { engine: requestedEngine } : {}
|
|
3149
|
+
})[0] : {
|
|
3150
|
+
executable: supplied,
|
|
3151
|
+
engine: parseBrowserEngine(supplied) ?? "chromium"
|
|
3152
|
+
};
|
|
3153
|
+
const executable = selected?.executable;
|
|
3154
|
+
const resolvedEngine = selected?.engine;
|
|
3083
3155
|
if (executable === void 0) throw new BrowserError("CONNECTION", "No Chromium browser found. Install Chrome, Edge, or Chromium.", requestedEngine === void 0 ? void 0 : { engine: requestedEngine });
|
|
3084
3156
|
this.#engine = resolvedEngine ?? "chromium";
|
|
3085
3157
|
await this.#releaseProfile();
|
|
3086
3158
|
const profile = await createBrowserProfile(this.#options.profile);
|
|
3087
3159
|
this.#profile = profile;
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
process = launchBrowserProcess(executable, this.#options.cdp?.port, this.#options.headless ?? true, profile.path, this.#options.args);
|
|
3091
|
-
} catch (error) {
|
|
3160
|
+
const launched = attempt(() => launchBrowserProcess(executable, this.#options.cdp?.port, this.#options.headless ?? true, profile.path, this.#options.args));
|
|
3161
|
+
if (!launched.success) {
|
|
3092
3162
|
await this.#releaseProfile();
|
|
3093
|
-
throw error;
|
|
3163
|
+
throw launched.error;
|
|
3094
3164
|
}
|
|
3165
|
+
const process = launched.value;
|
|
3095
3166
|
this.#process = process;
|
|
3096
|
-
|
|
3167
|
+
const cleanup = [];
|
|
3097
3168
|
try {
|
|
3098
3169
|
const endpoint = await this.#waitForLaunch(process, executable, this.#options.args);
|
|
3099
3170
|
const transport = createWebSocketCDPTransport({
|
|
3100
3171
|
url: endpoint,
|
|
3101
3172
|
timeout: this.#timeout()
|
|
3102
3173
|
});
|
|
3103
|
-
client = new CDPClient({
|
|
3174
|
+
const client = new CDPClient({
|
|
3104
3175
|
transport,
|
|
3105
3176
|
timeout: this.#timeout()
|
|
3106
3177
|
});
|
|
3178
|
+
cleanup.push(this.#closeClient.bind(this, client));
|
|
3107
3179
|
await this.#raceAbort(client.connect());
|
|
3108
3180
|
await this.#takeEndpointOwner(process, client);
|
|
3109
3181
|
const connection = this.#options.profile === void 0 ? "launch" : "persistent";
|
|
@@ -3120,7 +3192,7 @@ var Browser = class {
|
|
|
3120
3192
|
} catch (error) {
|
|
3121
3193
|
this.#unbindTransport();
|
|
3122
3194
|
this.#unbindProcess();
|
|
3123
|
-
await
|
|
3195
|
+
for (const release of cleanup) await release();
|
|
3124
3196
|
await this.#terminate(process);
|
|
3125
3197
|
await this.#releaseProfile();
|
|
3126
3198
|
this.#process = void 0;
|
|
@@ -3146,18 +3218,8 @@ var Browser = class {
|
|
|
3146
3218
|
*/
|
|
3147
3219
|
async #takeEndpointOwner(process, client) {
|
|
3148
3220
|
if (process.exitCode === null && process.signalCode === null) return;
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
result = await client.send("SystemInfo.getProcessInfo");
|
|
3152
|
-
} catch {
|
|
3153
|
-
result = void 0;
|
|
3154
|
-
}
|
|
3155
|
-
let pid;
|
|
3156
|
-
if (isRecord(result) && isArray(result["processInfo"])) for (const entry of result["processInfo"]) {
|
|
3157
|
-
if (!isRecord(entry) || entry["type"] !== "browser" || !isInteger(entry["id"])) continue;
|
|
3158
|
-
pid = entry["id"];
|
|
3159
|
-
break;
|
|
3160
|
-
}
|
|
3221
|
+
const result = await client.send("SystemInfo.getProcessInfo").catch(() => void 0);
|
|
3222
|
+
const pid = isRecord(result) && isArray(result["processInfo"]) ? Iterator.from(result["processInfo"]).map((entry) => isRecord(entry) && entry["type"] === "browser" && isInteger(entry["id"]) ? entry["id"] : void 0).find((id) => id !== void 0) : void 0;
|
|
3161
3223
|
if (pid === void 0) {
|
|
3162
3224
|
await this.#closeRemote(client);
|
|
3163
3225
|
throw new BrowserError("CONNECTION", "The browser launcher exited without naming the process serving its CDP endpoint", {
|
|
@@ -3225,12 +3287,7 @@ var Browser = class {
|
|
|
3225
3287
|
async #syncContexts() {
|
|
3226
3288
|
const client = this.#client;
|
|
3227
3289
|
if (client === void 0) return;
|
|
3228
|
-
|
|
3229
|
-
try {
|
|
3230
|
-
result = await client.send("Target.getTargets");
|
|
3231
|
-
} catch {
|
|
3232
|
-
return;
|
|
3233
|
-
}
|
|
3290
|
+
const result = await client.send("Target.getTargets").catch(() => void 0);
|
|
3234
3291
|
if (!isRecord(result) || !isArray(result["targetInfos"])) return;
|
|
3235
3292
|
const pages = [];
|
|
3236
3293
|
for (const target of result["targetInfos"]) {
|
|
@@ -3251,63 +3308,63 @@ var Browser = class {
|
|
|
3251
3308
|
this.#registerContext(context);
|
|
3252
3309
|
}
|
|
3253
3310
|
async #destroyResources() {
|
|
3254
|
-
let terminated = false;
|
|
3255
3311
|
try {
|
|
3256
|
-
await this.#
|
|
3257
|
-
|
|
3258
|
-
this.#
|
|
3259
|
-
|
|
3260
|
-
if (this.#owned === true) {
|
|
3261
|
-
await this.#closeContexts();
|
|
3262
|
-
if (this.#process === void 0) await this.#closeRemote(client);
|
|
3263
|
-
} else await this.#destroyContexts();
|
|
3264
|
-
this.#contexts = [];
|
|
3265
|
-
await this.#terminate(this.#process);
|
|
3266
|
-
this.#process = void 0;
|
|
3267
|
-
this.#servingPid = void 0;
|
|
3268
|
-
terminated = true;
|
|
3269
|
-
} finally {
|
|
3270
|
-
const contexts = this.#contexts;
|
|
3271
|
-
this.#contexts = [];
|
|
3272
|
-
const client = this.#client;
|
|
3273
|
-
this.#client = void 0;
|
|
3274
|
-
try {
|
|
3275
|
-
await this.#cleanupLocal(contexts, client);
|
|
3276
|
-
if (terminated) await this.#releaseProfile();
|
|
3277
|
-
} finally {
|
|
3278
|
-
this.#finish();
|
|
3279
|
-
}
|
|
3312
|
+
await this.#destroySession();
|
|
3313
|
+
} catch (error) {
|
|
3314
|
+
await this.#finishResources(false);
|
|
3315
|
+
throw error;
|
|
3280
3316
|
}
|
|
3317
|
+
await this.#finishResources(true);
|
|
3318
|
+
}
|
|
3319
|
+
async #destroySession() {
|
|
3320
|
+
await this.#settle();
|
|
3321
|
+
this.#unbindTransport();
|
|
3322
|
+
this.#unbindProcess();
|
|
3323
|
+
const client = this.#client;
|
|
3324
|
+
if (this.#owned === true) {
|
|
3325
|
+
await this.#closeContexts();
|
|
3326
|
+
if (this.#process === void 0) await this.#closeRemote(client);
|
|
3327
|
+
} else await this.#destroyContexts();
|
|
3328
|
+
this.#contexts = [];
|
|
3329
|
+
await this.#terminate(this.#process);
|
|
3330
|
+
this.#process = void 0;
|
|
3331
|
+
this.#servingPid = void 0;
|
|
3281
3332
|
}
|
|
3282
3333
|
async #closeResources() {
|
|
3283
|
-
let terminated = false;
|
|
3284
3334
|
try {
|
|
3285
|
-
await this.#
|
|
3286
|
-
|
|
3287
|
-
this.#
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3335
|
+
await this.#closeSession();
|
|
3336
|
+
} catch (error) {
|
|
3337
|
+
await this.#finishResources(false);
|
|
3338
|
+
throw error;
|
|
3339
|
+
}
|
|
3340
|
+
await this.#finishResources(true);
|
|
3341
|
+
}
|
|
3342
|
+
async #closeSession() {
|
|
3343
|
+
await this.#settle();
|
|
3344
|
+
this.#unbindTransport();
|
|
3345
|
+
this.#unbindProcess();
|
|
3346
|
+
const client = this.#client;
|
|
3347
|
+
await this.#closeContexts();
|
|
3348
|
+
this.#contexts = [];
|
|
3349
|
+
await this.#closeRemote(client);
|
|
3350
|
+
const process = this.#process;
|
|
3351
|
+
if (process !== void 0) {
|
|
3352
|
+
if (!await this.#waitForTerminationWithin(process, 3e3)) await this.#terminate(process);
|
|
3353
|
+
await this.#closeProcessPipe(process);
|
|
3354
|
+
}
|
|
3355
|
+
this.#process = void 0;
|
|
3356
|
+
this.#servingPid = void 0;
|
|
3357
|
+
}
|
|
3358
|
+
async #finishResources(release) {
|
|
3359
|
+
const contexts = this.#contexts;
|
|
3360
|
+
this.#contexts = [];
|
|
3361
|
+
const client = this.#client;
|
|
3362
|
+
this.#client = void 0;
|
|
3363
|
+
try {
|
|
3364
|
+
await this.#cleanupLocal(contexts, client);
|
|
3365
|
+
if (release) await this.#releaseProfile();
|
|
3300
3366
|
} finally {
|
|
3301
|
-
|
|
3302
|
-
this.#contexts = [];
|
|
3303
|
-
const client = this.#client;
|
|
3304
|
-
this.#client = void 0;
|
|
3305
|
-
try {
|
|
3306
|
-
await this.#cleanupLocal(contexts, client);
|
|
3307
|
-
if (terminated) await this.#releaseProfile();
|
|
3308
|
-
} finally {
|
|
3309
|
-
this.#finish();
|
|
3310
|
-
}
|
|
3367
|
+
this.#finish();
|
|
3311
3368
|
}
|
|
3312
3369
|
}
|
|
3313
3370
|
async #settle() {
|
|
@@ -3323,29 +3380,29 @@ var Browser = class {
|
|
|
3323
3380
|
if (this.#exitCleanup === cleanup) this.#exitCleanup = void 0;
|
|
3324
3381
|
}
|
|
3325
3382
|
async #closeRemote(client) {
|
|
3326
|
-
|
|
3327
|
-
let temporary = false;
|
|
3328
|
-
if (remote === void 0 && this.#owned === true && this.#endpoint !== void 0) {
|
|
3329
|
-
const transport = createWebSocketCDPTransport({
|
|
3330
|
-
url: this.#endpoint,
|
|
3331
|
-
timeout: this.#timeout()
|
|
3332
|
-
});
|
|
3333
|
-
remote = new CDPClient({
|
|
3334
|
-
transport,
|
|
3335
|
-
timeout: this.#timeout()
|
|
3336
|
-
});
|
|
3337
|
-
try {
|
|
3338
|
-
await remote.connect();
|
|
3339
|
-
temporary = true;
|
|
3340
|
-
} catch {
|
|
3341
|
-
await this.#closeClient(remote);
|
|
3342
|
-
return;
|
|
3343
|
-
}
|
|
3344
|
-
}
|
|
3383
|
+
const remote = client ?? await this.#openRemote();
|
|
3345
3384
|
if (remote !== void 0) try {
|
|
3346
3385
|
await remote.send("Browser.close");
|
|
3347
3386
|
} catch {}
|
|
3348
|
-
if (
|
|
3387
|
+
if (client === void 0 && remote !== void 0) await this.#closeClient(remote);
|
|
3388
|
+
}
|
|
3389
|
+
async #openRemote() {
|
|
3390
|
+
if (this.#owned !== true || this.#endpoint === void 0) return void 0;
|
|
3391
|
+
const transport = createWebSocketCDPTransport({
|
|
3392
|
+
url: this.#endpoint,
|
|
3393
|
+
timeout: this.#timeout()
|
|
3394
|
+
});
|
|
3395
|
+
const remote = new CDPClient({
|
|
3396
|
+
transport,
|
|
3397
|
+
timeout: this.#timeout()
|
|
3398
|
+
});
|
|
3399
|
+
try {
|
|
3400
|
+
await remote.connect();
|
|
3401
|
+
return remote;
|
|
3402
|
+
} catch {
|
|
3403
|
+
await this.#closeClient(remote);
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3349
3406
|
}
|
|
3350
3407
|
async #closeContexts() {
|
|
3351
3408
|
for (const context of [...this.#contexts]) try {
|
|
@@ -3380,14 +3437,12 @@ var Browser = class {
|
|
|
3380
3437
|
if (profile !== void 0) await removeBrowserProfile(profile);
|
|
3381
3438
|
}
|
|
3382
3439
|
async #cleanupExitedProcess(process, contexts, client) {
|
|
3383
|
-
let terminated = false;
|
|
3384
3440
|
try {
|
|
3385
3441
|
await this.#terminate(process);
|
|
3386
|
-
terminated = true;
|
|
3387
3442
|
} finally {
|
|
3388
3443
|
await this.#cleanupLocal(contexts, client);
|
|
3389
3444
|
}
|
|
3390
|
-
|
|
3445
|
+
await this.#releaseProfile();
|
|
3391
3446
|
}
|
|
3392
3447
|
async #cleanupLocal(contexts, client) {
|
|
3393
3448
|
await this.#destroyContextList(contexts);
|
|
@@ -3437,31 +3492,9 @@ var Browser = class {
|
|
|
3437
3492
|
});
|
|
3438
3493
|
}
|
|
3439
3494
|
}
|
|
3440
|
-
/**
|
|
3441
|
-
* Waits within a bound for the rest of a terminated process group, or the process a
|
|
3442
|
-
* launcher handed the endpoint to, to disappear.
|
|
3443
|
-
*
|
|
3444
|
-
* @remarks
|
|
3445
|
-
* This is the one interval loop in the server. Node raises an exit event for the direct
|
|
3446
|
-
* child only, so no event source reports the remainder of a POSIX process group or a
|
|
3447
|
-
* handed-off process, and the drain probes on `BROWSER_DRAIN_INTERVAL_MS` until the
|
|
3448
|
-
* remainder is gone or `timeout` elapses.
|
|
3449
|
-
*/
|
|
3450
|
-
async #waitForRemainderWithin(process, timeout) {
|
|
3451
|
-
const deadline = performance.now() + timeout;
|
|
3452
|
-
let confirmed = false;
|
|
3453
|
-
while (true) {
|
|
3454
|
-
const alive = this.#inspectRemainder(process);
|
|
3455
|
-
if (alive === false) return true;
|
|
3456
|
-
if (alive === true) confirmed = true;
|
|
3457
|
-
const remaining = deadline - performance.now();
|
|
3458
|
-
if (remaining <= 0) return confirmed ? false : void 0;
|
|
3459
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(100, remaining)));
|
|
3460
|
-
}
|
|
3461
|
-
}
|
|
3462
3495
|
async #waitForTerminationWithin(process, timeout, final = false) {
|
|
3463
3496
|
const exited = this.#waitForProcessWithin(process, timeout);
|
|
3464
|
-
const drained = this.#
|
|
3497
|
+
const drained = new BrowserProcessDrain(this.#inspectRemainder.bind(this, process), timeout).result;
|
|
3465
3498
|
const results = await Promise.all([exited, drained]);
|
|
3466
3499
|
return results[0] && (results[1] === true || final && results[1] === void 0);
|
|
3467
3500
|
}
|
|
@@ -3501,7 +3534,6 @@ var Browser = class {
|
|
|
3501
3534
|
this.#emitter.destroy();
|
|
3502
3535
|
}
|
|
3503
3536
|
};
|
|
3504
|
-
|
|
3505
|
-
export { BROWSER_CDP_LIST_PATH, BROWSER_CDP_PROTOCOL, BROWSER_CDP_VERSION_PATH, BROWSER_DEFAULT_CDP_PORT, BROWSER_DEFAULT_HOST, BROWSER_DEVTOOLS_PATTERN, BROWSER_DRAIN_INTERVAL_MS, BROWSER_ENGINE_HINTS, BROWSER_ENV_PATH_KEYS, BROWSER_EXECUTABLE_NAMES, BROWSER_EXECUTABLE_PATHS, BROWSER_FILE_STORE_LIMIT, BROWSER_HEADLESS_ARG, BROWSER_JOURNEY_LOCK_ATTEMPTS, BROWSER_JOURNEY_LOCK_DIRECTORY, BROWSER_JOURNEY_REVISION_FILE, BROWSER_JOURNEY_SNAPSHOT_FILE, BROWSER_KILL_GRACE_MS, BROWSER_LAUNCH_ARGS, BROWSER_PORT_PROBE_TIMEOUT_MS, BROWSER_PROCESS_EXIT_CAUSE, BROWSER_PROFILE_PREFIX, BROWSER_RUN_DIRECTORY, BROWSER_RUN_FILE, BROWSER_SERVER_BUSY, BROWSER_SERVER_CONTEXTS, BROWSER_SERVER_CONTEXTS_LIMIT, BROWSER_SERVER_COPY, BROWSER_SERVER_CRASH, BROWSER_SERVER_EXHAUSTED, BROWSER_SERVER_HOLDER, BROWSER_SERVER_LAUNCH, BROWSER_SERVER_OPTIONS, BROWSER_SERVER_POOL_LIMIT, BROWSER_SERVER_POOL_SIZE, BROWSER_SERVER_RECORD, BROWSER_SERVER_RESTARTS, BROWSER_SERVER_SWEEP, BROWSER_SERVER_TEARDOWN, BROWSER_SERVER_UNAVAILABLE, BROWSER_SERVER_UNRESOLVED, BROWSER_STORE_CACHE_DIRS, BROWSER_STORE_DEFAULT_DIRS, BROWSER_STORE_ENV_KEY, BROWSER_STORE_GLOBS, BROWSER_STORE_LINK_NAME, BROWSER_TRANSPORT_LOSS_CAUSE, BROWSER_TRANSPORT_LOSS_DEFER_MS, BROWSER_WINDOWS_ROOT_FALLBACKS, BROWSER_WINDOWS_SUFFIXES, Browser, BrowserMCPServer, FileBrowserJourneyStore, FileBrowserRunStore, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserMCPServer, createBrowserProfile, createFileBrowserJourneyStore, createFileBrowserRunStore, createFileBrowserWriter, createWebSocketCDPTransport, describeBrowserServerLoss, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowsers, formatBrowserLockEntry, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, parseBrowserLockEntry, parseBrowserProfileRecord, parseBrowserViewport, probePathNames, probeProcess, readBrowserEndpoint, readFirstLine, removeBrowserProfile };
|
|
3537
|
+
export { BROWSER_CDP_LIST_PATH, BROWSER_CDP_PROTOCOL, BROWSER_CDP_VERSION_PATH, BROWSER_DEFAULT_CDP_PORT, BROWSER_DEFAULT_HOST, BROWSER_DEVTOOLS_PATTERN, BROWSER_DRAIN_INTERVAL_MS, BROWSER_ENGINE_HINTS, BROWSER_ENV_PATH_KEYS, BROWSER_EXECUTABLE_NAMES, BROWSER_EXECUTABLE_PATHS, BROWSER_FILE_STORE_LIMIT, BROWSER_HEADLESS_ARG, BROWSER_JOURNEY_LOCK_ATTEMPTS, BROWSER_JOURNEY_LOCK_DIRECTORY, BROWSER_JOURNEY_REVISION_FILE, BROWSER_JOURNEY_SNAPSHOT_FILE, BROWSER_KILL_GRACE_MS, BROWSER_LAUNCH_ARGS, BROWSER_PORT_PROBE_TIMEOUT_MS, BROWSER_PROCESS_EXIT_CAUSE, BROWSER_PROFILE_PREFIX, BROWSER_RUN_DIRECTORY, BROWSER_RUN_FILE, BROWSER_SERVER_BUSY, BROWSER_SERVER_CONTEXTS, BROWSER_SERVER_CONTEXTS_LIMIT, BROWSER_SERVER_COPY, BROWSER_SERVER_CRASH, BROWSER_SERVER_EXHAUSTED, BROWSER_SERVER_HOLDER, BROWSER_SERVER_LAUNCH, BROWSER_SERVER_OPTIONS, BROWSER_SERVER_POOL_LIMIT, BROWSER_SERVER_POOL_SIZE, BROWSER_SERVER_RECORD, BROWSER_SERVER_RESTARTS, BROWSER_SERVER_SWEEP, BROWSER_SERVER_TEARDOWN, BROWSER_SERVER_UNAVAILABLE, BROWSER_SERVER_UNRESOLVED, BROWSER_STORE_CACHE_DIRS, BROWSER_STORE_DEFAULT_DIRS, BROWSER_STORE_ENV_KEY, BROWSER_STORE_GLOBS, BROWSER_STORE_LINK_NAME, BROWSER_TRANSPORT_LOSS_CAUSE, BROWSER_TRANSPORT_LOSS_DEFER_MS, BROWSER_WINDOWS_ROOT_FALLBACKS, BROWSER_WINDOWS_SUFFIXES, Browser, BrowserEndpoint, BrowserMCPServer, BrowserProcessDrain, FileBrowserJourneyStore, FileBrowserRunStore, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserMCPServer, createBrowserProfile, createFileBrowserJourneyStore, createFileBrowserRunStore, createFileBrowserWriter, createWebSocketCDPTransport, describeBrowserServerLoss, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowsers, formatBrowserLockEntry, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, parseBrowserLockEntry, parseBrowserProfileRecord, parseBrowserViewport, probePathNames, readBrowserEndpoint, readFirstLine, removeBrowserProfile };
|
|
3506
3538
|
|
|
3507
3539
|
//# sourceMappingURL=index.js.map
|