@xbrowser/cli 1.0.0 → 1.0.2
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-GURRY444.js → browser-GITRHHFO.js} +4 -3
- package/dist/{browser-DSVV4GHS.js → browser-R56O3CW6.js} +3 -3
- package/dist/{browser-53KUFEEM.js → browser-ZJOZB5CR.js} +4 -4
- package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-BE3FOMRN.js} +324 -58
- package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-TOPYJIFL.js} +3 -3
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
- package/dist/chunk-BBMRDUYQ.js +260 -0
- package/dist/{chunk-E4O5ZU3H.js → chunk-CAFNSGYM.js} +393 -95
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/{chunk-YKOHDEFV.js → chunk-JPA2ZT2R.js} +69 -36
- package/dist/{chunk-T4J4C2NZ.js → chunk-JPHCY4TC.js} +12 -2
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/{chunk-42RPMJ76.js → chunk-PPG4D2EW.js} +325 -59
- package/dist/{chunk-IDVD44ED.js → chunk-Q4IGYTKR.js} +19 -7
- package/dist/{chunk-2BQZIT3S.js → chunk-QIK2I3VQ.js} +86 -2501
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +1435 -1077
- package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DRCUMNHK.js} +25 -74
- package/dist/{daemon-client-YAVQ343A.js → daemon-client-UZZEHHIV.js} +2 -2
- package/dist/daemon-main.js +2200 -1691
- 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 +165 -108
- package/dist/index.js +2531 -1680
- package/dist/launcher-QUJ4M2VS.js +19 -0
- package/dist/{launcher-KA7J32K5.js → launcher-YARP45UY.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-LV4BJ5RC.js} +1 -1
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-GLTUICSD.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,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connectToCDP,
|
|
3
3
|
launchChrome
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BBMRDUYQ.js";
|
|
5
5
|
import {
|
|
6
6
|
__require
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KFQGP6VL.js";
|
|
8
8
|
|
|
9
9
|
// src/cdp-driver/browser.ts
|
|
10
10
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
@@ -244,36 +244,36 @@ function resolveKeyMapping(key) {
|
|
|
244
244
|
return { key, code: key };
|
|
245
245
|
}
|
|
246
246
|
var KEY_MAP = {
|
|
247
|
-
Enter: { key: "Enter", code: "Enter", text: "\r" },
|
|
248
|
-
Tab: { key: "Tab", code: "Tab", text: " " },
|
|
249
|
-
Escape: { key: "Escape", code: "Escape" },
|
|
250
|
-
Backspace: { key: "Backspace", code: "Backspace" },
|
|
251
|
-
Delete: { key: "Delete", code: "Delete" },
|
|
252
|
-
Space: { key: " ", code: "Space", text: " " },
|
|
253
|
-
ArrowUp: { key: "ArrowUp", code: "ArrowUp" },
|
|
254
|
-
ArrowDown: { key: "ArrowDown", code: "ArrowDown" },
|
|
255
|
-
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft" },
|
|
256
|
-
ArrowRight: { key: "ArrowRight", code: "ArrowRight" },
|
|
257
|
-
Home: { key: "Home", code: "Home" },
|
|
258
|
-
End: { key: "End", code: "End" },
|
|
259
|
-
PageUp: { key: "PageUp", code: "PageUp" },
|
|
260
|
-
PageDown: { key: "PageDown", code: "PageDown" },
|
|
261
|
-
Control: { key: "Control", code: "ControlLeft" },
|
|
262
|
-
Shift: { key: "Shift", code: "ShiftLeft" },
|
|
263
|
-
Alt: { key: "Alt", code: "AltLeft" },
|
|
264
|
-
Meta: { key: "Meta", code: "MetaLeft" },
|
|
265
|
-
F1: { key: "F1", code: "F1" },
|
|
266
|
-
F2: { key: "F2", code: "F2" },
|
|
267
|
-
F3: { key: "F3", code: "F3" },
|
|
268
|
-
F4: { key: "F4", code: "F4" },
|
|
269
|
-
F5: { key: "F5", code: "F5" },
|
|
270
|
-
F6: { key: "F6", code: "F6" },
|
|
271
|
-
F7: { key: "F7", code: "F7" },
|
|
272
|
-
F8: { key: "F8", code: "F8" },
|
|
273
|
-
F9: { key: "F9", code: "F9" },
|
|
274
|
-
F10: { key: "F10", code: "F10" },
|
|
275
|
-
F11: { key: "F11", code: "F11" },
|
|
276
|
-
F12: { key: "F12", code: "F12" }
|
|
247
|
+
Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
|
|
248
|
+
Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
|
|
249
|
+
Escape: { key: "Escape", code: "Escape", keyCode: 27 },
|
|
250
|
+
Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
|
|
251
|
+
Delete: { key: "Delete", code: "Delete", keyCode: 46 },
|
|
252
|
+
Space: { key: " ", code: "Space", text: " ", keyCode: 32 },
|
|
253
|
+
ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
|
|
254
|
+
ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
|
|
255
|
+
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
|
|
256
|
+
ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
|
|
257
|
+
Home: { key: "Home", code: "Home", keyCode: 36 },
|
|
258
|
+
End: { key: "End", code: "End", keyCode: 35 },
|
|
259
|
+
PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
|
|
260
|
+
PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
|
|
261
|
+
Control: { key: "Control", code: "ControlLeft", keyCode: 17 },
|
|
262
|
+
Shift: { key: "Shift", code: "ShiftLeft", keyCode: 16 },
|
|
263
|
+
Alt: { key: "Alt", code: "AltLeft", keyCode: 18 },
|
|
264
|
+
Meta: { key: "Meta", code: "MetaLeft", keyCode: 91 },
|
|
265
|
+
F1: { key: "F1", code: "F1", keyCode: 112 },
|
|
266
|
+
F2: { key: "F2", code: "F2", keyCode: 113 },
|
|
267
|
+
F3: { key: "F3", code: "F3", keyCode: 114 },
|
|
268
|
+
F4: { key: "F4", code: "F4", keyCode: 115 },
|
|
269
|
+
F5: { key: "F5", code: "F5", keyCode: 116 },
|
|
270
|
+
F6: { key: "F6", code: "F6", keyCode: 117 },
|
|
271
|
+
F7: { key: "F7", code: "F7", keyCode: 118 },
|
|
272
|
+
F8: { key: "F8", code: "F8", keyCode: 119 },
|
|
273
|
+
F9: { key: "F9", code: "F9", keyCode: 120 },
|
|
274
|
+
F10: { key: "F10", code: "F10", keyCode: 121 },
|
|
275
|
+
F11: { key: "F11", code: "F11", keyCode: 122 },
|
|
276
|
+
F12: { key: "F12", code: "F12", keyCode: 123 }
|
|
277
277
|
};
|
|
278
278
|
function sleep2(ms) {
|
|
279
279
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -968,7 +968,7 @@ function createXBRequest(page, data) {
|
|
|
968
968
|
}
|
|
969
969
|
};
|
|
970
970
|
}
|
|
971
|
-
function createXBRouteFetch(conn, sessionId, params) {
|
|
971
|
+
function createXBRouteFetch(conn, sessionId, params, emitter) {
|
|
972
972
|
const request = createXBRequest(null, {
|
|
973
973
|
requestId: params.requestId,
|
|
974
974
|
url: params.request.url,
|
|
@@ -1006,6 +1006,16 @@ function createXBRouteFetch(conn, sessionId, params) {
|
|
|
1006
1006
|
responseHeaders: Object.entries(headers).map(([k, v]) => ({ name: k, value: v })),
|
|
1007
1007
|
body: bodyBytes.toString("base64")
|
|
1008
1008
|
}, sessionId);
|
|
1009
|
+
if (emitter) {
|
|
1010
|
+
const responseData = {
|
|
1011
|
+
requestId: params.requestId,
|
|
1012
|
+
status: opts.status ?? 200,
|
|
1013
|
+
url: params.request.url,
|
|
1014
|
+
headers
|
|
1015
|
+
};
|
|
1016
|
+
const response = createXBResponse(responseData, conn, sessionId);
|
|
1017
|
+
emitter.emit("response", response);
|
|
1018
|
+
}
|
|
1009
1019
|
}
|
|
1010
1020
|
};
|
|
1011
1021
|
}
|
|
@@ -1227,6 +1237,25 @@ Last error: ${lastError.message}` : "";
|
|
|
1227
1237
|
}
|
|
1228
1238
|
return result.result?.value;
|
|
1229
1239
|
}
|
|
1240
|
+
/** evaluateHandle — evaluates fn and returns a handle for element bounding box */
|
|
1241
|
+
async evaluateHandle(fn, ...args) {
|
|
1242
|
+
let expression;
|
|
1243
|
+
if (typeof fn === "string") {
|
|
1244
|
+
expression = fn;
|
|
1245
|
+
} else {
|
|
1246
|
+
const argStr = args.length > 0 ? `...${JSON.stringify(args)}` : "";
|
|
1247
|
+
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;})()`;
|
|
1248
|
+
}
|
|
1249
|
+
const result = await this.conn.send("Runtime.evaluate", { expression, returnByValue: true }).catch(() => ({ result: { value: null } }));
|
|
1250
|
+
let box = null;
|
|
1251
|
+
try {
|
|
1252
|
+
box = JSON.parse(result.result?.value);
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
asElement: () => box ? { boundingBox: async () => box } : null
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1230
1259
|
async $eval(selector, fn, ...args) {
|
|
1231
1260
|
const fnBody = typeof fn === "function" ? fn.toString() : fn;
|
|
1232
1261
|
return this.evaluate(
|
|
@@ -1442,6 +1471,26 @@ Last error: ${lastError.message}` : "";
|
|
|
1442
1471
|
off(event, handler) {
|
|
1443
1472
|
this._emitter.off(event, handler);
|
|
1444
1473
|
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Wait for a one-shot event (Playwright-compatible subset).
|
|
1476
|
+
* Used to listen for 'filechooser', 'dialog', 'popup', 'framenavigated', etc.
|
|
1477
|
+
*/
|
|
1478
|
+
async waitForEvent(event, opts = {}) {
|
|
1479
|
+
const timeout = opts.timeout ?? 3e4;
|
|
1480
|
+
return new Promise((resolve, reject) => {
|
|
1481
|
+
const timer = setTimeout(() => {
|
|
1482
|
+
this._emitter.off(event, handler);
|
|
1483
|
+
reject(new Error(`waitForEvent('${event}') timeout after ${timeout}ms`));
|
|
1484
|
+
}, timeout);
|
|
1485
|
+
const handler = (...args) => {
|
|
1486
|
+
if (opts.predicate && !opts.predicate(...args)) return;
|
|
1487
|
+
clearTimeout(timer);
|
|
1488
|
+
this._emitter.off(event, handler);
|
|
1489
|
+
resolve(args.length === 1 ? args[0] : args);
|
|
1490
|
+
};
|
|
1491
|
+
this._emitter.on(event, handler);
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1445
1494
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
1446
1495
|
async close() {
|
|
1447
1496
|
if (this._closed) return;
|
|
@@ -1605,6 +1654,40 @@ Last error: ${lastError.message}` : "";
|
|
|
1605
1654
|
this._emit("dialog", dialog);
|
|
1606
1655
|
})
|
|
1607
1656
|
);
|
|
1657
|
+
this._subscriptions.push(
|
|
1658
|
+
this.conn.subscribe("Page.fileChooserOpened", this.sessionId, async (params) => {
|
|
1659
|
+
const p = params;
|
|
1660
|
+
let selector = "";
|
|
1661
|
+
try {
|
|
1662
|
+
const result = await this.conn.send("DOM.describeNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1663
|
+
const attrs = result.node?.attributes || [];
|
|
1664
|
+
const idIdx = attrs.indexOf("id");
|
|
1665
|
+
if (idIdx >= 0) selector = "#" + attrs[idIdx + 1];
|
|
1666
|
+
} catch {
|
|
1667
|
+
}
|
|
1668
|
+
if (!selector) {
|
|
1669
|
+
try {
|
|
1670
|
+
const result = await this.conn.send("DOM.resolveNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1671
|
+
const evalResult = await this.conn.send("Runtime.callFunctionOn", {
|
|
1672
|
+
objectId: result.objectId,
|
|
1673
|
+
functionDeclaration: 'function() { return this.id || this.name || "" }',
|
|
1674
|
+
returnByValue: true
|
|
1675
|
+
}, this.sessionId);
|
|
1676
|
+
if (evalResult.result?.value) selector = "#" + evalResult.result.value;
|
|
1677
|
+
} catch {
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
const fileChooser = {
|
|
1681
|
+
selector,
|
|
1682
|
+
isMultiple: p.mode === "selectMultiple",
|
|
1683
|
+
setFiles: async (files) => {
|
|
1684
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
1685
|
+
await this.setInputFiles(selector || 'input[type="file"]', fileArray);
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
this._emit("filechooser", fileChooser);
|
|
1689
|
+
})
|
|
1690
|
+
);
|
|
1608
1691
|
}
|
|
1609
1692
|
setupNetworkEvents() {
|
|
1610
1693
|
this._subscriptions.push(
|
|
@@ -1618,7 +1701,17 @@ Last error: ${lastError.message}` : "";
|
|
|
1618
1701
|
postData: p.request.postData ?? null,
|
|
1619
1702
|
resourceType: p.type
|
|
1620
1703
|
});
|
|
1621
|
-
this._emit("request",
|
|
1704
|
+
this._emit("request", createXBRequest(
|
|
1705
|
+
null,
|
|
1706
|
+
{
|
|
1707
|
+
requestId: p.requestId,
|
|
1708
|
+
url: p.request.url,
|
|
1709
|
+
method: p.request.method,
|
|
1710
|
+
headers: p.request.headers,
|
|
1711
|
+
postData: p.request.postData ?? null,
|
|
1712
|
+
resourceType: p.type
|
|
1713
|
+
}
|
|
1714
|
+
));
|
|
1622
1715
|
this.checkNetworkIdle();
|
|
1623
1716
|
})
|
|
1624
1717
|
);
|
|
@@ -1630,7 +1723,11 @@ Last error: ${lastError.message}` : "";
|
|
|
1630
1723
|
url: p.response.url,
|
|
1631
1724
|
headers: p.response.headers
|
|
1632
1725
|
});
|
|
1633
|
-
this._emit("response",
|
|
1726
|
+
this._emit("response", createXBResponse(
|
|
1727
|
+
{ requestId: p.requestId, status: p.response.status, url: p.response.url, headers: p.response.headers },
|
|
1728
|
+
this.conn,
|
|
1729
|
+
this.sessionId
|
|
1730
|
+
));
|
|
1634
1731
|
})
|
|
1635
1732
|
);
|
|
1636
1733
|
this._subscriptions.push(
|
|
@@ -1716,14 +1813,21 @@ Last error: ${lastError.message}` : "";
|
|
|
1716
1813
|
reject(new Error(`waitForResponse timed out after ${timeout}ms`));
|
|
1717
1814
|
}, timeout);
|
|
1718
1815
|
const handler = (params) => {
|
|
1719
|
-
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1816
|
+
let response;
|
|
1817
|
+
const respObj = params;
|
|
1818
|
+
if (respObj.response) {
|
|
1819
|
+
const data = {
|
|
1820
|
+
requestId: respObj.requestId || "",
|
|
1821
|
+
status: respObj.response.status || 0,
|
|
1822
|
+
url: respObj.response.url || "",
|
|
1823
|
+
headers: respObj.response.headers || {}
|
|
1824
|
+
};
|
|
1825
|
+
response = createXBResponse(data, this.conn, this.sessionId);
|
|
1826
|
+
} else if (typeof params.status === "function") {
|
|
1827
|
+
response = params;
|
|
1828
|
+
} else {
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1727
1831
|
if (predicate(response)) {
|
|
1728
1832
|
clearTimeout(timer);
|
|
1729
1833
|
this._emitter.removeListener("response", handler);
|
|
@@ -1747,16 +1851,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1747
1851
|
reject(new Error(`waitForRequest timed out after ${timeout}ms`));
|
|
1748
1852
|
}, timeout);
|
|
1749
1853
|
const handler = (params) => {
|
|
1750
|
-
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1854
|
+
let request;
|
|
1855
|
+
const reqObj = params;
|
|
1856
|
+
if (reqObj.request) {
|
|
1857
|
+
request = createXBRequest(this, {
|
|
1858
|
+
requestId: reqObj.requestId || "",
|
|
1859
|
+
url: reqObj.request.url || "",
|
|
1860
|
+
method: reqObj.request.method || "",
|
|
1861
|
+
headers: reqObj.request.headers || {},
|
|
1862
|
+
postData: reqObj.request.postData ?? null,
|
|
1863
|
+
resourceType: reqObj.type || ""
|
|
1864
|
+
});
|
|
1865
|
+
} else if (typeof params.url === "function") {
|
|
1866
|
+
request = params;
|
|
1867
|
+
} else {
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1760
1870
|
if (predicate(request)) {
|
|
1761
1871
|
clearTimeout(timer);
|
|
1762
1872
|
this._emitter.removeListener("request", handler);
|
|
@@ -1817,7 +1927,18 @@ Last error: ${lastError.message}` : "";
|
|
|
1817
1927
|
const requestUrl = params.request.url;
|
|
1818
1928
|
for (const { regex, handler } of this._routeHandlers) {
|
|
1819
1929
|
if (regex.test(requestUrl)) {
|
|
1820
|
-
|
|
1930
|
+
this._emit("request", createXBRequest(
|
|
1931
|
+
null,
|
|
1932
|
+
{
|
|
1933
|
+
requestId: params.requestId,
|
|
1934
|
+
url: params.request.url,
|
|
1935
|
+
method: params.request.method,
|
|
1936
|
+
headers: params.request.headers,
|
|
1937
|
+
postData: params.request.postData ?? null,
|
|
1938
|
+
resourceType: params.resourceType
|
|
1939
|
+
}
|
|
1940
|
+
));
|
|
1941
|
+
const route = createXBRouteFetch(this.conn, this.sessionId, params, this._emitter);
|
|
1821
1942
|
try {
|
|
1822
1943
|
await handler(route);
|
|
1823
1944
|
} catch {
|
|
@@ -1984,6 +2105,7 @@ var XBContextImpl = class {
|
|
|
1984
2105
|
const page = new XBPageImpl(this.conn, sessionId, targetId, this, this._browser);
|
|
1985
2106
|
await page._init();
|
|
1986
2107
|
this._pages.push(page);
|
|
2108
|
+
this.forwardPageEvents(page);
|
|
1987
2109
|
if (this.options.viewport) {
|
|
1988
2110
|
await page.setViewportSize(this.options.viewport).catch(() => {
|
|
1989
2111
|
});
|
|
@@ -2025,7 +2147,10 @@ var XBContextImpl = class {
|
|
|
2025
2147
|
}
|
|
2026
2148
|
this._browser._removeContext(this.contextId);
|
|
2027
2149
|
}
|
|
2028
|
-
async newCDPSession(
|
|
2150
|
+
async newCDPSession(page) {
|
|
2151
|
+
if (page instanceof XBPageImpl) {
|
|
2152
|
+
return new XBCDPSessionImpl(this.conn, page.sessionId);
|
|
2153
|
+
}
|
|
2029
2154
|
return new XBCDPSessionImpl(this.conn);
|
|
2030
2155
|
}
|
|
2031
2156
|
async addInitScript(script) {
|
|
@@ -2062,7 +2187,31 @@ var XBContextImpl = class {
|
|
|
2062
2187
|
off(event, handler) {
|
|
2063
2188
|
this._emitter.off(event, handler);
|
|
2064
2189
|
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Register a page that was attached to an existing target (discovered via
|
|
2192
|
+
* Target.getTargets). Used by XBBrowserImpl.discoverContexts() to wire up
|
|
2193
|
+
* pages from the user's existing browser session into the context wrapper
|
|
2194
|
+
* so they appear in `context.pages()` and can be reused by plugins.
|
|
2195
|
+
*/
|
|
2196
|
+
_addDiscoveredPage(page) {
|
|
2197
|
+
const exists = this._pages.some((p) => p._targetId === page._targetId);
|
|
2198
|
+
if (exists) return;
|
|
2199
|
+
this._pages.push(page);
|
|
2200
|
+
this.forwardPageEvents(page);
|
|
2201
|
+
}
|
|
2065
2202
|
// ── Private ─────────────────────────────────────────────────
|
|
2203
|
+
/** Forward page-level events (request, response, etc.) to context listeners */
|
|
2204
|
+
forwardPageEvents(page) {
|
|
2205
|
+
const forward = (event) => {
|
|
2206
|
+
page.on(event, (...args) => {
|
|
2207
|
+
this._emitter.emit(event, ...args);
|
|
2208
|
+
});
|
|
2209
|
+
};
|
|
2210
|
+
forward("request");
|
|
2211
|
+
forward("response");
|
|
2212
|
+
forward("requestfailed");
|
|
2213
|
+
forward("requestfinished");
|
|
2214
|
+
}
|
|
2066
2215
|
setupAutoAttach() {
|
|
2067
2216
|
this.targetAttachedHandler = (paramsRaw) => {
|
|
2068
2217
|
const params = paramsRaw;
|
|
@@ -2087,6 +2236,7 @@ var XBContextImpl = class {
|
|
|
2087
2236
|
});
|
|
2088
2237
|
}
|
|
2089
2238
|
this._pages.push(page);
|
|
2239
|
+
this.forwardPageEvents(page);
|
|
2090
2240
|
this._emitter.emit("page", page);
|
|
2091
2241
|
});
|
|
2092
2242
|
};
|
|
@@ -2103,10 +2253,17 @@ var XBBrowserImpl = class {
|
|
|
2103
2253
|
childProcess = null;
|
|
2104
2254
|
tmpDir;
|
|
2105
2255
|
_exitHandler = null;
|
|
2106
|
-
|
|
2256
|
+
/**
|
|
2257
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
2258
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
2259
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
2260
|
+
*/
|
|
2261
|
+
cdpEndpoint;
|
|
2262
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
2107
2263
|
this.conn = conn;
|
|
2108
2264
|
this.childProcess = childProcess ?? null;
|
|
2109
2265
|
this.tmpDir = tmpDir;
|
|
2266
|
+
this.cdpEndpoint = cdpEndpoint;
|
|
2110
2267
|
conn.on("disconnect", () => {
|
|
2111
2268
|
this._disconnected = true;
|
|
2112
2269
|
this._emitter.emit("disconnected");
|
|
@@ -2150,7 +2307,7 @@ var XBBrowserImpl = class {
|
|
|
2150
2307
|
this._exitHandler = null;
|
|
2151
2308
|
}
|
|
2152
2309
|
if (this.childProcess) {
|
|
2153
|
-
const { killChrome: killChrome2 } = await import("./launcher-
|
|
2310
|
+
const { killChrome: killChrome2 } = await import("./launcher-QUJ4M2VS.js");
|
|
2154
2311
|
await killChrome2(this.childProcess, this.tmpDir);
|
|
2155
2312
|
}
|
|
2156
2313
|
await this.conn.close();
|
|
@@ -2180,6 +2337,10 @@ var XBBrowserImpl = class {
|
|
|
2180
2337
|
contextId,
|
|
2181
2338
|
context
|
|
2182
2339
|
});
|
|
2340
|
+
if (this.childProcess) {
|
|
2341
|
+
this._enableAutoAttach().catch(() => {
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2183
2344
|
return context;
|
|
2184
2345
|
}
|
|
2185
2346
|
contexts() {
|
|
@@ -2208,6 +2369,23 @@ var XBBrowserImpl = class {
|
|
|
2208
2369
|
async _detachFromTarget(sessionId) {
|
|
2209
2370
|
await this.conn.send("Target.detachFromTarget", { sessionId });
|
|
2210
2371
|
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Derive the HTTP /json base URL from the original cdpEndpoint for use
|
|
2374
|
+
* as a fallback when Target.getTargets doesn't return page targets.
|
|
2375
|
+
* Supports both http:// and ws:// input formats.
|
|
2376
|
+
*/
|
|
2377
|
+
_httpFallbackURL() {
|
|
2378
|
+
if (!this.cdpEndpoint) return void 0;
|
|
2379
|
+
if (this.cdpEndpoint.startsWith("http://") || this.cdpEndpoint.startsWith("https://")) {
|
|
2380
|
+
return this.cdpEndpoint;
|
|
2381
|
+
}
|
|
2382
|
+
if (this.cdpEndpoint.startsWith("ws://") || this.cdpEndpoint.startsWith("wss://")) {
|
|
2383
|
+
const url = this.cdpEndpoint.replace(/^ws/, "http");
|
|
2384
|
+
const slashIdx = url.indexOf("/", url.indexOf("//") + 2);
|
|
2385
|
+
return slashIdx >= 0 ? url.substring(0, slashIdx) : url;
|
|
2386
|
+
}
|
|
2387
|
+
return void 0;
|
|
2388
|
+
}
|
|
2211
2389
|
/** Create a new page target within a browser context */
|
|
2212
2390
|
async _createTarget(contextId, url = "about:blank") {
|
|
2213
2391
|
const params = { url };
|
|
@@ -2224,10 +2402,97 @@ var XBBrowserImpl = class {
|
|
|
2224
2402
|
async _enableAutoAttach() {
|
|
2225
2403
|
await this.conn.send("Target.setAutoAttach", {
|
|
2226
2404
|
autoAttach: true,
|
|
2227
|
-
waitForDebuggerOnStart:
|
|
2405
|
+
waitForDebuggerOnStart: false,
|
|
2228
2406
|
flatten: true
|
|
2229
2407
|
});
|
|
2230
2408
|
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Discover existing browser contexts and pages via Target.getTargets.
|
|
2411
|
+
*
|
|
2412
|
+
* For CDP tunnel connections (cdp-tunnel, attach scenarios), the
|
|
2413
|
+
* Target.attachedToTarget auto-attach flow is unreliable. Without this
|
|
2414
|
+
* call, `b.contexts()` would return [] and callers would fall back to
|
|
2415
|
+
* `b.newContext()` — which creates an isolated context with NO cookies
|
|
2416
|
+
* shared with the user's existing browser session (causing login failures).
|
|
2417
|
+
*
|
|
2418
|
+
* This method:
|
|
2419
|
+
* 1. Queries Target.getTargets to enumerate all page targets
|
|
2420
|
+
* 2. Groups them by browserContextId
|
|
2421
|
+
* 3. Attaches to each existing page via Target.attachToTarget
|
|
2422
|
+
* 4. Wraps the discovered pages in a XBContextImpl and registers it in
|
|
2423
|
+
* this._contexts so `contexts()` returns the user's actual contexts
|
|
2424
|
+
* 5. Enables Target.setAutoAttach for future pages
|
|
2425
|
+
*
|
|
2426
|
+
* No-op for self-launched browsers (they already populated contexts via
|
|
2427
|
+
* newContext() + childProcess-gated auto-attach).
|
|
2428
|
+
*/
|
|
2429
|
+
async discoverContexts() {
|
|
2430
|
+
if (this._disconnected) return;
|
|
2431
|
+
let targetInfos = [];
|
|
2432
|
+
try {
|
|
2433
|
+
const result = await this.conn.send(
|
|
2434
|
+
"Target.getTargets"
|
|
2435
|
+
);
|
|
2436
|
+
targetInfos = result.targetInfos ?? [];
|
|
2437
|
+
} catch {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const pageTargets = targetInfos.filter((t) => t.type === "page");
|
|
2441
|
+
const httpFallbackUrl = this._httpFallbackURL();
|
|
2442
|
+
if (pageTargets.length === 0 && httpFallbackUrl) {
|
|
2443
|
+
console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
|
|
2444
|
+
try {
|
|
2445
|
+
const { getCDPTargets: getCDPTargets2 } = await import("./launcher-QUJ4M2VS.js");
|
|
2446
|
+
const httpPages = await getCDPTargets2(httpFallbackUrl);
|
|
2447
|
+
console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
|
|
2448
|
+
for (const p of httpPages) {
|
|
2449
|
+
if (p.type !== "page") continue;
|
|
2450
|
+
if (!p.url || p.url.startsWith("chrome://") || p.url.startsWith("devtools://")) continue;
|
|
2451
|
+
targetInfos.push({
|
|
2452
|
+
targetId: p.id,
|
|
2453
|
+
type: "page",
|
|
2454
|
+
url: p.url,
|
|
2455
|
+
title: p.title
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
console.log(`[discoverContexts] After HTTP fallback: ${targetInfos.length} total targets, ${targetInfos.filter((t) => t.type === "page").length} pages`);
|
|
2459
|
+
} catch (err) {
|
|
2460
|
+
console.log(`[discoverContexts] HTTP fallback failed: ${err.message}`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
const pagesByContext = /* @__PURE__ */ new Map();
|
|
2464
|
+
for (const t of targetInfos) {
|
|
2465
|
+
if (t.type !== "page") continue;
|
|
2466
|
+
if (!t.url || t.url.startsWith("chrome://") || t.url.startsWith("devtools://")) {
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2469
|
+
const ctxId = t.browserContextId || "default";
|
|
2470
|
+
if (!pagesByContext.has(ctxId)) pagesByContext.set(ctxId, []);
|
|
2471
|
+
pagesByContext.get(ctxId).push(t);
|
|
2472
|
+
}
|
|
2473
|
+
for (const [ctxId, pages] of pagesByContext) {
|
|
2474
|
+
if (this._contexts.has(ctxId)) continue;
|
|
2475
|
+
const context = new XBContextImpl(this.conn, ctxId, this, {});
|
|
2476
|
+
for (const p of pages) {
|
|
2477
|
+
try {
|
|
2478
|
+
const sessionId = await this._attachToTarget(p.targetId);
|
|
2479
|
+
const page = new XBPageImpl(this.conn, sessionId, p.targetId, context, this);
|
|
2480
|
+
await page._init();
|
|
2481
|
+
context._addDiscoveredPage(page);
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
this._contexts.set(ctxId, { contextId: ctxId, context });
|
|
2486
|
+
}
|
|
2487
|
+
try {
|
|
2488
|
+
await this.conn.send("Target.setAutoAttach", {
|
|
2489
|
+
autoAttach: true,
|
|
2490
|
+
waitForDebuggerOnStart: false,
|
|
2491
|
+
flatten: true
|
|
2492
|
+
});
|
|
2493
|
+
} catch {
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2231
2496
|
};
|
|
2232
2497
|
|
|
2233
2498
|
// src/cdp-driver/connection.ts
|
|
@@ -2507,7 +2772,8 @@ async function launch(options = {}) {
|
|
|
2507
2772
|
}
|
|
2508
2773
|
const conn = new CDPConnection(wsEndpoint);
|
|
2509
2774
|
await conn.ready();
|
|
2510
|
-
const
|
|
2775
|
+
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
2776
|
+
const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
|
|
2511
2777
|
return { browser, wsEndpoint };
|
|
2512
2778
|
}
|
|
2513
2779
|
|
|
@@ -110,6 +110,12 @@ async function ensureDaemonRunning() {
|
|
|
110
110
|
_ensurePromise = null;
|
|
111
111
|
throw new Error("Daemon not available");
|
|
112
112
|
}
|
|
113
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
114
|
+
const ready = await fetch(`${DAEMON_BASE}/health`, { signal: AbortSignal.timeout(1e3) }).then((r) => r.ok ? r.json() : null).then((d) => d?.status === "ok").catch(() => false);
|
|
115
|
+
if (ready) return;
|
|
116
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
117
|
+
}
|
|
118
|
+
throw new Error("Daemon HTTP server not ready after 4s");
|
|
113
119
|
}
|
|
114
120
|
async function rpcCall(method, params = {}, timeoutMs = 1e4) {
|
|
115
121
|
await ensureDaemonRunning();
|
|
@@ -120,7 +126,13 @@ async function rpcCall(method, params = {}, timeoutMs = 1e4) {
|
|
|
120
126
|
signal: AbortSignal.timeout(timeoutMs)
|
|
121
127
|
});
|
|
122
128
|
if (!resp.ok) {
|
|
123
|
-
|
|
129
|
+
let detail = resp.statusText;
|
|
130
|
+
try {
|
|
131
|
+
const body = await resp.json();
|
|
132
|
+
if (body?.error) detail = body.error;
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Daemon error: ${detail}`);
|
|
124
136
|
}
|
|
125
137
|
return resp.json();
|
|
126
138
|
}
|
|
@@ -157,8 +169,8 @@ async function forwardExec(command, params, session = "default", cdpEndpoint, ti
|
|
|
157
169
|
if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
|
|
158
170
|
try {
|
|
159
171
|
return await rpcCall("exec", rpcParams, timeoutMs);
|
|
160
|
-
} catch {
|
|
161
|
-
return { success: false, data: null, message:
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return { success: false, data: null, message: e.message, duration: 0 };
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
176
|
async function forwardChain(input, session = "default", cdpEndpoint) {
|
|
@@ -166,8 +178,8 @@ async function forwardChain(input, session = "default", cdpEndpoint) {
|
|
|
166
178
|
if (cdpEndpoint) params.cdpEndpoint = cdpEndpoint;
|
|
167
179
|
try {
|
|
168
180
|
return await rpcCall("chain", params, 12e4);
|
|
169
|
-
} catch {
|
|
170
|
-
return { success: false, steps: [], totalDuration: 0, stoppedReason:
|
|
181
|
+
} catch (e) {
|
|
182
|
+
return { success: false, steps: [], totalDuration: 0, stoppedReason: e.message };
|
|
171
183
|
}
|
|
172
184
|
}
|
|
173
185
|
async function forwardAgentObserve(session = "default", options) {
|
|
@@ -223,8 +235,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
|
|
|
223
235
|
async function forwardNetworkInspect(sessionName, id) {
|
|
224
236
|
return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
|
|
225
237
|
}
|
|
226
|
-
async function forwardRecordStart(session, url) {
|
|
227
|
-
return rpcCall("record:start", { session, url }, 15e3);
|
|
238
|
+
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
239
|
+
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
228
240
|
}
|
|
229
241
|
async function forwardRecordStop(session) {
|
|
230
242
|
return rpcCall("record:stop", { session }, 1e4);
|