@itmetsam/samurai-framework 0.1.0 → 0.2.0
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 +129 -0
- package/bin/samurai.mjs +31 -0
- package/dist/api.js +3 -0
- package/dist/assert/assertion-error.d.ts +23 -0
- package/dist/assert/assertion-error.js +45 -0
- package/dist/assert/expect.d.ts +55 -0
- package/dist/assert/expect.js +188 -0
- package/dist/browser/browser-context.d.ts +91 -0
- package/dist/browser/browser-context.js +146 -0
- package/dist/browser/browser-finder.d.ts +2 -0
- package/dist/browser/browser-finder.js +95 -0
- package/dist/browser/browser.d.ts +95 -0
- package/dist/browser/browser.js +372 -0
- package/dist/browser/context-tree.d.ts +23 -0
- package/dist/browser/context-tree.js +60 -0
- package/dist/browser/dialog.d.ts +37 -0
- package/dist/browser/dialog.js +61 -0
- package/dist/browser/download.d.ts +71 -0
- package/dist/browser/download.js +137 -0
- package/dist/browser/downloads-dir.d.ts +8 -0
- package/dist/browser/downloads-dir.js +38 -0
- package/dist/browser/emulation.d.ts +61 -0
- package/dist/browser/emulation.js +144 -0
- package/dist/browser/file-chooser.d.ts +39 -0
- package/dist/browser/file-chooser.js +86 -0
- package/dist/browser/file-paths.d.ts +4 -0
- package/dist/browser/file-paths.js +18 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/navigation-error.d.ts +13 -0
- package/dist/browser/navigation-error.js +19 -0
- package/dist/browser/page-logs.d.ts +86 -0
- package/dist/browser/page-logs.js +149 -0
- package/dist/browser/page-wait.d.ts +9 -0
- package/dist/browser/page-wait.js +17 -0
- package/dist/browser/page.d.ts +370 -0
- package/dist/browser/page.js +1038 -0
- package/dist/browser/pdf.d.ts +72 -0
- package/dist/browser/pdf.js +37 -0
- package/dist/browser/screenshot.d.ts +29 -0
- package/dist/browser/screenshot.js +42 -0
- package/dist/cli/args.d.ts +35 -0
- package/dist/cli/args.js +69 -0
- package/dist/cli/cli.cli-test.d.ts +1 -0
- package/dist/cli/cli.cli-test.js +170 -0
- package/dist/cli/console-reporter.d.ts +15 -0
- package/dist/cli/console-reporter.js +52 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +61 -0
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.js +156 -0
- package/dist/config/config.d.ts +24 -0
- package/dist/config/config.js +42 -0
- package/dist/config/default.d.ts +2 -0
- package/dist/config/default.js +3 -0
- package/dist/config/mask.d.ts +11 -0
- package/dist/config/mask.js +85 -0
- package/dist/config/run-settings.d.ts +28 -0
- package/dist/config/run-settings.js +61 -0
- package/dist/config/secrets.d.ts +10 -0
- package/dist/config/secrets.js +67 -0
- package/dist/config/variables.d.ts +11 -0
- package/dist/config/variables.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/locator/action-timeout-error.d.ts +25 -0
- package/dist/locator/action-timeout-error.js +35 -0
- package/dist/locator/element-state.d.ts +44 -0
- package/dist/locator/element-state.js +109 -0
- package/dist/locator/label-locator.d.ts +13 -0
- package/dist/locator/label-locator.js +49 -0
- package/dist/locator/locator.d.ts +168 -0
- package/dist/locator/locator.js +591 -0
- package/dist/locator/selector-errors.d.ts +9 -0
- package/dist/locator/selector-errors.js +15 -0
- package/dist/locator/selector.d.ts +60 -0
- package/dist/locator/selector.js +113 -0
- package/dist/locator/text-locator.d.ts +13 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +30 -0
- package/dist/network/data-collector.d.ts +26 -0
- package/dist/network/data-collector.js +77 -0
- package/dist/network/network-tracker.d.ts +94 -0
- package/dist/network/network-tracker.js +198 -0
- package/dist/network/network-wait.d.ts +16 -0
- package/dist/network/network-wait.js +24 -0
- package/dist/network/response.d.ts +22 -0
- package/dist/network/response.js +42 -0
- package/dist/network/router.d.ts +120 -0
- package/dist/network/router.js +343 -0
- package/dist/network/url-match.d.ts +8 -0
- package/dist/network/url-match.js +38 -0
- package/dist/recorder/apply.d.ts +3 -0
- package/dist/recorder/apply.js +10 -0
- package/dist/recorder/capture.d.ts +18 -0
- package/dist/recorder/capture.js +198 -0
- package/dist/recorder/index.d.ts +3 -0
- package/dist/recorder/index.js +3 -0
- package/dist/recorder/normalise.d.ts +68 -0
- package/dist/recorder/normalise.js +104 -0
- package/dist/recorder/ranking.d.ts +9 -0
- package/dist/recorder/ranking.js +21 -0
- package/dist/recorder/recorder.d.ts +52 -0
- package/dist/recorder/recorder.js +143 -0
- package/dist/recorder/session.d.ts +49 -0
- package/dist/recorder/session.js +113 -0
- package/dist/runner/module-type.d.ts +6 -0
- package/dist/runner/module-type.js +29 -0
- package/dist/runner/page-logs-report.d.ts +40 -0
- package/dist/runner/page-logs-report.js +44 -0
- package/dist/runner/prepare-run.d.ts +14 -0
- package/dist/runner/prepare-run.js +20 -0
- package/dist/runner/project.d.ts +17 -0
- package/dist/runner/project.js +33 -0
- package/dist/runner/registry.d.ts +7 -0
- package/dist/runner/registry.js +40 -0
- package/dist/runner/reporter.d.ts +43 -0
- package/dist/runner/reporter.js +83 -0
- package/dist/runner/run.d.ts +27 -0
- package/dist/runner/run.js +34 -0
- package/dist/runner/test-error.d.ts +3 -0
- package/dist/runner/test-error.js +18 -0
- package/dist/runner/test-runner.d.ts +44 -0
- package/dist/runner/test-runner.js +205 -0
- package/dist/script/call-function.d.ts +34 -0
- package/dist/script/call-function.js +71 -0
- package/dist/script/element-handle.d.ts +10 -0
- package/{src/script/element-handle.ts → dist/script/element-handle.js} +4 -4
- package/dist/script/helpers.d.ts +28 -0
- package/dist/script/helpers.js +50 -0
- package/dist/script/preload.d.ts +31 -0
- package/dist/script/preload.js +71 -0
- package/dist/script/serialize.d.ts +29 -0
- package/dist/script/serialize.js +207 -0
- package/dist/steps/build.d.ts +5 -0
- package/dist/steps/build.js +38 -0
- package/dist/steps/edit.d.ts +30 -0
- package/dist/steps/edit.js +80 -0
- package/dist/steps/emit.d.ts +6 -0
- package/dist/steps/emit.js +73 -0
- package/dist/steps/index.d.ts +5 -0
- package/dist/steps/index.js +4 -0
- package/dist/steps/model.d.ts +103 -0
- package/dist/steps/model.js +1 -0
- package/dist/steps/parse.d.ts +3 -0
- package/dist/steps/parse.js +272 -0
- package/dist/transport/bidi-connection.d.ts +46 -0
- package/dist/transport/bidi-connection.js +176 -0
- package/dist/transport/bidi-error.d.ts +10 -0
- package/dist/transport/bidi-error.js +10 -0
- package/dist/transport/subscriptions.d.ts +36 -0
- package/dist/transport/subscriptions.js +81 -0
- package/{src → dist}/types/test.d.ts +10 -4
- package/dist/wait/wait-until.d.ts +20 -0
- package/dist/wait/wait-until.js +63 -0
- package/package.json +44 -7
- package/.claude/settings.local.json +0 -7
- package/.env.example +0 -3
- package/.github/actions/setup/action.yml +0 -16
- package/.github/workflows/ci.yml +0 -71
- package/.trunk/configs/.markdownlint.yaml +0 -2
- package/.trunk/configs/.yamllint.yaml +0 -7
- package/.trunk/configs/svgo.config.mjs +0 -14
- package/.trunk/trunk.yaml +0 -34
- package/docs/superpowers/bidi-roadmap.md +0 -60
- package/docs/superpowers/plans/2026-09-29-auto-wait.md +0 -1827
- package/docs/superpowers/plans/2026-09-29-expect.md +0 -1350
- package/docs/superpowers/plans/2026-10-01-ci.md +0 -350
- package/docs/superpowers/plans/2026-10-01-test-api-alignment.md +0 -1821
- package/docs/superpowers/recorder-roadmap.md +0 -52
- package/docs/superpowers/specs/2026-09-29-auto-wait-design.md +0 -185
- package/docs/superpowers/specs/2026-09-29-bidi-foundation-design.md +0 -130
- package/docs/superpowers/specs/2026-09-29-contexts-and-storage-design.md +0 -88
- package/docs/superpowers/specs/2026-09-29-downloads-and-file-dialogs-design.md +0 -94
- package/docs/superpowers/specs/2026-09-29-emulation-design.md +0 -88
- package/docs/superpowers/specs/2026-09-29-expect-design.md +0 -130
- package/docs/superpowers/specs/2026-09-29-locate-nodes-design.md +0 -90
- package/docs/superpowers/specs/2026-09-29-logs-and-errors-design.md +0 -87
- package/docs/superpowers/specs/2026-09-29-navigation-waits-design.md +0 -83
- package/docs/superpowers/specs/2026-09-29-network-data-design.md +0 -78
- package/docs/superpowers/specs/2026-09-29-network-interception-design.md +0 -91
- package/docs/superpowers/specs/2026-09-29-network-tracking-design.md +0 -86
- package/docs/superpowers/specs/2026-09-29-page-api-design.md +0 -90
- package/docs/superpowers/specs/2026-09-29-preload-scripts-design.md +0 -82
- package/docs/superpowers/specs/2026-09-29-script-call-function-design.md +0 -107
- package/docs/superpowers/specs/2026-09-29-set-files-design.md +0 -62
- package/docs/superpowers/specs/2026-09-29-user-prompts-design.md +0 -75
- package/docs/superpowers/specs/2026-10-01-ci-design.md +0 -106
- package/docs/superpowers/specs/2026-10-01-test-api-alignment-design.md +0 -241
- package/public/logo-dark.svg +0 -236
- package/public/logo-light.svg +0 -262
- package/samurai.config.ts +0 -7
- package/src/assert/assertion-error.test.ts +0 -55
- package/src/assert/assertion-error.ts +0 -62
- package/src/assert/expect-locator.test.ts +0 -217
- package/src/assert/expect.test.ts +0 -73
- package/src/assert/expect.ts +0 -299
- package/src/browser/browser-context.browser-test.ts +0 -201
- package/src/browser/browser-context.test.ts +0 -407
- package/src/browser/browser-context.ts +0 -208
- package/src/browser/browser-finder.ts +0 -110
- package/src/browser/browser.test.ts +0 -88
- package/src/browser/browser.ts +0 -515
- package/src/browser/context-tree.test.ts +0 -86
- package/src/browser/context-tree.ts +0 -70
- package/src/browser/dialog.browser-test.ts +0 -144
- package/src/browser/dialog.test.ts +0 -322
- package/src/browser/dialog.ts +0 -83
- package/src/browser/download.test.ts +0 -161
- package/src/browser/download.ts +0 -172
- package/src/browser/downloads-dir.test.ts +0 -75
- package/src/browser/downloads-dir.ts +0 -39
- package/src/browser/emulation.browser-test.ts +0 -186
- package/src/browser/emulation.test.ts +0 -200
- package/src/browser/emulation.ts +0 -204
- package/src/browser/file-chooser.test.ts +0 -142
- package/src/browser/file-chooser.ts +0 -99
- package/src/browser/file-paths.ts +0 -18
- package/src/browser/goto.browser-test.ts +0 -44
- package/src/browser/navigation-error.ts +0 -26
- package/src/browser/navigation.browser-test.ts +0 -172
- package/src/browser/page-api.browser-test.ts +0 -228
- package/src/browser/page-logs.browser-test.ts +0 -119
- package/src/browser/page-logs.test.ts +0 -212
- package/src/browser/page-logs.ts +0 -208
- package/src/browser/page-wait.ts +0 -19
- package/src/browser/page.test.ts +0 -1567
- package/src/browser/page.ts +0 -1176
- package/src/browser/pdf.ts +0 -62
- package/src/browser/screenshot.ts +0 -65
- package/src/browser/transfers.browser-test.ts +0 -180
- package/src/config/config.ts +0 -33
- package/src/config/default.ts +0 -5
- package/src/config/fixtures.test.ts +0 -137
- package/src/config/mask.test.ts +0 -143
- package/src/config/mask.ts +0 -87
- package/src/config/run-settings.test.ts +0 -163
- package/src/config/run-settings.ts +0 -114
- package/src/config/secrets.ts +0 -70
- package/src/config/variables.ts +0 -31
- package/src/index.ts +0 -10
- package/src/locator/action-timeout-error.test.ts +0 -83
- package/src/locator/action-timeout-error.ts +0 -72
- package/src/locator/element-state.test.ts +0 -181
- package/src/locator/element-state.ts +0 -159
- package/src/locator/locate-nodes.browser-test.ts +0 -169
- package/src/locator/locator-actions.test.ts +0 -355
- package/src/locator/locator-chain.test.ts +0 -292
- package/src/locator/locator.browser-test.ts +0 -137
- package/src/locator/locator.test.ts +0 -179
- package/src/locator/locator.ts +0 -656
- package/src/locator/selector-errors.ts +0 -18
- package/src/locator/selector.test.ts +0 -124
- package/src/locator/selector.ts +0 -107
- package/src/locator/set-files.browser-test.ts +0 -141
- package/src/locator/set-files.test.ts +0 -191
- package/src/locator/text-locator.test.ts +0 -118
- package/src/logger/index.test.ts +0 -28
- package/src/logger/index.ts +0 -40
- package/src/network/data-collector.test.ts +0 -111
- package/src/network/data-collector.ts +0 -92
- package/src/network/network-data.browser-test.ts +0 -192
- package/src/network/network-tracker.browser-test.ts +0 -151
- package/src/network/network-tracker.test.ts +0 -307
- package/src/network/network-tracker.ts +0 -286
- package/src/network/network-wait.ts +0 -28
- package/src/network/response.test.ts +0 -93
- package/src/network/response.ts +0 -58
- package/src/network/router.browser-test.ts +0 -260
- package/src/network/router.test.ts +0 -528
- package/src/network/router.ts +0 -420
- package/src/network/url-match.test.ts +0 -69
- package/src/network/url-match.ts +0 -41
- package/src/runner/page-logs-report.test.ts +0 -144
- package/src/runner/page-logs-report.ts +0 -82
- package/src/runner/prepare-run.test.ts +0 -33
- package/src/runner/prepare-run.ts +0 -36
- package/src/runner/registry.test.ts +0 -59
- package/src/runner/registry.ts +0 -46
- package/src/runner/reporter.test.ts +0 -73
- package/src/runner/reporter.ts +0 -109
- package/src/runner/test-error.test.ts +0 -39
- package/src/runner/test-error.ts +0 -20
- package/src/runner/test-runner.ts +0 -184
- package/src/script/call-function.browser-test.ts +0 -112
- package/src/script/call-function.test.ts +0 -174
- package/src/script/call-function.ts +0 -113
- package/src/script/helpers.test.ts +0 -116
- package/src/script/helpers.ts +0 -68
- package/src/script/preload.browser-test.ts +0 -144
- package/src/script/preload.test.ts +0 -180
- package/src/script/preload.ts +0 -102
- package/src/script/serialize.test.ts +0 -182
- package/src/script/serialize.ts +0 -203
- package/src/testing/browser-fixture.ts +0 -18
- package/src/testing/fake-websocket.ts +0 -72
- package/src/testing/stub-connector.ts +0 -154
- package/src/tests/index.spec.ts +0 -30
- package/src/transport/bidi-connection.test.ts +0 -155
- package/src/transport/bidi-connection.ts +0 -256
- package/src/transport/bidi-error.ts +0 -23
- package/src/transport/foundation.browser-test.ts +0 -73
- package/src/transport/subscriptions.test.ts +0 -140
- package/src/transport/subscriptions.ts +0 -110
- package/src/types/bidi-commands.test-d.ts +0 -55
- package/src/wait/wait-until.test.ts +0 -117
- package/src/wait/wait-until.ts +0 -86
- package/tsconfig.json +0 -44
- /package/{src/api.ts → dist/api.d.ts} +0 -0
- /package/{src/locator/text-locator.ts → dist/locator/text-locator.js} +0 -0
- /package/{src → dist}/types/bidi-modules/browser.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/browsing-context.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/emulation.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/input.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/log.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/network.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/permissions.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/script.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/session.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/storage.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/web-extensions.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/command.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/error.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/event.d.ts +0 -0
- /package/{src → dist}/types/bidi.d.ts +0 -0
- /package/{src → dist}/types/browser.d.ts +0 -0
- /package/{src → dist}/types/config.d.ts +0 -0
- /package/{src → dist}/types/env.d.ts +0 -0
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
import type { BiDiCommands, BiDiEvents } from "../types/bidi.js";
|
|
3
|
-
import logger from "../logger/index.js";
|
|
4
|
-
import { WaitTimeoutError } from "../wait/wait-until.js";
|
|
5
|
-
import { BiDiError } from "./bidi-error.js";
|
|
6
|
-
import {
|
|
7
|
-
SubscriptionManager,
|
|
8
|
-
type EventName,
|
|
9
|
-
type Subscription,
|
|
10
|
-
type SubscribeOptions,
|
|
11
|
-
} from "./subscriptions.js";
|
|
12
|
-
|
|
13
|
-
/** Time (ms) a command may wait for its reply before it fails with code "timeout" */
|
|
14
|
-
const DEFAULT_COMMAND_TIMEOUT = 30000;
|
|
15
|
-
|
|
16
|
-
export interface ConnectorOptions {
|
|
17
|
-
/** @default 30000 */
|
|
18
|
-
commandTimeout?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface SendOptions {
|
|
22
|
-
/** Overrides the connector's commandTimeout for this command */
|
|
23
|
-
timeout?: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface BiDiMessage {
|
|
27
|
-
id: number;
|
|
28
|
-
method: string;
|
|
29
|
-
params?: object | undefined;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export class BiDiConnector {
|
|
33
|
-
private resolveMap: Map<
|
|
34
|
-
number,
|
|
35
|
-
{
|
|
36
|
-
method: string;
|
|
37
|
-
resolve: <M extends keyof BiDiCommands>(
|
|
38
|
-
value: BiDiCommands[M]["result"],
|
|
39
|
-
) => void;
|
|
40
|
-
reject: (reason: unknown) => void;
|
|
41
|
-
}
|
|
42
|
-
> = new Map();
|
|
43
|
-
private currentId: number = 0;
|
|
44
|
-
private webSocket: WebSocket;
|
|
45
|
-
private eventEmitter: EventEmitter;
|
|
46
|
-
private commandTimeout: number;
|
|
47
|
-
private subscriptions: SubscriptionManager;
|
|
48
|
-
|
|
49
|
-
constructor(ws: WebSocket, options: ConnectorOptions = {}) {
|
|
50
|
-
this.commandTimeout = options.commandTimeout ?? DEFAULT_COMMAND_TIMEOUT;
|
|
51
|
-
this.subscriptions = new SubscriptionManager({
|
|
52
|
-
subscribe: (params) => this.send("session.subscribe", params),
|
|
53
|
-
unsubscribe: (params) => this.send("session.unsubscribe", params),
|
|
54
|
-
});
|
|
55
|
-
this.webSocket = ws;
|
|
56
|
-
this.webSocket.addEventListener("message", this.messageListener);
|
|
57
|
-
this.webSocket.addEventListener("close", this.onWebsocketClose);
|
|
58
|
-
this.webSocket.addEventListener("error", this.onWebsocketError);
|
|
59
|
-
this.eventEmitter = new EventEmitter();
|
|
60
|
-
console.info("BiDiConnector: Websocket handshake finished");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public static async connect(url: string, options?: ConnectorOptions) {
|
|
64
|
-
console.log("Connecting to WebSocket");
|
|
65
|
-
const ws = new WebSocket(url);
|
|
66
|
-
return new Promise<BiDiConnector>((resolve, reject) => {
|
|
67
|
-
console.log("Waiting for WebSocket handshake");
|
|
68
|
-
ws.addEventListener("open", () => {
|
|
69
|
-
console.log("WebSocket handshake finished");
|
|
70
|
-
resolve(new BiDiConnector(ws, options));
|
|
71
|
-
});
|
|
72
|
-
ws.addEventListener("error", (err) => {
|
|
73
|
-
console.error(err);
|
|
74
|
-
reject(new Error("WebSocket connection failed"));
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private onWebsocketClose = () => {
|
|
80
|
-
this.rejectAll(
|
|
81
|
-
(pending) => new Error(`Websocket closed${pendingSuffix(pending)}`),
|
|
82
|
-
);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
private onWebsocketError = (ev: Event) => {
|
|
86
|
-
this.rejectAll((pending) =>
|
|
87
|
-
Object.assign(new Error(`Websocket error${pendingSuffix(pending)}`), {
|
|
88
|
-
cause: ev,
|
|
89
|
-
}),
|
|
90
|
-
);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/** Rejects every pending command; `reason` gets the pending method names for its message */
|
|
94
|
-
private rejectAll(reason: (pendingMethods: string[]) => unknown) {
|
|
95
|
-
const entries = [...this.resolveMap.values()];
|
|
96
|
-
const error = reason(entries.map((entry) => entry.method));
|
|
97
|
-
this.resolveMap.clear();
|
|
98
|
-
entries.forEach(({ reject }) => reject(error));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private getId() {
|
|
102
|
-
return this.currentId++;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public send<M extends keyof BiDiCommands>(
|
|
106
|
-
method: M,
|
|
107
|
-
params?: BiDiCommands[M]["params"],
|
|
108
|
-
options: SendOptions = {},
|
|
109
|
-
): Promise<BiDiCommands[M]["result"]> {
|
|
110
|
-
return new Promise((resolve, reject) => {
|
|
111
|
-
try {
|
|
112
|
-
const id = this.getId();
|
|
113
|
-
const timeout = options.timeout ?? this.commandTimeout;
|
|
114
|
-
const timer = setTimeout(() => {
|
|
115
|
-
this.resolveMap.delete(id);
|
|
116
|
-
reject(
|
|
117
|
-
new BiDiError(method, "timeout", `no reply within ${timeout}ms`),
|
|
118
|
-
);
|
|
119
|
-
}, timeout);
|
|
120
|
-
this.resolveMap.set(id, {
|
|
121
|
-
method,
|
|
122
|
-
resolve: (value) => {
|
|
123
|
-
clearTimeout(timer);
|
|
124
|
-
resolve(value);
|
|
125
|
-
},
|
|
126
|
-
reject: (reason) => {
|
|
127
|
-
clearTimeout(timer);
|
|
128
|
-
reject(reason);
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
const commandBody: BiDiMessage = {
|
|
132
|
-
id,
|
|
133
|
-
method,
|
|
134
|
-
params,
|
|
135
|
-
};
|
|
136
|
-
logger.debug("Sending message to BiDi Websocket", commandBody);
|
|
137
|
-
this.webSocket.send(JSON.stringify(commandBody));
|
|
138
|
-
} catch (err: unknown) {
|
|
139
|
-
reject(err);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Subscribes to events. Unscoped subscriptions are refcounted per event, so features can
|
|
146
|
-
* subscribe independently; scoped ones (`contexts`) always send their own command.
|
|
147
|
-
*/
|
|
148
|
-
public subscribe(
|
|
149
|
-
events: EventName[],
|
|
150
|
-
options?: SubscribeOptions,
|
|
151
|
-
): Promise<Subscription> {
|
|
152
|
-
return this.subscriptions.subscribe(events, options);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Resolves with the params of the first `event` for which `predicate` is true.
|
|
157
|
-
* The listener is registered before this returns, so start the triggering command afterwards.
|
|
158
|
-
* Rejects with WaitTimeoutError after `timeout` ms, or with the abort reason.
|
|
159
|
-
*/
|
|
160
|
-
public waitForEvent<E extends keyof BiDiEvents>(
|
|
161
|
-
event: E,
|
|
162
|
-
predicate: (params: BiDiEvents[E]["params"]) => boolean,
|
|
163
|
-
options: { timeout: number; signal?: AbortSignal },
|
|
164
|
-
): Promise<BiDiEvents[E]["params"]> {
|
|
165
|
-
const { timeout, signal } = options;
|
|
166
|
-
return new Promise((resolve, reject) => {
|
|
167
|
-
if (signal?.aborted) {
|
|
168
|
-
reject(signal.reason);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
const cleanup = () => {
|
|
172
|
-
clearTimeout(timer);
|
|
173
|
-
this.offEvent(event, listener);
|
|
174
|
-
signal?.removeEventListener("abort", onAbort);
|
|
175
|
-
};
|
|
176
|
-
const listener = (params: BiDiEvents[E]["params"]) => {
|
|
177
|
-
if (!predicate(params)) return;
|
|
178
|
-
cleanup();
|
|
179
|
-
resolve(params);
|
|
180
|
-
};
|
|
181
|
-
const onAbort = () => {
|
|
182
|
-
cleanup();
|
|
183
|
-
reject(signal?.reason);
|
|
184
|
-
};
|
|
185
|
-
const timer = setTimeout(() => {
|
|
186
|
-
cleanup();
|
|
187
|
-
reject(new WaitTimeoutError<undefined>(timeout, undefined));
|
|
188
|
-
}, timeout);
|
|
189
|
-
this.onEvent(event, listener);
|
|
190
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** Register an event listener */
|
|
195
|
-
public onEvent<E extends keyof BiDiEvents>(
|
|
196
|
-
event: E,
|
|
197
|
-
listener: (params: BiDiEvents[E]["params"]) => void,
|
|
198
|
-
): void {
|
|
199
|
-
this.eventEmitter.on(event, listener);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Remove an event listener */
|
|
203
|
-
public offEvent<E extends keyof BiDiEvents>(
|
|
204
|
-
event: E,
|
|
205
|
-
listener: (params: BiDiEvents[E]["params"]) => void,
|
|
206
|
-
): void {
|
|
207
|
-
this.eventEmitter.off(event, listener);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private messageListener = (ev: MessageEvent) => {
|
|
211
|
-
const message = JSON.parse(ev.data);
|
|
212
|
-
logger.debug("Received message from BiDi Websocket: ", message);
|
|
213
|
-
if (message.type === "success" && message.id !== undefined) {
|
|
214
|
-
const targetToResolve = this.resolveMap.get(message.id);
|
|
215
|
-
if (targetToResolve) {
|
|
216
|
-
this.resolveMap.delete(message.id);
|
|
217
|
-
targetToResolve.resolve(message.result);
|
|
218
|
-
}
|
|
219
|
-
} else if (message.type === "error") {
|
|
220
|
-
const target =
|
|
221
|
-
message.id === null || message.id === undefined
|
|
222
|
-
? undefined
|
|
223
|
-
: this.resolveMap.get(message.id);
|
|
224
|
-
if (!target) {
|
|
225
|
-
logger.debug("BiDi error reply without a pending command", message);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
this.resolveMap.delete(message.id);
|
|
229
|
-
target.reject(
|
|
230
|
-
new BiDiError(
|
|
231
|
-
target.method,
|
|
232
|
-
message.error,
|
|
233
|
-
message.message,
|
|
234
|
-
message.stacktrace,
|
|
235
|
-
),
|
|
236
|
-
);
|
|
237
|
-
} else if (message.type === "event") {
|
|
238
|
-
this.eventEmitter.emit(message.method, message.params);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
public kill() {
|
|
243
|
-
this.webSocket.removeEventListener("message", this.messageListener);
|
|
244
|
-
this.webSocket.removeEventListener("close", this.onWebsocketClose);
|
|
245
|
-
this.webSocket.removeEventListener("error", this.onWebsocketError);
|
|
246
|
-
this.rejectAll(
|
|
247
|
-
(pending) => new Error(`BiDiConnector killed${pendingSuffix(pending)}`),
|
|
248
|
-
);
|
|
249
|
-
this.eventEmitter.removeAllListeners();
|
|
250
|
-
this.webSocket.close();
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function pendingSuffix(pending: string[]) {
|
|
255
|
-
return pending.length > 0 ? ` (pending: ${pending.join(", ")})` : "";
|
|
256
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ErrorCode } from "../types/bidi-protocols/error.js";
|
|
2
|
-
|
|
3
|
-
/** A failed BiDi command: an error reply from the browser, or `"timeout"` when none came in time */
|
|
4
|
-
export class BiDiError extends Error {
|
|
5
|
-
/** BiDi error code such as "no such node", or "timeout" */
|
|
6
|
-
public readonly code: ErrorCode | "timeout";
|
|
7
|
-
/** The command that failed, e.g. "script.callFunction" */
|
|
8
|
-
public readonly command: string;
|
|
9
|
-
public readonly remoteStack?: string | undefined;
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
command: string,
|
|
13
|
-
code: ErrorCode | "timeout",
|
|
14
|
-
message: string,
|
|
15
|
-
remoteStack?: string,
|
|
16
|
-
) {
|
|
17
|
-
super(`${command} failed (${code}): ${message}`);
|
|
18
|
-
this.name = "BiDiError";
|
|
19
|
-
this.command = command;
|
|
20
|
-
this.code = code;
|
|
21
|
-
this.remoteStack = remoteStack;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { after, before, test } from "node:test";
|
|
3
|
-
import { Browser } from "../browser/browser.js";
|
|
4
|
-
import type Page from "../browser/page.js";
|
|
5
|
-
import { BiDiError } from "./bidi-error.js";
|
|
6
|
-
|
|
7
|
-
let browser: Browser;
|
|
8
|
-
let page: Page;
|
|
9
|
-
|
|
10
|
-
before(async () => {
|
|
11
|
-
({ browser, page } = await Browser.launch("firefox", { port: 9232, headless: true }));
|
|
12
|
-
await page.navigateTo("about:blank", "complete");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
after(async () => {
|
|
16
|
-
await browser?.close();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const OPTIONS = { timeout: 15000 };
|
|
20
|
-
|
|
21
|
-
test("newPage returns a page that can navigate", OPTIONS, async () => {
|
|
22
|
-
const created = await browser.newPage({ type: "tab" });
|
|
23
|
-
assert.notEqual(created.contextId, "");
|
|
24
|
-
assert.notEqual(created.contextId, page.contextId);
|
|
25
|
-
await created.navigateTo("about:blank", "complete");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("a failing command rejects with the BiDi error code instead of hanging", OPTIONS, async () => {
|
|
29
|
-
const connector = (page as unknown as { biDiConnector: import("./bidi-connection.js").BiDiConnector })
|
|
30
|
-
.biDiConnector;
|
|
31
|
-
await assert.rejects(
|
|
32
|
-
connector.send("script.evaluate", {
|
|
33
|
-
expression: "1",
|
|
34
|
-
awaitPromise: false,
|
|
35
|
-
target: { context: "does-not-exist" },
|
|
36
|
-
}),
|
|
37
|
-
(err: unknown) => err instanceof BiDiError && err.code === "no such frame",
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("context tree attributes iframe contexts to the page", OPTIONS, async () => {
|
|
42
|
-
await page.navigateTo("about:blank", "complete");
|
|
43
|
-
const tree = await page.tree();
|
|
44
|
-
const connector = (page as unknown as { biDiConnector: import("./bidi-connection.js").BiDiConnector })
|
|
45
|
-
.biDiConnector;
|
|
46
|
-
const created = connector.waitForEvent("browsingContext.contextCreated", (info) => info.parent === page.contextId, {
|
|
47
|
-
timeout: 5000,
|
|
48
|
-
});
|
|
49
|
-
await connector.send("script.evaluate", {
|
|
50
|
-
expression: `document.body.appendChild(document.createElement("iframe")).src = "about:blank"; 1`,
|
|
51
|
-
awaitPromise: false,
|
|
52
|
-
target: { context: page.contextId },
|
|
53
|
-
});
|
|
54
|
-
const frame = await created;
|
|
55
|
-
assert.equal(tree.isWithin(frame.context, page.contextId), true);
|
|
56
|
-
assert.equal(tree.rootOf(frame.context), page.contextId);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("refcounted subscriptions deliver events to every subscriber", OPTIONS, async () => {
|
|
60
|
-
const connector = (page as unknown as { biDiConnector: import("./bidi-connection.js").BiDiConnector })
|
|
61
|
-
.biDiConnector;
|
|
62
|
-
const a = await connector.subscribe(["browsingContext.load"]);
|
|
63
|
-
const b = await connector.subscribe(["browsingContext.load"]);
|
|
64
|
-
const seen = connector.waitForEvent("browsingContext.load", (p) => p.context === page.contextId, { timeout: 5000 });
|
|
65
|
-
await page.navigateTo("about:blank?again", "none");
|
|
66
|
-
await seen;
|
|
67
|
-
await a.unsubscribe();
|
|
68
|
-
// b still holds the subscription, so events keep flowing
|
|
69
|
-
const again = connector.waitForEvent("browsingContext.load", (p) => p.context === page.contextId, { timeout: 5000 });
|
|
70
|
-
await page.navigateTo("about:blank?third", "none");
|
|
71
|
-
await again;
|
|
72
|
-
await b.unsubscribe();
|
|
73
|
-
});
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import { tick } from "../testing/fake-websocket.js";
|
|
4
|
-
import { SubscriptionManager, type SessionCommands } from "./subscriptions.js";
|
|
5
|
-
|
|
6
|
-
function setup(options: { failSubscribe?: boolean; delay?: number } = {}) {
|
|
7
|
-
const calls: Array<{ command: "subscribe" | "unsubscribe"; params: any }> = [];
|
|
8
|
-
let next = 0;
|
|
9
|
-
const commands: SessionCommands = {
|
|
10
|
-
async subscribe(params) {
|
|
11
|
-
calls.push({ command: "subscribe", params });
|
|
12
|
-
await tick(options.delay ?? 0);
|
|
13
|
-
if (options.failSubscribe) throw new Error("subscribe failed");
|
|
14
|
-
return { subscription: `s${++next}` };
|
|
15
|
-
},
|
|
16
|
-
async unsubscribe(params) {
|
|
17
|
-
calls.push({ command: "unsubscribe", params });
|
|
18
|
-
await tick(options.delay ?? 0);
|
|
19
|
-
return {};
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
return { calls, manager: new SubscriptionManager(commands) };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe("SubscriptionManager", () => {
|
|
26
|
-
it("shares one subscribe between concurrent first subscribers", async () => {
|
|
27
|
-
const { calls, manager } = setup({ delay: 5 });
|
|
28
|
-
const [a, b] = await Promise.all([
|
|
29
|
-
manager.subscribe(["browsingContext.load"]),
|
|
30
|
-
manager.subscribe(["browsingContext.load"]),
|
|
31
|
-
]);
|
|
32
|
-
assert.equal(calls.filter((c) => c.command === "subscribe").length, 1);
|
|
33
|
-
await a.unsubscribe();
|
|
34
|
-
await b.unsubscribe();
|
|
35
|
-
assert.deepEqual(calls[calls.length - 1], { command: "unsubscribe", params: { subscriptions: ["s1"] } });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("subscribes each event separately and releases them together", async () => {
|
|
39
|
-
const { calls, manager } = setup();
|
|
40
|
-
const sub = await manager.subscribe(["network.beforeRequestSent", "network.fetchError"]);
|
|
41
|
-
assert.deepEqual(
|
|
42
|
-
calls.map((c) => c.params.events),
|
|
43
|
-
[["network.beforeRequestSent"], ["network.fetchError"]],
|
|
44
|
-
);
|
|
45
|
-
await sub.unsubscribe();
|
|
46
|
-
assert.equal(calls.filter((c) => c.command === "unsubscribe").length, 2);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("keeps an event subscribed while another subscriber still needs it", async () => {
|
|
50
|
-
const { calls, manager } = setup();
|
|
51
|
-
const a = await manager.subscribe(["network.fetchError", "network.responseCompleted"]);
|
|
52
|
-
const b = await manager.subscribe(["network.fetchError"]);
|
|
53
|
-
await a.unsubscribe();
|
|
54
|
-
// only responseCompleted was released
|
|
55
|
-
assert.deepEqual(
|
|
56
|
-
calls.filter((c) => c.command === "unsubscribe").map((c) => c.params.subscriptions),
|
|
57
|
-
[["s2"]],
|
|
58
|
-
);
|
|
59
|
-
await b.unsubscribe();
|
|
60
|
-
assert.equal(calls.filter((c) => c.command === "unsubscribe").length, 2);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("keeps the subscription when a new subscriber arrives while the last one is leaving", async () => {
|
|
64
|
-
const { calls, manager } = setup({ delay: 5 });
|
|
65
|
-
const first = await manager.subscribe(["log.entryAdded"]);
|
|
66
|
-
const leaving = first.unsubscribe();
|
|
67
|
-
const again = manager.subscribe(["log.entryAdded"]); // arrives before the queued unsubscribe runs
|
|
68
|
-
const second = await again;
|
|
69
|
-
await leaving;
|
|
70
|
-
assert.deepEqual(calls.map((c) => c.command), ["subscribe"]);
|
|
71
|
-
await second.unsubscribe();
|
|
72
|
-
assert.deepEqual(calls.map((c) => c.command), ["subscribe", "unsubscribe"]);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("resubscribes when a subscriber arrives after the last one fully left", async () => {
|
|
76
|
-
const { calls, manager } = setup();
|
|
77
|
-
const first = await manager.subscribe(["log.entryAdded"]);
|
|
78
|
-
await first.unsubscribe();
|
|
79
|
-
const again = await manager.subscribe(["log.entryAdded"]);
|
|
80
|
-
assert.deepEqual(calls.map((c) => c.command), ["subscribe", "unsubscribe", "subscribe"]);
|
|
81
|
-
await again.unsubscribe();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("unsubscribe is idempotent", async () => {
|
|
85
|
-
const { calls, manager } = setup();
|
|
86
|
-
const a = await manager.subscribe(["log.entryAdded"]);
|
|
87
|
-
const b = await manager.subscribe(["log.entryAdded"]);
|
|
88
|
-
await a.unsubscribe();
|
|
89
|
-
await a.unsubscribe();
|
|
90
|
-
assert.equal(calls.filter((c) => c.command === "unsubscribe").length, 0); // b still holds it
|
|
91
|
-
await b.unsubscribe();
|
|
92
|
-
assert.equal(calls.filter((c) => c.command === "unsubscribe").length, 1);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("scoped subscriptions send their own command and are not shared", async () => {
|
|
96
|
-
const { calls, manager } = setup();
|
|
97
|
-
const a = await manager.subscribe(["log.entryAdded"], { contexts: ["c1"] });
|
|
98
|
-
const b = await manager.subscribe(["log.entryAdded"], { contexts: ["c1"] });
|
|
99
|
-
assert.equal(calls.filter((c) => c.command === "subscribe").length, 2);
|
|
100
|
-
assert.deepEqual(calls[0]!.params, { events: ["log.entryAdded"], contexts: ["c1"] });
|
|
101
|
-
await a.unsubscribe();
|
|
102
|
-
await a.unsubscribe();
|
|
103
|
-
assert.deepEqual(
|
|
104
|
-
calls.filter((c) => c.command === "unsubscribe").map((c) => c.params.subscriptions),
|
|
105
|
-
[["s1"]],
|
|
106
|
-
);
|
|
107
|
-
await b.unsubscribe();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("a failed subscribe rolls back and lets a later subscribe try again", async () => {
|
|
111
|
-
const { calls, manager } = setup({ failSubscribe: true });
|
|
112
|
-
await assert.rejects(manager.subscribe(["log.entryAdded"]), /subscribe failed/);
|
|
113
|
-
await assert.rejects(manager.subscribe(["log.entryAdded"]), /subscribe failed/);
|
|
114
|
-
assert.equal(calls.filter((c) => c.command === "subscribe").length, 2);
|
|
115
|
-
assert.equal(calls.filter((c) => c.command === "unsubscribe").length, 0);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("rolls back events already acquired when a later one fails", async () => {
|
|
119
|
-
const calls: Array<{ command: string; params: any }> = [];
|
|
120
|
-
let count = 0;
|
|
121
|
-
const flaky = new SubscriptionManager({
|
|
122
|
-
async subscribe(params) {
|
|
123
|
-
calls.push({ command: "subscribe", params });
|
|
124
|
-
if (++count === 2) throw new Error("second failed");
|
|
125
|
-
return { subscription: "ok" };
|
|
126
|
-
},
|
|
127
|
-
async unsubscribe(params) {
|
|
128
|
-
calls.push({ command: "unsubscribe", params });
|
|
129
|
-
return {};
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
await assert.rejects(
|
|
133
|
-
flaky.subscribe(["network.fetchError", "network.responseCompleted"]),
|
|
134
|
-
/second failed/,
|
|
135
|
-
);
|
|
136
|
-
assert.deepEqual(calls.filter((c) => c.command === "unsubscribe").map((c) => c.params), [
|
|
137
|
-
{ subscriptions: ["ok"] },
|
|
138
|
-
]);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import type { BiDiEvents } from "../types/bidi.js";
|
|
2
|
-
|
|
3
|
-
export type EventName = keyof BiDiEvents;
|
|
4
|
-
|
|
5
|
-
export interface Subscription {
|
|
6
|
-
/** Idempotent */
|
|
7
|
-
unsubscribe(): Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface SubscribeOptions {
|
|
11
|
-
/** Scope to these browsing contexts. Scoped subscriptions are never shared */
|
|
12
|
-
contexts?: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Sends the two session commands; the connector provides it */
|
|
16
|
-
export interface SessionCommands {
|
|
17
|
-
subscribe(params: { events: string[]; contexts?: string[] }): Promise<{ subscription: string }>;
|
|
18
|
-
unsubscribe(params: { subscriptions: string[] }): Promise<unknown>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Shared {
|
|
22
|
-
count: number;
|
|
23
|
-
id: string | undefined;
|
|
24
|
-
/** Serialises subscribe/unsubscribe commands for this event so they never interleave */
|
|
25
|
-
chain: Promise<void>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Global (unscoped) subscriptions are refcounted per event name: the first subscriber sends
|
|
30
|
-
* `session.subscribe`, the last one leaving sends `session.unsubscribe`.
|
|
31
|
-
*/
|
|
32
|
-
export class SubscriptionManager {
|
|
33
|
-
private shared = new Map<EventName, Shared>();
|
|
34
|
-
|
|
35
|
-
constructor(private commands: SessionCommands) {}
|
|
36
|
-
|
|
37
|
-
public async subscribe(events: EventName[], options: SubscribeOptions = {}): Promise<Subscription> {
|
|
38
|
-
if (options.contexts) return this.subscribeScoped(events, options.contexts);
|
|
39
|
-
|
|
40
|
-
const acquired: EventName[] = [];
|
|
41
|
-
try {
|
|
42
|
-
for (const event of events) {
|
|
43
|
-
await this.acquire(event);
|
|
44
|
-
acquired.push(event);
|
|
45
|
-
}
|
|
46
|
-
} catch (err) {
|
|
47
|
-
await Promise.allSettled(acquired.map((event) => this.release(event)));
|
|
48
|
-
throw err;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let released = false;
|
|
52
|
-
return {
|
|
53
|
-
unsubscribe: async () => {
|
|
54
|
-
if (released) return;
|
|
55
|
-
released = true;
|
|
56
|
-
await Promise.all(acquired.map((event) => this.release(event)));
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private async subscribeScoped(events: EventName[], contexts: string[]): Promise<Subscription> {
|
|
62
|
-
const { subscription } = await this.commands.subscribe({ events, contexts });
|
|
63
|
-
let released = false;
|
|
64
|
-
return {
|
|
65
|
-
unsubscribe: async () => {
|
|
66
|
-
if (released) return;
|
|
67
|
-
released = true;
|
|
68
|
-
await this.commands.unsubscribe({ subscriptions: [subscription] });
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private state(event: EventName): Shared {
|
|
74
|
-
let state = this.shared.get(event);
|
|
75
|
-
if (!state) {
|
|
76
|
-
state = { count: 0, id: undefined, chain: Promise.resolve() };
|
|
77
|
-
this.shared.set(event, state);
|
|
78
|
-
}
|
|
79
|
-
return state;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private acquire(event: EventName): Promise<void> {
|
|
83
|
-
const state = this.state(event);
|
|
84
|
-
state.count++;
|
|
85
|
-
const step = state.chain.then(async () => {
|
|
86
|
-
if (state.count > 0 && state.id === undefined) {
|
|
87
|
-
state.id = (await this.commands.subscribe({ events: [event] })).subscription;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
state.chain = step.catch(() => {});
|
|
91
|
-
return step.catch((err) => {
|
|
92
|
-
state.count--;
|
|
93
|
-
throw err;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private release(event: EventName): Promise<void> {
|
|
98
|
-
const state = this.state(event);
|
|
99
|
-
state.count--;
|
|
100
|
-
const step = state.chain.then(async () => {
|
|
101
|
-
if (state.count === 0 && state.id !== undefined) {
|
|
102
|
-
const id = state.id;
|
|
103
|
-
state.id = undefined;
|
|
104
|
-
await this.commands.unsubscribe({ subscriptions: [id] });
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
state.chain = step.catch(() => {});
|
|
108
|
-
return step;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Compile-time check, run by `tsc --noEmit`: every command the BiDi specs rely on is typed.
|
|
2
|
-
import type { BiDiCommands } from "./bidi.js";
|
|
3
|
-
|
|
4
|
-
type Expect<T extends true> = T;
|
|
5
|
-
type HasCommand<M extends string> = M extends keyof BiDiCommands
|
|
6
|
-
? BiDiCommands[M] extends { params: unknown; result: unknown }
|
|
7
|
-
? true
|
|
8
|
-
: false
|
|
9
|
-
: false;
|
|
10
|
-
|
|
11
|
-
type Commands = [
|
|
12
|
-
Expect<HasCommand<"script.callFunction">>,
|
|
13
|
-
Expect<HasCommand<"script.disown">>,
|
|
14
|
-
Expect<HasCommand<"script.addPreloadScript">>,
|
|
15
|
-
Expect<HasCommand<"script.removePreloadScript">>,
|
|
16
|
-
Expect<HasCommand<"browsingContext.locateNodes">>,
|
|
17
|
-
Expect<HasCommand<"browsingContext.handleUserPrompt">>,
|
|
18
|
-
Expect<HasCommand<"browsingContext.setViewport">>,
|
|
19
|
-
Expect<HasCommand<"browsingContext.reload">>,
|
|
20
|
-
Expect<HasCommand<"browsingContext.traverseHistory">>,
|
|
21
|
-
Expect<HasCommand<"browsingContext.close">>,
|
|
22
|
-
Expect<HasCommand<"browsingContext.print">>,
|
|
23
|
-
Expect<HasCommand<"input.setFiles">>,
|
|
24
|
-
Expect<HasCommand<"network.addIntercept">>,
|
|
25
|
-
Expect<HasCommand<"network.removeIntercept">>,
|
|
26
|
-
Expect<HasCommand<"network.continueRequest">>,
|
|
27
|
-
Expect<HasCommand<"network.continueResponse">>,
|
|
28
|
-
Expect<HasCommand<"network.continueWithAuth">>,
|
|
29
|
-
Expect<HasCommand<"network.provideResponse">>,
|
|
30
|
-
Expect<HasCommand<"network.failRequest">>,
|
|
31
|
-
Expect<HasCommand<"network.setCacheBehavior">>,
|
|
32
|
-
Expect<HasCommand<"network.addDataCollector">>,
|
|
33
|
-
Expect<HasCommand<"network.removeDataCollector">>,
|
|
34
|
-
Expect<HasCommand<"network.getData">>,
|
|
35
|
-
Expect<HasCommand<"network.disownData">>,
|
|
36
|
-
Expect<HasCommand<"browser.createUserContext">>,
|
|
37
|
-
Expect<HasCommand<"browser.removeUserContext">>,
|
|
38
|
-
Expect<HasCommand<"browser.getUserContexts">>,
|
|
39
|
-
Expect<HasCommand<"browser.setDownloadBehavior">>,
|
|
40
|
-
Expect<HasCommand<"session.unsubscribe">>,
|
|
41
|
-
Expect<HasCommand<"emulation.setForcedColorsModeThemeOverride">>,
|
|
42
|
-
Expect<HasCommand<"emulation.setGeolocationOverride">>,
|
|
43
|
-
Expect<HasCommand<"emulation.setLocaleOverride">>,
|
|
44
|
-
Expect<HasCommand<"emulation.setNetworkConditions">>,
|
|
45
|
-
Expect<HasCommand<"emulation.setScreenOrientationOverride">>,
|
|
46
|
-
Expect<HasCommand<"emulation.setScreenSettingsOverride">>,
|
|
47
|
-
Expect<HasCommand<"emulation.setScriptingEnabled">>,
|
|
48
|
-
Expect<HasCommand<"emulation.setScrollbarTypeOverride">>,
|
|
49
|
-
Expect<HasCommand<"emulation.setTimezoneOverride">>,
|
|
50
|
-
Expect<HasCommand<"emulation.setTouchOverride">>,
|
|
51
|
-
Expect<HasCommand<"emulation.setUserAgentOverride">>,
|
|
52
|
-
Expect<HasCommand<"permissions.setPermission">>,
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
export type { Commands };
|