@orkestrel/browser 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/main.js +12 -3
- package/dist/bin/main.js.map +1 -1
- package/dist/src/core/index.cjs +5 -3
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +3 -1
- package/dist/src/core/index.d.ts +3 -1
- package/dist/src/core/index.js +5 -3
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +505 -82
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +125 -16
- package/dist/src/server/index.d.ts +125 -16
- package/dist/src/server/index.js +494 -86
- package/dist/src/server/index.js.map +1 -1
- package/package.json +3 -2
package/dist/src/server/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_JOURNEY_FORMAT_VERSION, BROWSER_JOURNEY_NAME_PATTERN, BROWSER_JOURNEY_TOOL_NAMES, BROWSER_RUN_ID_PATTERN, BROWSER_TOOL_COPY, BROWSER_TOOL_NAMES, BrowserConnectionError, BrowserContext, BrowserError, BrowserTransition, CDPClient, createBrowserToolset, generateBrowserRunId, isBrowserConnectionError, normalizeBrowserJourneyReason, validateBrowserContextOptions, validateBrowserJourney, validateBrowserRun, validateBrowserStorePage } from "../core/index.js";
|
|
2
|
-
import { attempt, isArray, isError, isInstance, isInteger, isRecord, isString, parseJSON } from "@orkestrel/contract";
|
|
1
|
+
import { BROWSER_DEFAULT_TIMEOUT_MS, BROWSER_JOURNEY_FORMAT_VERSION, BROWSER_JOURNEY_NAME_PATTERN, BROWSER_JOURNEY_TOOL_NAMES, BROWSER_RUN_ID_PATTERN, BROWSER_TOOL_COPY, BROWSER_TOOL_NAMES, BrowserConnectionError, BrowserContext, BrowserError, BrowserTransition, CDPClient, createBrowserToolset, createCDPClient, generateBrowserRunId, isBrowserConnectionError, isBrowserError, isCDPTimeoutError, normalizeBrowserJourneyReason, validateBrowserContextOptions, validateBrowserJourney, validateBrowserRun, validateBrowserStorePage } from "../core/index.js";
|
|
2
|
+
import { attempt, isArray, isError, isInstance, isInteger, isNumber, isRecord, isString, parseJSON } from "@orkestrel/contract";
|
|
3
3
|
import { existsSync, globSync, realpathSync } from "node:fs";
|
|
4
4
|
import { lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -11,7 +11,8 @@ 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 { createMCPLegacy, createMCPServer } from "@orkestrel/mcp";
|
|
14
|
+
import { JSONRPC_SERVER_ERROR, MCPError, createMCPLegacy, createMCPServer } from "@orkestrel/mcp";
|
|
15
|
+
import { createPool, isPoolError } from "@orkestrel/pool";
|
|
15
16
|
import { createStdioServer } from "@orkestrel/mcp/server";
|
|
16
17
|
import { createTool, createToolManager, toolToDefinition } from "@orkestrel/tool";
|
|
17
18
|
//#region src/server/constants.ts
|
|
@@ -211,6 +212,30 @@ var BROWSER_RUN_FILE = "run.json";
|
|
|
211
212
|
var BROWSER_RUN_DIRECTORY = "runs";
|
|
212
213
|
/** Bounds a file-store listing page by default. */
|
|
213
214
|
var BROWSER_FILE_STORE_LIMIT = 100;
|
|
215
|
+
/** Names the refusal when no browser can serve a call. */
|
|
216
|
+
var BROWSER_SERVER_UNAVAILABLE = "BROWSER_SERVER_UNAVAILABLE";
|
|
217
|
+
/** Names a failed attempt to warm a browser. */
|
|
218
|
+
var BROWSER_SERVER_LAUNCH = "BROWSER_SERVER_LAUNCH";
|
|
219
|
+
/** Sets the default number of warm browsers to 1. */
|
|
220
|
+
var BROWSER_SERVER_POOL_SIZE = 1;
|
|
221
|
+
/** Limits the number of warm browsers to 3. */
|
|
222
|
+
var BROWSER_SERVER_POOL_LIMIT = 3;
|
|
223
|
+
/** Permits one failed refill before the next failure spends the bound. */
|
|
224
|
+
var BROWSER_SERVER_RESTARTS = 1;
|
|
225
|
+
/** Names the browser process record in each profile. */
|
|
226
|
+
var BROWSER_SERVER_RECORD = "browse.json";
|
|
227
|
+
/** Names the diagnostic when the warm floor spends its restart bound. */
|
|
228
|
+
var BROWSER_SERVER_EXHAUSTED = "BROWSER_SERVER_EXHAUSTED";
|
|
229
|
+
/** Names a failed browser server teardown. */
|
|
230
|
+
var BROWSER_SERVER_TEARDOWN = "BROWSER_SERVER_TEARDOWN";
|
|
231
|
+
/** Names a failed profile sweep. */
|
|
232
|
+
var BROWSER_SERVER_SWEEP = "BROWSER_SERVER_SWEEP";
|
|
233
|
+
/** Names a refused browser server option. */
|
|
234
|
+
var BROWSER_SERVER_OPTIONS = "BROWSER_SERVER_OPTIONS";
|
|
235
|
+
/** Names the notice that a browser and its session state were lost. */
|
|
236
|
+
var BROWSER_SERVER_CRASH = "BROWSER_SERVER_CRASH";
|
|
237
|
+
/** Names an interrupted call whose outcome is unknown. */
|
|
238
|
+
var BROWSER_SERVER_UNRESOLVED = "BROWSER_SERVER_UNRESOLVED";
|
|
214
239
|
//#endregion
|
|
215
240
|
//#region src/server/errors.ts
|
|
216
241
|
/**
|
|
@@ -254,6 +279,58 @@ function isBrowserDestroyedError(value) {
|
|
|
254
279
|
//#endregion
|
|
255
280
|
//#region src/server/helpers.ts
|
|
256
281
|
/**
|
|
282
|
+
* Probes whether a process has not been confirmed absent.
|
|
283
|
+
* @param pid - Process identifier to probe with signal zero
|
|
284
|
+
* @returns True if the host has not confirmed absence with ESRCH; false otherwise
|
|
285
|
+
* @example
|
|
286
|
+
* probeProcess(process.pid) // true
|
|
287
|
+
*/
|
|
288
|
+
function probeProcess(pid) {
|
|
289
|
+
try {
|
|
290
|
+
process.kill(pid, 0);
|
|
291
|
+
return true;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return !(isError(error) && "code" in error && error.code === "ESRCH");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Parses a profile record naming a positive process identifier and a loopback DevTools endpoint.
|
|
298
|
+
* @param text - JSON record read from a browse profile
|
|
299
|
+
* @returns The record, or undefined when its shape or endpoint is refused
|
|
300
|
+
* @example
|
|
301
|
+
* parseBrowserProfileRecord('{"pid":123,"endpoint":"ws://127.0.0.1:9222/devtools/browser/session"}')
|
|
302
|
+
*/
|
|
303
|
+
function parseBrowserProfileRecord(text) {
|
|
304
|
+
const record = parseJSON(text);
|
|
305
|
+
if (!isRecord(record)) return void 0;
|
|
306
|
+
const pid = record["pid"];
|
|
307
|
+
const endpoint = record["endpoint"];
|
|
308
|
+
if (!isNumber(pid) || !Number.isSafeInteger(pid) || pid <= 0 || !isString(endpoint)) return void 0;
|
|
309
|
+
const match = /^ws:\/\/127\.0\.0\.1:([1-9]\d*)\/devtools\/browser\/[^/?#\s\\]+$/.exec(endpoint);
|
|
310
|
+
if (match === null || Number(match[1]) > 65535) return void 0;
|
|
311
|
+
return {
|
|
312
|
+
pid,
|
|
313
|
+
endpoint
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Describes a browser loss with its code, cause, and the session state it invalidates.
|
|
318
|
+
* @param code - Browser server diagnostic code
|
|
319
|
+
* @param cause - The loss or refusal, including its process context when present
|
|
320
|
+
* @param url - The lost page's last URL, when known
|
|
321
|
+
* @returns The coded diagnostic and any operation-lifecycle guidance
|
|
322
|
+
* @example
|
|
323
|
+
* describeBrowserServerLoss('BROWSER_SERVER_UNAVAILABLE', new Error('Browser exited'))
|
|
324
|
+
*/
|
|
325
|
+
function describeBrowserServerLoss(code, cause, url) {
|
|
326
|
+
const pid = isBrowserError(cause) ? cause.context?.["pid"] : void 0;
|
|
327
|
+
const message = `${code}: ${(cause === void 0 ? "Browser session lost" : isError(cause) ? cause.message : String(cause)).replace(/\.+$/u, "")}${isNumber(pid) ? ` (pid ${pid})` : ""}.`;
|
|
328
|
+
if (code !== "BROWSER_SERVER_CRASH" && code !== "BROWSER_SERVER_UNRESOLVED") return message;
|
|
329
|
+
const lost = `Lost the page${url === void 0 ? "" : ` at ${url}`}, its tabs, every element reference, the retained reading, dialogs, holds, an unsaved recording, the active replay, and the isolated context's cookies.`;
|
|
330
|
+
const next = `The next call acquires a browser that starts at about:blank, or answers ${BROWSER_SERVER_UNAVAILABLE} when none can serve.`;
|
|
331
|
+
return code === "BROWSER_SERVER_UNRESOLVED" ? `${message} The outcome is unknown. ${lost} Browse did not repeat the call. ${next}` : `${message} ${lost}`;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
257
334
|
* Formats a process identifier and UUID token as a lock entry name.
|
|
258
335
|
* @param pid - Positive safe integer identifying the holder process
|
|
259
336
|
* @param token - Lowercase UUID token identifying this acquisition
|
|
@@ -867,12 +944,12 @@ var FileBrowserWriter = class {
|
|
|
867
944
|
};
|
|
868
945
|
//#endregion
|
|
869
946
|
//#region package.json
|
|
870
|
-
var version = "0.0.
|
|
947
|
+
var version = "0.0.23";
|
|
871
948
|
//#endregion
|
|
872
949
|
//#region src/server/BrowserMCPServer.ts
|
|
873
950
|
/**
|
|
874
951
|
* Implements `BrowserMCPServerInterface`: serves the browser vocabulary and the journey tools over
|
|
875
|
-
* the Model Context Protocol on stdio, and
|
|
952
|
+
* the Model Context Protocol on stdio, and warms Chromium at server start.
|
|
876
953
|
*
|
|
877
954
|
* @remarks
|
|
878
955
|
* The server's own manager holds one dispatcher per name of the vocabulary — `look`, `read`, `plain`,
|
|
@@ -882,18 +959,22 @@ var version = "0.0.22";
|
|
|
882
959
|
* absent. After the launch, a tool the toolset's manager adds under another name, such as a page
|
|
883
960
|
* tool it adopts, is mirrored as a dispatcher with that tool's definition, and the mirror is
|
|
884
961
|
* removed when the toolset withdraws the tool, so the server's tool list changes and a subscribed
|
|
885
|
-
* client is notified.
|
|
886
|
-
*
|
|
887
|
-
* A launch creates `ROOT/.profiles/
|
|
962
|
+
* client is notified. Startup warms the pool and leases one validated browser. The legacy
|
|
963
|
+
* handshake and every tool call await that setup; concurrent callers share a replacement lease.
|
|
964
|
+
* A launch creates `ROOT/.profiles/PID-UUID/` exclusively, connects a browser that never attaches to an
|
|
888
965
|
* existing endpoint, opens one page in an isolated context, and constructs the page toolset with
|
|
889
966
|
* that context and the file journey and run stores under the root, on a manager the toolset owns.
|
|
890
967
|
* A dispatcher forwards its arguments and the call's context, signal included, to the toolset's
|
|
891
968
|
* manager and returns that manager's value, or rejects with its failure message; a text value
|
|
892
969
|
* reaches the client as one text block rather than as its JSON text.
|
|
970
|
+
* A disconnect or current-page crash retires its slot. A failed call receives a liveness ping;
|
|
971
|
+
* loss makes its outcome unresolved, while a known success remains successful. Pending losses
|
|
972
|
+
* prefix the next outcome on a successor, or a refusal, and calls are never repeated.
|
|
893
973
|
*
|
|
894
974
|
* `start()` serves stdio and destroys the server at the end of its input, on `SIGINT`, and on
|
|
895
|
-
* `SIGTERM`. `destroy()` stops reading requests and removes every dispatcher, then destroys
|
|
896
|
-
* toolset, which aborts the active replay, then
|
|
975
|
+
* `SIGTERM`. `destroy()` stops reading requests and removes every dispatcher, then destroys every
|
|
976
|
+
* slot through the pool — its toolset, which aborts the active replay, then its browser, then its
|
|
977
|
+
* profile — and rechecks the profile folders this server made or kept.
|
|
897
978
|
*
|
|
898
979
|
* Chromium on Linux refuses to start as the root user unless its sandbox is disabled, so a server
|
|
899
980
|
* running as root on Linux launches Chromium with `--no-sandbox`; a launch that could run
|
|
@@ -923,11 +1004,21 @@ var BrowserMCPServer = class {
|
|
|
923
1004
|
#added;
|
|
924
1005
|
#removed;
|
|
925
1006
|
#cleared;
|
|
926
|
-
#
|
|
927
|
-
#
|
|
928
|
-
#
|
|
929
|
-
#
|
|
930
|
-
#
|
|
1007
|
+
#pool;
|
|
1008
|
+
#log;
|
|
1009
|
+
#folders = /* @__PURE__ */ new Set();
|
|
1010
|
+
#faults = /* @__PURE__ */ new Set();
|
|
1011
|
+
#losses = /* @__PURE__ */ new WeakMap();
|
|
1012
|
+
#watches = /* @__PURE__ */ new Map();
|
|
1013
|
+
#notice = /* @__PURE__ */ new Set();
|
|
1014
|
+
#reference;
|
|
1015
|
+
#references = 0;
|
|
1016
|
+
#failure;
|
|
1017
|
+
#stranded;
|
|
1018
|
+
#lease;
|
|
1019
|
+
#granting;
|
|
1020
|
+
#starting;
|
|
1021
|
+
#sweeping;
|
|
931
1022
|
#closing;
|
|
932
1023
|
/**
|
|
933
1024
|
* Registers the dispatchers and binds the protocol to the stdio streams without reading them.
|
|
@@ -935,6 +1026,19 @@ var BrowserMCPServer = class {
|
|
|
935
1026
|
* @param options - The root, the launch, the journeys' read-only switch, and the streams
|
|
936
1027
|
*/
|
|
937
1028
|
constructor(options) {
|
|
1029
|
+
const size = options?.pool?.size ?? 1;
|
|
1030
|
+
if (!isInteger(size) || size < 1 || size > 3) throw new BrowserError(`pool.size must be an integer from 1 through 3`, BROWSER_SERVER_OPTIONS);
|
|
1031
|
+
this.#log = options?.log ?? process.stderr;
|
|
1032
|
+
this.#reference = this.#issue.bind(this);
|
|
1033
|
+
this.#pool = createPool({
|
|
1034
|
+
create: this.#warm.bind(this),
|
|
1035
|
+
destroy: (slot) => this.#destroySlot(slot.profile, slot.browser, slot.toolset, this.#losses.get(slot)?.cause),
|
|
1036
|
+
validate: this.#validate.bind(this),
|
|
1037
|
+
watch: this.#watch.bind(this),
|
|
1038
|
+
error: this.#fault.bind(this),
|
|
1039
|
+
min: size,
|
|
1040
|
+
restarts: 1
|
|
1041
|
+
});
|
|
938
1042
|
this.#root = resolve(options?.root ?? "tmp/browsers");
|
|
939
1043
|
this.#headless = options?.headless ?? true;
|
|
940
1044
|
this.#executable = options?.executable;
|
|
@@ -954,7 +1058,8 @@ var BrowserMCPServer = class {
|
|
|
954
1058
|
version
|
|
955
1059
|
},
|
|
956
1060
|
tools: this.#tools,
|
|
957
|
-
execution: this.#execute.bind(this)
|
|
1061
|
+
execution: this.#execute.bind(this),
|
|
1062
|
+
handshake: this.#handshake.bind(this)
|
|
958
1063
|
});
|
|
959
1064
|
this.#transport = createStdioServer(createMCPLegacy(server), {
|
|
960
1065
|
input: this.#input,
|
|
@@ -967,17 +1072,32 @@ var BrowserMCPServer = class {
|
|
|
967
1072
|
}
|
|
968
1073
|
async start() {
|
|
969
1074
|
if (this.#closing !== void 0) throw this.#ended();
|
|
970
|
-
|
|
971
|
-
this.#
|
|
972
|
-
this.#transport.start();
|
|
973
|
-
this.#input.on("end", this.#signal);
|
|
974
|
-
process.on("SIGINT", this.#signal);
|
|
975
|
-
process.on("SIGTERM", this.#signal);
|
|
1075
|
+
this.#starting ??= this.#setup();
|
|
1076
|
+
return this.#starting;
|
|
976
1077
|
}
|
|
977
1078
|
destroy() {
|
|
978
|
-
this.#closing ??= this.#destroy();
|
|
1079
|
+
this.#closing ??= Promise.resolve().then(this.#destroy.bind(this));
|
|
979
1080
|
return this.#closing;
|
|
980
1081
|
}
|
|
1082
|
+
async #setup() {
|
|
1083
|
+
this.#input.on("end", this.#signal);
|
|
1084
|
+
process.on("SIGINT", this.#signal);
|
|
1085
|
+
process.on("SIGTERM", this.#signal);
|
|
1086
|
+
this.#transport.start();
|
|
1087
|
+
try {
|
|
1088
|
+
await mkdir(join(this.#root, ".profiles"), { recursive: true });
|
|
1089
|
+
if (this.#closing !== void 0) return;
|
|
1090
|
+
this.#sweeping = this.#sweep();
|
|
1091
|
+
const warming = this.#pool.start().then(void 0, (error) => error);
|
|
1092
|
+
await this.#grant(this.#abort.signal);
|
|
1093
|
+
if (this.#closing !== void 0) return;
|
|
1094
|
+
warming.then(this.#exhaust.bind(this));
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
if (this.#closing !== void 0) return;
|
|
1097
|
+
if (isBrowserError(error) && error.code === "BROWSER_SERVER_UNAVAILABLE") throw error;
|
|
1098
|
+
throw this.#unavailable(isPoolError(error) ? error.cause ?? this.#failure : error);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
981
1101
|
async #destroy() {
|
|
982
1102
|
process.removeListener("SIGINT", this.#signal);
|
|
983
1103
|
process.removeListener("SIGTERM", this.#signal);
|
|
@@ -985,19 +1105,34 @@ var BrowserMCPServer = class {
|
|
|
985
1105
|
this.#transport.stop();
|
|
986
1106
|
for (const tool of this.#tools.tools()) this.#tools.remove(tool.name);
|
|
987
1107
|
this.#unmirror();
|
|
1108
|
+
this.#lease = void 0;
|
|
988
1109
|
this.#abort.abort();
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
await this.#browser?.destroy().catch((error) => failures.push(error));
|
|
992
|
-
this.#browser = void 0;
|
|
993
|
-
const profile = this.#profile;
|
|
994
|
-
this.#profile = void 0;
|
|
995
|
-
if (profile !== void 0) await rm(profile, {
|
|
996
|
-
recursive: true,
|
|
997
|
-
force: true,
|
|
998
|
-
maxRetries: 5
|
|
1110
|
+
const barrier = this.#pool.destroy().catch((error) => {
|
|
1111
|
+
for (const failure of isPoolError(error) ? error.context?.failures ?? [error] : [error]) this.#faults.add(failure);
|
|
999
1112
|
});
|
|
1000
|
-
|
|
1113
|
+
await this.#starting?.catch(() => void 0);
|
|
1114
|
+
await barrier;
|
|
1115
|
+
await this.#sweeping;
|
|
1116
|
+
if (this.#stranded !== void 0) this.#faults.add(this.#stranded);
|
|
1117
|
+
for (const folder of this.#folders) {
|
|
1118
|
+
try {
|
|
1119
|
+
const record = parseBrowserProfileRecord(await readFile(join(folder, BROWSER_SERVER_RECORD), "utf8"));
|
|
1120
|
+
if (record === void 0 || probeProcess(record.pid)) continue;
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
if (!isError(error) || !("code" in error) || error.code !== "ENOENT") continue;
|
|
1123
|
+
}
|
|
1124
|
+
try {
|
|
1125
|
+
await rm(folder, {
|
|
1126
|
+
recursive: true,
|
|
1127
|
+
force: true,
|
|
1128
|
+
maxRetries: 5
|
|
1129
|
+
});
|
|
1130
|
+
this.#folders.delete(folder);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
this.#faults.add(error);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (this.#faults.size > 0) throw new AggregateError([...this.#faults], "The browse server teardown failed");
|
|
1001
1136
|
}
|
|
1002
1137
|
async #execute(context) {
|
|
1003
1138
|
const result = await context.tools.execute(context.call, {
|
|
@@ -1014,38 +1149,224 @@ var BrowserMCPServer = class {
|
|
|
1014
1149
|
};
|
|
1015
1150
|
}
|
|
1016
1151
|
#end() {
|
|
1017
|
-
this
|
|
1152
|
+
if (this.#closing !== void 0) return;
|
|
1153
|
+
this.destroy().catch(this.#endFailure.bind(this));
|
|
1154
|
+
}
|
|
1155
|
+
#endFailure(error) {
|
|
1156
|
+
process.exitCode = 1;
|
|
1157
|
+
this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_TEARDOWN, error)}`);
|
|
1018
1158
|
}
|
|
1019
1159
|
async #forward(name, args, context) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1160
|
+
let slot;
|
|
1161
|
+
try {
|
|
1162
|
+
slot = await this.#serve(context.signal);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
context.signal.throwIfAborted();
|
|
1165
|
+
if (isBrowserError(error)) throw new BrowserError(this.#annotate(`${error.code}: ${error.message}`), error.code);
|
|
1166
|
+
throw error;
|
|
1167
|
+
}
|
|
1168
|
+
let result;
|
|
1169
|
+
try {
|
|
1170
|
+
result = await slot.toolset.tools.execute({
|
|
1171
|
+
id: name,
|
|
1172
|
+
name,
|
|
1173
|
+
arguments: args
|
|
1174
|
+
}, context);
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
result = {
|
|
1177
|
+
id: name,
|
|
1178
|
+
name,
|
|
1179
|
+
success: false,
|
|
1180
|
+
error: isError(error) ? error.message : String(error)
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
context.signal.throwIfAborted();
|
|
1184
|
+
if (!result.success) {
|
|
1185
|
+
if (this.#lease?.value === slot && !context.signal.aborted) try {
|
|
1186
|
+
await slot.browser.ping({ signal: AbortSignal.any([context.signal, this.#abort.signal]) });
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
if (!context.signal.aborted && !this.#abort.signal.aborted) this.#lose(slot, error);
|
|
1189
|
+
}
|
|
1190
|
+
context.signal.throwIfAborted();
|
|
1191
|
+
if (this.#lease?.value !== slot) {
|
|
1192
|
+
const loss = this.#losses.get(slot);
|
|
1193
|
+
throw new BrowserError(this.#annotate(describeBrowserServerLoss(BROWSER_SERVER_UNRESOLVED, loss?.cause, loss?.url), slot), BROWSER_SERVER_UNRESOLVED);
|
|
1194
|
+
}
|
|
1195
|
+
throw new BrowserError(this.#annotate(result.error, slot));
|
|
1196
|
+
}
|
|
1197
|
+
return typeof result.value === "string" ? this.#annotate(result.value, slot) : result.value;
|
|
1198
|
+
}
|
|
1199
|
+
#annotate(value, slot) {
|
|
1200
|
+
const notes = [];
|
|
1201
|
+
for (const lost of this.#notice) {
|
|
1202
|
+
if (lost === slot) continue;
|
|
1203
|
+
const loss = this.#losses.get(lost);
|
|
1204
|
+
notes.push(describeBrowserServerLoss(BROWSER_SERVER_CRASH, loss?.cause, loss?.url));
|
|
1205
|
+
this.#notice.delete(lost);
|
|
1206
|
+
}
|
|
1207
|
+
return [...notes, value].join("\n");
|
|
1208
|
+
}
|
|
1209
|
+
async #handshake(options) {
|
|
1210
|
+
try {
|
|
1211
|
+
await this.#race(this.#starting, options.signal);
|
|
1212
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
options.signal.throwIfAborted();
|
|
1215
|
+
if (isBrowserError(error)) throw new MCPError(`${error.code}: ${error.message}`, JSONRPC_SERVER_ERROR, { code: error.code });
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
async #race(promise, signal) {
|
|
1220
|
+
signal.throwIfAborted();
|
|
1221
|
+
const aborted = Promise.withResolvers();
|
|
1222
|
+
const listener = addAbortListener(signal, () => aborted.reject(signal.reason));
|
|
1223
|
+
try {
|
|
1224
|
+
return await Promise.race([promise, aborted.promise]);
|
|
1225
|
+
} finally {
|
|
1226
|
+
listener[Symbol.dispose]();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async #grant(signal) {
|
|
1230
|
+
if (this.#lease !== void 0) return this.#lease;
|
|
1231
|
+
this.#granting ??= this.#pool.acquire(this.#abort.signal).then(this.#hold.bind(this), this.#refuse.bind(this));
|
|
1232
|
+
const token = await this.#race(this.#granting, signal);
|
|
1233
|
+
if (token === void 0) throw this.#ended();
|
|
1234
|
+
return token;
|
|
1235
|
+
}
|
|
1236
|
+
#hold(token) {
|
|
1237
|
+
this.#granting = void 0;
|
|
1238
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1239
|
+
if (this.#losses.has(token.value)) {
|
|
1240
|
+
token.destroy().catch(() => void 0);
|
|
1241
|
+
throw this.#unavailable(this.#losses.get(token.value)?.cause);
|
|
1242
|
+
}
|
|
1243
|
+
this.#lease = token;
|
|
1244
|
+
const tools = token.value.toolset.tools;
|
|
1245
|
+
for (const tool of tools.tools()) this.#mirror(tool);
|
|
1246
|
+
tools.emitter.on("add", this.#added);
|
|
1247
|
+
tools.emitter.on("remove", this.#removed);
|
|
1248
|
+
tools.emitter.on("clear", this.#cleared);
|
|
1249
|
+
return token;
|
|
1250
|
+
}
|
|
1251
|
+
#refuse(error) {
|
|
1252
|
+
this.#granting = void 0;
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
async #serve(signal) {
|
|
1256
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1257
|
+
await this.#race(this.#starting, signal);
|
|
1258
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1259
|
+
const held = this.#lease;
|
|
1260
|
+
if (held !== void 0) try {
|
|
1261
|
+
await held.value.browser.ping({ signal: AbortSignal.any([signal, this.#abort.signal]) });
|
|
1262
|
+
if (this.#lease === held) return held.value;
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
signal.throwIfAborted();
|
|
1265
|
+
if (this.#abort.signal.aborted) throw this.#ended();
|
|
1266
|
+
this.#lose(held.value, error);
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
const token = await this.#grant(signal);
|
|
1270
|
+
if (this.#lease !== token) throw this.#unavailable(this.#losses.get(token.value)?.cause);
|
|
1271
|
+
return token.value;
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
signal.throwIfAborted();
|
|
1274
|
+
if (this.#abort.signal.aborted || isPoolError(error) && error.code === "destroyed") throw this.#ended();
|
|
1275
|
+
if (isPoolError(error) && (error.code === "create" || error.code === "cleanup")) {
|
|
1276
|
+
this.#exhaust(error);
|
|
1277
|
+
throw this.#unavailable(error.cause ?? this.#failure);
|
|
1278
|
+
}
|
|
1279
|
+
throw error;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async #validate(slot) {
|
|
1283
|
+
try {
|
|
1284
|
+
await slot.browser.ping({ signal: this.#abort.signal });
|
|
1285
|
+
return true;
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
if (!this.#abort.signal.aborted) this.#lose(slot, error);
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
#lose(slot, cause) {
|
|
1292
|
+
if (this.#closing !== void 0 || this.#losses.has(slot)) return;
|
|
1293
|
+
this.#losses.set(slot, {
|
|
1294
|
+
cause,
|
|
1295
|
+
url: slot.toolset.view.url
|
|
1296
|
+
});
|
|
1297
|
+
this.#failure = cause;
|
|
1298
|
+
if (this.#lease?.value !== slot) return;
|
|
1299
|
+
const token = this.#lease;
|
|
1300
|
+
this.#unmirror();
|
|
1301
|
+
this.#lease = void 0;
|
|
1302
|
+
this.#notice.add(slot);
|
|
1303
|
+
token.destroy().catch(() => void 0);
|
|
1304
|
+
}
|
|
1305
|
+
#watch(slot, signal) {
|
|
1306
|
+
const loss = Promise.withResolvers();
|
|
1307
|
+
const disconnect = this.#disconnect.bind(this, slot);
|
|
1308
|
+
const page = this.#track.bind(this, slot);
|
|
1309
|
+
const subscription = addAbortListener(signal, this.#unwatch.bind(this, slot));
|
|
1310
|
+
this.#watches.set(slot, {
|
|
1311
|
+
resolve: loss.resolve,
|
|
1312
|
+
disconnect,
|
|
1313
|
+
page,
|
|
1314
|
+
subscription,
|
|
1315
|
+
crashes: /* @__PURE__ */ new Map()
|
|
1316
|
+
});
|
|
1317
|
+
slot.browser.emitter.on("disconnect", disconnect);
|
|
1318
|
+
slot.context.emitter.on("page", page);
|
|
1319
|
+
for (const current of slot.context.pages()) this.#track(slot, current);
|
|
1320
|
+
if (signal.aborted) this.#unwatch(slot);
|
|
1321
|
+
else if (slot.browser.status !== "connected") this.#disconnect(slot);
|
|
1322
|
+
return loss.promise;
|
|
1323
|
+
}
|
|
1324
|
+
#track(slot, page) {
|
|
1325
|
+
const watch = this.#watches.get(slot);
|
|
1326
|
+
if (watch === void 0 || watch.crashes.has(page)) return;
|
|
1327
|
+
const crash = this.#createCrash(slot, page);
|
|
1328
|
+
page.emitter.on("crash", crash);
|
|
1329
|
+
this.#watches.set(slot, {
|
|
1330
|
+
...watch,
|
|
1331
|
+
crashes: new Map([...watch.crashes, [page, crash]])
|
|
1036
1332
|
});
|
|
1037
|
-
return session;
|
|
1038
1333
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
this.#
|
|
1334
|
+
#createCrash(slot, page) {
|
|
1335
|
+
return () => {
|
|
1336
|
+
if (page === slot.toolset.view) this.#report(slot, /* @__PURE__ */ new Error("The current page renderer crashed"));
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
#disconnect(slot) {
|
|
1340
|
+
this.#report(slot, /* @__PURE__ */ new Error(slot.browser.pid === void 0 ? "The browser process exited" : "The browser transport disconnected"));
|
|
1341
|
+
}
|
|
1342
|
+
#report(slot, cause) {
|
|
1343
|
+
const watch = this.#watches.get(slot);
|
|
1344
|
+
if (watch === void 0) return;
|
|
1345
|
+
this.#unwatch(slot);
|
|
1346
|
+
this.#lose(slot, cause);
|
|
1347
|
+
watch.resolve(cause);
|
|
1348
|
+
}
|
|
1349
|
+
#unwatch(slot) {
|
|
1350
|
+
const watch = this.#watches.get(slot);
|
|
1351
|
+
if (watch === void 0) return;
|
|
1352
|
+
this.#watches.delete(slot);
|
|
1353
|
+
slot.browser.emitter.off("disconnect", watch.disconnect);
|
|
1354
|
+
slot.context.emitter.off("page", watch.page);
|
|
1355
|
+
for (const [page, crash] of watch.crashes) page.emitter.off("crash", crash);
|
|
1356
|
+
watch.subscription[Symbol.dispose]();
|
|
1357
|
+
}
|
|
1358
|
+
#fault(error) {
|
|
1359
|
+
this.#faults.add(error);
|
|
1360
|
+
}
|
|
1361
|
+
async #warm() {
|
|
1362
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1363
|
+
if (this.#stranded !== void 0) throw new BrowserError(isError(this.#stranded) ? this.#stranded.message : String(this.#stranded), BROWSER_SERVER_TEARDOWN, isBrowserError(this.#stranded) ? this.#stranded.context : void 0);
|
|
1364
|
+
const profile = join(this.#root, ".profiles", formatBrowserLockEntry(process.pid, randomUUID()));
|
|
1046
1365
|
let browser;
|
|
1047
1366
|
let toolset;
|
|
1048
1367
|
try {
|
|
1368
|
+
await mkdir(profile);
|
|
1369
|
+
this.#folders.add(profile);
|
|
1049
1370
|
browser = this.#launcher({
|
|
1050
1371
|
headless: this.#headless,
|
|
1051
1372
|
profile,
|
|
@@ -1054,9 +1375,16 @@ var BrowserMCPServer = class {
|
|
|
1054
1375
|
...this.#executable === void 0 ? {} : { executable: this.#executable },
|
|
1055
1376
|
...process.platform === "linux" && process.getuid?.() === 0 ? { args: ["--no-sandbox"] } : {}
|
|
1056
1377
|
});
|
|
1057
|
-
this.#browser = browser;
|
|
1058
1378
|
await browser.connect();
|
|
1059
|
-
|
|
1379
|
+
if (browser.pid !== void 0 && browser.endpoint !== void 0) {
|
|
1380
|
+
const record = join(profile, BROWSER_SERVER_RECORD);
|
|
1381
|
+
await writeFile(`${record}.tmp`, JSON.stringify({
|
|
1382
|
+
pid: browser.pid,
|
|
1383
|
+
endpoint: browser.endpoint
|
|
1384
|
+
}));
|
|
1385
|
+
await rename(`${record}.tmp`, record);
|
|
1386
|
+
}
|
|
1387
|
+
const context = await browser.isolate({ reference: this.#reference });
|
|
1060
1388
|
const page = await context.create();
|
|
1061
1389
|
toolset = createBrowserToolset(page, {
|
|
1062
1390
|
context,
|
|
@@ -1066,26 +1394,103 @@ var BrowserMCPServer = class {
|
|
|
1066
1394
|
readonly: this.#readonly
|
|
1067
1395
|
}
|
|
1068
1396
|
});
|
|
1069
|
-
this.#mirrored = toolset.tools;
|
|
1070
|
-
toolset.tools.emitter.on("add", this.#added);
|
|
1071
|
-
toolset.tools.emitter.on("remove", this.#removed);
|
|
1072
|
-
toolset.tools.emitter.on("clear", this.#cleared);
|
|
1073
1397
|
await toolset.start();
|
|
1074
|
-
return
|
|
1398
|
+
return {
|
|
1399
|
+
browser,
|
|
1400
|
+
profile,
|
|
1401
|
+
context,
|
|
1402
|
+
toolset
|
|
1403
|
+
};
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
if (this.#folders.has(profile)) await this.#destroySlot(profile, browser, toolset).catch((failure) => {
|
|
1406
|
+
this.#stranded = failure;
|
|
1407
|
+
});
|
|
1408
|
+
if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_LAUNCH, error)}`);
|
|
1409
|
+
throw error;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
async #destroySlot(profile, browser, toolset, cause) {
|
|
1413
|
+
if (isCDPTimeoutError(cause) && browser?.pid !== void 0) try {
|
|
1414
|
+
process.kill(browser.pid, "SIGKILL");
|
|
1075
1415
|
} catch (error) {
|
|
1076
|
-
this.#
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
await browser?.destroy()
|
|
1416
|
+
if (!isError(error) || !("code" in error) || error.code !== "ESRCH") this.#faults.add(error);
|
|
1417
|
+
}
|
|
1418
|
+
await toolset?.destroy().catch((error) => this.#faults.add(error));
|
|
1419
|
+
try {
|
|
1420
|
+
await browser?.destroy();
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_TEARDOWN, error)}`);
|
|
1423
|
+
throw error;
|
|
1424
|
+
}
|
|
1425
|
+
try {
|
|
1081
1426
|
await rm(profile, {
|
|
1082
1427
|
recursive: true,
|
|
1083
1428
|
force: true,
|
|
1084
1429
|
maxRetries: 5
|
|
1085
1430
|
});
|
|
1086
|
-
|
|
1431
|
+
this.#folders.delete(profile);
|
|
1432
|
+
} catch {}
|
|
1433
|
+
}
|
|
1434
|
+
async #sweep() {
|
|
1435
|
+
const profiles = join(this.#root, ".profiles");
|
|
1436
|
+
try {
|
|
1437
|
+
for (const entry of await readdir(profiles)) {
|
|
1438
|
+
if (this.#closing !== void 0) return;
|
|
1439
|
+
const pid = parseBrowserLockEntry(entry);
|
|
1440
|
+
if (pid === void 0 || pid === process.pid || probeProcess(pid)) continue;
|
|
1441
|
+
const folder = join(profiles, entry);
|
|
1442
|
+
this.#folders.add(folder);
|
|
1443
|
+
try {
|
|
1444
|
+
const record = parseBrowserProfileRecord(await readFile(join(folder, BROWSER_SERVER_RECORD), "utf8"));
|
|
1445
|
+
if (record === void 0) continue;
|
|
1446
|
+
if (probeProcess(record.pid)) {
|
|
1447
|
+
const transport = createCDPTransport({ url: record.endpoint });
|
|
1448
|
+
const client = createCDPClient({ transport });
|
|
1449
|
+
try {
|
|
1450
|
+
this.#abort.signal.throwIfAborted();
|
|
1451
|
+
await this.#race(client.connect(), this.#abort.signal);
|
|
1452
|
+
await client.send("Browser.close", void 0, { signal: this.#abort.signal });
|
|
1453
|
+
} catch {} finally {
|
|
1454
|
+
await transport.close();
|
|
1455
|
+
await client.close();
|
|
1456
|
+
}
|
|
1457
|
+
if (probeProcess(record.pid)) continue;
|
|
1458
|
+
}
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
if (!isError(error) || !("code" in error) || error.code !== "ENOENT") continue;
|
|
1461
|
+
}
|
|
1462
|
+
try {
|
|
1463
|
+
await rm(folder, {
|
|
1464
|
+
recursive: true,
|
|
1465
|
+
force: true,
|
|
1466
|
+
maxRetries: 5
|
|
1467
|
+
});
|
|
1468
|
+
this.#folders.delete(folder);
|
|
1469
|
+
} catch {}
|
|
1470
|
+
}
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_SWEEP, error)}`);
|
|
1087
1473
|
}
|
|
1088
1474
|
}
|
|
1475
|
+
#write(line) {
|
|
1476
|
+
try {
|
|
1477
|
+
this.#log.write(`${line}\n`, (error) => {
|
|
1478
|
+
if (error !== void 0 && error !== null) this.#faults.add(error);
|
|
1479
|
+
});
|
|
1480
|
+
} catch (error) {
|
|
1481
|
+
this.#faults.add(error);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
#exhaust(error) {
|
|
1485
|
+
if (isPoolError(error) && error.code === "create") this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_EXHAUSTED, error.cause ?? this.#failure)}`);
|
|
1486
|
+
}
|
|
1487
|
+
#unavailable(cause) {
|
|
1488
|
+
const message = describeBrowserServerLoss(BROWSER_SERVER_UNAVAILABLE, cause);
|
|
1489
|
+
return new BrowserError(message.slice(BROWSER_SERVER_UNAVAILABLE.length + 2), BROWSER_SERVER_UNAVAILABLE);
|
|
1490
|
+
}
|
|
1491
|
+
#issue() {
|
|
1492
|
+
return `e${++this.#references}`;
|
|
1493
|
+
}
|
|
1089
1494
|
#mirror(tool) {
|
|
1090
1495
|
if (this.#vocabulary.has(tool.name)) return;
|
|
1091
1496
|
this.#tools.add(createTool({
|
|
@@ -1095,7 +1500,7 @@ var BrowserMCPServer = class {
|
|
|
1095
1500
|
}
|
|
1096
1501
|
#withdraw(tool) {
|
|
1097
1502
|
if (this.#vocabulary.has(tool.name)) return;
|
|
1098
|
-
const current = this.#
|
|
1503
|
+
const current = this.#lease?.value.toolset.tools.tool(tool.name);
|
|
1099
1504
|
if (current === void 0) this.#tools.remove(tool.name);
|
|
1100
1505
|
else this.#mirror(current);
|
|
1101
1506
|
}
|
|
@@ -1103,9 +1508,9 @@ var BrowserMCPServer = class {
|
|
|
1103
1508
|
for (const tool of tools) this.#withdraw(tool);
|
|
1104
1509
|
}
|
|
1105
1510
|
#unmirror() {
|
|
1106
|
-
const mirrored = this.#
|
|
1511
|
+
const mirrored = this.#lease?.value.toolset.tools;
|
|
1107
1512
|
if (mirrored === void 0) return;
|
|
1108
|
-
|
|
1513
|
+
for (const tool of mirrored.tools()) if (!this.#vocabulary.has(tool.name)) this.#tools.remove(tool.name);
|
|
1109
1514
|
mirrored.emitter.off("add", this.#added);
|
|
1110
1515
|
mirrored.emitter.off("remove", this.#removed);
|
|
1111
1516
|
mirrored.emitter.off("clear", this.#cleared);
|
|
@@ -1278,13 +1683,7 @@ var FileBrowserStore = class {
|
|
|
1278
1683
|
const holder = entries[0];
|
|
1279
1684
|
if (holder !== void 0) {
|
|
1280
1685
|
const pid = parseBrowserLockEntry(holder);
|
|
1281
|
-
if (pid === void 0) break;
|
|
1282
|
-
try {
|
|
1283
|
-
process.kill(pid, 0);
|
|
1284
|
-
break;
|
|
1285
|
-
} catch (cause) {
|
|
1286
|
-
if (!(cause instanceof Error && "code" in cause && cause.code === "ESRCH")) break;
|
|
1287
|
-
}
|
|
1686
|
+
if (pid === void 0 || probeProcess(pid)) break;
|
|
1288
1687
|
if (!await this.#unlinkLock(this.resolvePath(path, holder))) continue;
|
|
1289
1688
|
}
|
|
1290
1689
|
await this.#removeLock(path);
|
|
@@ -1726,7 +2125,7 @@ function createFileBrowserRunStore(options) {
|
|
|
1726
2125
|
}
|
|
1727
2126
|
/**
|
|
1728
2127
|
* Creates the browse server, which serves the browser vocabulary and the journey tools over the
|
|
1729
|
-
* Model Context Protocol on stdio and
|
|
2128
|
+
* Model Context Protocol on stdio and warms Chromium at server start.
|
|
1730
2129
|
*
|
|
1731
2130
|
* @param options - The root, the launch, the journeys' read-only switch, and the streams
|
|
1732
2131
|
* @returns A {@link BrowserMCPServerInterface} that reads nothing until `start()`
|
|
@@ -1736,7 +2135,7 @@ function createFileBrowserRunStore(options) {
|
|
|
1736
2135
|
* import { createBrowserMCPServer } from '@orkestrel/browser/server'
|
|
1737
2136
|
*
|
|
1738
2137
|
* const server = createBrowserMCPServer({ root: 'tmp/browsers', headless: true })
|
|
1739
|
-
* await server.start() //
|
|
2138
|
+
* await server.start() // resolves after leasing a connected browser
|
|
1740
2139
|
* await server.destroy()
|
|
1741
2140
|
* ```
|
|
1742
2141
|
*/
|
|
@@ -1812,6 +2211,15 @@ var Browser = class {
|
|
|
1812
2211
|
get pid() {
|
|
1813
2212
|
return this.#servingPid ?? this.#process?.pid;
|
|
1814
2213
|
}
|
|
2214
|
+
get endpoint() {
|
|
2215
|
+
return this.#endpoint;
|
|
2216
|
+
}
|
|
2217
|
+
async ping(options) {
|
|
2218
|
+
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
2219
|
+
const client = this.#client;
|
|
2220
|
+
if (this.#status !== "connected" || client === void 0) throw new BrowserNotConnectedError();
|
|
2221
|
+
await client.send("Browser.getVersion", void 0, options);
|
|
2222
|
+
}
|
|
1815
2223
|
async discover() {
|
|
1816
2224
|
const result = await this.#discoverCDP();
|
|
1817
2225
|
this.#emitter.emit("discover", result);
|
|
@@ -1937,7 +2345,7 @@ var Browser = class {
|
|
|
1937
2345
|
await this.#connectCDP(discovery.endpoint);
|
|
1938
2346
|
return;
|
|
1939
2347
|
}
|
|
1940
|
-
} else await this.#raceAbort(this.#assertPortFree());
|
|
2348
|
+
} else if (this.#options.cdp?.port !== void 0) await this.#raceAbort(this.#assertPortFree());
|
|
1941
2349
|
this.#assertNotAborted();
|
|
1942
2350
|
await this.#launch();
|
|
1943
2351
|
} catch (error) {
|
|
@@ -2573,6 +2981,6 @@ var Browser = class {
|
|
|
2573
2981
|
}
|
|
2574
2982
|
};
|
|
2575
2983
|
//#endregion
|
|
2576
|
-
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_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, BrowserDestroyedError, BrowserMCPServer, BrowserNotConnectedError, FileBrowserJourneyStore, FileBrowserRunStore, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserMCPServer, createBrowserProfile, createBrowserWriter, createCDPTransport, createFileBrowserJourneyStore, createFileBrowserRunStore, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowser, findSystemBrowsers, formatBrowserLockEntry, isBrowserDestroyedError, isBrowserNotConnectedError, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, parseBrowserLockEntry, probePathNames, readBrowserEndpoint, readFirstLine, removeBrowserProfile };
|
|
2984
|
+
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_CRASH, BROWSER_SERVER_EXHAUSTED, 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, BrowserDestroyedError, BrowserMCPServer, BrowserNotConnectedError, FileBrowserJourneyStore, FileBrowserRunStore, FileBrowserWriter, WebSocketCDPTransport, browserToEngine, buildInstallPaths, buildStoreBases, buildWindowsRoots, createBrowser, createBrowserMCPServer, createBrowserProfile, createBrowserWriter, createCDPTransport, createFileBrowserJourneyStore, createFileBrowserRunStore, describeBrowserServerLoss, fetchCDPTargets, findEnvOverrides, findInstallPaths, findStorePaths, findSystemBrowser, findSystemBrowsers, formatBrowserLockEntry, isBrowserDestroyedError, isBrowserNotConnectedError, launchBrowserProcess, normalizeExecutablePath, parseBrowserEngine, parseBrowserLockEntry, parseBrowserProfileRecord, probePathNames, probeProcess, readBrowserEndpoint, readFirstLine, removeBrowserProfile };
|
|
2577
2985
|
|
|
2578
2986
|
//# sourceMappingURL=index.js.map
|