@orkestrel/browser 0.0.23 → 0.0.25
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 +17 -3
- package/dist/bin/main.js.map +1 -1
- package/dist/src/core/index.cjs +99 -25
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +30 -13
- package/dist/src/core/index.d.ts +30 -13
- package/dist/src/core/index.js +99 -25
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +701 -187
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +107 -13
- package/dist/src/server/index.d.ts +107 -13
- package/dist/src/server/index.js +698 -190
- package/dist/src/server/index.js.map +1 -1
- package/package.json +6 -6
package/dist/bin/main.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseBoolean, parseInteger } from "@orkestrel/contract";
|
|
3
3
|
import { BrowserError, isBrowserError } from "../src/core/index.js";
|
|
4
|
-
import { createBrowserMCPServer } from "../src/server/index.js";
|
|
4
|
+
import { createBrowserMCPServer, parseBrowserViewport } from "../src/server/index.js";
|
|
5
5
|
//#region src/bin/main.ts
|
|
6
6
|
process.stderr.on("error", () => {
|
|
7
7
|
process.exitCode = 1;
|
|
8
8
|
});
|
|
9
9
|
try {
|
|
10
|
-
const { BROWSE_ROOT, BROWSE_HEADLESS, BROWSE_EXECUTABLE, BROWSE_READONLY, BROWSE_POOL } = process.env;
|
|
10
|
+
const { BROWSE_ROOT, BROWSE_HEADLESS, BROWSE_EXECUTABLE, BROWSE_READONLY, BROWSE_POOL, BROWSE_CONTEXTS, BROWSE_VIEWPORT } = process.env;
|
|
11
11
|
const headless = parseBoolean(BROWSE_HEADLESS);
|
|
12
12
|
const readonly = parseBoolean(BROWSE_READONLY);
|
|
13
13
|
const size = parseInteger(BROWSE_POOL);
|
|
14
|
+
const contexts = parseInteger(BROWSE_CONTEXTS);
|
|
15
|
+
const viewport = parseBrowserViewport(BROWSE_VIEWPORT);
|
|
14
16
|
for (const [name, value, parsed] of [[
|
|
15
17
|
"BROWSE_HEADLESS",
|
|
16
18
|
BROWSE_HEADLESS,
|
|
@@ -27,12 +29,24 @@ try {
|
|
|
27
29
|
name: "BROWSE_POOL",
|
|
28
30
|
value: BROWSE_POOL
|
|
29
31
|
});
|
|
32
|
+
if (BROWSE_VIEWPORT !== void 0 && BROWSE_VIEWPORT !== "" && viewport === void 0) throw new BrowserError(`BROWSE_VIEWPORT must be positive integers in WIDTHxHEIGHT form, not ${JSON.stringify(BROWSE_VIEWPORT)}`, "BROWSER_SERVER_ENVIRONMENT", {
|
|
33
|
+
name: "BROWSE_VIEWPORT",
|
|
34
|
+
value: BROWSE_VIEWPORT
|
|
35
|
+
});
|
|
36
|
+
if (BROWSE_CONTEXTS !== void 0 && BROWSE_CONTEXTS !== "" && contexts === void 0) throw new BrowserError(`BROWSE_CONTEXTS must be an integer, not ${JSON.stringify(BROWSE_CONTEXTS)}`, "BROWSER_SERVER_ENVIRONMENT", {
|
|
37
|
+
name: "BROWSE_CONTEXTS",
|
|
38
|
+
value: BROWSE_CONTEXTS
|
|
39
|
+
});
|
|
30
40
|
await createBrowserMCPServer({
|
|
31
41
|
...BROWSE_ROOT === void 0 || BROWSE_ROOT === "" ? {} : { root: BROWSE_ROOT },
|
|
32
42
|
...headless === void 0 ? {} : { headless },
|
|
33
43
|
...BROWSE_EXECUTABLE === void 0 || BROWSE_EXECUTABLE === "" ? {} : { executable: BROWSE_EXECUTABLE },
|
|
34
44
|
...readonly === void 0 ? {} : { readonly },
|
|
35
|
-
|
|
45
|
+
pool: {
|
|
46
|
+
...size === void 0 ? {} : { size },
|
|
47
|
+
...contexts === void 0 ? {} : { contexts }
|
|
48
|
+
},
|
|
49
|
+
...viewport === void 0 ? {} : { viewport }
|
|
36
50
|
}).start();
|
|
37
51
|
} catch (error) {
|
|
38
52
|
if (!isBrowserError(error)) throw error;
|
package/dist/bin/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","names":[],"sources":["../../src/bin/main.ts"],"sourcesContent":["import { parseBoolean, parseInteger } from '@orkestrel/contract'\nimport { BrowserError, isBrowserError } from '@src/core'\nimport { createBrowserMCPServer } from '@src/server'\n\n// The entry owns stderr; a closed reader must not interrupt the server's teardown barrier.\nprocess.stderr.on('error', () => {\n\tprocess.exitCode = 1\n})\n\ntry {\n\tconst {
|
|
1
|
+
{"version":3,"file":"main.js","names":[],"sources":["../../src/bin/main.ts"],"sourcesContent":["import { parseBoolean, parseInteger } from '@orkestrel/contract'\nimport { BrowserError, isBrowserError } from '@src/core'\nimport { createBrowserMCPServer, parseBrowserViewport } from '@src/server'\n\n// The entry owns stderr; a closed reader must not interrupt the server's teardown barrier.\nprocess.stderr.on('error', () => {\n\tprocess.exitCode = 1\n})\n\ntry {\n\tconst {\n\t\tBROWSE_ROOT,\n\t\tBROWSE_HEADLESS,\n\t\tBROWSE_EXECUTABLE,\n\t\tBROWSE_READONLY,\n\t\tBROWSE_POOL,\n\t\tBROWSE_CONTEXTS,\n\t\tBROWSE_VIEWPORT,\n\t} = process.env\n\tconst headless = parseBoolean(BROWSE_HEADLESS)\n\tconst readonly = parseBoolean(BROWSE_READONLY)\n\tconst size = parseInteger(BROWSE_POOL)\n\tconst contexts = parseInteger(BROWSE_CONTEXTS)\n\tconst viewport = parseBrowserViewport(BROWSE_VIEWPORT)\n\t// An empty variable counts as unset, as a shell's `NAME=` leaves it.\n\tfor (const [name, value, parsed] of [\n\t\t['BROWSE_HEADLESS', BROWSE_HEADLESS, headless],\n\t\t['BROWSE_READONLY', BROWSE_READONLY, readonly],\n\t] as const) {\n\t\tif (value !== undefined && value !== '' && parsed === undefined) {\n\t\t\tthrow new BrowserError(\n\t\t\t\t`${name} must be true, false, 1, or 0, not ${JSON.stringify(value)}`,\n\t\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t\t{ name, value },\n\t\t\t)\n\t\t}\n\t}\n\tif (BROWSE_POOL !== undefined && BROWSE_POOL !== '' && size === undefined)\n\t\tthrow new BrowserError(\n\t\t\t`BROWSE_POOL must be an integer, not ${JSON.stringify(BROWSE_POOL)}`,\n\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t{ name: 'BROWSE_POOL', value: BROWSE_POOL },\n\t\t)\n\tif (BROWSE_VIEWPORT !== undefined && BROWSE_VIEWPORT !== '' && viewport === undefined)\n\t\tthrow new BrowserError(\n\t\t\t`BROWSE_VIEWPORT must be positive integers in WIDTHxHEIGHT form, not ${JSON.stringify(BROWSE_VIEWPORT)}`,\n\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t{ name: 'BROWSE_VIEWPORT', value: BROWSE_VIEWPORT },\n\t\t)\n\tif (BROWSE_CONTEXTS !== undefined && BROWSE_CONTEXTS !== '' && contexts === undefined)\n\t\tthrow new BrowserError(\n\t\t\t`BROWSE_CONTEXTS must be an integer, not ${JSON.stringify(BROWSE_CONTEXTS)}`,\n\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t{ name: 'BROWSE_CONTEXTS', value: BROWSE_CONTEXTS },\n\t\t)\n\tawait createBrowserMCPServer({\n\t\t...(BROWSE_ROOT === undefined || BROWSE_ROOT === '' ? {} : { root: BROWSE_ROOT }),\n\t\t...(headless === undefined ? {} : { headless }),\n\t\t...(BROWSE_EXECUTABLE === undefined || BROWSE_EXECUTABLE === ''\n\t\t\t? {}\n\t\t\t: { executable: BROWSE_EXECUTABLE }),\n\t\t...(readonly === undefined ? {} : { readonly }),\n\t\tpool: {\n\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t...(contexts === undefined ? {} : { contexts }),\n\t\t},\n\t\t...(viewport === undefined ? {} : { viewport }),\n\t}).start()\n} catch (error) {\n\tif (!isBrowserError(error)) throw error\n\tconsole.error(`browse: ${error.code}: ${error.message.split(/\\r\\n|\\n/u).join(' ')}`)\n\tprocess.exitCode = 1\n}\n"],"mappings":";;;;;AAKA,QAAQ,OAAO,GAAG,eAAe;CAChC,QAAQ,WAAW;AACpB,CAAC;AAED,IAAI;CACH,MAAM,EACL,aACA,iBACA,mBACA,iBACA,aACA,iBACA,oBACG,QAAQ;CACZ,MAAM,WAAW,aAAa,eAAe;CAC7C,MAAM,WAAW,aAAa,eAAe;CAC7C,MAAM,OAAO,aAAa,WAAW;CACrC,MAAM,WAAW,aAAa,eAAe;CAC7C,MAAM,WAAW,qBAAqB,eAAe;CAErD,KAAK,MAAM,CAAC,MAAM,OAAO,WAAW,CACnC;EAAC;EAAmB;EAAiB;CAAQ,GAC7C;EAAC;EAAmB;EAAiB;CAAQ,CAC9C,GACC,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,WAAW,KAAA,GACrD,MAAM,IAAI,aACT,GAAG,KAAK,qCAAqC,KAAK,UAAU,KAAK,KACjE,8BACA;EAAE;EAAM;CAAM,CACf;CAGF,IAAI,gBAAgB,KAAA,KAAa,gBAAgB,MAAM,SAAS,KAAA,GAC/D,MAAM,IAAI,aACT,uCAAuC,KAAK,UAAU,WAAW,KACjE,8BACA;EAAE,MAAM;EAAe,OAAO;CAAY,CAC3C;CACD,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,MAAM,aAAa,KAAA,GAC3E,MAAM,IAAI,aACT,uEAAuE,KAAK,UAAU,eAAe,KACrG,8BACA;EAAE,MAAM;EAAmB,OAAO;CAAgB,CACnD;CACD,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,MAAM,aAAa,KAAA,GAC3E,MAAM,IAAI,aACT,2CAA2C,KAAK,UAAU,eAAe,KACzE,8BACA;EAAE,MAAM;EAAmB,OAAO;CAAgB,CACnD;CACD,MAAM,uBAAuB;EAC5B,GAAI,gBAAgB,KAAA,KAAa,gBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,YAAY;EAC/E,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;EAC7C,GAAI,sBAAsB,KAAA,KAAa,sBAAsB,KAC1D,CAAC,IACD,EAAE,YAAY,kBAAkB;EACnC,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;EAC7C,MAAM;GACL,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;GACrC,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;EAC9C;EACA,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;CAC9C,CAAC,CAAC,CAAC,MAAM;AACV,SAAS,OAAO;CACf,IAAI,CAAC,eAAe,KAAK,GAAG,MAAM;CAClC,QAAQ,MAAM,WAAW,MAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,UAAU,CAAC,CAAC,KAAK,GAAG,GAAG;CACnF,QAAQ,WAAW;AACpB"}
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -163,7 +163,7 @@ var BROWSER_MOUSE_BUTTON_MASKS = Object.freeze({
|
|
|
163
163
|
*/
|
|
164
164
|
var BROWSER_HAR_CREATOR = Object.freeze({
|
|
165
165
|
name: "@orkestrel/browser",
|
|
166
|
-
version: "0.0.
|
|
166
|
+
version: "0.0.25"
|
|
167
167
|
});
|
|
168
168
|
/** Names the attribute that tags temporary screenshot styles and masks. */
|
|
169
169
|
var BROWSER_SCREENSHOT_ATTRIBUTE = "data-orkestrel-screenshot";
|
|
@@ -742,7 +742,7 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
742
742
|
description: "The journey name, such as add-kettle."
|
|
743
743
|
}),
|
|
744
744
|
edits: Object.freeze({
|
|
745
|
-
description: "The changes, as an array or
|
|
745
|
+
description: "The changes, as an array or its JSON string, applied in order; one invalid change refuses them all.",
|
|
746
746
|
anyOf: Object.freeze([Object.freeze({
|
|
747
747
|
type: "array",
|
|
748
748
|
items: Object.freeze({
|
|
@@ -818,6 +818,18 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
818
818
|
required: Object.freeze(["journey"])
|
|
819
819
|
})
|
|
820
820
|
}),
|
|
821
|
+
capture: Object.freeze({
|
|
822
|
+
name: "capture",
|
|
823
|
+
description: "Saves the current view as a PNG in the runs directory and returns its path.",
|
|
824
|
+
parameters: Object.freeze({
|
|
825
|
+
type: "object",
|
|
826
|
+
properties: Object.freeze({ full: Object.freeze({
|
|
827
|
+
type: "boolean",
|
|
828
|
+
description: "True captures the full page; false captures the viewport."
|
|
829
|
+
}) }),
|
|
830
|
+
required: Object.freeze(["full"])
|
|
831
|
+
})
|
|
832
|
+
}),
|
|
821
833
|
forget: Object.freeze({
|
|
822
834
|
name: "forget",
|
|
823
835
|
description: "Removes a saved journey and all its runs; the name is free to record again.",
|
|
@@ -886,7 +898,7 @@ var BROWSER_ACTION_STAGES = Object.freeze([
|
|
|
886
898
|
var BROWSER_JOURNEY_EMPTY_LISTING = "No journeys are saved; call record to start one.";
|
|
887
899
|
/**
|
|
888
900
|
* Names the journey tools a toolset constructed with `journeys` registers and reserves: `record`,
|
|
889
|
-
* `save`, `journeys`, `edit`, `replay`, and `
|
|
901
|
+
* `save`, `journeys`, `edit`, `replay`, `forget`, and `capture`.
|
|
890
902
|
*/
|
|
891
903
|
var BROWSER_JOURNEY_TOOL_NAMES = Object.freeze([
|
|
892
904
|
"record",
|
|
@@ -894,7 +906,8 @@ var BROWSER_JOURNEY_TOOL_NAMES = Object.freeze([
|
|
|
894
906
|
"journeys",
|
|
895
907
|
"edit",
|
|
896
908
|
"replay",
|
|
897
|
-
"forget"
|
|
909
|
+
"forget",
|
|
910
|
+
"capture"
|
|
898
911
|
]);
|
|
899
912
|
/** Names observation and journey tools that cannot become journey steps. */
|
|
900
913
|
var BROWSER_JOURNEY_NON_STEP_TOOLS = Object.freeze([...BROWSER_OBSERVATION_TOOL_NAMES, ...BROWSER_JOURNEY_TOOL_NAMES]);
|
|
@@ -1000,9 +1013,7 @@ var CDPError = class extends BrowserError {
|
|
|
1000
1013
|
}
|
|
1001
1014
|
};
|
|
1002
1015
|
/**
|
|
1003
|
-
* Reports that a CDP request could not be sent or completed because the client was not
|
|
1004
|
-
* connectable state — not connected, closed while connecting, or the connection dropped
|
|
1005
|
-
* mid-request — under the code `BROWSER_CDP_CONNECTION_ERROR`.
|
|
1016
|
+
* Reports that a CDP request could not be sent or completed because the client was not connected, closed while connecting, lost its connection, or lost the request's target session, under the code `BROWSER_CDP_CONNECTION_ERROR`.
|
|
1006
1017
|
*/
|
|
1007
1018
|
var CDPConnectionError = class extends BrowserError {
|
|
1008
1019
|
constructor(message, context) {
|
|
@@ -5212,6 +5223,7 @@ var CDPClient = class {
|
|
|
5212
5223
|
const listener = signal === void 0 ? void 0 : this.#abort(id, signal);
|
|
5213
5224
|
this.#pending.set(id, {
|
|
5214
5225
|
method,
|
|
5226
|
+
session: options?.session,
|
|
5215
5227
|
resolve,
|
|
5216
5228
|
reject,
|
|
5217
5229
|
timer,
|
|
@@ -5356,6 +5368,16 @@ var CDPClient = class {
|
|
|
5356
5368
|
const method = parsed["method"];
|
|
5357
5369
|
const rawParams = parsed["params"];
|
|
5358
5370
|
const params = (0, _orkestrel_contract.isRecord)(rawParams) ? rawParams : Object.freeze({});
|
|
5371
|
+
if (method === "Target.detachedFromTarget" && (0, _orkestrel_contract.isString)(params["sessionId"])) {
|
|
5372
|
+
const detached = params["sessionId"];
|
|
5373
|
+
for (const [id, entry] of this.#pending) {
|
|
5374
|
+
if (entry.session !== detached) continue;
|
|
5375
|
+
this.#settle(id)?.reject(new CDPConnectionError("CDP session detached", {
|
|
5376
|
+
method: entry.method,
|
|
5377
|
+
session: detached
|
|
5378
|
+
}));
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5359
5381
|
const global = this.#subscriptions.get(void 0)?.get(method);
|
|
5360
5382
|
if (global !== void 0) this.#dispatch(global, method, params);
|
|
5361
5383
|
const session = parsed["sessionId"];
|
|
@@ -6451,9 +6473,11 @@ var BrowserPageElement = class {
|
|
|
6451
6473
|
object: result["object"]["objectId"],
|
|
6452
6474
|
context
|
|
6453
6475
|
};
|
|
6454
|
-
} catch {
|
|
6476
|
+
} catch (error) {
|
|
6455
6477
|
options?.signal?.throwIfAborted();
|
|
6456
|
-
|
|
6478
|
+
if (isCDPTimeoutError(error) || isCDPConnectionError(error)) throw error;
|
|
6479
|
+
if (isCDPError(error) && /Could not find node|No node with given id|No node found for given backend id/i.test(error.message)) throw new BrowserElementError(this.reference, "GONE");
|
|
6480
|
+
throw error;
|
|
6457
6481
|
}
|
|
6458
6482
|
}
|
|
6459
6483
|
async #call(declaration, options, target) {
|
|
@@ -6482,7 +6506,7 @@ var BrowserPageElement = class {
|
|
|
6482
6506
|
}
|
|
6483
6507
|
#failure(options, error) {
|
|
6484
6508
|
options?.signal?.throwIfAborted();
|
|
6485
|
-
if (isBrowserElementError(error)) throw error;
|
|
6509
|
+
if (isBrowserElementError(error) || isCDPTimeoutError(error) || isCDPConnectionError(error)) throw error;
|
|
6486
6510
|
const message = ((0, _orkestrel_contract.isError)(error) ? error.message : String(error)).split("\n", 1)[0] ?? "";
|
|
6487
6511
|
const known = BROWSER_ELEMENT_REFUSALS.get(message.replace(/^Error: /, ""));
|
|
6488
6512
|
if (known !== void 0) throw new BrowserElementError(this.reference, known.reason, known.detail);
|
|
@@ -6514,11 +6538,20 @@ var BrowserPageElement = class {
|
|
|
6514
6538
|
session: this.#input.node.session,
|
|
6515
6539
|
...options
|
|
6516
6540
|
}).catch(this.#failure.bind(this, options));
|
|
6517
|
-
await this.#call(
|
|
6541
|
+
await this.#call(`async function() {
|
|
6542
|
+
await (${compileActionabilityFunction({
|
|
6518
6543
|
visible: true,
|
|
6519
6544
|
enabled: true,
|
|
6520
6545
|
stable: true
|
|
6521
|
-
})
|
|
6546
|
+
})}).call(this)
|
|
6547
|
+
let previous
|
|
6548
|
+
for (;;) {
|
|
6549
|
+
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
6550
|
+
const current = [window.scrollX, window.scrollY]
|
|
6551
|
+
if (previous && current.every((value, index) => value === previous[index])) return true
|
|
6552
|
+
previous = current
|
|
6553
|
+
}
|
|
6554
|
+
}`, options);
|
|
6522
6555
|
const geometry = await this.#geometry(options);
|
|
6523
6556
|
const point = geometry.page.center;
|
|
6524
6557
|
const local = geometry.local.center;
|
|
@@ -6529,6 +6562,10 @@ var BrowserPageElement = class {
|
|
|
6529
6562
|
}, {
|
|
6530
6563
|
session: this.#input.node.session,
|
|
6531
6564
|
...options
|
|
6565
|
+
}).catch((error) => {
|
|
6566
|
+
options?.signal?.throwIfAborted();
|
|
6567
|
+
if (isCDPError(error) && error.context?.["message"] === "No node found at given location") throw new BrowserElementError(this.reference, "OCCLUDED", "hit-test location held no node");
|
|
6568
|
+
throw error;
|
|
6532
6569
|
});
|
|
6533
6570
|
if (!(0, _orkestrel_contract.isRecord)(hit) || !(0, _orkestrel_contract.isInteger)(hit["backendNodeId"]) || hit["frameId"] !== void 0 && hit["frameId"] !== this.#input.frame) throw new BrowserElementError(this.reference, "OCCLUDED");
|
|
6534
6571
|
if (hit["backendNodeId"] !== this.#input.backend && await this.#call(compileHitFunction(), options, hit["backendNodeId"]) !== true) {
|
|
@@ -7180,10 +7217,12 @@ var BrowserRegistry = class {
|
|
|
7180
7217
|
for (const frame of new Set([...this.tools(), ...this.#pending].map((entry) => entry.frame))) this.#invalidate(frame);
|
|
7181
7218
|
}
|
|
7182
7219
|
#navigated(params) {
|
|
7220
|
+
if (this.#page.closed || this.#destroying !== void 0) return;
|
|
7183
7221
|
const frame = params["frame"];
|
|
7184
7222
|
if ((0, _orkestrel_contract.isRecord)(frame) && (0, _orkestrel_contract.isString)(frame["id"])) this.#invalidate(frame["id"]);
|
|
7185
7223
|
}
|
|
7186
7224
|
#detached(params) {
|
|
7225
|
+
if (this.#page.closed || this.#destroying !== void 0) return;
|
|
7187
7226
|
if ((0, _orkestrel_contract.isString)(params["frameId"])) this.#invalidate(params["frameId"]);
|
|
7188
7227
|
}
|
|
7189
7228
|
#invalidate(frame) {
|
|
@@ -7197,6 +7236,7 @@ var BrowserRegistry = class {
|
|
|
7197
7236
|
if (removed) this.#emitter.emit("change");
|
|
7198
7237
|
}
|
|
7199
7238
|
#add(session, params) {
|
|
7239
|
+
if (this.#page.closed || this.#destroying !== void 0) return;
|
|
7200
7240
|
if (!(0, _orkestrel_contract.isArray)(params["tools"])) return;
|
|
7201
7241
|
for (const value of params["tools"]) {
|
|
7202
7242
|
const tool = parseBrowserTool(value);
|
|
@@ -7207,6 +7247,7 @@ var BrowserRegistry = class {
|
|
|
7207
7247
|
this.#emitter.emit("change");
|
|
7208
7248
|
}
|
|
7209
7249
|
#remove(params) {
|
|
7250
|
+
if (this.#page.closed || this.#destroying !== void 0) return;
|
|
7210
7251
|
if (!(0, _orkestrel_contract.isArray)(params["tools"])) return;
|
|
7211
7252
|
for (const value of params["tools"]) {
|
|
7212
7253
|
const key = parseBrowserRemoval(value);
|
|
@@ -10142,10 +10183,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
10142
10183
|
} catch {}
|
|
10143
10184
|
}
|
|
10144
10185
|
async #close() {
|
|
10145
|
-
await this.#release();
|
|
10146
10186
|
try {
|
|
10147
10187
|
await this.#client.send("Target.closeTarget", { targetId: this.#targetId });
|
|
10148
10188
|
} catch {}
|
|
10189
|
+
await this.#release();
|
|
10149
10190
|
}
|
|
10150
10191
|
#release() {
|
|
10151
10192
|
const active = this.#releasing;
|
|
@@ -10446,12 +10487,14 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
10446
10487
|
}
|
|
10447
10488
|
async #attachWorker(session, id, url, category) {
|
|
10448
10489
|
try {
|
|
10449
|
-
|
|
10490
|
+
const enabling = this.#client.send("Runtime.enable", void 0, { session });
|
|
10491
|
+
if (category === "service_worker") this.#resumeTarget(session);
|
|
10492
|
+
await enabling;
|
|
10450
10493
|
if (this.#closed) {
|
|
10451
10494
|
await this.#detachChild(session, this.#sessionId);
|
|
10452
10495
|
return;
|
|
10453
10496
|
}
|
|
10454
|
-
this.#resumeTarget(session);
|
|
10497
|
+
if (category !== "service_worker") this.#resumeTarget(session);
|
|
10455
10498
|
const worker = new BrowserWorker(this.#client, session, id, url, category);
|
|
10456
10499
|
this.#workers.set(id, worker);
|
|
10457
10500
|
this.#emitter.emit("worker", worker);
|
|
@@ -10788,6 +10831,7 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
10788
10831
|
this.#emitter.emit("navigate", params["url"], true);
|
|
10789
10832
|
}
|
|
10790
10833
|
#handleFrameDetached(session, params) {
|
|
10834
|
+
if (this.#closed) return;
|
|
10791
10835
|
const frame = params["frameId"];
|
|
10792
10836
|
if (!(0, _orkestrel_contract.isString)(frame) || !this.#accepts(session, frame)) return;
|
|
10793
10837
|
const swapped = params["reason"] === "swap";
|
|
@@ -10888,7 +10932,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
10888
10932
|
const session = params["sessionId"];
|
|
10889
10933
|
if (!(0, _orkestrel_contract.isRecord)(target) || !(0, _orkestrel_contract.isString)(target["targetId"]) || !(0, _orkestrel_contract.isString)(session)) return;
|
|
10890
10934
|
const category = target["type"];
|
|
10891
|
-
if (owner !== this.#sessionId && category !== "iframe")
|
|
10935
|
+
if (owner !== this.#sessionId && category !== "iframe") {
|
|
10936
|
+
this.#detachChild(session, owner);
|
|
10937
|
+
return;
|
|
10938
|
+
}
|
|
10892
10939
|
if (category === "worker" || category === "service_worker" || category === "shared_worker") {
|
|
10893
10940
|
this.#attachWorker(session, target["targetId"], (0, _orkestrel_contract.isString)(target["url"]) ? target["url"] : "", category);
|
|
10894
10941
|
return;
|
|
@@ -11196,6 +11243,7 @@ var BrowserContext = class {
|
|
|
11196
11243
|
#publishing = /* @__PURE__ */ new Map();
|
|
11197
11244
|
#observed = /* @__PURE__ */ new WeakSet();
|
|
11198
11245
|
#shutdown;
|
|
11246
|
+
#disposal;
|
|
11199
11247
|
#reference = 0;
|
|
11200
11248
|
constructor(client, id, viewport, writer, emulation, downloads, options) {
|
|
11201
11249
|
this.#client = client;
|
|
@@ -11220,6 +11268,9 @@ var BrowserContext = class {
|
|
|
11220
11268
|
get id() {
|
|
11221
11269
|
return this.#id;
|
|
11222
11270
|
}
|
|
11271
|
+
get disposal() {
|
|
11272
|
+
return this.#disposal;
|
|
11273
|
+
}
|
|
11223
11274
|
get cookies() {
|
|
11224
11275
|
return this.#cookies;
|
|
11225
11276
|
}
|
|
@@ -11262,14 +11313,14 @@ var BrowserContext = class {
|
|
|
11262
11313
|
destroy() {
|
|
11263
11314
|
const active = this.#shutdown;
|
|
11264
11315
|
if (active !== void 0) return active;
|
|
11265
|
-
const shutdown = this.#destroyResources();
|
|
11316
|
+
const shutdown = Promise.resolve().then(() => this.#destroyResources());
|
|
11266
11317
|
this.#shutdown = shutdown;
|
|
11267
11318
|
return shutdown;
|
|
11268
11319
|
}
|
|
11269
11320
|
close() {
|
|
11270
11321
|
const active = this.#shutdown;
|
|
11271
11322
|
if (active !== void 0) return active;
|
|
11272
|
-
const shutdown = this.#closeResources();
|
|
11323
|
+
const shutdown = Promise.resolve().then(() => this.#closeResources());
|
|
11273
11324
|
this.#shutdown = shutdown;
|
|
11274
11325
|
return shutdown;
|
|
11275
11326
|
}
|
|
@@ -11391,9 +11442,15 @@ var BrowserContext = class {
|
|
|
11391
11442
|
let failure = await settleBrowserTeardown(...[...this.#pages.values()].map((page) => () => page.close()));
|
|
11392
11443
|
this.#pages.clear();
|
|
11393
11444
|
const id = this.#id;
|
|
11394
|
-
if (id !== void 0) {
|
|
11395
|
-
|
|
11396
|
-
|
|
11445
|
+
if (id !== void 0) try {
|
|
11446
|
+
await this.#client.send("Target.disposeBrowserContext", { browserContextId: id });
|
|
11447
|
+
this.#disposal = { confirmed: true };
|
|
11448
|
+
} catch (error) {
|
|
11449
|
+
this.#disposal = {
|
|
11450
|
+
confirmed: false,
|
|
11451
|
+
error
|
|
11452
|
+
};
|
|
11453
|
+
failure ??= error;
|
|
11397
11454
|
}
|
|
11398
11455
|
if (failure !== void 0) throw failure;
|
|
11399
11456
|
} finally {
|
|
@@ -12278,13 +12335,13 @@ function createMemoryBrowserRunStore() {
|
|
|
12278
12335
|
//#endregion
|
|
12279
12336
|
//#region src/core/BrowserJourneyToolset.ts
|
|
12280
12337
|
/**
|
|
12281
|
-
* Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, and `
|
|
12338
|
+
* Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, `forget`, and `capture` over a toolset
|
|
12282
12339
|
* and owns the recording and the active replay.
|
|
12283
12340
|
*
|
|
12284
12341
|
* @remarks
|
|
12285
12342
|
* The journey toolset composes the toolset's public members: `perform` reads the view a receipt
|
|
12286
12343
|
* carries through `look`, `hold` and `emitter` drive the recorder and the replay, `view` converts
|
|
12287
|
-
* an edit's `ref` to a target, and `tools` receives the
|
|
12344
|
+
* an edit's `ref` to a target, and `tools` receives the journey tools at construction, which refuses
|
|
12288
12345
|
* with `BROWSER_TOOLSET_RESERVED` when the manager already holds one of the names. Every refusal
|
|
12289
12346
|
* is a sentence that names the next call, and a reason inside it is a clause without a directive
|
|
12290
12347
|
* or a final period.
|
|
@@ -12300,7 +12357,7 @@ function createMemoryBrowserRunStore() {
|
|
|
12300
12357
|
* by the view after the run.
|
|
12301
12358
|
*
|
|
12302
12359
|
* `destroy()` aborts the active replay and waits for it to finish, stops a recording without
|
|
12303
|
-
* saving it, and removes the
|
|
12360
|
+
* saving it, and removes the journey tools the manager still holds under the instances it added.
|
|
12304
12361
|
*
|
|
12305
12362
|
* @example
|
|
12306
12363
|
* ```ts
|
|
@@ -12341,7 +12398,8 @@ var BrowserJourneyToolset = class {
|
|
|
12341
12398
|
this.#create("journeys", this.#journeys.bind(this)),
|
|
12342
12399
|
this.#create("edit", this.#edit.bind(this)),
|
|
12343
12400
|
this.#create("replay", this.#replayJourney.bind(this)),
|
|
12344
|
-
this.#create("forget", this.#forget.bind(this))
|
|
12401
|
+
this.#create("forget", this.#forget.bind(this)),
|
|
12402
|
+
this.#create("capture", this.#capture.bind(this))
|
|
12345
12403
|
]);
|
|
12346
12404
|
toolset.tools.add(this.#tools);
|
|
12347
12405
|
}
|
|
@@ -12368,6 +12426,22 @@ var BrowserJourneyToolset = class {
|
|
|
12368
12426
|
signal.throwIfAborted();
|
|
12369
12427
|
return handler(args, signal);
|
|
12370
12428
|
}
|
|
12429
|
+
async #capture(args, signal) {
|
|
12430
|
+
const full = args["full"];
|
|
12431
|
+
if (!(0, _orkestrel_contract.isBoolean)(full)) throw new BrowserError("The full parameter must be true or false.", "BROWSER_TOOLSET_ARGUMENT");
|
|
12432
|
+
this.#idleReplay();
|
|
12433
|
+
const view = this.#toolset.view;
|
|
12434
|
+
if (!view.trusted) throw new BrowserError("This view is untrusted; capture requires a trusted browser view.", "BROWSER_CAPTURE_UNTRUSTED");
|
|
12435
|
+
if (view.screenshot === void 0) throw new BrowserError("This view cannot capture an image.", "BROWSER_CAPTURE_UNAVAILABLE");
|
|
12436
|
+
const runs = this.#runs;
|
|
12437
|
+
if (runs?.snapshot === void 0) throw new BrowserError("Capture requires a runs store with standalone file storage.", "BROWSER_CAPTURE_UNAVAILABLE");
|
|
12438
|
+
const screenshot = await view.screenshot({
|
|
12439
|
+
format: "png",
|
|
12440
|
+
full
|
|
12441
|
+
});
|
|
12442
|
+
signal.throwIfAborted();
|
|
12443
|
+
return runs.snapshot(screenshot.bytes, { signal });
|
|
12444
|
+
}
|
|
12371
12445
|
async #record(args, signal) {
|
|
12372
12446
|
if (this.#readonly) throw new BrowserError(BROWSER_JOURNEY_READONLY_REFUSAL, "BROWSER_JOURNEY_READONLY");
|
|
12373
12447
|
this.#idleReplay();
|