browser-pilot 0.0.13 → 0.0.14
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 +59 -3
- package/dist/actions.cjs +418 -14
- package/dist/actions.d.cts +13 -3
- package/dist/actions.d.ts +13 -3
- package/dist/actions.mjs +1 -1
- package/dist/browser-LZTEHUDI.mjs +9 -0
- package/dist/browser.cjs +600 -20
- package/dist/browser.d.cts +12 -3
- package/dist/browser.d.ts +12 -3
- package/dist/browser.mjs +3 -3
- package/dist/cdp.cjs +31 -2
- package/dist/cdp.d.cts +1 -1
- package/dist/cdp.d.ts +1 -1
- package/dist/cdp.mjs +3 -1
- package/dist/chunk-7NDR6V7S.mjs +7788 -0
- package/dist/{chunk-VDAMDOS6.mjs → chunk-IN5HPAPB.mjs} +147 -7
- package/dist/{chunk-HP6R3W32.mjs → chunk-KIFB526Y.mjs} +44 -2
- package/dist/chunk-LUGLEMVR.mjs +11 -0
- package/dist/chunk-SPSZZH22.mjs +308 -0
- package/dist/{chunk-A2ZRAEO3.mjs → chunk-XMJABKCF.mjs} +408 -14
- package/dist/cli.mjs +1063 -7746
- package/dist/client-3AFV2IAF.mjs +10 -0
- package/dist/{client-DRqxBdHv.d.ts → client-Ck2nQksT.d.cts} +8 -6
- package/dist/{client-DRqxBdHv.d.cts → client-Ck2nQksT.d.ts} +8 -6
- package/dist/index.cjs +600 -20
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/transport-WHEBAZUP.mjs +83 -0
- package/dist/{types-CzgQjai9.d.ts → types-BSoh5v1Y.d.cts} +62 -2
- package/dist/{types-BXMGFtnB.d.cts → types-CjT0vClo.d.ts} +62 -2
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createCDPClient
|
|
3
|
-
|
|
2
|
+
createCDPClient,
|
|
3
|
+
stringifyUnknown
|
|
4
|
+
} from "./chunk-KIFB526Y.mjs";
|
|
4
5
|
import {
|
|
5
6
|
createProvider
|
|
6
7
|
} from "./chunk-BRAFQUMG.mjs";
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
TimeoutError,
|
|
12
13
|
ensureActionable,
|
|
13
14
|
generateHints
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XMJABKCF.mjs";
|
|
15
16
|
|
|
16
17
|
// src/audio/encoding.ts
|
|
17
18
|
function bufferToBase64(data) {
|
|
@@ -2327,6 +2328,9 @@ var Page = class {
|
|
|
2327
2328
|
brokenFrame = null;
|
|
2328
2329
|
/** Last matched selector from findElement (for selectorUsed tracking) */
|
|
2329
2330
|
_lastMatchedSelector;
|
|
2331
|
+
_lastActionCoordinates = null;
|
|
2332
|
+
_lastActionBoundingBox = null;
|
|
2333
|
+
_lastActionTargetMetadata = null;
|
|
2330
2334
|
/** Last snapshot for stale ref recovery */
|
|
2331
2335
|
lastSnapshot;
|
|
2332
2336
|
/** Audio input controller (lazy-initialized) */
|
|
@@ -2358,6 +2362,76 @@ var Page = class {
|
|
|
2358
2362
|
getLastMatchedSelector() {
|
|
2359
2363
|
return this._lastMatchedSelector;
|
|
2360
2364
|
}
|
|
2365
|
+
async getActionTargetMetadata(identifiers) {
|
|
2366
|
+
try {
|
|
2367
|
+
const objectId = identifiers.objectId ?? (identifiers.nodeId ? await this.resolveObjectId(identifiers.nodeId) : void 0);
|
|
2368
|
+
if (!objectId) return null;
|
|
2369
|
+
const response = await this.cdp.send("Runtime.callFunctionOn", {
|
|
2370
|
+
objectId,
|
|
2371
|
+
functionDeclaration: `function() {
|
|
2372
|
+
const tagName = this.tagName?.toLowerCase?.() || '';
|
|
2373
|
+
const inputType =
|
|
2374
|
+
tagName === 'input' && typeof this.type === 'string' ? this.type.toLowerCase() : '';
|
|
2375
|
+
const autocomplete =
|
|
2376
|
+
typeof this.autocomplete === 'string' ? this.autocomplete.toLowerCase() : '';
|
|
2377
|
+
return { tagName, inputType, autocomplete };
|
|
2378
|
+
}`,
|
|
2379
|
+
returnByValue: true
|
|
2380
|
+
});
|
|
2381
|
+
return response.result.value ?? null;
|
|
2382
|
+
} catch {
|
|
2383
|
+
return null;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
async getElementPosition(identifiers) {
|
|
2387
|
+
try {
|
|
2388
|
+
const { quads } = await this.cdp.send(
|
|
2389
|
+
"DOM.getContentQuads",
|
|
2390
|
+
identifiers
|
|
2391
|
+
);
|
|
2392
|
+
if (quads?.length > 0) {
|
|
2393
|
+
const q = quads[0];
|
|
2394
|
+
const minX = Math.min(q[0], q[2], q[4], q[6]);
|
|
2395
|
+
const maxX = Math.max(q[0], q[2], q[4], q[6]);
|
|
2396
|
+
const minY = Math.min(q[1], q[3], q[5], q[7]);
|
|
2397
|
+
const maxY = Math.max(q[1], q[3], q[5], q[7]);
|
|
2398
|
+
return {
|
|
2399
|
+
center: { x: (minX + maxX) / 2, y: (minY + maxY) / 2 },
|
|
2400
|
+
bbox: { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2405
|
+
if (identifiers.nodeId) {
|
|
2406
|
+
const box = await this.getBoxModel(identifiers.nodeId);
|
|
2407
|
+
if (box) {
|
|
2408
|
+
return {
|
|
2409
|
+
center: { x: box.content[0] + box.width / 2, y: box.content[1] + box.height / 2 },
|
|
2410
|
+
bbox: { x: box.content[0], y: box.content[1], width: box.width, height: box.height }
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return null;
|
|
2415
|
+
}
|
|
2416
|
+
setLastActionPosition(coords, bbox) {
|
|
2417
|
+
this._lastActionCoordinates = coords;
|
|
2418
|
+
this._lastActionBoundingBox = bbox;
|
|
2419
|
+
}
|
|
2420
|
+
getLastActionCoordinates() {
|
|
2421
|
+
return this._lastActionCoordinates;
|
|
2422
|
+
}
|
|
2423
|
+
getLastActionBoundingBox() {
|
|
2424
|
+
return this._lastActionBoundingBox;
|
|
2425
|
+
}
|
|
2426
|
+
getLastActionTargetMetadata() {
|
|
2427
|
+
return this._lastActionTargetMetadata;
|
|
2428
|
+
}
|
|
2429
|
+
/** Reset position tracking (call before each executor step) */
|
|
2430
|
+
resetLastActionPosition() {
|
|
2431
|
+
this._lastActionCoordinates = null;
|
|
2432
|
+
this._lastActionBoundingBox = null;
|
|
2433
|
+
this._lastActionTargetMetadata = null;
|
|
2434
|
+
}
|
|
2361
2435
|
/**
|
|
2362
2436
|
* Initialize the page (enable required CDP domains)
|
|
2363
2437
|
*/
|
|
@@ -2525,6 +2599,14 @@ var Page = class {
|
|
|
2525
2599
|
const quad = quads[0];
|
|
2526
2600
|
clickX = (quad[0] + quad[2] + quad[4] + quad[6]) / 4;
|
|
2527
2601
|
clickY = (quad[1] + quad[3] + quad[5] + quad[7]) / 4;
|
|
2602
|
+
const minX = Math.min(quad[0], quad[2], quad[4], quad[6]);
|
|
2603
|
+
const maxX = Math.max(quad[0], quad[2], quad[4], quad[6]);
|
|
2604
|
+
const minY = Math.min(quad[1], quad[3], quad[5], quad[7]);
|
|
2605
|
+
const maxY = Math.max(quad[1], quad[3], quad[5], quad[7]);
|
|
2606
|
+
this.setLastActionPosition(
|
|
2607
|
+
{ x: clickX, y: clickY },
|
|
2608
|
+
{ x: minX, y: minY, width: maxX - minX, height: maxY - minY }
|
|
2609
|
+
);
|
|
2528
2610
|
} else {
|
|
2529
2611
|
throw new Error("No quads");
|
|
2530
2612
|
}
|
|
@@ -2533,6 +2615,10 @@ var Page = class {
|
|
|
2533
2615
|
if (!box) throw new Error("Could not get element position");
|
|
2534
2616
|
clickX = box.content[0] + box.width / 2;
|
|
2535
2617
|
clickY = box.content[1] + box.height / 2;
|
|
2618
|
+
this.setLastActionPosition(
|
|
2619
|
+
{ x: clickX, y: clickY },
|
|
2620
|
+
{ x: box.content[0], y: box.content[1], width: box.width, height: box.height }
|
|
2621
|
+
);
|
|
2536
2622
|
}
|
|
2537
2623
|
const hitTargetCoordinates = this.currentFrame ? void 0 : { x: clickX, y: clickY };
|
|
2538
2624
|
const HIT_TARGET_RETRIES = 3;
|
|
@@ -2583,13 +2669,20 @@ var Page = class {
|
|
|
2583
2669
|
if (options.optional) return false;
|
|
2584
2670
|
throw e;
|
|
2585
2671
|
}
|
|
2672
|
+
const fillPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
2673
|
+
if (fillPos) this.setLastActionPosition(fillPos.center, fillPos.bbox);
|
|
2586
2674
|
const tagInfo = await this.cdp.send("Runtime.callFunctionOn", {
|
|
2587
2675
|
objectId,
|
|
2588
2676
|
functionDeclaration: `function() {
|
|
2589
|
-
return {
|
|
2677
|
+
return {
|
|
2678
|
+
tagName: this.tagName?.toLowerCase() || '',
|
|
2679
|
+
inputType: (this.type || '').toLowerCase(),
|
|
2680
|
+
autocomplete: typeof this.autocomplete === 'string' ? this.autocomplete.toLowerCase() : '',
|
|
2681
|
+
};
|
|
2590
2682
|
}`,
|
|
2591
2683
|
returnByValue: true
|
|
2592
2684
|
});
|
|
2685
|
+
this._lastActionTargetMetadata = tagInfo.result.value;
|
|
2593
2686
|
const { tagName, inputType } = tagInfo.result.value;
|
|
2594
2687
|
const specialInputTypes = /* @__PURE__ */ new Set([
|
|
2595
2688
|
"date",
|
|
@@ -2671,6 +2764,9 @@ var Page = class {
|
|
|
2671
2764
|
if (options.optional) return false;
|
|
2672
2765
|
throw e;
|
|
2673
2766
|
}
|
|
2767
|
+
const typePos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
2768
|
+
if (typePos) this.setLastActionPosition(typePos.center, typePos.bbox);
|
|
2769
|
+
this._lastActionTargetMetadata = await this.getActionTargetMetadata({ objectId });
|
|
2674
2770
|
await this.cdp.send("DOM.focus", { nodeId: element.nodeId });
|
|
2675
2771
|
for (const char of text) {
|
|
2676
2772
|
const def = US_KEYBOARD[char];
|
|
@@ -2750,6 +2846,9 @@ var Page = class {
|
|
|
2750
2846
|
if (options.optional) return false;
|
|
2751
2847
|
throw e;
|
|
2752
2848
|
}
|
|
2849
|
+
const selectPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
2850
|
+
if (selectPos) this.setLastActionPosition(selectPos.center, selectPos.bbox);
|
|
2851
|
+
this._lastActionTargetMetadata = await this.getActionTargetMetadata({ objectId });
|
|
2753
2852
|
const metadata = await this.getNativeSelectMetadata(objectId, values);
|
|
2754
2853
|
if (!metadata.isSelect) {
|
|
2755
2854
|
throw new Error("select() target must be a native <select> element");
|
|
@@ -2886,6 +2985,8 @@ var Page = class {
|
|
|
2886
2985
|
if (options.optional) return false;
|
|
2887
2986
|
throw e;
|
|
2888
2987
|
}
|
|
2988
|
+
const checkPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
2989
|
+
if (checkPos) this.setLastActionPosition(checkPos.center, checkPos.bbox);
|
|
2889
2990
|
const before = await this.cdp.send("Runtime.callFunctionOn", {
|
|
2890
2991
|
objectId: object.objectId,
|
|
2891
2992
|
functionDeclaration: "function() { return !!this.checked; }",
|
|
@@ -2934,6 +3035,8 @@ var Page = class {
|
|
|
2934
3035
|
if (options.optional) return false;
|
|
2935
3036
|
throw e;
|
|
2936
3037
|
}
|
|
3038
|
+
const uncheckPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
3039
|
+
if (uncheckPos) this.setLastActionPosition(uncheckPos.center, uncheckPos.bbox);
|
|
2937
3040
|
const isRadio = await this.cdp.send(
|
|
2938
3041
|
"Runtime.callFunctionOn",
|
|
2939
3042
|
{
|
|
@@ -2989,6 +3092,8 @@ var Page = class {
|
|
|
2989
3092
|
throw new ElementNotFoundError(selector, hints);
|
|
2990
3093
|
}
|
|
2991
3094
|
const objectId = await this.resolveObjectId(element.nodeId);
|
|
3095
|
+
const submitPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
3096
|
+
if (submitPos) this.setLastActionPosition(submitPos.center, submitPos.bbox);
|
|
2992
3097
|
const isFormElement = await this.cdp.send(
|
|
2993
3098
|
"Runtime.callFunctionOn",
|
|
2994
3099
|
{
|
|
@@ -3085,6 +3190,8 @@ var Page = class {
|
|
|
3085
3190
|
const hints = await generateHints(this, selectorList, "focus");
|
|
3086
3191
|
throw new ElementNotFoundError(selector, hints);
|
|
3087
3192
|
}
|
|
3193
|
+
const focusPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
3194
|
+
if (focusPos) this.setLastActionPosition(focusPos.center, focusPos.bbox);
|
|
3088
3195
|
await this.cdp.send("DOM.focus", { nodeId: element.nodeId });
|
|
3089
3196
|
return true;
|
|
3090
3197
|
}
|
|
@@ -3120,6 +3227,14 @@ var Page = class {
|
|
|
3120
3227
|
const quad = quads[0];
|
|
3121
3228
|
x = (quad[0] + quad[2] + quad[4] + quad[6]) / 4;
|
|
3122
3229
|
y = (quad[1] + quad[3] + quad[5] + quad[7]) / 4;
|
|
3230
|
+
const minX = Math.min(quad[0], quad[2], quad[4], quad[6]);
|
|
3231
|
+
const maxX = Math.max(quad[0], quad[2], quad[4], quad[6]);
|
|
3232
|
+
const minY = Math.min(quad[1], quad[3], quad[5], quad[7]);
|
|
3233
|
+
const maxY = Math.max(quad[1], quad[3], quad[5], quad[7]);
|
|
3234
|
+
this.setLastActionPosition(
|
|
3235
|
+
{ x, y },
|
|
3236
|
+
{ x: minX, y: minY, width: maxX - minX, height: maxY - minY }
|
|
3237
|
+
);
|
|
3123
3238
|
} else {
|
|
3124
3239
|
throw new Error("No quads");
|
|
3125
3240
|
}
|
|
@@ -3131,6 +3246,10 @@ var Page = class {
|
|
|
3131
3246
|
}
|
|
3132
3247
|
x = box.content[0] + box.width / 2;
|
|
3133
3248
|
y = box.content[1] + box.height / 2;
|
|
3249
|
+
this.setLastActionPosition(
|
|
3250
|
+
{ x, y },
|
|
3251
|
+
{ x: box.content[0], y: box.content[1], width: box.width, height: box.height }
|
|
3252
|
+
);
|
|
3134
3253
|
}
|
|
3135
3254
|
await this.cdp.send("Input.dispatchMouseEvent", {
|
|
3136
3255
|
type: "mouseMoved",
|
|
@@ -3156,6 +3275,8 @@ var Page = class {
|
|
|
3156
3275
|
if (options.optional) return false;
|
|
3157
3276
|
throw new ElementNotFoundError(selector);
|
|
3158
3277
|
}
|
|
3278
|
+
const scrollPos = await this.getElementPosition({ nodeId: element.nodeId });
|
|
3279
|
+
if (scrollPos) this.setLastActionPosition(scrollPos.center, scrollPos.bbox);
|
|
3159
3280
|
await this.scrollIntoView(element.nodeId);
|
|
3160
3281
|
return true;
|
|
3161
3282
|
}
|
|
@@ -3917,7 +4038,7 @@ var Page = class {
|
|
|
3917
4038
|
return {
|
|
3918
4039
|
role,
|
|
3919
4040
|
name,
|
|
3920
|
-
value: value !== void 0 ?
|
|
4041
|
+
value: value !== void 0 ? stringifyUnknown(value) : void 0,
|
|
3921
4042
|
ref,
|
|
3922
4043
|
children: children.length > 0 ? children : void 0,
|
|
3923
4044
|
disabled,
|
|
@@ -3979,7 +4100,7 @@ var Page = class {
|
|
|
3979
4100
|
selector,
|
|
3980
4101
|
disabled,
|
|
3981
4102
|
checked,
|
|
3982
|
-
value: value !== void 0 ?
|
|
4103
|
+
value: value !== void 0 ? stringifyUnknown(value) : void 0
|
|
3983
4104
|
});
|
|
3984
4105
|
}
|
|
3985
4106
|
}
|
|
@@ -4449,7 +4570,7 @@ var Page = class {
|
|
|
4449
4570
|
*/
|
|
4450
4571
|
formatConsoleArgs(args) {
|
|
4451
4572
|
return args.map((arg) => {
|
|
4452
|
-
if (arg.value !== void 0) return
|
|
4573
|
+
if (arg.value !== void 0) return stringifyUnknown(arg.value);
|
|
4453
4574
|
if (arg.description) return arg.description;
|
|
4454
4575
|
return "[object]";
|
|
4455
4576
|
}).join(" ");
|
|
@@ -5238,6 +5359,25 @@ var Browser = class _Browser {
|
|
|
5238
5359
|
this.cdp = cdp;
|
|
5239
5360
|
this.providerSession = providerSession;
|
|
5240
5361
|
}
|
|
5362
|
+
/**
|
|
5363
|
+
* Create a Browser from an existing CDPClient (used by daemon fast-path).
|
|
5364
|
+
* The caller is responsible for the CDP connection lifecycle.
|
|
5365
|
+
*/
|
|
5366
|
+
static fromCDP(cdp, sessionInfo) {
|
|
5367
|
+
const providerSession = {
|
|
5368
|
+
wsUrl: sessionInfo.wsUrl,
|
|
5369
|
+
sessionId: sessionInfo.sessionId,
|
|
5370
|
+
async close() {
|
|
5371
|
+
}
|
|
5372
|
+
};
|
|
5373
|
+
const provider = {
|
|
5374
|
+
name: sessionInfo.provider ?? "daemon",
|
|
5375
|
+
async createSession() {
|
|
5376
|
+
return providerSession;
|
|
5377
|
+
}
|
|
5378
|
+
};
|
|
5379
|
+
return new _Browser(cdp, provider, providerSession, { provider: "generic" });
|
|
5380
|
+
}
|
|
5241
5381
|
/**
|
|
5242
5382
|
* Connect to a browser instance
|
|
5243
5383
|
*/
|
|
@@ -2,6 +2,24 @@ import {
|
|
|
2
2
|
CDPError
|
|
3
3
|
} from "./chunk-JXAUPHZM.mjs";
|
|
4
4
|
|
|
5
|
+
// src/utils/json.ts
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null;
|
|
8
|
+
}
|
|
9
|
+
function stringifyUnknown(value) {
|
|
10
|
+
if (typeof value === "string") return value;
|
|
11
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
12
|
+
return String(value);
|
|
13
|
+
}
|
|
14
|
+
if (value === null) return "null";
|
|
15
|
+
if (value === void 0) return "undefined";
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
} catch {
|
|
19
|
+
return Object.prototype.toString.call(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
// src/cdp/transport.ts
|
|
6
24
|
function createTransport(wsUrl, options = {}) {
|
|
7
25
|
const { timeout = 3e4 } = options;
|
|
@@ -104,9 +122,16 @@ function getReadyStateString(state) {
|
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
// src/cdp/client.ts
|
|
125
|
+
function createCDPClientFromTransport(transport, options = {}) {
|
|
126
|
+
return buildCDPClient(transport, options);
|
|
127
|
+
}
|
|
107
128
|
async function createCDPClient(wsUrl, options = {}) {
|
|
108
|
-
const {
|
|
129
|
+
const { timeout = 3e4 } = options;
|
|
109
130
|
const transport = await createTransport(wsUrl, { timeout });
|
|
131
|
+
return buildCDPClient(transport, options);
|
|
132
|
+
}
|
|
133
|
+
function buildCDPClient(transport, options = {}) {
|
|
134
|
+
const { debug = false, timeout = 3e4 } = options;
|
|
110
135
|
let messageId = 0;
|
|
111
136
|
let currentSessionId;
|
|
112
137
|
let connected = true;
|
|
@@ -116,7 +141,19 @@ async function createCDPClient(wsUrl, options = {}) {
|
|
|
116
141
|
transport.onMessage((raw) => {
|
|
117
142
|
let msg;
|
|
118
143
|
try {
|
|
119
|
-
|
|
144
|
+
const parsed = JSON.parse(raw);
|
|
145
|
+
if (!isRecord(parsed)) {
|
|
146
|
+
if (debug) console.error("[CDP] Ignoring non-object message:", raw);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if ("id" in parsed && typeof parsed["id"] === "number") {
|
|
150
|
+
msg = parsed;
|
|
151
|
+
} else if ("method" in parsed && typeof parsed["method"] === "string") {
|
|
152
|
+
msg = parsed;
|
|
153
|
+
} else {
|
|
154
|
+
if (debug) console.error("[CDP] Ignoring invalid message shape:", raw);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
120
157
|
} catch {
|
|
121
158
|
if (debug) console.error("[CDP] Failed to parse message:", raw);
|
|
122
159
|
return;
|
|
@@ -229,6 +266,9 @@ async function createCDPClient(wsUrl, options = {}) {
|
|
|
229
266
|
onAny(handler) {
|
|
230
267
|
anyEventHandlers.add(handler);
|
|
231
268
|
},
|
|
269
|
+
offAny(handler) {
|
|
270
|
+
anyEventHandlers.delete(handler);
|
|
271
|
+
},
|
|
232
272
|
async close() {
|
|
233
273
|
connected = false;
|
|
234
274
|
await transport.close();
|
|
@@ -252,6 +292,8 @@ async function createCDPClient(wsUrl, options = {}) {
|
|
|
252
292
|
}
|
|
253
293
|
|
|
254
294
|
export {
|
|
295
|
+
stringifyUnknown,
|
|
255
296
|
createTransport,
|
|
297
|
+
createCDPClientFromTransport,
|
|
256
298
|
createCDPClient
|
|
257
299
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/daemon/types.ts
|
|
2
|
+
var DAEMON_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
3
|
+
var DAEMON_CONNECT_TIMEOUT_MS = 500;
|
|
4
|
+
var DAEMON_IDLE_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
5
|
+
var DAEMON_READY_TIMEOUT_MS = 3e3;
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
DAEMON_MAX_AGE_MS,
|
|
9
|
+
DAEMON_CONNECT_TIMEOUT_MS,
|
|
10
|
+
DAEMON_READY_TIMEOUT_MS
|
|
11
|
+
};
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// src/utils/json.ts
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null;
|
|
4
|
+
}
|
|
5
|
+
function stringifyUnknown(value) {
|
|
6
|
+
if (typeof value === "string") return value;
|
|
7
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
8
|
+
return String(value);
|
|
9
|
+
}
|
|
10
|
+
if (value === null) return "null";
|
|
11
|
+
if (value === void 0) return "undefined";
|
|
12
|
+
try {
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
} catch {
|
|
15
|
+
return Object.prototype.toString.call(value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/cdp/protocol.ts
|
|
20
|
+
var CDPError = class extends Error {
|
|
21
|
+
code;
|
|
22
|
+
data;
|
|
23
|
+
constructor(error) {
|
|
24
|
+
super(error.message);
|
|
25
|
+
this.name = "CDPError";
|
|
26
|
+
this.code = error.code;
|
|
27
|
+
this.data = error.data;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/cdp/transport.ts
|
|
32
|
+
function createTransport(wsUrl, options = {}) {
|
|
33
|
+
const { timeout = 3e4 } = options;
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timeoutId = setTimeout(() => {
|
|
36
|
+
reject(new Error(`WebSocket connection timeout after ${timeout}ms`));
|
|
37
|
+
}, timeout);
|
|
38
|
+
const ws = new WebSocket(wsUrl);
|
|
39
|
+
const messageHandlers = [];
|
|
40
|
+
const closeHandlers = [];
|
|
41
|
+
const errorHandlers = [];
|
|
42
|
+
ws.addEventListener("open", () => {
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
const transport = {
|
|
45
|
+
send(message) {
|
|
46
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
47
|
+
ws.send(message);
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Cannot send message, WebSocket is ${getReadyStateString(ws.readyState)}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
async close() {
|
|
55
|
+
return new Promise((resolveClose) => {
|
|
56
|
+
if (ws.readyState === WebSocket.CLOSED) {
|
|
57
|
+
resolveClose();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let settled = false;
|
|
61
|
+
let fallbackTimer;
|
|
62
|
+
const finish = () => {
|
|
63
|
+
if (settled) return;
|
|
64
|
+
settled = true;
|
|
65
|
+
if (fallbackTimer) clearTimeout(fallbackTimer);
|
|
66
|
+
ws.removeEventListener("close", onClose);
|
|
67
|
+
resolveClose();
|
|
68
|
+
};
|
|
69
|
+
const onClose = () => {
|
|
70
|
+
finish();
|
|
71
|
+
};
|
|
72
|
+
ws.addEventListener("close", onClose);
|
|
73
|
+
try {
|
|
74
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
75
|
+
ws.close();
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
finish();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
fallbackTimer = setTimeout(finish, 200);
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
onMessage(handler) {
|
|
85
|
+
messageHandlers.push(handler);
|
|
86
|
+
},
|
|
87
|
+
onClose(handler) {
|
|
88
|
+
closeHandlers.push(handler);
|
|
89
|
+
},
|
|
90
|
+
onError(handler) {
|
|
91
|
+
errorHandlers.push(handler);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
resolve(transport);
|
|
95
|
+
});
|
|
96
|
+
ws.addEventListener("message", (event) => {
|
|
97
|
+
const data = typeof event.data === "string" ? event.data : String(event.data);
|
|
98
|
+
for (const handler of messageHandlers) {
|
|
99
|
+
handler(data);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
ws.addEventListener("close", () => {
|
|
103
|
+
for (const handler of closeHandlers) {
|
|
104
|
+
handler();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
ws.addEventListener("error", (_event) => {
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
const error = new Error("WebSocket connection error");
|
|
110
|
+
for (const handler of errorHandlers) {
|
|
111
|
+
handler(error);
|
|
112
|
+
}
|
|
113
|
+
reject(error);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function getReadyStateString(state) {
|
|
118
|
+
switch (state) {
|
|
119
|
+
case WebSocket.CONNECTING:
|
|
120
|
+
return "CONNECTING";
|
|
121
|
+
case WebSocket.OPEN:
|
|
122
|
+
return "OPEN";
|
|
123
|
+
case WebSocket.CLOSING:
|
|
124
|
+
return "CLOSING";
|
|
125
|
+
case WebSocket.CLOSED:
|
|
126
|
+
return "CLOSED";
|
|
127
|
+
default:
|
|
128
|
+
return "UNKNOWN";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/cdp/client.ts
|
|
133
|
+
function createCDPClientFromTransport(transport, options = {}) {
|
|
134
|
+
return buildCDPClient(transport, options);
|
|
135
|
+
}
|
|
136
|
+
async function createCDPClient(wsUrl, options = {}) {
|
|
137
|
+
const { timeout = 3e4 } = options;
|
|
138
|
+
const transport = await createTransport(wsUrl, { timeout });
|
|
139
|
+
return buildCDPClient(transport, options);
|
|
140
|
+
}
|
|
141
|
+
function buildCDPClient(transport, options = {}) {
|
|
142
|
+
const { debug = false, timeout = 3e4 } = options;
|
|
143
|
+
let messageId = 0;
|
|
144
|
+
let currentSessionId;
|
|
145
|
+
let connected = true;
|
|
146
|
+
const pending = /* @__PURE__ */ new Map();
|
|
147
|
+
const eventHandlers = /* @__PURE__ */ new Map();
|
|
148
|
+
const anyEventHandlers = /* @__PURE__ */ new Set();
|
|
149
|
+
transport.onMessage((raw) => {
|
|
150
|
+
let msg;
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(raw);
|
|
153
|
+
if (!isRecord(parsed)) {
|
|
154
|
+
if (debug) console.error("[CDP] Ignoring non-object message:", raw);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if ("id" in parsed && typeof parsed["id"] === "number") {
|
|
158
|
+
msg = parsed;
|
|
159
|
+
} else if ("method" in parsed && typeof parsed["method"] === "string") {
|
|
160
|
+
msg = parsed;
|
|
161
|
+
} else {
|
|
162
|
+
if (debug) console.error("[CDP] Ignoring invalid message shape:", raw);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
if (debug) console.error("[CDP] Failed to parse message:", raw);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (debug) {
|
|
170
|
+
console.log("[CDP] <--", JSON.stringify(msg, null, 2).slice(0, 500));
|
|
171
|
+
}
|
|
172
|
+
if ("id" in msg && typeof msg.id === "number") {
|
|
173
|
+
const response = msg;
|
|
174
|
+
const request = pending.get(response.id);
|
|
175
|
+
if (request) {
|
|
176
|
+
pending.delete(response.id);
|
|
177
|
+
clearTimeout(request.timer);
|
|
178
|
+
if (response.error) {
|
|
179
|
+
request.reject(new CDPError(response.error));
|
|
180
|
+
} else {
|
|
181
|
+
request.resolve(response.result);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if ("method" in msg) {
|
|
187
|
+
const event = msg;
|
|
188
|
+
const params = event.params ?? {};
|
|
189
|
+
for (const handler of anyEventHandlers) {
|
|
190
|
+
try {
|
|
191
|
+
handler(event.method, params);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
if (debug) console.error("[CDP] Error in any-event handler:", e);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const handlers = eventHandlers.get(event.method);
|
|
197
|
+
if (handlers) {
|
|
198
|
+
for (const handler of handlers) {
|
|
199
|
+
try {
|
|
200
|
+
handler(params);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
if (debug) console.error(`[CDP] Error in handler for ${event.method}:`, e);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
transport.onClose(() => {
|
|
209
|
+
connected = false;
|
|
210
|
+
for (const [id, request] of pending) {
|
|
211
|
+
clearTimeout(request.timer);
|
|
212
|
+
request.reject(new Error("WebSocket connection closed"));
|
|
213
|
+
pending.delete(id);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
transport.onError((error) => {
|
|
217
|
+
if (debug) console.error("[CDP] Transport error:", error);
|
|
218
|
+
});
|
|
219
|
+
const client = {
|
|
220
|
+
async send(method, params, sessionId) {
|
|
221
|
+
if (!connected) {
|
|
222
|
+
throw new Error("CDP client is not connected");
|
|
223
|
+
}
|
|
224
|
+
const id = ++messageId;
|
|
225
|
+
const effectiveSessionId = sessionId === null ? void 0 : sessionId ?? currentSessionId;
|
|
226
|
+
const request = { id, method };
|
|
227
|
+
if (params !== void 0) {
|
|
228
|
+
request.params = params;
|
|
229
|
+
}
|
|
230
|
+
if (effectiveSessionId !== void 0) {
|
|
231
|
+
request.sessionId = effectiveSessionId;
|
|
232
|
+
}
|
|
233
|
+
const message = JSON.stringify(request);
|
|
234
|
+
if (debug) {
|
|
235
|
+
console.log("[CDP] -->", message.slice(0, 500));
|
|
236
|
+
}
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const timer = setTimeout(() => {
|
|
239
|
+
pending.delete(id);
|
|
240
|
+
reject(new Error(`CDP command ${method} timed out after ${timeout}ms`));
|
|
241
|
+
}, timeout);
|
|
242
|
+
pending.set(id, {
|
|
243
|
+
resolve,
|
|
244
|
+
reject,
|
|
245
|
+
method,
|
|
246
|
+
timer
|
|
247
|
+
});
|
|
248
|
+
try {
|
|
249
|
+
transport.send(message);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
pending.delete(id);
|
|
252
|
+
clearTimeout(timer);
|
|
253
|
+
reject(e);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
on(event, handler) {
|
|
258
|
+
let handlers = eventHandlers.get(event);
|
|
259
|
+
if (!handlers) {
|
|
260
|
+
handlers = /* @__PURE__ */ new Set();
|
|
261
|
+
eventHandlers.set(event, handlers);
|
|
262
|
+
}
|
|
263
|
+
handlers.add(handler);
|
|
264
|
+
},
|
|
265
|
+
off(event, handler) {
|
|
266
|
+
const handlers = eventHandlers.get(event);
|
|
267
|
+
if (handlers) {
|
|
268
|
+
handlers.delete(handler);
|
|
269
|
+
if (handlers.size === 0) {
|
|
270
|
+
eventHandlers.delete(event);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
onAny(handler) {
|
|
275
|
+
anyEventHandlers.add(handler);
|
|
276
|
+
},
|
|
277
|
+
offAny(handler) {
|
|
278
|
+
anyEventHandlers.delete(handler);
|
|
279
|
+
},
|
|
280
|
+
async close() {
|
|
281
|
+
connected = false;
|
|
282
|
+
await transport.close();
|
|
283
|
+
},
|
|
284
|
+
async attachToTarget(targetId) {
|
|
285
|
+
const result = await this.send("Target.attachToTarget", {
|
|
286
|
+
targetId,
|
|
287
|
+
flatten: true
|
|
288
|
+
});
|
|
289
|
+
currentSessionId = result.sessionId;
|
|
290
|
+
return result.sessionId;
|
|
291
|
+
},
|
|
292
|
+
get sessionId() {
|
|
293
|
+
return currentSessionId;
|
|
294
|
+
},
|
|
295
|
+
get isConnected() {
|
|
296
|
+
return connected;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
return client;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export {
|
|
303
|
+
CDPError,
|
|
304
|
+
isRecord,
|
|
305
|
+
stringifyUnknown,
|
|
306
|
+
createCDPClientFromTransport,
|
|
307
|
+
createCDPClient
|
|
308
|
+
};
|