@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,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connectToCDP,
|
|
3
3
|
launchChrome
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TNEN6VQ2.js";
|
|
5
|
+
import {
|
|
6
|
+
errMsg
|
|
7
|
+
} from "./chunk-GDKLH7ZY.js";
|
|
5
8
|
import {
|
|
6
9
|
__require
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-KFQGP6VL.js";
|
|
8
11
|
|
|
9
12
|
// src/browser.ts
|
|
10
13
|
import { randomUUID } from "crypto";
|
|
@@ -250,36 +253,36 @@ function resolveKeyMapping(key) {
|
|
|
250
253
|
return { key, code: key };
|
|
251
254
|
}
|
|
252
255
|
var KEY_MAP = {
|
|
253
|
-
Enter: { key: "Enter", code: "Enter", text: "\r" },
|
|
254
|
-
Tab: { key: "Tab", code: "Tab", text: " " },
|
|
255
|
-
Escape: { key: "Escape", code: "Escape" },
|
|
256
|
-
Backspace: { key: "Backspace", code: "Backspace" },
|
|
257
|
-
Delete: { key: "Delete", code: "Delete" },
|
|
258
|
-
Space: { key: " ", code: "Space", text: " " },
|
|
259
|
-
ArrowUp: { key: "ArrowUp", code: "ArrowUp" },
|
|
260
|
-
ArrowDown: { key: "ArrowDown", code: "ArrowDown" },
|
|
261
|
-
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft" },
|
|
262
|
-
ArrowRight: { key: "ArrowRight", code: "ArrowRight" },
|
|
263
|
-
Home: { key: "Home", code: "Home" },
|
|
264
|
-
End: { key: "End", code: "End" },
|
|
265
|
-
PageUp: { key: "PageUp", code: "PageUp" },
|
|
266
|
-
PageDown: { key: "PageDown", code: "PageDown" },
|
|
267
|
-
Control: { key: "Control", code: "ControlLeft" },
|
|
268
|
-
Shift: { key: "Shift", code: "ShiftLeft" },
|
|
269
|
-
Alt: { key: "Alt", code: "AltLeft" },
|
|
270
|
-
Meta: { key: "Meta", code: "MetaLeft" },
|
|
271
|
-
F1: { key: "F1", code: "F1" },
|
|
272
|
-
F2: { key: "F2", code: "F2" },
|
|
273
|
-
F3: { key: "F3", code: "F3" },
|
|
274
|
-
F4: { key: "F4", code: "F4" },
|
|
275
|
-
F5: { key: "F5", code: "F5" },
|
|
276
|
-
F6: { key: "F6", code: "F6" },
|
|
277
|
-
F7: { key: "F7", code: "F7" },
|
|
278
|
-
F8: { key: "F8", code: "F8" },
|
|
279
|
-
F9: { key: "F9", code: "F9" },
|
|
280
|
-
F10: { key: "F10", code: "F10" },
|
|
281
|
-
F11: { key: "F11", code: "F11" },
|
|
282
|
-
F12: { key: "F12", code: "F12" }
|
|
256
|
+
Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
|
|
257
|
+
Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
|
|
258
|
+
Escape: { key: "Escape", code: "Escape", keyCode: 27 },
|
|
259
|
+
Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
|
|
260
|
+
Delete: { key: "Delete", code: "Delete", keyCode: 46 },
|
|
261
|
+
Space: { key: " ", code: "Space", text: " ", keyCode: 32 },
|
|
262
|
+
ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
|
|
263
|
+
ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
|
|
264
|
+
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
|
|
265
|
+
ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
|
|
266
|
+
Home: { key: "Home", code: "Home", keyCode: 36 },
|
|
267
|
+
End: { key: "End", code: "End", keyCode: 35 },
|
|
268
|
+
PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
|
|
269
|
+
PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
|
|
270
|
+
Control: { key: "Control", code: "ControlLeft", keyCode: 17 },
|
|
271
|
+
Shift: { key: "Shift", code: "ShiftLeft", keyCode: 16 },
|
|
272
|
+
Alt: { key: "Alt", code: "AltLeft", keyCode: 18 },
|
|
273
|
+
Meta: { key: "Meta", code: "MetaLeft", keyCode: 91 },
|
|
274
|
+
F1: { key: "F1", code: "F1", keyCode: 112 },
|
|
275
|
+
F2: { key: "F2", code: "F2", keyCode: 113 },
|
|
276
|
+
F3: { key: "F3", code: "F3", keyCode: 114 },
|
|
277
|
+
F4: { key: "F4", code: "F4", keyCode: 115 },
|
|
278
|
+
F5: { key: "F5", code: "F5", keyCode: 116 },
|
|
279
|
+
F6: { key: "F6", code: "F6", keyCode: 117 },
|
|
280
|
+
F7: { key: "F7", code: "F7", keyCode: 118 },
|
|
281
|
+
F8: { key: "F8", code: "F8", keyCode: 119 },
|
|
282
|
+
F9: { key: "F9", code: "F9", keyCode: 120 },
|
|
283
|
+
F10: { key: "F10", code: "F10", keyCode: 121 },
|
|
284
|
+
F11: { key: "F11", code: "F11", keyCode: 122 },
|
|
285
|
+
F12: { key: "F12", code: "F12", keyCode: 123 }
|
|
283
286
|
};
|
|
284
287
|
function sleep2(ms) {
|
|
285
288
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -974,7 +977,7 @@ function createXBRequest(page, data) {
|
|
|
974
977
|
}
|
|
975
978
|
};
|
|
976
979
|
}
|
|
977
|
-
function createXBRouteFetch(conn, sessionId, params) {
|
|
980
|
+
function createXBRouteFetch(conn, sessionId, params, emitter) {
|
|
978
981
|
const request = createXBRequest(null, {
|
|
979
982
|
requestId: params.requestId,
|
|
980
983
|
url: params.request.url,
|
|
@@ -1012,6 +1015,16 @@ function createXBRouteFetch(conn, sessionId, params) {
|
|
|
1012
1015
|
responseHeaders: Object.entries(headers).map(([k, v]) => ({ name: k, value: v })),
|
|
1013
1016
|
body: bodyBytes.toString("base64")
|
|
1014
1017
|
}, sessionId);
|
|
1018
|
+
if (emitter) {
|
|
1019
|
+
const responseData = {
|
|
1020
|
+
requestId: params.requestId,
|
|
1021
|
+
status: opts.status ?? 200,
|
|
1022
|
+
url: params.request.url,
|
|
1023
|
+
headers
|
|
1024
|
+
};
|
|
1025
|
+
const response = createXBResponse(responseData, conn, sessionId);
|
|
1026
|
+
emitter.emit("response", response);
|
|
1027
|
+
}
|
|
1015
1028
|
}
|
|
1016
1029
|
};
|
|
1017
1030
|
}
|
|
@@ -1193,7 +1206,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1193
1206
|
);
|
|
1194
1207
|
if (result) return result;
|
|
1195
1208
|
} catch (err) {
|
|
1196
|
-
lastError = err;
|
|
1209
|
+
lastError = err instanceof Error ? err : new Error(errMsg(err));
|
|
1197
1210
|
}
|
|
1198
1211
|
const pollMs = typeof polling === "number" ? polling : 16;
|
|
1199
1212
|
await this.waitForTimeout(pollMs);
|
|
@@ -1233,6 +1246,25 @@ Last error: ${lastError.message}` : "";
|
|
|
1233
1246
|
}
|
|
1234
1247
|
return result.result?.value;
|
|
1235
1248
|
}
|
|
1249
|
+
/** evaluateHandle — evaluates fn and returns a handle for element bounding box */
|
|
1250
|
+
async evaluateHandle(fn, ...args) {
|
|
1251
|
+
let expression;
|
|
1252
|
+
if (typeof fn === "string") {
|
|
1253
|
+
expression = fn;
|
|
1254
|
+
} else {
|
|
1255
|
+
const argStr = args.length > 0 ? `...${JSON.stringify(args)}` : "";
|
|
1256
|
+
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;})()`;
|
|
1257
|
+
}
|
|
1258
|
+
const result = await this.conn.send("Runtime.evaluate", { expression, returnByValue: true }).catch(() => ({ result: { value: null } }));
|
|
1259
|
+
let box = null;
|
|
1260
|
+
try {
|
|
1261
|
+
box = JSON.parse(result.result?.value);
|
|
1262
|
+
} catch {
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
asElement: () => box ? { boundingBox: async () => box } : null
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1236
1268
|
async $eval(selector, fn, ...args) {
|
|
1237
1269
|
const fnBody = typeof fn === "function" ? fn.toString() : fn;
|
|
1238
1270
|
return this.evaluate(
|
|
@@ -1448,6 +1480,26 @@ Last error: ${lastError.message}` : "";
|
|
|
1448
1480
|
off(event, handler) {
|
|
1449
1481
|
this._emitter.off(event, handler);
|
|
1450
1482
|
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Wait for a one-shot event (Playwright-compatible subset).
|
|
1485
|
+
* Used to listen for 'filechooser', 'dialog', 'popup', 'framenavigated', etc.
|
|
1486
|
+
*/
|
|
1487
|
+
async waitForEvent(event, opts = {}) {
|
|
1488
|
+
const timeout = opts.timeout ?? 3e4;
|
|
1489
|
+
return new Promise((resolve, reject) => {
|
|
1490
|
+
const timer = setTimeout(() => {
|
|
1491
|
+
this._emitter.off(event, handler);
|
|
1492
|
+
reject(new Error(`waitForEvent('${event}') timeout after ${timeout}ms`));
|
|
1493
|
+
}, timeout);
|
|
1494
|
+
const handler = (...args) => {
|
|
1495
|
+
if (opts.predicate && !opts.predicate(...args)) return;
|
|
1496
|
+
clearTimeout(timer);
|
|
1497
|
+
this._emitter.off(event, handler);
|
|
1498
|
+
resolve(args.length === 1 ? args[0] : args);
|
|
1499
|
+
};
|
|
1500
|
+
this._emitter.on(event, handler);
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1451
1503
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
1452
1504
|
async close() {
|
|
1453
1505
|
if (this._closed) return;
|
|
@@ -1564,6 +1616,19 @@ Last error: ${lastError.message}` : "";
|
|
|
1564
1616
|
async _cdpSend(method, params) {
|
|
1565
1617
|
return this.conn.send(method, params, this.sessionId);
|
|
1566
1618
|
}
|
|
1619
|
+
/**
|
|
1620
|
+
* 开启/关闭原生文件选择框拦截(CDP stateful 开关)。
|
|
1621
|
+
*
|
|
1622
|
+
* - enabled=true(执行期默认):点击上传按钮时不弹系统文件框,改发 Page.fileChooserOpened 事件,
|
|
1623
|
+
* page 的 'filechooser' 监听器拿到 chooser 后用 setFiles/setInputFiles 注入文件。
|
|
1624
|
+
* - enabled=false(录制期默认):真实文件选择框正常弹出,用户手动选文件;
|
|
1625
|
+
* 前端 input[type=file] 的 change 事件由 action signal 脚本捕获记录。
|
|
1626
|
+
*
|
|
1627
|
+
* 可多次调用切换状态(CDP 协议是 stateful 的)。
|
|
1628
|
+
*/
|
|
1629
|
+
async setFileDialogInterception(enabled) {
|
|
1630
|
+
await this.conn.send("Page.setInterceptFileChooserDialog", { enabled }, this.sessionId).catch((e) => console.error("[XBPage] setInterceptFileChooserDialog failed:", errMsg(e)));
|
|
1631
|
+
}
|
|
1567
1632
|
/** Subscribe to a CDP event on this page's session. Returns unsubscribe function. */
|
|
1568
1633
|
_subscribe(event, handler) {
|
|
1569
1634
|
return this.conn.subscribe(event, this.sessionId, handler);
|
|
@@ -1611,6 +1676,40 @@ Last error: ${lastError.message}` : "";
|
|
|
1611
1676
|
this._emit("dialog", dialog);
|
|
1612
1677
|
})
|
|
1613
1678
|
);
|
|
1679
|
+
this._subscriptions.push(
|
|
1680
|
+
this.conn.subscribe("Page.fileChooserOpened", this.sessionId, async (params) => {
|
|
1681
|
+
const p = params;
|
|
1682
|
+
let selector = "";
|
|
1683
|
+
try {
|
|
1684
|
+
const result = await this.conn.send("DOM.describeNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1685
|
+
const attrs = result.node?.attributes || [];
|
|
1686
|
+
const idIdx = attrs.indexOf("id");
|
|
1687
|
+
if (idIdx >= 0) selector = "#" + attrs[idIdx + 1];
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
if (!selector) {
|
|
1691
|
+
try {
|
|
1692
|
+
const result = await this.conn.send("DOM.resolveNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1693
|
+
const evalResult = await this.conn.send("Runtime.callFunctionOn", {
|
|
1694
|
+
objectId: result.objectId,
|
|
1695
|
+
functionDeclaration: 'function() { return this.id || this.name || "" }',
|
|
1696
|
+
returnByValue: true
|
|
1697
|
+
}, this.sessionId);
|
|
1698
|
+
if (evalResult.result?.value) selector = "#" + evalResult.result.value;
|
|
1699
|
+
} catch {
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
const fileChooser = {
|
|
1703
|
+
selector,
|
|
1704
|
+
isMultiple: p.mode === "selectMultiple",
|
|
1705
|
+
setFiles: async (files) => {
|
|
1706
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
1707
|
+
await this.setInputFiles(selector || 'input[type="file"]', fileArray);
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
this._emit("filechooser", fileChooser);
|
|
1711
|
+
})
|
|
1712
|
+
);
|
|
1614
1713
|
}
|
|
1615
1714
|
setupNetworkEvents() {
|
|
1616
1715
|
this._subscriptions.push(
|
|
@@ -1624,7 +1723,17 @@ Last error: ${lastError.message}` : "";
|
|
|
1624
1723
|
postData: p.request.postData ?? null,
|
|
1625
1724
|
resourceType: p.type
|
|
1626
1725
|
});
|
|
1627
|
-
this._emit("request",
|
|
1726
|
+
this._emit("request", createXBRequest(
|
|
1727
|
+
null,
|
|
1728
|
+
{
|
|
1729
|
+
requestId: p.requestId,
|
|
1730
|
+
url: p.request.url,
|
|
1731
|
+
method: p.request.method,
|
|
1732
|
+
headers: p.request.headers,
|
|
1733
|
+
postData: p.request.postData ?? null,
|
|
1734
|
+
resourceType: p.type
|
|
1735
|
+
}
|
|
1736
|
+
));
|
|
1628
1737
|
this.checkNetworkIdle();
|
|
1629
1738
|
})
|
|
1630
1739
|
);
|
|
@@ -1636,7 +1745,11 @@ Last error: ${lastError.message}` : "";
|
|
|
1636
1745
|
url: p.response.url,
|
|
1637
1746
|
headers: p.response.headers
|
|
1638
1747
|
});
|
|
1639
|
-
this._emit("response",
|
|
1748
|
+
this._emit("response", createXBResponse(
|
|
1749
|
+
{ requestId: p.requestId, status: p.response.status, url: p.response.url, headers: p.response.headers },
|
|
1750
|
+
this.conn,
|
|
1751
|
+
this.sessionId
|
|
1752
|
+
));
|
|
1640
1753
|
})
|
|
1641
1754
|
);
|
|
1642
1755
|
this._subscriptions.push(
|
|
@@ -1722,14 +1835,21 @@ Last error: ${lastError.message}` : "";
|
|
|
1722
1835
|
reject(new Error(`waitForResponse timed out after ${timeout}ms`));
|
|
1723
1836
|
}, timeout);
|
|
1724
1837
|
const handler = (params) => {
|
|
1725
|
-
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1838
|
+
let response;
|
|
1839
|
+
const respObj = params;
|
|
1840
|
+
if (respObj.response) {
|
|
1841
|
+
const data = {
|
|
1842
|
+
requestId: respObj.requestId || "",
|
|
1843
|
+
status: respObj.response.status || 0,
|
|
1844
|
+
url: respObj.response.url || "",
|
|
1845
|
+
headers: respObj.response.headers || {}
|
|
1846
|
+
};
|
|
1847
|
+
response = createXBResponse(data, this.conn, this.sessionId);
|
|
1848
|
+
} else if (typeof params.status === "function") {
|
|
1849
|
+
response = params;
|
|
1850
|
+
} else {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1733
1853
|
if (predicate(response)) {
|
|
1734
1854
|
clearTimeout(timer);
|
|
1735
1855
|
this._emitter.removeListener("response", handler);
|
|
@@ -1753,16 +1873,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1753
1873
|
reject(new Error(`waitForRequest timed out after ${timeout}ms`));
|
|
1754
1874
|
}, timeout);
|
|
1755
1875
|
const handler = (params) => {
|
|
1756
|
-
|
|
1757
|
-
const
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1876
|
+
let request;
|
|
1877
|
+
const reqObj = params;
|
|
1878
|
+
if (reqObj.request) {
|
|
1879
|
+
request = createXBRequest(this, {
|
|
1880
|
+
requestId: reqObj.requestId || "",
|
|
1881
|
+
url: reqObj.request.url || "",
|
|
1882
|
+
method: reqObj.request.method || "",
|
|
1883
|
+
headers: reqObj.request.headers || {},
|
|
1884
|
+
postData: reqObj.request.postData ?? null,
|
|
1885
|
+
resourceType: reqObj.type || ""
|
|
1886
|
+
});
|
|
1887
|
+
} else if (typeof params.url === "function") {
|
|
1888
|
+
request = params;
|
|
1889
|
+
} else {
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1766
1892
|
if (predicate(request)) {
|
|
1767
1893
|
clearTimeout(timer);
|
|
1768
1894
|
this._emitter.removeListener("request", handler);
|
|
@@ -1823,7 +1949,18 @@ Last error: ${lastError.message}` : "";
|
|
|
1823
1949
|
const requestUrl = params.request.url;
|
|
1824
1950
|
for (const { regex, handler } of this._routeHandlers) {
|
|
1825
1951
|
if (regex.test(requestUrl)) {
|
|
1826
|
-
|
|
1952
|
+
this._emit("request", createXBRequest(
|
|
1953
|
+
null,
|
|
1954
|
+
{
|
|
1955
|
+
requestId: params.requestId,
|
|
1956
|
+
url: params.request.url,
|
|
1957
|
+
method: params.request.method,
|
|
1958
|
+
headers: params.request.headers,
|
|
1959
|
+
postData: params.request.postData ?? null,
|
|
1960
|
+
resourceType: params.resourceType
|
|
1961
|
+
}
|
|
1962
|
+
));
|
|
1963
|
+
const route = createXBRouteFetch(this.conn, this.sessionId, params, this._emitter);
|
|
1827
1964
|
try {
|
|
1828
1965
|
await handler(route);
|
|
1829
1966
|
} catch {
|
|
@@ -1990,6 +2127,7 @@ var XBContextImpl = class {
|
|
|
1990
2127
|
const page = new XBPageImpl(this.conn, sessionId, targetId, this, this._browser);
|
|
1991
2128
|
await page._init();
|
|
1992
2129
|
this._pages.push(page);
|
|
2130
|
+
this.forwardPageEvents(page);
|
|
1993
2131
|
if (this.options.viewport) {
|
|
1994
2132
|
await page.setViewportSize(this.options.viewport).catch(() => {
|
|
1995
2133
|
});
|
|
@@ -2031,7 +2169,10 @@ var XBContextImpl = class {
|
|
|
2031
2169
|
}
|
|
2032
2170
|
this._browser._removeContext(this.contextId);
|
|
2033
2171
|
}
|
|
2034
|
-
async newCDPSession(
|
|
2172
|
+
async newCDPSession(page) {
|
|
2173
|
+
if (page instanceof XBPageImpl) {
|
|
2174
|
+
return new XBCDPSessionImpl(this.conn, page.sessionId);
|
|
2175
|
+
}
|
|
2035
2176
|
return new XBCDPSessionImpl(this.conn);
|
|
2036
2177
|
}
|
|
2037
2178
|
async addInitScript(script) {
|
|
@@ -2068,7 +2209,31 @@ var XBContextImpl = class {
|
|
|
2068
2209
|
off(event, handler) {
|
|
2069
2210
|
this._emitter.off(event, handler);
|
|
2070
2211
|
}
|
|
2212
|
+
/**
|
|
2213
|
+
* Register a page that was attached to an existing target (discovered via
|
|
2214
|
+
* Target.getTargets). Used by XBBrowserImpl.discoverContexts() to wire up
|
|
2215
|
+
* pages from the user's existing browser session into the context wrapper
|
|
2216
|
+
* so they appear in `context.pages()` and can be reused by plugins.
|
|
2217
|
+
*/
|
|
2218
|
+
_addDiscoveredPage(page) {
|
|
2219
|
+
const exists = this._pages.some((p) => p._targetId === page._targetId);
|
|
2220
|
+
if (exists) return;
|
|
2221
|
+
this._pages.push(page);
|
|
2222
|
+
this.forwardPageEvents(page);
|
|
2223
|
+
}
|
|
2071
2224
|
// ── Private ─────────────────────────────────────────────────
|
|
2225
|
+
/** Forward page-level events (request, response, etc.) to context listeners */
|
|
2226
|
+
forwardPageEvents(page) {
|
|
2227
|
+
const forward = (event) => {
|
|
2228
|
+
page.on(event, (...args) => {
|
|
2229
|
+
this._emitter.emit(event, ...args);
|
|
2230
|
+
});
|
|
2231
|
+
};
|
|
2232
|
+
forward("request");
|
|
2233
|
+
forward("response");
|
|
2234
|
+
forward("requestfailed");
|
|
2235
|
+
forward("requestfinished");
|
|
2236
|
+
}
|
|
2072
2237
|
setupAutoAttach() {
|
|
2073
2238
|
this.targetAttachedHandler = (paramsRaw) => {
|
|
2074
2239
|
const params = paramsRaw;
|
|
@@ -2093,6 +2258,7 @@ var XBContextImpl = class {
|
|
|
2093
2258
|
});
|
|
2094
2259
|
}
|
|
2095
2260
|
this._pages.push(page);
|
|
2261
|
+
this.forwardPageEvents(page);
|
|
2096
2262
|
this._emitter.emit("page", page);
|
|
2097
2263
|
});
|
|
2098
2264
|
};
|
|
@@ -2109,10 +2275,17 @@ var XBBrowserImpl = class {
|
|
|
2109
2275
|
childProcess = null;
|
|
2110
2276
|
tmpDir;
|
|
2111
2277
|
_exitHandler = null;
|
|
2112
|
-
|
|
2278
|
+
/**
|
|
2279
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
2280
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
2281
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
2282
|
+
*/
|
|
2283
|
+
cdpEndpoint;
|
|
2284
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
2113
2285
|
this.conn = conn;
|
|
2114
2286
|
this.childProcess = childProcess ?? null;
|
|
2115
2287
|
this.tmpDir = tmpDir;
|
|
2288
|
+
this.cdpEndpoint = cdpEndpoint;
|
|
2116
2289
|
conn.on("disconnect", () => {
|
|
2117
2290
|
this._disconnected = true;
|
|
2118
2291
|
this._emitter.emit("disconnected");
|
|
@@ -2156,7 +2329,7 @@ var XBBrowserImpl = class {
|
|
|
2156
2329
|
this._exitHandler = null;
|
|
2157
2330
|
}
|
|
2158
2331
|
if (this.childProcess) {
|
|
2159
|
-
const { killChrome: killChrome2 } = await import("./launcher-
|
|
2332
|
+
const { killChrome: killChrome2 } = await import("./launcher-L2JNDB2H.js");
|
|
2160
2333
|
await killChrome2(this.childProcess, this.tmpDir);
|
|
2161
2334
|
}
|
|
2162
2335
|
await this.conn.close();
|
|
@@ -2186,6 +2359,10 @@ var XBBrowserImpl = class {
|
|
|
2186
2359
|
contextId,
|
|
2187
2360
|
context
|
|
2188
2361
|
});
|
|
2362
|
+
if (this.childProcess) {
|
|
2363
|
+
this._enableAutoAttach().catch(() => {
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2189
2366
|
return context;
|
|
2190
2367
|
}
|
|
2191
2368
|
contexts() {
|
|
@@ -2214,6 +2391,23 @@ var XBBrowserImpl = class {
|
|
|
2214
2391
|
async _detachFromTarget(sessionId) {
|
|
2215
2392
|
await this.conn.send("Target.detachFromTarget", { sessionId });
|
|
2216
2393
|
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Derive the HTTP /json base URL from the original cdpEndpoint for use
|
|
2396
|
+
* as a fallback when Target.getTargets doesn't return page targets.
|
|
2397
|
+
* Supports both http:// and ws:// input formats.
|
|
2398
|
+
*/
|
|
2399
|
+
_httpFallbackURL() {
|
|
2400
|
+
if (!this.cdpEndpoint) return void 0;
|
|
2401
|
+
if (this.cdpEndpoint.startsWith("http://") || this.cdpEndpoint.startsWith("https://")) {
|
|
2402
|
+
return this.cdpEndpoint;
|
|
2403
|
+
}
|
|
2404
|
+
if (this.cdpEndpoint.startsWith("ws://") || this.cdpEndpoint.startsWith("wss://")) {
|
|
2405
|
+
const url = this.cdpEndpoint.replace(/^ws/, "http");
|
|
2406
|
+
const slashIdx = url.indexOf("/", url.indexOf("//") + 2);
|
|
2407
|
+
return slashIdx >= 0 ? url.substring(0, slashIdx) : url;
|
|
2408
|
+
}
|
|
2409
|
+
return void 0;
|
|
2410
|
+
}
|
|
2217
2411
|
/** Create a new page target within a browser context */
|
|
2218
2412
|
async _createTarget(contextId, url = "about:blank") {
|
|
2219
2413
|
const params = { url };
|
|
@@ -2230,10 +2424,97 @@ var XBBrowserImpl = class {
|
|
|
2230
2424
|
async _enableAutoAttach() {
|
|
2231
2425
|
await this.conn.send("Target.setAutoAttach", {
|
|
2232
2426
|
autoAttach: true,
|
|
2233
|
-
waitForDebuggerOnStart:
|
|
2427
|
+
waitForDebuggerOnStart: false,
|
|
2234
2428
|
flatten: true
|
|
2235
2429
|
});
|
|
2236
2430
|
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Discover existing browser contexts and pages via Target.getTargets.
|
|
2433
|
+
*
|
|
2434
|
+
* For CDP tunnel connections (cdp-tunnel, attach scenarios), the
|
|
2435
|
+
* Target.attachedToTarget auto-attach flow is unreliable. Without this
|
|
2436
|
+
* call, `b.contexts()` would return [] and callers would fall back to
|
|
2437
|
+
* `b.newContext()` — which creates an isolated context with NO cookies
|
|
2438
|
+
* shared with the user's existing browser session (causing login failures).
|
|
2439
|
+
*
|
|
2440
|
+
* This method:
|
|
2441
|
+
* 1. Queries Target.getTargets to enumerate all page targets
|
|
2442
|
+
* 2. Groups them by browserContextId
|
|
2443
|
+
* 3. Attaches to each existing page via Target.attachToTarget
|
|
2444
|
+
* 4. Wraps the discovered pages in a XBContextImpl and registers it in
|
|
2445
|
+
* this._contexts so `contexts()` returns the user's actual contexts
|
|
2446
|
+
* 5. Enables Target.setAutoAttach for future pages
|
|
2447
|
+
*
|
|
2448
|
+
* No-op for self-launched browsers (they already populated contexts via
|
|
2449
|
+
* newContext() + childProcess-gated auto-attach).
|
|
2450
|
+
*/
|
|
2451
|
+
async discoverContexts() {
|
|
2452
|
+
if (this._disconnected) return;
|
|
2453
|
+
let targetInfos = [];
|
|
2454
|
+
try {
|
|
2455
|
+
const result = await this.conn.send(
|
|
2456
|
+
"Target.getTargets"
|
|
2457
|
+
);
|
|
2458
|
+
targetInfos = result.targetInfos ?? [];
|
|
2459
|
+
} catch {
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
const pageTargets = targetInfos.filter((t) => t.type === "page");
|
|
2463
|
+
const httpFallbackUrl = this._httpFallbackURL();
|
|
2464
|
+
if (pageTargets.length === 0 && httpFallbackUrl) {
|
|
2465
|
+
console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
|
|
2466
|
+
try {
|
|
2467
|
+
const { getCDPTargets: getCDPTargets3 } = await import("./launcher-L2JNDB2H.js");
|
|
2468
|
+
const httpPages = await getCDPTargets3(httpFallbackUrl);
|
|
2469
|
+
console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
|
|
2470
|
+
for (const p of httpPages) {
|
|
2471
|
+
if (p.type !== "page") continue;
|
|
2472
|
+
if (!p.url || p.url.startsWith("chrome://") || p.url.startsWith("devtools://")) continue;
|
|
2473
|
+
targetInfos.push({
|
|
2474
|
+
targetId: p.id,
|
|
2475
|
+
type: "page",
|
|
2476
|
+
url: p.url,
|
|
2477
|
+
title: p.title
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
console.log(`[discoverContexts] After HTTP fallback: ${targetInfos.length} total targets, ${targetInfos.filter((t) => t.type === "page").length} pages`);
|
|
2481
|
+
} catch (err) {
|
|
2482
|
+
console.log(`[discoverContexts] HTTP fallback failed: ${errMsg(err)}`);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const pagesByContext = /* @__PURE__ */ new Map();
|
|
2486
|
+
for (const t of targetInfos) {
|
|
2487
|
+
if (t.type !== "page") continue;
|
|
2488
|
+
if (!t.url || t.url.startsWith("chrome://") || t.url.startsWith("devtools://")) {
|
|
2489
|
+
continue;
|
|
2490
|
+
}
|
|
2491
|
+
const ctxId = t.browserContextId || "default";
|
|
2492
|
+
if (!pagesByContext.has(ctxId)) pagesByContext.set(ctxId, []);
|
|
2493
|
+
pagesByContext.get(ctxId).push(t);
|
|
2494
|
+
}
|
|
2495
|
+
for (const [ctxId, pages] of pagesByContext) {
|
|
2496
|
+
if (this._contexts.has(ctxId)) continue;
|
|
2497
|
+
const context = new XBContextImpl(this.conn, ctxId, this, {});
|
|
2498
|
+
for (const p of pages) {
|
|
2499
|
+
try {
|
|
2500
|
+
const sessionId = await this._attachToTarget(p.targetId);
|
|
2501
|
+
const page = new XBPageImpl(this.conn, sessionId, p.targetId, context, this);
|
|
2502
|
+
await page._init();
|
|
2503
|
+
context._addDiscoveredPage(page);
|
|
2504
|
+
} catch {
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
this._contexts.set(ctxId, { contextId: ctxId, context });
|
|
2508
|
+
}
|
|
2509
|
+
try {
|
|
2510
|
+
await this.conn.send("Target.setAutoAttach", {
|
|
2511
|
+
autoAttach: true,
|
|
2512
|
+
waitForDebuggerOnStart: false,
|
|
2513
|
+
flatten: true
|
|
2514
|
+
});
|
|
2515
|
+
} catch {
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2237
2518
|
};
|
|
2238
2519
|
|
|
2239
2520
|
// src/cdp-driver/connection.ts
|
|
@@ -2453,7 +2734,8 @@ async function launch(options = {}) {
|
|
|
2453
2734
|
}
|
|
2454
2735
|
const conn = new CDPConnection(wsEndpoint);
|
|
2455
2736
|
await conn.ready();
|
|
2456
|
-
const
|
|
2737
|
+
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
2738
|
+
const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
|
|
2457
2739
|
return { browser, wsEndpoint };
|
|
2458
2740
|
}
|
|
2459
2741
|
|
|
@@ -3749,7 +4031,7 @@ var CDPInterceptorProxy = class {
|
|
|
3749
4031
|
const ctx = {
|
|
3750
4032
|
method: request.method,
|
|
3751
4033
|
params: request.params ?? {},
|
|
3752
|
-
sessionId: makeCompoundId(browserWs._cdpSession, request.sessionId),
|
|
4034
|
+
sessionId: makeCompoundId("_cdpSession" in browserWs ? browserWs._cdpSession : void 0, request.sessionId),
|
|
3753
4035
|
direction: "client\u2192browser"
|
|
3754
4036
|
};
|
|
3755
4037
|
const decision = this.engine.evaluate(ctx);
|
|
@@ -3879,6 +4161,7 @@ async function resolveCDPEndpoint(raw) {
|
|
|
3879
4161
|
}
|
|
3880
4162
|
|
|
3881
4163
|
// src/browser.ts
|
|
4164
|
+
import { SessionStore } from "@dyyz1993/xcli-core";
|
|
3882
4165
|
function logSessionEvent(event, details) {
|
|
3883
4166
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
3884
4167
|
const pid = process.pid;
|
|
@@ -3891,7 +4174,7 @@ function sessionFile(name) {
|
|
|
3891
4174
|
function ensureSessionDir() {
|
|
3892
4175
|
mkdirSync(SESSION_DIR, { recursive: true });
|
|
3893
4176
|
}
|
|
3894
|
-
var sessions =
|
|
4177
|
+
var sessions = new SessionStore();
|
|
3895
4178
|
var _sharedBrowser = null;
|
|
3896
4179
|
var _sharedCdpProxy = null;
|
|
3897
4180
|
var IDLE_TIMEOUT_MS = (process.env.XBROWSER_IDLE_TIMEOUT ? parseInt(process.env.XBROWSER_IDLE_TIMEOUT, 10) : 30) * 60 * 1e3;
|
|
@@ -3902,7 +4185,7 @@ function resetIdleTimer() {
|
|
|
3902
4185
|
const now = Date.now();
|
|
3903
4186
|
let allIdle = true;
|
|
3904
4187
|
const idleSessions = [];
|
|
3905
|
-
for (const
|
|
4188
|
+
for (const s of sessions) {
|
|
3906
4189
|
if (now - s.lastActivityAt < IDLE_TIMEOUT_MS) {
|
|
3907
4190
|
allIdle = false;
|
|
3908
4191
|
} else {
|
|
@@ -3925,7 +4208,7 @@ function touchSession(id) {
|
|
|
3925
4208
|
resetIdleTimer();
|
|
3926
4209
|
}
|
|
3927
4210
|
process.on("exit", () => {
|
|
3928
|
-
for (const session of sessions.
|
|
4211
|
+
for (const session of sessions.list()) {
|
|
3929
4212
|
if (session.isCDP) {
|
|
3930
4213
|
logSessionEvent("process_exit", `Session "${session.name}": CDP connection (not closing external browser).`);
|
|
3931
4214
|
} else {
|
|
@@ -4015,6 +4298,9 @@ async function createBrowser(options) {
|
|
|
4015
4298
|
return browser3;
|
|
4016
4299
|
}
|
|
4017
4300
|
const { browser: browser2 } = await launch({ cdpEndpoint: realEndpoint });
|
|
4301
|
+
await browser2.discoverContexts().catch((err) => {
|
|
4302
|
+
console.error(`[browser] discoverContexts failed: ${errMsg(err)}`);
|
|
4303
|
+
});
|
|
4018
4304
|
return browser2;
|
|
4019
4305
|
}
|
|
4020
4306
|
const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
|
|
@@ -4029,10 +4315,7 @@ async function getBrowser(options) {
|
|
|
4029
4315
|
return _sharedBrowser;
|
|
4030
4316
|
}
|
|
4031
4317
|
function findSession(name) {
|
|
4032
|
-
|
|
4033
|
-
if (session.name === name) return session;
|
|
4034
|
-
}
|
|
4035
|
-
return void 0;
|
|
4318
|
+
return sessions.find(name);
|
|
4036
4319
|
}
|
|
4037
4320
|
function getSessionById(id) {
|
|
4038
4321
|
return sessions.get(id);
|
|
@@ -4136,9 +4419,14 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
4136
4419
|
return void 0;
|
|
4137
4420
|
}
|
|
4138
4421
|
const targetUrl = meta.conversationUrl || meta.url;
|
|
4139
|
-
if (targetUrl && page.url() !== targetUrl
|
|
4140
|
-
|
|
4141
|
-
|
|
4422
|
+
if (targetUrl && page.url() !== targetUrl) {
|
|
4423
|
+
try {
|
|
4424
|
+
if (!page.url().includes(new URL(targetUrl).hostname)) {
|
|
4425
|
+
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4428
|
+
} catch {
|
|
4429
|
+
}
|
|
4142
4430
|
}
|
|
4143
4431
|
const session = {
|
|
4144
4432
|
id: meta.id || randomUUID(),
|
|
@@ -4151,18 +4439,18 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
4151
4439
|
isCDP: true,
|
|
4152
4440
|
cdpEndpoint: ep
|
|
4153
4441
|
};
|
|
4154
|
-
for (const
|
|
4442
|
+
for (const existingSession of sessions.list()) {
|
|
4155
4443
|
if (existingSession.name === name) {
|
|
4156
|
-
logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${
|
|
4157
|
-
sessions.
|
|
4444
|
+
logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingSession.id}" during restore`);
|
|
4445
|
+
sessions.removeById(existingSession.id);
|
|
4158
4446
|
}
|
|
4159
4447
|
}
|
|
4160
|
-
sessions.set(session
|
|
4448
|
+
sessions.set(session);
|
|
4161
4449
|
resetIdleTimer();
|
|
4162
4450
|
await installNetworkCapture(page, name);
|
|
4163
4451
|
return session;
|
|
4164
4452
|
} catch (e) {
|
|
4165
|
-
console.error(`[Session Restore] Failed for "${name}":`, e
|
|
4453
|
+
console.error(`[Session Restore] Failed for "${name}":`, errMsg(e));
|
|
4166
4454
|
deleteSessionDiskMeta(name);
|
|
4167
4455
|
return void 0;
|
|
4168
4456
|
}
|
|
@@ -4173,7 +4461,12 @@ async function createEphemeralContext(options) {
|
|
|
4173
4461
|
const { browser: b2 } = await launch({ cdpEndpoint: endpoint });
|
|
4174
4462
|
const contexts = b2.contexts();
|
|
4175
4463
|
const ctx = contexts[0] || await b2.newContext();
|
|
4176
|
-
const
|
|
4464
|
+
const allPages = ctx.pages();
|
|
4465
|
+
const existingPages = allPages.filter((p) => {
|
|
4466
|
+
const url = p.url();
|
|
4467
|
+
return url !== "about:blank" && !url.startsWith("chrome://");
|
|
4468
|
+
});
|
|
4469
|
+
const page2 = existingPages.length > 0 ? existingPages[0] : allPages.length > 0 ? allPages[0] : await ctx.newPage();
|
|
4177
4470
|
resetIdleTimer();
|
|
4178
4471
|
ephemeralConnections.set(page2, b2);
|
|
4179
4472
|
return { context: ctx, page: page2 };
|
|
@@ -4205,11 +4498,11 @@ async function closeEphemeralContext(context) {
|
|
|
4205
4498
|
}
|
|
4206
4499
|
}
|
|
4207
4500
|
function getAllSessions() {
|
|
4208
|
-
return
|
|
4501
|
+
return sessions.list();
|
|
4209
4502
|
}
|
|
4210
4503
|
async function installNetworkCapture(page, sessionName) {
|
|
4211
4504
|
if (process.env.XBROWSER_DAEMON_WORKER !== "1") return;
|
|
4212
|
-
const { networkStore } = await import("./network-store-
|
|
4505
|
+
const { networkStore } = await import("./network-store-YVDNUREI.js");
|
|
4213
4506
|
const requestData = /* @__PURE__ */ new Map();
|
|
4214
4507
|
const responseMeta = /* @__PURE__ */ new Map();
|
|
4215
4508
|
const xbPage = page;
|
|
@@ -4330,16 +4623,38 @@ async function createSession(name, url, options) {
|
|
|
4330
4623
|
}
|
|
4331
4624
|
context = contexts[0] || await b.newContext();
|
|
4332
4625
|
let targetPage = null;
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4626
|
+
const targetHostname = url ? (() => {
|
|
4627
|
+
try {
|
|
4628
|
+
return new URL(url).hostname;
|
|
4629
|
+
} catch {
|
|
4630
|
+
return "";
|
|
4631
|
+
}
|
|
4632
|
+
})() : "";
|
|
4633
|
+
if (targetHostname) {
|
|
4634
|
+
for (const ctx of contexts) {
|
|
4635
|
+
const pages = ctx.pages();
|
|
4636
|
+
for (const p of pages) {
|
|
4637
|
+
const pUrl = p.url();
|
|
4638
|
+
if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && pUrl.includes(targetHostname)) {
|
|
4639
|
+
targetPage = p;
|
|
4640
|
+
break;
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
if (targetPage) break;
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
if (!targetPage) {
|
|
4647
|
+
for (const ctx of contexts) {
|
|
4648
|
+
const pages = ctx.pages();
|
|
4649
|
+
for (const p of pages) {
|
|
4650
|
+
const pUrl = p.url();
|
|
4651
|
+
if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
|
|
4652
|
+
targetPage = p;
|
|
4653
|
+
break;
|
|
4654
|
+
}
|
|
4340
4655
|
}
|
|
4656
|
+
if (targetPage) break;
|
|
4341
4657
|
}
|
|
4342
|
-
if (targetPage) break;
|
|
4343
4658
|
}
|
|
4344
4659
|
if (!targetPage && options?.cdpEndpoint) {
|
|
4345
4660
|
const targets = await getCDPTargets2(options.cdpEndpoint);
|
|
@@ -4380,14 +4695,14 @@ async function createSession(name, url, options) {
|
|
|
4380
4695
|
isCDP,
|
|
4381
4696
|
cdpEndpoint: options?.cdpEndpoint
|
|
4382
4697
|
};
|
|
4383
|
-
sessions.set(session
|
|
4698
|
+
sessions.set(session);
|
|
4384
4699
|
logSessionEvent("create_session", `name="${name}" id="${session.id}" url="${url || "(no url)"}" isCDP=${isCDP} cdpEndpoint=${options?.cdpEndpoint || "(none)"}`);
|
|
4385
4700
|
resetIdleTimer();
|
|
4386
4701
|
await installNetworkCapture(page, name);
|
|
4387
4702
|
return session;
|
|
4388
4703
|
}
|
|
4389
4704
|
async function closeSessionByName(name) {
|
|
4390
|
-
for (const
|
|
4705
|
+
for (const session of sessions) {
|
|
4391
4706
|
if (session.name === name || session.id === name) {
|
|
4392
4707
|
logSessionEvent("close_session", `name="${session.name}" id="${session.id}" url="${session.page.url()}"`);
|
|
4393
4708
|
if (session.isCDP) {
|
|
@@ -4406,20 +4721,20 @@ async function closeSessionByName(name) {
|
|
|
4406
4721
|
});
|
|
4407
4722
|
}
|
|
4408
4723
|
}
|
|
4409
|
-
sessions.
|
|
4724
|
+
sessions.removeById(session.id);
|
|
4410
4725
|
const file2 = sessionFile(session.name);
|
|
4411
4726
|
try {
|
|
4412
4727
|
unlinkSync(file2);
|
|
4413
4728
|
} catch {
|
|
4414
4729
|
}
|
|
4415
4730
|
try {
|
|
4416
|
-
const { networkStore, commandLogStore } = await import("./network-store-
|
|
4731
|
+
const { networkStore, commandLogStore } = await import("./network-store-YVDNUREI.js");
|
|
4417
4732
|
networkStore.clear(session.name);
|
|
4418
4733
|
commandLogStore.clear(session.name);
|
|
4419
4734
|
} catch {
|
|
4420
4735
|
}
|
|
4421
4736
|
try {
|
|
4422
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
4737
|
+
const { SessionRecorder } = await import("./session-recorder-RTDGURIJ.js");
|
|
4423
4738
|
SessionRecorder.cleanup(session.name);
|
|
4424
4739
|
} catch {
|
|
4425
4740
|
}
|
|
@@ -4434,9 +4749,9 @@ async function closeSessionByName(name) {
|
|
|
4434
4749
|
return false;
|
|
4435
4750
|
}
|
|
4436
4751
|
async function closeAllSessions() {
|
|
4437
|
-
const names =
|
|
4752
|
+
const names = sessions.list().map((s) => `${s.name}(${s.page.url()})`).join(", ");
|
|
4438
4753
|
if (names) logSessionEvent("close_all_sessions", `Closing ${sessions.size} sessions: ${names}`);
|
|
4439
|
-
for (const
|
|
4754
|
+
for (const session of sessions.list()) {
|
|
4440
4755
|
try {
|
|
4441
4756
|
if (!session.isCDP) {
|
|
4442
4757
|
await session.context.close();
|
|
@@ -4445,9 +4760,9 @@ async function closeAllSessions() {
|
|
|
4445
4760
|
await session.browser.close().catch(() => {
|
|
4446
4761
|
});
|
|
4447
4762
|
}
|
|
4448
|
-
sessions.
|
|
4763
|
+
sessions.removeById(session.id);
|
|
4449
4764
|
} catch {
|
|
4450
|
-
sessions.
|
|
4765
|
+
sessions.removeById(session.id);
|
|
4451
4766
|
}
|
|
4452
4767
|
}
|
|
4453
4768
|
}
|
|
@@ -4485,7 +4800,7 @@ async function ensureProcessCanExit() {
|
|
|
4485
4800
|
clearTimeout(idleTimer);
|
|
4486
4801
|
idleTimer = null;
|
|
4487
4802
|
}
|
|
4488
|
-
for (const session of sessions.
|
|
4803
|
+
for (const session of sessions.list()) {
|
|
4489
4804
|
if (session.browser) {
|
|
4490
4805
|
if (session.isCDP) {
|
|
4491
4806
|
await session.browser.close().catch(() => {
|
|
@@ -4511,7 +4826,6 @@ async function ensureProcessCanExit() {
|
|
|
4511
4826
|
|
|
4512
4827
|
export {
|
|
4513
4828
|
createRuleEngine,
|
|
4514
|
-
resolveCDPEndpoint,
|
|
4515
4829
|
touchSession,
|
|
4516
4830
|
findTargetPage,
|
|
4517
4831
|
resolveLaunchOpts,
|