@xbrowser/cli 1.0.0 → 1.0.3
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 +17 -26
- package/dist/{browser-DSVV4GHS.js → browser-5CTOA2WS.js} +4 -3
- package/dist/{browser-53KUFEEM.js → browser-ITLZZDHJ.js} +5 -5
- package/dist/{browser-GURRY444.js → browser-IUJXXNBT.js} +6 -3
- package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-4X3DK6PS.js} +339 -59
- package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-D6WMSMWX.js} +4 -3
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-IDVD44ED.js → chunk-6WOSXSCQ.js} +23 -7
- package/dist/{chunk-ZZ2TFWIV.js → chunk-ABXMBNQ6.js} +1 -1
- package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
- package/dist/chunk-AMI64BSD.js +268 -0
- package/dist/{chunk-E4O5ZU3H.js → chunk-DKWR54XQ.js} +412 -98
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/chunk-GDKLH7ZY.js +8 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-2BQZIT3S.js → chunk-LRBSUKUZ.js} +85 -2497
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/{chunk-42RPMJ76.js → chunk-N2JFPWMI.js} +342 -60
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/{chunk-T4J4C2NZ.js → chunk-TNEN6VQ2.js} +17 -4
- package/dist/{chunk-YKOHDEFV.js → chunk-TWWOIJM7.js} +74 -38
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +1558 -1122
- package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-YAVQ343A.js → daemon-client-3JOKX2L2.js} +3 -2
- package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DIEHGP5B.js} +28 -74
- package/dist/daemon-main.js +2296 -1722
- package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
- package/dist/{extract-L2IW3IUB.js → extract-BSYBM4MR.js} +1 -1
- package/dist/{filter-HC4RA7JY.js → filter-KCFO4RSV.js} +1 -1
- package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
- package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
- package/dist/index.d.ts +166 -109
- package/dist/index.js +2668 -1742
- package/dist/launcher-L2JNDB2H.js +20 -0
- package/dist/{launcher-KA7J32K5.js → launcher-OZXJQPNG.js} +1 -1
- package/dist/{network-store-66A2RATI.js → network-store-XGZ25FFC.js} +1 -1
- package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
- package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
- package/dist/{proxy-WKGUCH2C.js → proxy-C6CK3UH5.js} +2 -2
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-MY27H4DX.js +276 -0
- package/dist/site-knowledge-SYC6VCDB.js +23 -0
- package/package.json +5 -4
- package/dist/screenshot-CWAWMXVA.js +0 -28
- package/dist/session-recorder-MA75PKTQ.js +0 -7
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connectToCDP,
|
|
3
|
+
errMsg,
|
|
3
4
|
findChrome,
|
|
4
5
|
getCDPTargets,
|
|
5
6
|
killChrome,
|
|
6
7
|
launchChrome
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-AMI64BSD.js";
|
|
8
9
|
import {
|
|
9
10
|
__require
|
|
10
11
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -247,36 +248,36 @@ function resolveKeyMapping(key) {
|
|
|
247
248
|
return { key, code: key };
|
|
248
249
|
}
|
|
249
250
|
var KEY_MAP = {
|
|
250
|
-
Enter: { key: "Enter", code: "Enter", text: "\r" },
|
|
251
|
-
Tab: { key: "Tab", code: "Tab", text: " " },
|
|
252
|
-
Escape: { key: "Escape", code: "Escape" },
|
|
253
|
-
Backspace: { key: "Backspace", code: "Backspace" },
|
|
254
|
-
Delete: { key: "Delete", code: "Delete" },
|
|
255
|
-
Space: { key: " ", code: "Space", text: " " },
|
|
256
|
-
ArrowUp: { key: "ArrowUp", code: "ArrowUp" },
|
|
257
|
-
ArrowDown: { key: "ArrowDown", code: "ArrowDown" },
|
|
258
|
-
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft" },
|
|
259
|
-
ArrowRight: { key: "ArrowRight", code: "ArrowRight" },
|
|
260
|
-
Home: { key: "Home", code: "Home" },
|
|
261
|
-
End: { key: "End", code: "End" },
|
|
262
|
-
PageUp: { key: "PageUp", code: "PageUp" },
|
|
263
|
-
PageDown: { key: "PageDown", code: "PageDown" },
|
|
264
|
-
Control: { key: "Control", code: "ControlLeft" },
|
|
265
|
-
Shift: { key: "Shift", code: "ShiftLeft" },
|
|
266
|
-
Alt: { key: "Alt", code: "AltLeft" },
|
|
267
|
-
Meta: { key: "Meta", code: "MetaLeft" },
|
|
268
|
-
F1: { key: "F1", code: "F1" },
|
|
269
|
-
F2: { key: "F2", code: "F2" },
|
|
270
|
-
F3: { key: "F3", code: "F3" },
|
|
271
|
-
F4: { key: "F4", code: "F4" },
|
|
272
|
-
F5: { key: "F5", code: "F5" },
|
|
273
|
-
F6: { key: "F6", code: "F6" },
|
|
274
|
-
F7: { key: "F7", code: "F7" },
|
|
275
|
-
F8: { key: "F8", code: "F8" },
|
|
276
|
-
F9: { key: "F9", code: "F9" },
|
|
277
|
-
F10: { key: "F10", code: "F10" },
|
|
278
|
-
F11: { key: "F11", code: "F11" },
|
|
279
|
-
F12: { key: "F12", code: "F12" }
|
|
251
|
+
Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
|
|
252
|
+
Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
|
|
253
|
+
Escape: { key: "Escape", code: "Escape", keyCode: 27 },
|
|
254
|
+
Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
|
|
255
|
+
Delete: { key: "Delete", code: "Delete", keyCode: 46 },
|
|
256
|
+
Space: { key: " ", code: "Space", text: " ", keyCode: 32 },
|
|
257
|
+
ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
|
|
258
|
+
ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
|
|
259
|
+
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
|
|
260
|
+
ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
|
|
261
|
+
Home: { key: "Home", code: "Home", keyCode: 36 },
|
|
262
|
+
End: { key: "End", code: "End", keyCode: 35 },
|
|
263
|
+
PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
|
|
264
|
+
PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
|
|
265
|
+
Control: { key: "Control", code: "ControlLeft", keyCode: 17 },
|
|
266
|
+
Shift: { key: "Shift", code: "ShiftLeft", keyCode: 16 },
|
|
267
|
+
Alt: { key: "Alt", code: "AltLeft", keyCode: 18 },
|
|
268
|
+
Meta: { key: "Meta", code: "MetaLeft", keyCode: 91 },
|
|
269
|
+
F1: { key: "F1", code: "F1", keyCode: 112 },
|
|
270
|
+
F2: { key: "F2", code: "F2", keyCode: 113 },
|
|
271
|
+
F3: { key: "F3", code: "F3", keyCode: 114 },
|
|
272
|
+
F4: { key: "F4", code: "F4", keyCode: 115 },
|
|
273
|
+
F5: { key: "F5", code: "F5", keyCode: 116 },
|
|
274
|
+
F6: { key: "F6", code: "F6", keyCode: 117 },
|
|
275
|
+
F7: { key: "F7", code: "F7", keyCode: 118 },
|
|
276
|
+
F8: { key: "F8", code: "F8", keyCode: 119 },
|
|
277
|
+
F9: { key: "F9", code: "F9", keyCode: 120 },
|
|
278
|
+
F10: { key: "F10", code: "F10", keyCode: 121 },
|
|
279
|
+
F11: { key: "F11", code: "F11", keyCode: 122 },
|
|
280
|
+
F12: { key: "F12", code: "F12", keyCode: 123 }
|
|
280
281
|
};
|
|
281
282
|
function sleep2(ms) {
|
|
282
283
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -971,7 +972,7 @@ function createXBRequest(page, data) {
|
|
|
971
972
|
}
|
|
972
973
|
};
|
|
973
974
|
}
|
|
974
|
-
function createXBRouteFetch(conn, sessionId, params) {
|
|
975
|
+
function createXBRouteFetch(conn, sessionId, params, emitter) {
|
|
975
976
|
const request = createXBRequest(null, {
|
|
976
977
|
requestId: params.requestId,
|
|
977
978
|
url: params.request.url,
|
|
@@ -1009,6 +1010,16 @@ function createXBRouteFetch(conn, sessionId, params) {
|
|
|
1009
1010
|
responseHeaders: Object.entries(headers).map(([k, v]) => ({ name: k, value: v })),
|
|
1010
1011
|
body: bodyBytes.toString("base64")
|
|
1011
1012
|
}, sessionId);
|
|
1013
|
+
if (emitter) {
|
|
1014
|
+
const responseData = {
|
|
1015
|
+
requestId: params.requestId,
|
|
1016
|
+
status: opts.status ?? 200,
|
|
1017
|
+
url: params.request.url,
|
|
1018
|
+
headers
|
|
1019
|
+
};
|
|
1020
|
+
const response = createXBResponse(responseData, conn, sessionId);
|
|
1021
|
+
emitter.emit("response", response);
|
|
1022
|
+
}
|
|
1012
1023
|
}
|
|
1013
1024
|
};
|
|
1014
1025
|
}
|
|
@@ -1190,7 +1201,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1190
1201
|
);
|
|
1191
1202
|
if (result) return result;
|
|
1192
1203
|
} catch (err) {
|
|
1193
|
-
lastError = err;
|
|
1204
|
+
lastError = err instanceof Error ? err : new Error(errMsg(err));
|
|
1194
1205
|
}
|
|
1195
1206
|
const pollMs = typeof polling === "number" ? polling : 16;
|
|
1196
1207
|
await this.waitForTimeout(pollMs);
|
|
@@ -1230,6 +1241,25 @@ Last error: ${lastError.message}` : "";
|
|
|
1230
1241
|
}
|
|
1231
1242
|
return result.result?.value;
|
|
1232
1243
|
}
|
|
1244
|
+
/** evaluateHandle — evaluates fn and returns a handle for element bounding box */
|
|
1245
|
+
async evaluateHandle(fn, ...args) {
|
|
1246
|
+
let expression;
|
|
1247
|
+
if (typeof fn === "string") {
|
|
1248
|
+
expression = fn;
|
|
1249
|
+
} else {
|
|
1250
|
+
const argStr = args.length > 0 ? `...${JSON.stringify(args)}` : "";
|
|
1251
|
+
expression = `(()=>{const __fn=(${fn.toString()});const __el=__fn(${argStr});if(__el&&typeof __el.getBoundingClientRect==='function'){const r=__el.getBoundingClientRect();return JSON.parse(JSON.stringify({x:r.x,y:r.y,w:r.width,h:r.height}));}return null;})()`;
|
|
1252
|
+
}
|
|
1253
|
+
const result = await this.conn.send("Runtime.evaluate", { expression, returnByValue: true }).catch(() => ({ result: { value: null } }));
|
|
1254
|
+
let box = null;
|
|
1255
|
+
try {
|
|
1256
|
+
box = JSON.parse(result.result?.value);
|
|
1257
|
+
} catch {
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
asElement: () => box ? { boundingBox: async () => box } : null
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1233
1263
|
async $eval(selector, fn, ...args) {
|
|
1234
1264
|
const fnBody = typeof fn === "function" ? fn.toString() : fn;
|
|
1235
1265
|
return this.evaluate(
|
|
@@ -1445,6 +1475,26 @@ Last error: ${lastError.message}` : "";
|
|
|
1445
1475
|
off(event, handler) {
|
|
1446
1476
|
this._emitter.off(event, handler);
|
|
1447
1477
|
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Wait for a one-shot event (Playwright-compatible subset).
|
|
1480
|
+
* Used to listen for 'filechooser', 'dialog', 'popup', 'framenavigated', etc.
|
|
1481
|
+
*/
|
|
1482
|
+
async waitForEvent(event, opts = {}) {
|
|
1483
|
+
const timeout = opts.timeout ?? 3e4;
|
|
1484
|
+
return new Promise((resolve, reject) => {
|
|
1485
|
+
const timer = setTimeout(() => {
|
|
1486
|
+
this._emitter.off(event, handler);
|
|
1487
|
+
reject(new Error(`waitForEvent('${event}') timeout after ${timeout}ms`));
|
|
1488
|
+
}, timeout);
|
|
1489
|
+
const handler = (...args) => {
|
|
1490
|
+
if (opts.predicate && !opts.predicate(...args)) return;
|
|
1491
|
+
clearTimeout(timer);
|
|
1492
|
+
this._emitter.off(event, handler);
|
|
1493
|
+
resolve(args.length === 1 ? args[0] : args);
|
|
1494
|
+
};
|
|
1495
|
+
this._emitter.on(event, handler);
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1448
1498
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
1449
1499
|
async close() {
|
|
1450
1500
|
if (this._closed) return;
|
|
@@ -1561,6 +1611,19 @@ Last error: ${lastError.message}` : "";
|
|
|
1561
1611
|
async _cdpSend(method, params) {
|
|
1562
1612
|
return this.conn.send(method, params, this.sessionId);
|
|
1563
1613
|
}
|
|
1614
|
+
/**
|
|
1615
|
+
* 开启/关闭原生文件选择框拦截(CDP stateful 开关)。
|
|
1616
|
+
*
|
|
1617
|
+
* - enabled=true(执行期默认):点击上传按钮时不弹系统文件框,改发 Page.fileChooserOpened 事件,
|
|
1618
|
+
* page 的 'filechooser' 监听器拿到 chooser 后用 setFiles/setInputFiles 注入文件。
|
|
1619
|
+
* - enabled=false(录制期默认):真实文件选择框正常弹出,用户手动选文件;
|
|
1620
|
+
* 前端 input[type=file] 的 change 事件由 action signal 脚本捕获记录。
|
|
1621
|
+
*
|
|
1622
|
+
* 可多次调用切换状态(CDP 协议是 stateful 的)。
|
|
1623
|
+
*/
|
|
1624
|
+
async setFileDialogInterception(enabled) {
|
|
1625
|
+
await this.conn.send("Page.setInterceptFileChooserDialog", { enabled }, this.sessionId).catch((e) => console.error("[XBPage] setInterceptFileChooserDialog failed:", errMsg(e)));
|
|
1626
|
+
}
|
|
1564
1627
|
/** Subscribe to a CDP event on this page's session. Returns unsubscribe function. */
|
|
1565
1628
|
_subscribe(event, handler) {
|
|
1566
1629
|
return this.conn.subscribe(event, this.sessionId, handler);
|
|
@@ -1608,6 +1671,40 @@ Last error: ${lastError.message}` : "";
|
|
|
1608
1671
|
this._emit("dialog", dialog);
|
|
1609
1672
|
})
|
|
1610
1673
|
);
|
|
1674
|
+
this._subscriptions.push(
|
|
1675
|
+
this.conn.subscribe("Page.fileChooserOpened", this.sessionId, async (params) => {
|
|
1676
|
+
const p = params;
|
|
1677
|
+
let selector = "";
|
|
1678
|
+
try {
|
|
1679
|
+
const result = await this.conn.send("DOM.describeNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1680
|
+
const attrs = result.node?.attributes || [];
|
|
1681
|
+
const idIdx = attrs.indexOf("id");
|
|
1682
|
+
if (idIdx >= 0) selector = "#" + attrs[idIdx + 1];
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
if (!selector) {
|
|
1686
|
+
try {
|
|
1687
|
+
const result = await this.conn.send("DOM.resolveNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1688
|
+
const evalResult = await this.conn.send("Runtime.callFunctionOn", {
|
|
1689
|
+
objectId: result.objectId,
|
|
1690
|
+
functionDeclaration: 'function() { return this.id || this.name || "" }',
|
|
1691
|
+
returnByValue: true
|
|
1692
|
+
}, this.sessionId);
|
|
1693
|
+
if (evalResult.result?.value) selector = "#" + evalResult.result.value;
|
|
1694
|
+
} catch {
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
const fileChooser = {
|
|
1698
|
+
selector,
|
|
1699
|
+
isMultiple: p.mode === "selectMultiple",
|
|
1700
|
+
setFiles: async (files) => {
|
|
1701
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
1702
|
+
await this.setInputFiles(selector || 'input[type="file"]', fileArray);
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
this._emit("filechooser", fileChooser);
|
|
1706
|
+
})
|
|
1707
|
+
);
|
|
1611
1708
|
}
|
|
1612
1709
|
setupNetworkEvents() {
|
|
1613
1710
|
this._subscriptions.push(
|
|
@@ -1621,7 +1718,17 @@ Last error: ${lastError.message}` : "";
|
|
|
1621
1718
|
postData: p.request.postData ?? null,
|
|
1622
1719
|
resourceType: p.type
|
|
1623
1720
|
});
|
|
1624
|
-
this._emit("request",
|
|
1721
|
+
this._emit("request", createXBRequest(
|
|
1722
|
+
null,
|
|
1723
|
+
{
|
|
1724
|
+
requestId: p.requestId,
|
|
1725
|
+
url: p.request.url,
|
|
1726
|
+
method: p.request.method,
|
|
1727
|
+
headers: p.request.headers,
|
|
1728
|
+
postData: p.request.postData ?? null,
|
|
1729
|
+
resourceType: p.type
|
|
1730
|
+
}
|
|
1731
|
+
));
|
|
1625
1732
|
this.checkNetworkIdle();
|
|
1626
1733
|
})
|
|
1627
1734
|
);
|
|
@@ -1633,7 +1740,11 @@ Last error: ${lastError.message}` : "";
|
|
|
1633
1740
|
url: p.response.url,
|
|
1634
1741
|
headers: p.response.headers
|
|
1635
1742
|
});
|
|
1636
|
-
this._emit("response",
|
|
1743
|
+
this._emit("response", createXBResponse(
|
|
1744
|
+
{ requestId: p.requestId, status: p.response.status, url: p.response.url, headers: p.response.headers },
|
|
1745
|
+
this.conn,
|
|
1746
|
+
this.sessionId
|
|
1747
|
+
));
|
|
1637
1748
|
})
|
|
1638
1749
|
);
|
|
1639
1750
|
this._subscriptions.push(
|
|
@@ -1719,14 +1830,21 @@ Last error: ${lastError.message}` : "";
|
|
|
1719
1830
|
reject(new Error(`waitForResponse timed out after ${timeout}ms`));
|
|
1720
1831
|
}, timeout);
|
|
1721
1832
|
const handler = (params) => {
|
|
1722
|
-
|
|
1723
|
-
const
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1833
|
+
let response;
|
|
1834
|
+
const respObj = params;
|
|
1835
|
+
if (respObj.response) {
|
|
1836
|
+
const data = {
|
|
1837
|
+
requestId: respObj.requestId || "",
|
|
1838
|
+
status: respObj.response.status || 0,
|
|
1839
|
+
url: respObj.response.url || "",
|
|
1840
|
+
headers: respObj.response.headers || {}
|
|
1841
|
+
};
|
|
1842
|
+
response = createXBResponse(data, this.conn, this.sessionId);
|
|
1843
|
+
} else if (typeof params.status === "function") {
|
|
1844
|
+
response = params;
|
|
1845
|
+
} else {
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1730
1848
|
if (predicate(response)) {
|
|
1731
1849
|
clearTimeout(timer);
|
|
1732
1850
|
this._emitter.removeListener("response", handler);
|
|
@@ -1750,16 +1868,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1750
1868
|
reject(new Error(`waitForRequest timed out after ${timeout}ms`));
|
|
1751
1869
|
}, timeout);
|
|
1752
1870
|
const handler = (params) => {
|
|
1753
|
-
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1871
|
+
let request;
|
|
1872
|
+
const reqObj = params;
|
|
1873
|
+
if (reqObj.request) {
|
|
1874
|
+
request = createXBRequest(this, {
|
|
1875
|
+
requestId: reqObj.requestId || "",
|
|
1876
|
+
url: reqObj.request.url || "",
|
|
1877
|
+
method: reqObj.request.method || "",
|
|
1878
|
+
headers: reqObj.request.headers || {},
|
|
1879
|
+
postData: reqObj.request.postData ?? null,
|
|
1880
|
+
resourceType: reqObj.type || ""
|
|
1881
|
+
});
|
|
1882
|
+
} else if (typeof params.url === "function") {
|
|
1883
|
+
request = params;
|
|
1884
|
+
} else {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1763
1887
|
if (predicate(request)) {
|
|
1764
1888
|
clearTimeout(timer);
|
|
1765
1889
|
this._emitter.removeListener("request", handler);
|
|
@@ -1820,7 +1944,18 @@ Last error: ${lastError.message}` : "";
|
|
|
1820
1944
|
const requestUrl = params.request.url;
|
|
1821
1945
|
for (const { regex, handler } of this._routeHandlers) {
|
|
1822
1946
|
if (regex.test(requestUrl)) {
|
|
1823
|
-
|
|
1947
|
+
this._emit("request", createXBRequest(
|
|
1948
|
+
null,
|
|
1949
|
+
{
|
|
1950
|
+
requestId: params.requestId,
|
|
1951
|
+
url: params.request.url,
|
|
1952
|
+
method: params.request.method,
|
|
1953
|
+
headers: params.request.headers,
|
|
1954
|
+
postData: params.request.postData ?? null,
|
|
1955
|
+
resourceType: params.resourceType
|
|
1956
|
+
}
|
|
1957
|
+
));
|
|
1958
|
+
const route = createXBRouteFetch(this.conn, this.sessionId, params, this._emitter);
|
|
1824
1959
|
try {
|
|
1825
1960
|
await handler(route);
|
|
1826
1961
|
} catch {
|
|
@@ -1987,6 +2122,7 @@ var XBContextImpl = class {
|
|
|
1987
2122
|
const page = new XBPageImpl(this.conn, sessionId, targetId, this, this._browser);
|
|
1988
2123
|
await page._init();
|
|
1989
2124
|
this._pages.push(page);
|
|
2125
|
+
this.forwardPageEvents(page);
|
|
1990
2126
|
if (this.options.viewport) {
|
|
1991
2127
|
await page.setViewportSize(this.options.viewport).catch(() => {
|
|
1992
2128
|
});
|
|
@@ -2028,7 +2164,10 @@ var XBContextImpl = class {
|
|
|
2028
2164
|
}
|
|
2029
2165
|
this._browser._removeContext(this.contextId);
|
|
2030
2166
|
}
|
|
2031
|
-
async newCDPSession(
|
|
2167
|
+
async newCDPSession(page) {
|
|
2168
|
+
if (page instanceof XBPageImpl) {
|
|
2169
|
+
return new XBCDPSessionImpl(this.conn, page.sessionId);
|
|
2170
|
+
}
|
|
2032
2171
|
return new XBCDPSessionImpl(this.conn);
|
|
2033
2172
|
}
|
|
2034
2173
|
async addInitScript(script) {
|
|
@@ -2065,7 +2204,31 @@ var XBContextImpl = class {
|
|
|
2065
2204
|
off(event, handler) {
|
|
2066
2205
|
this._emitter.off(event, handler);
|
|
2067
2206
|
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Register a page that was attached to an existing target (discovered via
|
|
2209
|
+
* Target.getTargets). Used by XBBrowserImpl.discoverContexts() to wire up
|
|
2210
|
+
* pages from the user's existing browser session into the context wrapper
|
|
2211
|
+
* so they appear in `context.pages()` and can be reused by plugins.
|
|
2212
|
+
*/
|
|
2213
|
+
_addDiscoveredPage(page) {
|
|
2214
|
+
const exists = this._pages.some((p) => p._targetId === page._targetId);
|
|
2215
|
+
if (exists) return;
|
|
2216
|
+
this._pages.push(page);
|
|
2217
|
+
this.forwardPageEvents(page);
|
|
2218
|
+
}
|
|
2068
2219
|
// ── Private ─────────────────────────────────────────────────
|
|
2220
|
+
/** Forward page-level events (request, response, etc.) to context listeners */
|
|
2221
|
+
forwardPageEvents(page) {
|
|
2222
|
+
const forward = (event) => {
|
|
2223
|
+
page.on(event, (...args) => {
|
|
2224
|
+
this._emitter.emit(event, ...args);
|
|
2225
|
+
});
|
|
2226
|
+
};
|
|
2227
|
+
forward("request");
|
|
2228
|
+
forward("response");
|
|
2229
|
+
forward("requestfailed");
|
|
2230
|
+
forward("requestfinished");
|
|
2231
|
+
}
|
|
2069
2232
|
setupAutoAttach() {
|
|
2070
2233
|
this.targetAttachedHandler = (paramsRaw) => {
|
|
2071
2234
|
const params = paramsRaw;
|
|
@@ -2090,6 +2253,7 @@ var XBContextImpl = class {
|
|
|
2090
2253
|
});
|
|
2091
2254
|
}
|
|
2092
2255
|
this._pages.push(page);
|
|
2256
|
+
this.forwardPageEvents(page);
|
|
2093
2257
|
this._emitter.emit("page", page);
|
|
2094
2258
|
});
|
|
2095
2259
|
};
|
|
@@ -2106,10 +2270,17 @@ var XBBrowserImpl = class {
|
|
|
2106
2270
|
childProcess = null;
|
|
2107
2271
|
tmpDir;
|
|
2108
2272
|
_exitHandler = null;
|
|
2109
|
-
|
|
2273
|
+
/**
|
|
2274
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
2275
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
2276
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
2277
|
+
*/
|
|
2278
|
+
cdpEndpoint;
|
|
2279
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
2110
2280
|
this.conn = conn;
|
|
2111
2281
|
this.childProcess = childProcess ?? null;
|
|
2112
2282
|
this.tmpDir = tmpDir;
|
|
2283
|
+
this.cdpEndpoint = cdpEndpoint;
|
|
2113
2284
|
conn.on("disconnect", () => {
|
|
2114
2285
|
this._disconnected = true;
|
|
2115
2286
|
this._emitter.emit("disconnected");
|
|
@@ -2153,7 +2324,7 @@ var XBBrowserImpl = class {
|
|
|
2153
2324
|
this._exitHandler = null;
|
|
2154
2325
|
}
|
|
2155
2326
|
if (this.childProcess) {
|
|
2156
|
-
const { killChrome: killChrome2 } = await import("./launcher-
|
|
2327
|
+
const { killChrome: killChrome2 } = await import("./launcher-OZXJQPNG.js");
|
|
2157
2328
|
await killChrome2(this.childProcess, this.tmpDir);
|
|
2158
2329
|
}
|
|
2159
2330
|
await this.conn.close();
|
|
@@ -2183,6 +2354,10 @@ var XBBrowserImpl = class {
|
|
|
2183
2354
|
contextId,
|
|
2184
2355
|
context
|
|
2185
2356
|
});
|
|
2357
|
+
if (this.childProcess) {
|
|
2358
|
+
this._enableAutoAttach().catch(() => {
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2186
2361
|
return context;
|
|
2187
2362
|
}
|
|
2188
2363
|
contexts() {
|
|
@@ -2211,6 +2386,23 @@ var XBBrowserImpl = class {
|
|
|
2211
2386
|
async _detachFromTarget(sessionId) {
|
|
2212
2387
|
await this.conn.send("Target.detachFromTarget", { sessionId });
|
|
2213
2388
|
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Derive the HTTP /json base URL from the original cdpEndpoint for use
|
|
2391
|
+
* as a fallback when Target.getTargets doesn't return page targets.
|
|
2392
|
+
* Supports both http:// and ws:// input formats.
|
|
2393
|
+
*/
|
|
2394
|
+
_httpFallbackURL() {
|
|
2395
|
+
if (!this.cdpEndpoint) return void 0;
|
|
2396
|
+
if (this.cdpEndpoint.startsWith("http://") || this.cdpEndpoint.startsWith("https://")) {
|
|
2397
|
+
return this.cdpEndpoint;
|
|
2398
|
+
}
|
|
2399
|
+
if (this.cdpEndpoint.startsWith("ws://") || this.cdpEndpoint.startsWith("wss://")) {
|
|
2400
|
+
const url = this.cdpEndpoint.replace(/^ws/, "http");
|
|
2401
|
+
const slashIdx = url.indexOf("/", url.indexOf("//") + 2);
|
|
2402
|
+
return slashIdx >= 0 ? url.substring(0, slashIdx) : url;
|
|
2403
|
+
}
|
|
2404
|
+
return void 0;
|
|
2405
|
+
}
|
|
2214
2406
|
/** Create a new page target within a browser context */
|
|
2215
2407
|
async _createTarget(contextId, url = "about:blank") {
|
|
2216
2408
|
const params = { url };
|
|
@@ -2227,10 +2419,97 @@ var XBBrowserImpl = class {
|
|
|
2227
2419
|
async _enableAutoAttach() {
|
|
2228
2420
|
await this.conn.send("Target.setAutoAttach", {
|
|
2229
2421
|
autoAttach: true,
|
|
2230
|
-
waitForDebuggerOnStart:
|
|
2422
|
+
waitForDebuggerOnStart: false,
|
|
2231
2423
|
flatten: true
|
|
2232
2424
|
});
|
|
2233
2425
|
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Discover existing browser contexts and pages via Target.getTargets.
|
|
2428
|
+
*
|
|
2429
|
+
* For CDP tunnel connections (cdp-tunnel, attach scenarios), the
|
|
2430
|
+
* Target.attachedToTarget auto-attach flow is unreliable. Without this
|
|
2431
|
+
* call, `b.contexts()` would return [] and callers would fall back to
|
|
2432
|
+
* `b.newContext()` — which creates an isolated context with NO cookies
|
|
2433
|
+
* shared with the user's existing browser session (causing login failures).
|
|
2434
|
+
*
|
|
2435
|
+
* This method:
|
|
2436
|
+
* 1. Queries Target.getTargets to enumerate all page targets
|
|
2437
|
+
* 2. Groups them by browserContextId
|
|
2438
|
+
* 3. Attaches to each existing page via Target.attachToTarget
|
|
2439
|
+
* 4. Wraps the discovered pages in a XBContextImpl and registers it in
|
|
2440
|
+
* this._contexts so `contexts()` returns the user's actual contexts
|
|
2441
|
+
* 5. Enables Target.setAutoAttach for future pages
|
|
2442
|
+
*
|
|
2443
|
+
* No-op for self-launched browsers (they already populated contexts via
|
|
2444
|
+
* newContext() + childProcess-gated auto-attach).
|
|
2445
|
+
*/
|
|
2446
|
+
async discoverContexts() {
|
|
2447
|
+
if (this._disconnected) return;
|
|
2448
|
+
let targetInfos = [];
|
|
2449
|
+
try {
|
|
2450
|
+
const result = await this.conn.send(
|
|
2451
|
+
"Target.getTargets"
|
|
2452
|
+
);
|
|
2453
|
+
targetInfos = result.targetInfos ?? [];
|
|
2454
|
+
} catch {
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
const pageTargets = targetInfos.filter((t) => t.type === "page");
|
|
2458
|
+
const httpFallbackUrl = this._httpFallbackURL();
|
|
2459
|
+
if (pageTargets.length === 0 && httpFallbackUrl) {
|
|
2460
|
+
console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
|
|
2461
|
+
try {
|
|
2462
|
+
const { getCDPTargets: getCDPTargets2 } = await import("./launcher-OZXJQPNG.js");
|
|
2463
|
+
const httpPages = await getCDPTargets2(httpFallbackUrl);
|
|
2464
|
+
console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
|
|
2465
|
+
for (const p of httpPages) {
|
|
2466
|
+
if (p.type !== "page") continue;
|
|
2467
|
+
if (!p.url || p.url.startsWith("chrome://") || p.url.startsWith("devtools://")) continue;
|
|
2468
|
+
targetInfos.push({
|
|
2469
|
+
targetId: p.id,
|
|
2470
|
+
type: "page",
|
|
2471
|
+
url: p.url,
|
|
2472
|
+
title: p.title
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
console.log(`[discoverContexts] After HTTP fallback: ${targetInfos.length} total targets, ${targetInfos.filter((t) => t.type === "page").length} pages`);
|
|
2476
|
+
} catch (err) {
|
|
2477
|
+
console.log(`[discoverContexts] HTTP fallback failed: ${errMsg(err)}`);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
const pagesByContext = /* @__PURE__ */ new Map();
|
|
2481
|
+
for (const t of targetInfos) {
|
|
2482
|
+
if (t.type !== "page") continue;
|
|
2483
|
+
if (!t.url || t.url.startsWith("chrome://") || t.url.startsWith("devtools://")) {
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
const ctxId = t.browserContextId || "default";
|
|
2487
|
+
if (!pagesByContext.has(ctxId)) pagesByContext.set(ctxId, []);
|
|
2488
|
+
pagesByContext.get(ctxId).push(t);
|
|
2489
|
+
}
|
|
2490
|
+
for (const [ctxId, pages] of pagesByContext) {
|
|
2491
|
+
if (this._contexts.has(ctxId)) continue;
|
|
2492
|
+
const context = new XBContextImpl(this.conn, ctxId, this, {});
|
|
2493
|
+
for (const p of pages) {
|
|
2494
|
+
try {
|
|
2495
|
+
const sessionId = await this._attachToTarget(p.targetId);
|
|
2496
|
+
const page = new XBPageImpl(this.conn, sessionId, p.targetId, context, this);
|
|
2497
|
+
await page._init();
|
|
2498
|
+
context._addDiscoveredPage(page);
|
|
2499
|
+
} catch {
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
this._contexts.set(ctxId, { contextId: ctxId, context });
|
|
2503
|
+
}
|
|
2504
|
+
try {
|
|
2505
|
+
await this.conn.send("Target.setAutoAttach", {
|
|
2506
|
+
autoAttach: true,
|
|
2507
|
+
waitForDebuggerOnStart: false,
|
|
2508
|
+
flatten: true
|
|
2509
|
+
});
|
|
2510
|
+
} catch {
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2234
2513
|
};
|
|
2235
2514
|
|
|
2236
2515
|
// src/cdp-driver/connection.ts
|
|
@@ -2510,7 +2789,8 @@ async function launch(options = {}) {
|
|
|
2510
2789
|
}
|
|
2511
2790
|
const conn = new CDPConnection(wsEndpoint);
|
|
2512
2791
|
await conn.ready();
|
|
2513
|
-
const
|
|
2792
|
+
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
2793
|
+
const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
|
|
2514
2794
|
return { browser, wsEndpoint };
|
|
2515
2795
|
}
|
|
2516
2796
|
export {
|
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
scrollIntoView,
|
|
15
15
|
waitForActionable,
|
|
16
16
|
waitForNetworkIdle
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-N2JFPWMI.js";
|
|
18
18
|
import {
|
|
19
19
|
connectToCDP,
|
|
20
20
|
findChrome,
|
|
21
21
|
getCDPTargets,
|
|
22
22
|
killChrome,
|
|
23
23
|
launchChrome
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-TNEN6VQ2.js";
|
|
25
|
+
import "./chunk-GDKLH7ZY.js";
|
|
26
|
+
import "./chunk-KFQGP6VL.js";
|
|
26
27
|
export {
|
|
27
28
|
CDPConnection,
|
|
28
29
|
CDPProtocolError,
|