@handstage/core 0.0.7 → 0.0.8
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 +43 -1
- package/dist/v3/logger.js +22 -31
- package/dist/v3/types/public/index.js +1 -0
- package/dist/v3/types/public/sdkErrors.js +22 -141
- package/dist/v3/understudy/a11y/snapshot/capture.js +1 -2
- package/dist/v3/understudy/cdp.js +64 -10
- package/dist/v3/understudy/context.js +163 -424
- package/dist/v3/understudy/fileUploadUtils.js +3 -3
- package/dist/v3/understudy/frame.js +11 -2
- package/dist/v3/understudy/initScripts.js +1 -1
- package/dist/v3/understudy/locator.js +3 -3
- package/dist/v3/understudy/page.js +40 -23
- package/dist/v3/understudy/piercer.js +4 -3
- package/dist/v3/understudy/selectorResolver.js +2 -3
- package/dist/v3/understudy/targetRouter.js +189 -0
- package/dist/v3/v3.js +340 -258
- package/package.json +1 -1
- package/src/v3/logger.ts +35 -38
- package/src/v3/types/private/internal.ts +24 -7
- package/src/v3/types/public/index.ts +6 -4
- package/src/v3/types/public/sdkErrors.ts +25 -0
- package/src/v3/understudy/a11y/snapshot/capture.ts +1 -2
- package/src/v3/understudy/cdp.ts +73 -7
- package/src/v3/understudy/context.ts +221 -492
- package/src/v3/understudy/frame.ts +12 -0
- package/src/v3/understudy/locator.ts +3 -3
- package/src/v3/understudy/page.ts +42 -33
- package/src/v3/understudy/piercer.ts +4 -2
- package/src/v3/understudy/selectorResolver.ts +2 -3
- package/src/v3/understudy/targetRouter.ts +236 -0
- package/src/v3/v3.ts +383 -267
package/README.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
1
|
# @handstage/core
|
|
2
2
|
|
|
3
|
-
Core browser automation engine for Handstage. Manages
|
|
3
|
+
Core browser automation engine for Handstage. Manages CDP connections,
|
|
4
|
+
target routing, page/frame lifecycle, and script injection for reliable
|
|
5
|
+
browser automation.
|
|
6
|
+
|
|
7
|
+
## Connection ownership
|
|
8
|
+
|
|
9
|
+
`V3` (alias `Handstage`) owns the CDP connection it constructs:
|
|
10
|
+
|
|
11
|
+
- `V3.connectLocal({ cdpUrl?, ... })` — opens (or attaches via WS) and owns
|
|
12
|
+
the connection. `close()` closes the WebSocket.
|
|
13
|
+
- `V3.connectTransport(transport)` — wraps and owns a raw `CDPTransport`.
|
|
14
|
+
Wrapping the same `transport` again throws
|
|
15
|
+
`HandstageTransportAlreadyOwnedError`.
|
|
16
|
+
- `V3.connectSession(session)` — wraps and owns an `ExternalCDPSession`.
|
|
17
|
+
Same ownership rule as transports.
|
|
18
|
+
- `V3.connectConnection(existingConnection)` — explicit sharing entrypoint.
|
|
19
|
+
V3 does NOT close the connection on `close()`; the caller does.
|
|
20
|
+
|
|
21
|
+
`V3Context.close()` only ever calls `Target.disposeBrowserContext` (for
|
|
22
|
+
dedicated contexts). It never tears down the underlying CDP connection —
|
|
23
|
+
that responsibility lives with `V3` or, for shared connections, the caller.
|
|
24
|
+
|
|
25
|
+
## Default-context attach
|
|
26
|
+
|
|
27
|
+
Handstage creates instances containing the `defaultBrowserContext()` by default (which aligns with Puppeteer's `puppeteer.connect` and `puppeteer.launch`).
|
|
28
|
+
Two Handstage clients on the same CDP websocket therefore share the default browser context natively without breaking. If you want isolation, call `v3.createBrowserContext()`, which returns an isolated browser context. Targets owned by other browser contexts are resumed/detached at the target router rather than left paused.
|
|
29
|
+
|
|
30
|
+
To intentionally attach to the shared default context (and accept that
|
|
31
|
+
other actors may race with you on a shared tab), simply use the default browser context (i.e. `v3.newPage()`).
|
|
32
|
+
|
|
33
|
+
## Active page is gone
|
|
34
|
+
|
|
35
|
+
Contexts no longer auto-create an initial page and there is no
|
|
36
|
+
`context.activePage()` / `setActivePage()` / `awaitActivePage()`. Track
|
|
37
|
+
`Page` references yourself (from `newPage()` / `pages()`) and call
|
|
38
|
+
`page.bringToFront()` if you want to foreground a tab.
|
|
39
|
+
|
|
40
|
+
## Per-instance logging
|
|
41
|
+
|
|
42
|
+
Pass `logger:` to any `V3.connect*` factory and that logger receives every
|
|
43
|
+
log from that V3's contexts / pages / network managers / target-router
|
|
44
|
+
delegate. Two V3 instances on a shared connection each receive router-level
|
|
45
|
+
debug lines via broadcast.
|
package/dist/v3/logger.js
CHANGED
|
@@ -1,37 +1,28 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
1
|
import { createConsoleLogger } from "./types/public/consoleLogger";
|
|
2
|
+
import { LogLevel, shouldEmitLogLine } from "./types/public/logs";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - `withInstanceLogContext`: run a function with that instance id on the async context.
|
|
8
|
-
* - `v3Logger`: emit a line for the current instance, or fall back to `createConsoleLogger()` when no context.
|
|
4
|
+
* Build a level-filtered logger from the caller-supplied logger (or a
|
|
5
|
+
* console fallback). Used by V3 at construction time to wrap the user's
|
|
6
|
+
* raw logger into one that already respects `verbose`.
|
|
9
7
|
*/
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
export function createFilteredLogger(rawLogger, verbose) {
|
|
9
|
+
const sink = rawLogger ?? createConsoleLogger();
|
|
10
|
+
const minLevel = verbose ?? LogLevel.Info;
|
|
11
|
+
return (line) => {
|
|
12
|
+
if (!shouldEmitLogLine(line.level, minLevel))
|
|
13
|
+
return;
|
|
14
|
+
sink({ ...line, level: line.level ?? LogLevel.Info });
|
|
15
|
+
};
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const fn = instanceLoggers.get(id);
|
|
26
|
-
if (fn) {
|
|
27
|
-
try {
|
|
28
|
-
fn(line);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// fall through to fallback
|
|
33
|
-
}
|
|
34
|
-
}
|
|
17
|
+
/**
|
|
18
|
+
* Lazily-constructed console fallback used for code paths that fire before
|
|
19
|
+
* a real logger is plumbed in (e.g. the few static utility functions that
|
|
20
|
+
* still take an optional logger arg).
|
|
21
|
+
*/
|
|
22
|
+
let _defaultLogger = null;
|
|
23
|
+
export function defaultLogger() {
|
|
24
|
+
if (!_defaultLogger) {
|
|
25
|
+
_defaultLogger = createFilteredLogger(undefined, LogLevel.Info);
|
|
35
26
|
}
|
|
36
|
-
|
|
27
|
+
return _defaultLogger;
|
|
37
28
|
}
|
|
@@ -10,68 +10,6 @@ export class HandstageError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
export class HandstageDefaultError extends HandstageError {
|
|
14
|
-
constructor(error) {
|
|
15
|
-
if (error instanceof Error || error instanceof HandstageError) {
|
|
16
|
-
super(`\nHey! We're sorry you ran into an error. \nHandstage version: ${HANDSTAGE_VERSION} \nIf you need help, please open a Github issue or reach out to us on Discord: https://handstage.dev/discord\n\nFull error:\n${error.message}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export class HandstageEnvironmentError extends HandstageError {
|
|
21
|
-
constructor(currentEnvironment, requiredEnvironment, feature) {
|
|
22
|
-
super(`You seem to be setting the current environment to ${currentEnvironment}.` +
|
|
23
|
-
`Ensure the environment is set to ${requiredEnvironment} if you want to use ${feature}.`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export class MissingEnvironmentVariableError extends HandstageError {
|
|
27
|
-
constructor(missingEnvironmentVariable, feature) {
|
|
28
|
-
super(`${missingEnvironmentVariable} is required to use ${feature}.` +
|
|
29
|
-
`Please set ${missingEnvironmentVariable} in your environment.`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export class UnsupportedModelError extends HandstageError {
|
|
33
|
-
constructor(supportedModels, feature) {
|
|
34
|
-
const message = feature
|
|
35
|
-
? `${feature} requires a valid model.`
|
|
36
|
-
: `Unsupported model.`;
|
|
37
|
-
const guidance = `\n\nPlease use the provider/model format (e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5", "google/gemini-3-flash-preview").` +
|
|
38
|
-
`\n\nFor a complete list of supported models and providers, see: https://docs.handstage.dev/v3/configuration/models#configuration-setup`;
|
|
39
|
-
super(`${message}${guidance}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export class UnsupportedModelProviderError extends HandstageError {
|
|
43
|
-
constructor(supportedProviders, feature) {
|
|
44
|
-
super(feature
|
|
45
|
-
? `${feature} requires one of the following model providers: ${supportedProviders}`
|
|
46
|
-
: `please use one of the supported model providers: ${supportedProviders}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export class UnsupportedAISDKModelProviderError extends HandstageError {
|
|
50
|
-
constructor(provider, supportedProviders) {
|
|
51
|
-
super(`${provider} is not currently supported for aiSDK. please use one of the supported model providers: ${supportedProviders}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export class InvalidAISDKModelFormatError extends HandstageError {
|
|
55
|
-
constructor(modelName) {
|
|
56
|
-
super(`${modelName} does not follow correct format for specifying aiSDK models. Please define your model as 'provider/model-name'. For example: \`model: 'openai/gpt-4o-mini'\``);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
export class CaptchaTimeoutError extends HandstageError {
|
|
60
|
-
constructor() {
|
|
61
|
-
super("Captcha timeout");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
export class MissingLLMConfigurationError extends HandstageError {
|
|
65
|
-
constructor() {
|
|
66
|
-
super("No LLM API key or LLM Client configured. An LLM API key or a custom LLM Client " +
|
|
67
|
-
"is required to use act, extract, or observe.");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export class HandlerNotInitializedError extends HandstageError {
|
|
71
|
-
constructor(handlerType) {
|
|
72
|
-
super(`${handlerType} handler not initialized`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
13
|
export class HandstageInvalidArgumentError extends HandstageError {
|
|
76
14
|
constructor(message) {
|
|
77
15
|
super(`InvalidArgumentError: ${message}`);
|
|
@@ -92,21 +30,6 @@ export class HandstageElementNotFoundError extends HandstageError {
|
|
|
92
30
|
super(`Could not find an element for the given xPath(s): ${xpaths}`);
|
|
93
31
|
}
|
|
94
32
|
}
|
|
95
|
-
export class AgentScreenshotProviderError extends HandstageError {
|
|
96
|
-
constructor(message) {
|
|
97
|
-
super(`ScreenshotProviderError: ${message}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
export class HandstageMissingArgumentError extends HandstageError {
|
|
101
|
-
constructor(message) {
|
|
102
|
-
super(`MissingArgumentError: ${message}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
export class CreateChatCompletionResponseError extends HandstageError {
|
|
106
|
-
constructor(message) {
|
|
107
|
-
super(`CreateChatCompletionResponseError: ${message}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
33
|
export class HandstageEvalError extends HandstageError {
|
|
111
34
|
constructor(message) {
|
|
112
35
|
super(`HandstageEvalError: ${message}`);
|
|
@@ -122,16 +45,6 @@ export class HandstageLocatorError extends HandstageError {
|
|
|
122
45
|
super(`Error ${action} Element with selector: ${selector} Reason: ${message}`);
|
|
123
46
|
}
|
|
124
47
|
}
|
|
125
|
-
export class HandstageClickError extends HandstageError {
|
|
126
|
-
constructor(message, selector) {
|
|
127
|
-
super(`Error Clicking Element with selector: ${selector} Reason: ${message}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
export class LLMResponseError extends HandstageError {
|
|
131
|
-
constructor(primitive, message) {
|
|
132
|
-
super(`${primitive} LLM response error: ${message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
48
|
export class HandstageIframeError extends HandstageError {
|
|
136
49
|
constructor(frameUrl, message) {
|
|
137
50
|
super(`Unable to resolve frameId for iframe with URL: ${frameUrl} Full error: ${message}`);
|
|
@@ -142,49 +55,6 @@ export class ContentFrameNotFoundError extends HandstageError {
|
|
|
142
55
|
super(`Unable to obtain a content frame for selector: ${selector}`);
|
|
143
56
|
}
|
|
144
57
|
}
|
|
145
|
-
export class XPathResolutionError extends HandstageError {
|
|
146
|
-
constructor(xpath) {
|
|
147
|
-
super(`XPath "${xpath}" does not resolve in the current page or frames`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
export class ZodSchemaValidationError extends Error {
|
|
151
|
-
received;
|
|
152
|
-
issues;
|
|
153
|
-
constructor(received, issues) {
|
|
154
|
-
super(`Zod schema validation failed
|
|
155
|
-
|
|
156
|
-
— Received —
|
|
157
|
-
${JSON.stringify(received, null, 2)}
|
|
158
|
-
|
|
159
|
-
— Issues —
|
|
160
|
-
${JSON.stringify(issues, null, 2)}`);
|
|
161
|
-
this.received = received;
|
|
162
|
-
this.issues = issues;
|
|
163
|
-
this.name = "ZodSchemaValidationError";
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
export class HandstageInitError extends HandstageError {
|
|
167
|
-
constructor(message) {
|
|
168
|
-
super(message);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
export class HandstageShadowRootMissingError extends HandstageError {
|
|
172
|
-
constructor(detail) {
|
|
173
|
-
super(`No shadow root present on the resolved host` +
|
|
174
|
-
(detail ? `: ${detail}` : ""));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
export class HandstageShadowSegmentEmptyError extends HandstageError {
|
|
178
|
-
constructor() {
|
|
179
|
-
super(`Empty selector segment after shadow-DOM hop ("//")`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
export class HandstageShadowSegmentNotFoundError extends HandstageError {
|
|
183
|
-
constructor(segment, hint) {
|
|
184
|
-
super(`Shadow segment '${segment}' matched no element inside shadow root` +
|
|
185
|
-
(hint ? ` ${hint}` : ""));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
58
|
export class ElementNotVisibleError extends HandstageError {
|
|
189
59
|
constructor(selector) {
|
|
190
60
|
super(`Element not visible (no box model): ${selector}`);
|
|
@@ -215,16 +85,33 @@ export class ConnectionTimeoutError extends HandstageError {
|
|
|
215
85
|
super(`Connection timeout: ${message}`);
|
|
216
86
|
}
|
|
217
87
|
}
|
|
218
|
-
export class HandstageClosedError extends HandstageError {
|
|
219
|
-
constructor() {
|
|
220
|
-
super("Handstage session was closed");
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
88
|
export class CDPConnectionClosedError extends HandstageError {
|
|
224
89
|
constructor(reason) {
|
|
225
90
|
super(`CDP connection closed: ${reason}`);
|
|
226
91
|
}
|
|
227
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Raised when a caller tries to wrap a `CDPTransport` (via
|
|
95
|
+
* `new CDPConnection(transport)` / `V3.connectTransport`) or an
|
|
96
|
+
* `ExternalCDPSession` (via `V3.connectSession`) that is already owned by
|
|
97
|
+
* another `CDPConnection` / `ExternalConnectionAdapter`.
|
|
98
|
+
*
|
|
99
|
+
* Silently double-wrapping would clobber `transport.onmessage` / `.onclose` /
|
|
100
|
+
* `.onerror` and stall the first owner. If you actually want two `V3`
|
|
101
|
+
* instances sharing one CDP connection, construct the connection once and
|
|
102
|
+
* use `V3.connectConnection(existingConnection)` for both instances.
|
|
103
|
+
*/
|
|
104
|
+
export class HandstageTransportAlreadyOwnedError extends HandstageError {
|
|
105
|
+
constructor(kind) {
|
|
106
|
+
super(kind === "transport"
|
|
107
|
+
? "CDPTransport already owned by another CDPConnection. " +
|
|
108
|
+
"Construct the CDPConnection once and share it via V3.connectConnection() " +
|
|
109
|
+
"instead of wrapping the same transport twice."
|
|
110
|
+
: "ExternalCDPSession already owned by another ExternalConnectionAdapter. " +
|
|
111
|
+
"Construct the adapter once and share the resulting CDPConnectionLike via " +
|
|
112
|
+
"V3.connectConnection() instead of calling connectSession twice with the same session.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
228
115
|
export class HandstageSetExtraHTTPHeadersError extends HandstageError {
|
|
229
116
|
failures;
|
|
230
117
|
constructor(failures) {
|
|
@@ -242,9 +129,3 @@ export class HandstageSnapshotError extends HandstageError {
|
|
|
242
129
|
super(`error taking snapshot${suffix}`, cause);
|
|
243
130
|
}
|
|
244
131
|
}
|
|
245
|
-
export class UnderstudyCommandException extends HandstageError {
|
|
246
|
-
constructor(message, cause) {
|
|
247
|
-
super(message, cause);
|
|
248
|
-
this.name = "UnderstudyCommandException";
|
|
249
|
-
}
|
|
250
|
-
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { v3Logger } from "../../../logger";
|
|
2
1
|
import { LogLevel } from "../../../types/public/logs";
|
|
3
2
|
import { a11yForFrame } from "./a11yTree";
|
|
4
3
|
import { buildSessionDomIndex, domMapsForSession, relativizeXPath, } from "./domTree";
|
|
@@ -71,7 +70,7 @@ export async function tryScopedSnapshot(page, options, context, pierce) {
|
|
|
71
70
|
if (!requestedFocus)
|
|
72
71
|
return null;
|
|
73
72
|
const logScopeFallback = () => {
|
|
74
|
-
|
|
73
|
+
page.logger({
|
|
75
74
|
message: `Unable to narrow scope with selector. Falling back to using full DOM`,
|
|
76
75
|
level: LogLevel.Info,
|
|
77
76
|
attributes: {
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { HANDSTAGE_VERSION } from "../../version";
|
|
2
|
-
import { CDPConnectionClosedError, PageNotFoundError, } from "../types/public/sdkErrors";
|
|
2
|
+
import { CDPConnectionClosedError, HandstageTransportAlreadyOwnedError, PageNotFoundError, } from "../types/public/sdkErrors";
|
|
3
|
+
/**
|
|
4
|
+
* Marker placed on a `CDPTransport` once a `CDPConnection` has bound its
|
|
5
|
+
* `onmessage` / `onclose` / `onerror` callbacks. A second wrap throws so
|
|
6
|
+
* the caller can't silently destroy the first owner. Use `Symbol.for(...)`
|
|
7
|
+
* so the marker survives across module realms (rare, but cheap to guard).
|
|
8
|
+
*/
|
|
9
|
+
const TRANSPORT_OWNED = Symbol.for("handstage.cdp.transportOwned");
|
|
10
|
+
/**
|
|
11
|
+
* Same marker as {@link TRANSPORT_OWNED} but for `ExternalCDPSession`
|
|
12
|
+
* wrapped by `ExternalConnectionAdapter`.
|
|
13
|
+
*/
|
|
14
|
+
const SESSION_OWNED = Symbol.for("handstage.cdp.sessionOwned");
|
|
15
|
+
function invokeEventHandler(handler, params) {
|
|
16
|
+
try {
|
|
17
|
+
const result = handler(params);
|
|
18
|
+
if (result &&
|
|
19
|
+
typeof result === "object" &&
|
|
20
|
+
"then" in result &&
|
|
21
|
+
typeof result.then === "function") {
|
|
22
|
+
void result.catch(() => { });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
}
|
|
3
27
|
export class BaseCDPConnection {
|
|
4
28
|
// Memoize the in-flight enable so concurrent V3Contexts sharing the
|
|
5
29
|
// connection don't all re-fire setAutoAttach on the browser session.
|
|
@@ -71,6 +95,12 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
71
95
|
}
|
|
72
96
|
constructor(transport) {
|
|
73
97
|
super();
|
|
98
|
+
const owned = transport[TRANSPORT_OWNED];
|
|
99
|
+
if (owned) {
|
|
100
|
+
throw new HandstageTransportAlreadyOwnedError("transport");
|
|
101
|
+
}
|
|
102
|
+
;
|
|
103
|
+
transport[TRANSPORT_OWNED] = this;
|
|
74
104
|
this.transport = transport;
|
|
75
105
|
this.transport.onclose = (reason) => {
|
|
76
106
|
this._isClosed = true;
|
|
@@ -154,7 +184,18 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
154
184
|
}
|
|
155
185
|
async close() {
|
|
156
186
|
this._isClosed = true;
|
|
157
|
-
|
|
187
|
+
try {
|
|
188
|
+
this.transport.close();
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
// Release ownership so a future caller could re-wrap a fresh
|
|
192
|
+
// transport with the same identity (rare; mainly relevant in
|
|
193
|
+
// long-running tests that reuse fake transports).
|
|
194
|
+
try {
|
|
195
|
+
delete this.transport[TRANSPORT_OWNED];
|
|
196
|
+
}
|
|
197
|
+
catch { }
|
|
198
|
+
}
|
|
158
199
|
}
|
|
159
200
|
rejectAllInflight(why) {
|
|
160
201
|
for (const [id, entry] of this.inflight.entries()) {
|
|
@@ -272,14 +313,14 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
272
313
|
const handlers = this.eventHandlers.get(method);
|
|
273
314
|
if (handlers)
|
|
274
315
|
for (const h of handlers)
|
|
275
|
-
h
|
|
316
|
+
invokeEventHandler(h, params);
|
|
276
317
|
}
|
|
277
318
|
return;
|
|
278
319
|
}
|
|
279
320
|
const handlers = this.eventHandlers.get(method);
|
|
280
321
|
if (handlers)
|
|
281
322
|
for (const h of handlers)
|
|
282
|
-
h
|
|
323
|
+
invokeEventHandler(h, params);
|
|
283
324
|
};
|
|
284
325
|
dispatch();
|
|
285
326
|
}
|
|
@@ -339,7 +380,7 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
339
380
|
const handlers = this.eventHandlers.get(key);
|
|
340
381
|
if (handlers)
|
|
341
382
|
for (const h of handlers)
|
|
342
|
-
h
|
|
383
|
+
invokeEventHandler(h, params);
|
|
343
384
|
}
|
|
344
385
|
}
|
|
345
386
|
export class ExternalConnectionAdapter extends BaseCDPConnection {
|
|
@@ -353,6 +394,13 @@ export class ExternalConnectionAdapter extends BaseCDPConnection {
|
|
|
353
394
|
constructor(externalSession) {
|
|
354
395
|
super();
|
|
355
396
|
this.externalSession = externalSession;
|
|
397
|
+
const owned = externalSession[SESSION_OWNED];
|
|
398
|
+
if (owned) {
|
|
399
|
+
throw new HandstageTransportAlreadyOwnedError("session");
|
|
400
|
+
}
|
|
401
|
+
;
|
|
402
|
+
externalSession[SESSION_OWNED] =
|
|
403
|
+
this;
|
|
356
404
|
// Listen for flattened child session events if the external wrapper passes them
|
|
357
405
|
this.on("Target.attachedToTarget", (params) => {
|
|
358
406
|
if (params?.sessionId && !this.sessions.has(params.sessionId)) {
|
|
@@ -390,21 +438,23 @@ export class ExternalConnectionAdapter extends BaseCDPConnection {
|
|
|
390
438
|
const childHandlers = this.eventHandlers.get(childKey);
|
|
391
439
|
if (childHandlers) {
|
|
392
440
|
for (const h of childHandlers)
|
|
393
|
-
h
|
|
441
|
+
invokeEventHandler(h, params);
|
|
394
442
|
}
|
|
395
443
|
// Forward target lifecycle events to root listeners as well.
|
|
396
444
|
if (event.startsWith("Target.")) {
|
|
397
445
|
const rootHandlers = this.eventHandlers.get(event);
|
|
398
|
-
if (rootHandlers)
|
|
446
|
+
if (rootHandlers) {
|
|
399
447
|
for (const h of rootHandlers)
|
|
400
|
-
h
|
|
448
|
+
invokeEventHandler(h, params);
|
|
449
|
+
}
|
|
401
450
|
}
|
|
402
451
|
}
|
|
403
452
|
else {
|
|
404
453
|
const rootHandlers = this.eventHandlers.get(event);
|
|
405
|
-
if (rootHandlers)
|
|
454
|
+
if (rootHandlers) {
|
|
406
455
|
for (const h of rootHandlers)
|
|
407
|
-
h
|
|
456
|
+
invokeEventHandler(h, params);
|
|
457
|
+
}
|
|
408
458
|
}
|
|
409
459
|
};
|
|
410
460
|
this.rootEventHandlers.set(event, rootHandler);
|
|
@@ -441,6 +491,10 @@ export class ExternalConnectionAdapter extends BaseCDPConnection {
|
|
|
441
491
|
if (this.externalSession.onclose) {
|
|
442
492
|
this.externalSession.onclose = undefined;
|
|
443
493
|
}
|
|
494
|
+
try {
|
|
495
|
+
delete this.externalSession[SESSION_OWNED];
|
|
496
|
+
}
|
|
497
|
+
catch { }
|
|
444
498
|
// If external session has a close method, invoke it, otherwise no-op.
|
|
445
499
|
if (typeof this.externalSession.close === "function") {
|
|
446
500
|
await this.externalSession.close();
|