@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
|
@@ -276,35 +276,17 @@ var WebhookNotifier = class {
|
|
|
276
276
|
};
|
|
277
277
|
|
|
278
278
|
// src/config.ts
|
|
279
|
-
import { existsSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
280
|
-
import { join } from "path";
|
|
281
279
|
import { homedir, tmpdir } from "os";
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
const content = readFileSync(filePath, "utf-8");
|
|
288
|
-
return JSON.parse(content);
|
|
289
|
-
} catch {
|
|
290
|
-
return defaultValue;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// src/config.ts
|
|
295
|
-
function getConfigFile() {
|
|
296
|
-
return join(homedir() || tmpdir(), ".xbrowser", "config.json");
|
|
280
|
+
import { join } from "path";
|
|
281
|
+
import { loadConfig as coreLoadConfig, saveConfig as coreSaveConfig } from "@dyyz1993/xcli-core";
|
|
282
|
+
function getConfigSource() {
|
|
283
|
+
return { configDir: join(homedir() || tmpdir(), ".xbrowser") };
|
|
297
284
|
}
|
|
298
285
|
function loadConfig() {
|
|
299
|
-
|
|
300
|
-
if (!existsSync(configFile)) return {};
|
|
301
|
-
return readJsonFile(configFile, {});
|
|
286
|
+
return coreLoadConfig(getConfigSource());
|
|
302
287
|
}
|
|
303
288
|
function saveConfig(config) {
|
|
304
|
-
|
|
305
|
-
const configFile = getConfigFile();
|
|
306
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
307
|
-
writeFileSync2(configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
289
|
+
coreSaveConfig(getConfigSource(), config);
|
|
308
290
|
}
|
|
309
291
|
function getConfigValue(key) {
|
|
310
292
|
return loadConfig()[key];
|
|
@@ -510,7 +492,6 @@ var HumanInteractionManager = class {
|
|
|
510
492
|
};
|
|
511
493
|
|
|
512
494
|
export {
|
|
513
|
-
readJsonFile,
|
|
514
495
|
ScreencastCapturer,
|
|
515
496
|
CaptchaDetector,
|
|
516
497
|
WebhookNotifier,
|
|
@@ -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/cdp-driver/browser.ts
|
|
10
13
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
@@ -244,36 +247,36 @@ function resolveKeyMapping(key) {
|
|
|
244
247
|
return { key, code: key };
|
|
245
248
|
}
|
|
246
249
|
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" }
|
|
250
|
+
Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
|
|
251
|
+
Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
|
|
252
|
+
Escape: { key: "Escape", code: "Escape", keyCode: 27 },
|
|
253
|
+
Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
|
|
254
|
+
Delete: { key: "Delete", code: "Delete", keyCode: 46 },
|
|
255
|
+
Space: { key: " ", code: "Space", text: " ", keyCode: 32 },
|
|
256
|
+
ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
|
|
257
|
+
ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
|
|
258
|
+
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
|
|
259
|
+
ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
|
|
260
|
+
Home: { key: "Home", code: "Home", keyCode: 36 },
|
|
261
|
+
End: { key: "End", code: "End", keyCode: 35 },
|
|
262
|
+
PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
|
|
263
|
+
PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
|
|
264
|
+
Control: { key: "Control", code: "ControlLeft", keyCode: 17 },
|
|
265
|
+
Shift: { key: "Shift", code: "ShiftLeft", keyCode: 16 },
|
|
266
|
+
Alt: { key: "Alt", code: "AltLeft", keyCode: 18 },
|
|
267
|
+
Meta: { key: "Meta", code: "MetaLeft", keyCode: 91 },
|
|
268
|
+
F1: { key: "F1", code: "F1", keyCode: 112 },
|
|
269
|
+
F2: { key: "F2", code: "F2", keyCode: 113 },
|
|
270
|
+
F3: { key: "F3", code: "F3", keyCode: 114 },
|
|
271
|
+
F4: { key: "F4", code: "F4", keyCode: 115 },
|
|
272
|
+
F5: { key: "F5", code: "F5", keyCode: 116 },
|
|
273
|
+
F6: { key: "F6", code: "F6", keyCode: 117 },
|
|
274
|
+
F7: { key: "F7", code: "F7", keyCode: 118 },
|
|
275
|
+
F8: { key: "F8", code: "F8", keyCode: 119 },
|
|
276
|
+
F9: { key: "F9", code: "F9", keyCode: 120 },
|
|
277
|
+
F10: { key: "F10", code: "F10", keyCode: 121 },
|
|
278
|
+
F11: { key: "F11", code: "F11", keyCode: 122 },
|
|
279
|
+
F12: { key: "F12", code: "F12", keyCode: 123 }
|
|
277
280
|
};
|
|
278
281
|
function sleep2(ms) {
|
|
279
282
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -968,7 +971,7 @@ function createXBRequest(page, data) {
|
|
|
968
971
|
}
|
|
969
972
|
};
|
|
970
973
|
}
|
|
971
|
-
function createXBRouteFetch(conn, sessionId, params) {
|
|
974
|
+
function createXBRouteFetch(conn, sessionId, params, emitter) {
|
|
972
975
|
const request = createXBRequest(null, {
|
|
973
976
|
requestId: params.requestId,
|
|
974
977
|
url: params.request.url,
|
|
@@ -1006,6 +1009,16 @@ function createXBRouteFetch(conn, sessionId, params) {
|
|
|
1006
1009
|
responseHeaders: Object.entries(headers).map(([k, v]) => ({ name: k, value: v })),
|
|
1007
1010
|
body: bodyBytes.toString("base64")
|
|
1008
1011
|
}, sessionId);
|
|
1012
|
+
if (emitter) {
|
|
1013
|
+
const responseData = {
|
|
1014
|
+
requestId: params.requestId,
|
|
1015
|
+
status: opts.status ?? 200,
|
|
1016
|
+
url: params.request.url,
|
|
1017
|
+
headers
|
|
1018
|
+
};
|
|
1019
|
+
const response = createXBResponse(responseData, conn, sessionId);
|
|
1020
|
+
emitter.emit("response", response);
|
|
1021
|
+
}
|
|
1009
1022
|
}
|
|
1010
1023
|
};
|
|
1011
1024
|
}
|
|
@@ -1187,7 +1200,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1187
1200
|
);
|
|
1188
1201
|
if (result) return result;
|
|
1189
1202
|
} catch (err) {
|
|
1190
|
-
lastError = err;
|
|
1203
|
+
lastError = err instanceof Error ? err : new Error(errMsg(err));
|
|
1191
1204
|
}
|
|
1192
1205
|
const pollMs = typeof polling === "number" ? polling : 16;
|
|
1193
1206
|
await this.waitForTimeout(pollMs);
|
|
@@ -1227,6 +1240,25 @@ Last error: ${lastError.message}` : "";
|
|
|
1227
1240
|
}
|
|
1228
1241
|
return result.result?.value;
|
|
1229
1242
|
}
|
|
1243
|
+
/** evaluateHandle — evaluates fn and returns a handle for element bounding box */
|
|
1244
|
+
async evaluateHandle(fn, ...args) {
|
|
1245
|
+
let expression;
|
|
1246
|
+
if (typeof fn === "string") {
|
|
1247
|
+
expression = fn;
|
|
1248
|
+
} else {
|
|
1249
|
+
const argStr = args.length > 0 ? `...${JSON.stringify(args)}` : "";
|
|
1250
|
+
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;})()`;
|
|
1251
|
+
}
|
|
1252
|
+
const result = await this.conn.send("Runtime.evaluate", { expression, returnByValue: true }).catch(() => ({ result: { value: null } }));
|
|
1253
|
+
let box = null;
|
|
1254
|
+
try {
|
|
1255
|
+
box = JSON.parse(result.result?.value);
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1258
|
+
return {
|
|
1259
|
+
asElement: () => box ? { boundingBox: async () => box } : null
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1230
1262
|
async $eval(selector, fn, ...args) {
|
|
1231
1263
|
const fnBody = typeof fn === "function" ? fn.toString() : fn;
|
|
1232
1264
|
return this.evaluate(
|
|
@@ -1442,6 +1474,26 @@ Last error: ${lastError.message}` : "";
|
|
|
1442
1474
|
off(event, handler) {
|
|
1443
1475
|
this._emitter.off(event, handler);
|
|
1444
1476
|
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Wait for a one-shot event (Playwright-compatible subset).
|
|
1479
|
+
* Used to listen for 'filechooser', 'dialog', 'popup', 'framenavigated', etc.
|
|
1480
|
+
*/
|
|
1481
|
+
async waitForEvent(event, opts = {}) {
|
|
1482
|
+
const timeout = opts.timeout ?? 3e4;
|
|
1483
|
+
return new Promise((resolve, reject) => {
|
|
1484
|
+
const timer = setTimeout(() => {
|
|
1485
|
+
this._emitter.off(event, handler);
|
|
1486
|
+
reject(new Error(`waitForEvent('${event}') timeout after ${timeout}ms`));
|
|
1487
|
+
}, timeout);
|
|
1488
|
+
const handler = (...args) => {
|
|
1489
|
+
if (opts.predicate && !opts.predicate(...args)) return;
|
|
1490
|
+
clearTimeout(timer);
|
|
1491
|
+
this._emitter.off(event, handler);
|
|
1492
|
+
resolve(args.length === 1 ? args[0] : args);
|
|
1493
|
+
};
|
|
1494
|
+
this._emitter.on(event, handler);
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1445
1497
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
1446
1498
|
async close() {
|
|
1447
1499
|
if (this._closed) return;
|
|
@@ -1558,6 +1610,19 @@ Last error: ${lastError.message}` : "";
|
|
|
1558
1610
|
async _cdpSend(method, params) {
|
|
1559
1611
|
return this.conn.send(method, params, this.sessionId);
|
|
1560
1612
|
}
|
|
1613
|
+
/**
|
|
1614
|
+
* 开启/关闭原生文件选择框拦截(CDP stateful 开关)。
|
|
1615
|
+
*
|
|
1616
|
+
* - enabled=true(执行期默认):点击上传按钮时不弹系统文件框,改发 Page.fileChooserOpened 事件,
|
|
1617
|
+
* page 的 'filechooser' 监听器拿到 chooser 后用 setFiles/setInputFiles 注入文件。
|
|
1618
|
+
* - enabled=false(录制期默认):真实文件选择框正常弹出,用户手动选文件;
|
|
1619
|
+
* 前端 input[type=file] 的 change 事件由 action signal 脚本捕获记录。
|
|
1620
|
+
*
|
|
1621
|
+
* 可多次调用切换状态(CDP 协议是 stateful 的)。
|
|
1622
|
+
*/
|
|
1623
|
+
async setFileDialogInterception(enabled) {
|
|
1624
|
+
await this.conn.send("Page.setInterceptFileChooserDialog", { enabled }, this.sessionId).catch((e) => console.error("[XBPage] setInterceptFileChooserDialog failed:", errMsg(e)));
|
|
1625
|
+
}
|
|
1561
1626
|
/** Subscribe to a CDP event on this page's session. Returns unsubscribe function. */
|
|
1562
1627
|
_subscribe(event, handler) {
|
|
1563
1628
|
return this.conn.subscribe(event, this.sessionId, handler);
|
|
@@ -1605,6 +1670,40 @@ Last error: ${lastError.message}` : "";
|
|
|
1605
1670
|
this._emit("dialog", dialog);
|
|
1606
1671
|
})
|
|
1607
1672
|
);
|
|
1673
|
+
this._subscriptions.push(
|
|
1674
|
+
this.conn.subscribe("Page.fileChooserOpened", this.sessionId, async (params) => {
|
|
1675
|
+
const p = params;
|
|
1676
|
+
let selector = "";
|
|
1677
|
+
try {
|
|
1678
|
+
const result = await this.conn.send("DOM.describeNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1679
|
+
const attrs = result.node?.attributes || [];
|
|
1680
|
+
const idIdx = attrs.indexOf("id");
|
|
1681
|
+
if (idIdx >= 0) selector = "#" + attrs[idIdx + 1];
|
|
1682
|
+
} catch {
|
|
1683
|
+
}
|
|
1684
|
+
if (!selector) {
|
|
1685
|
+
try {
|
|
1686
|
+
const result = await this.conn.send("DOM.resolveNode", { backendNodeId: p.backendNodeId }, this.sessionId);
|
|
1687
|
+
const evalResult = await this.conn.send("Runtime.callFunctionOn", {
|
|
1688
|
+
objectId: result.objectId,
|
|
1689
|
+
functionDeclaration: 'function() { return this.id || this.name || "" }',
|
|
1690
|
+
returnByValue: true
|
|
1691
|
+
}, this.sessionId);
|
|
1692
|
+
if (evalResult.result?.value) selector = "#" + evalResult.result.value;
|
|
1693
|
+
} catch {
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const fileChooser = {
|
|
1697
|
+
selector,
|
|
1698
|
+
isMultiple: p.mode === "selectMultiple",
|
|
1699
|
+
setFiles: async (files) => {
|
|
1700
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
1701
|
+
await this.setInputFiles(selector || 'input[type="file"]', fileArray);
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
this._emit("filechooser", fileChooser);
|
|
1705
|
+
})
|
|
1706
|
+
);
|
|
1608
1707
|
}
|
|
1609
1708
|
setupNetworkEvents() {
|
|
1610
1709
|
this._subscriptions.push(
|
|
@@ -1618,7 +1717,17 @@ Last error: ${lastError.message}` : "";
|
|
|
1618
1717
|
postData: p.request.postData ?? null,
|
|
1619
1718
|
resourceType: p.type
|
|
1620
1719
|
});
|
|
1621
|
-
this._emit("request",
|
|
1720
|
+
this._emit("request", createXBRequest(
|
|
1721
|
+
null,
|
|
1722
|
+
{
|
|
1723
|
+
requestId: p.requestId,
|
|
1724
|
+
url: p.request.url,
|
|
1725
|
+
method: p.request.method,
|
|
1726
|
+
headers: p.request.headers,
|
|
1727
|
+
postData: p.request.postData ?? null,
|
|
1728
|
+
resourceType: p.type
|
|
1729
|
+
}
|
|
1730
|
+
));
|
|
1622
1731
|
this.checkNetworkIdle();
|
|
1623
1732
|
})
|
|
1624
1733
|
);
|
|
@@ -1630,7 +1739,11 @@ Last error: ${lastError.message}` : "";
|
|
|
1630
1739
|
url: p.response.url,
|
|
1631
1740
|
headers: p.response.headers
|
|
1632
1741
|
});
|
|
1633
|
-
this._emit("response",
|
|
1742
|
+
this._emit("response", createXBResponse(
|
|
1743
|
+
{ requestId: p.requestId, status: p.response.status, url: p.response.url, headers: p.response.headers },
|
|
1744
|
+
this.conn,
|
|
1745
|
+
this.sessionId
|
|
1746
|
+
));
|
|
1634
1747
|
})
|
|
1635
1748
|
);
|
|
1636
1749
|
this._subscriptions.push(
|
|
@@ -1716,14 +1829,21 @@ Last error: ${lastError.message}` : "";
|
|
|
1716
1829
|
reject(new Error(`waitForResponse timed out after ${timeout}ms`));
|
|
1717
1830
|
}, timeout);
|
|
1718
1831
|
const handler = (params) => {
|
|
1719
|
-
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1832
|
+
let response;
|
|
1833
|
+
const respObj = params;
|
|
1834
|
+
if (respObj.response) {
|
|
1835
|
+
const data = {
|
|
1836
|
+
requestId: respObj.requestId || "",
|
|
1837
|
+
status: respObj.response.status || 0,
|
|
1838
|
+
url: respObj.response.url || "",
|
|
1839
|
+
headers: respObj.response.headers || {}
|
|
1840
|
+
};
|
|
1841
|
+
response = createXBResponse(data, this.conn, this.sessionId);
|
|
1842
|
+
} else if (typeof params.status === "function") {
|
|
1843
|
+
response = params;
|
|
1844
|
+
} else {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1727
1847
|
if (predicate(response)) {
|
|
1728
1848
|
clearTimeout(timer);
|
|
1729
1849
|
this._emitter.removeListener("response", handler);
|
|
@@ -1747,16 +1867,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1747
1867
|
reject(new Error(`waitForRequest timed out after ${timeout}ms`));
|
|
1748
1868
|
}, timeout);
|
|
1749
1869
|
const handler = (params) => {
|
|
1750
|
-
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1870
|
+
let request;
|
|
1871
|
+
const reqObj = params;
|
|
1872
|
+
if (reqObj.request) {
|
|
1873
|
+
request = createXBRequest(this, {
|
|
1874
|
+
requestId: reqObj.requestId || "",
|
|
1875
|
+
url: reqObj.request.url || "",
|
|
1876
|
+
method: reqObj.request.method || "",
|
|
1877
|
+
headers: reqObj.request.headers || {},
|
|
1878
|
+
postData: reqObj.request.postData ?? null,
|
|
1879
|
+
resourceType: reqObj.type || ""
|
|
1880
|
+
});
|
|
1881
|
+
} else if (typeof params.url === "function") {
|
|
1882
|
+
request = params;
|
|
1883
|
+
} else {
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1760
1886
|
if (predicate(request)) {
|
|
1761
1887
|
clearTimeout(timer);
|
|
1762
1888
|
this._emitter.removeListener("request", handler);
|
|
@@ -1817,7 +1943,18 @@ Last error: ${lastError.message}` : "";
|
|
|
1817
1943
|
const requestUrl = params.request.url;
|
|
1818
1944
|
for (const { regex, handler } of this._routeHandlers) {
|
|
1819
1945
|
if (regex.test(requestUrl)) {
|
|
1820
|
-
|
|
1946
|
+
this._emit("request", createXBRequest(
|
|
1947
|
+
null,
|
|
1948
|
+
{
|
|
1949
|
+
requestId: params.requestId,
|
|
1950
|
+
url: params.request.url,
|
|
1951
|
+
method: params.request.method,
|
|
1952
|
+
headers: params.request.headers,
|
|
1953
|
+
postData: params.request.postData ?? null,
|
|
1954
|
+
resourceType: params.resourceType
|
|
1955
|
+
}
|
|
1956
|
+
));
|
|
1957
|
+
const route = createXBRouteFetch(this.conn, this.sessionId, params, this._emitter);
|
|
1821
1958
|
try {
|
|
1822
1959
|
await handler(route);
|
|
1823
1960
|
} catch {
|
|
@@ -1984,6 +2121,7 @@ var XBContextImpl = class {
|
|
|
1984
2121
|
const page = new XBPageImpl(this.conn, sessionId, targetId, this, this._browser);
|
|
1985
2122
|
await page._init();
|
|
1986
2123
|
this._pages.push(page);
|
|
2124
|
+
this.forwardPageEvents(page);
|
|
1987
2125
|
if (this.options.viewport) {
|
|
1988
2126
|
await page.setViewportSize(this.options.viewport).catch(() => {
|
|
1989
2127
|
});
|
|
@@ -2025,7 +2163,10 @@ var XBContextImpl = class {
|
|
|
2025
2163
|
}
|
|
2026
2164
|
this._browser._removeContext(this.contextId);
|
|
2027
2165
|
}
|
|
2028
|
-
async newCDPSession(
|
|
2166
|
+
async newCDPSession(page) {
|
|
2167
|
+
if (page instanceof XBPageImpl) {
|
|
2168
|
+
return new XBCDPSessionImpl(this.conn, page.sessionId);
|
|
2169
|
+
}
|
|
2029
2170
|
return new XBCDPSessionImpl(this.conn);
|
|
2030
2171
|
}
|
|
2031
2172
|
async addInitScript(script) {
|
|
@@ -2062,7 +2203,31 @@ var XBContextImpl = class {
|
|
|
2062
2203
|
off(event, handler) {
|
|
2063
2204
|
this._emitter.off(event, handler);
|
|
2064
2205
|
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Register a page that was attached to an existing target (discovered via
|
|
2208
|
+
* Target.getTargets). Used by XBBrowserImpl.discoverContexts() to wire up
|
|
2209
|
+
* pages from the user's existing browser session into the context wrapper
|
|
2210
|
+
* so they appear in `context.pages()` and can be reused by plugins.
|
|
2211
|
+
*/
|
|
2212
|
+
_addDiscoveredPage(page) {
|
|
2213
|
+
const exists = this._pages.some((p) => p._targetId === page._targetId);
|
|
2214
|
+
if (exists) return;
|
|
2215
|
+
this._pages.push(page);
|
|
2216
|
+
this.forwardPageEvents(page);
|
|
2217
|
+
}
|
|
2065
2218
|
// ── Private ─────────────────────────────────────────────────
|
|
2219
|
+
/** Forward page-level events (request, response, etc.) to context listeners */
|
|
2220
|
+
forwardPageEvents(page) {
|
|
2221
|
+
const forward = (event) => {
|
|
2222
|
+
page.on(event, (...args) => {
|
|
2223
|
+
this._emitter.emit(event, ...args);
|
|
2224
|
+
});
|
|
2225
|
+
};
|
|
2226
|
+
forward("request");
|
|
2227
|
+
forward("response");
|
|
2228
|
+
forward("requestfailed");
|
|
2229
|
+
forward("requestfinished");
|
|
2230
|
+
}
|
|
2066
2231
|
setupAutoAttach() {
|
|
2067
2232
|
this.targetAttachedHandler = (paramsRaw) => {
|
|
2068
2233
|
const params = paramsRaw;
|
|
@@ -2087,6 +2252,7 @@ var XBContextImpl = class {
|
|
|
2087
2252
|
});
|
|
2088
2253
|
}
|
|
2089
2254
|
this._pages.push(page);
|
|
2255
|
+
this.forwardPageEvents(page);
|
|
2090
2256
|
this._emitter.emit("page", page);
|
|
2091
2257
|
});
|
|
2092
2258
|
};
|
|
@@ -2103,10 +2269,17 @@ var XBBrowserImpl = class {
|
|
|
2103
2269
|
childProcess = null;
|
|
2104
2270
|
tmpDir;
|
|
2105
2271
|
_exitHandler = null;
|
|
2106
|
-
|
|
2272
|
+
/**
|
|
2273
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
2274
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
2275
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
2276
|
+
*/
|
|
2277
|
+
cdpEndpoint;
|
|
2278
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
2107
2279
|
this.conn = conn;
|
|
2108
2280
|
this.childProcess = childProcess ?? null;
|
|
2109
2281
|
this.tmpDir = tmpDir;
|
|
2282
|
+
this.cdpEndpoint = cdpEndpoint;
|
|
2110
2283
|
conn.on("disconnect", () => {
|
|
2111
2284
|
this._disconnected = true;
|
|
2112
2285
|
this._emitter.emit("disconnected");
|
|
@@ -2150,7 +2323,7 @@ var XBBrowserImpl = class {
|
|
|
2150
2323
|
this._exitHandler = null;
|
|
2151
2324
|
}
|
|
2152
2325
|
if (this.childProcess) {
|
|
2153
|
-
const { killChrome: killChrome2 } = await import("./launcher-
|
|
2326
|
+
const { killChrome: killChrome2 } = await import("./launcher-L2JNDB2H.js");
|
|
2154
2327
|
await killChrome2(this.childProcess, this.tmpDir);
|
|
2155
2328
|
}
|
|
2156
2329
|
await this.conn.close();
|
|
@@ -2180,6 +2353,10 @@ var XBBrowserImpl = class {
|
|
|
2180
2353
|
contextId,
|
|
2181
2354
|
context
|
|
2182
2355
|
});
|
|
2356
|
+
if (this.childProcess) {
|
|
2357
|
+
this._enableAutoAttach().catch(() => {
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2183
2360
|
return context;
|
|
2184
2361
|
}
|
|
2185
2362
|
contexts() {
|
|
@@ -2208,6 +2385,23 @@ var XBBrowserImpl = class {
|
|
|
2208
2385
|
async _detachFromTarget(sessionId) {
|
|
2209
2386
|
await this.conn.send("Target.detachFromTarget", { sessionId });
|
|
2210
2387
|
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Derive the HTTP /json base URL from the original cdpEndpoint for use
|
|
2390
|
+
* as a fallback when Target.getTargets doesn't return page targets.
|
|
2391
|
+
* Supports both http:// and ws:// input formats.
|
|
2392
|
+
*/
|
|
2393
|
+
_httpFallbackURL() {
|
|
2394
|
+
if (!this.cdpEndpoint) return void 0;
|
|
2395
|
+
if (this.cdpEndpoint.startsWith("http://") || this.cdpEndpoint.startsWith("https://")) {
|
|
2396
|
+
return this.cdpEndpoint;
|
|
2397
|
+
}
|
|
2398
|
+
if (this.cdpEndpoint.startsWith("ws://") || this.cdpEndpoint.startsWith("wss://")) {
|
|
2399
|
+
const url = this.cdpEndpoint.replace(/^ws/, "http");
|
|
2400
|
+
const slashIdx = url.indexOf("/", url.indexOf("//") + 2);
|
|
2401
|
+
return slashIdx >= 0 ? url.substring(0, slashIdx) : url;
|
|
2402
|
+
}
|
|
2403
|
+
return void 0;
|
|
2404
|
+
}
|
|
2211
2405
|
/** Create a new page target within a browser context */
|
|
2212
2406
|
async _createTarget(contextId, url = "about:blank") {
|
|
2213
2407
|
const params = { url };
|
|
@@ -2224,10 +2418,97 @@ var XBBrowserImpl = class {
|
|
|
2224
2418
|
async _enableAutoAttach() {
|
|
2225
2419
|
await this.conn.send("Target.setAutoAttach", {
|
|
2226
2420
|
autoAttach: true,
|
|
2227
|
-
waitForDebuggerOnStart:
|
|
2421
|
+
waitForDebuggerOnStart: false,
|
|
2228
2422
|
flatten: true
|
|
2229
2423
|
});
|
|
2230
2424
|
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Discover existing browser contexts and pages via Target.getTargets.
|
|
2427
|
+
*
|
|
2428
|
+
* For CDP tunnel connections (cdp-tunnel, attach scenarios), the
|
|
2429
|
+
* Target.attachedToTarget auto-attach flow is unreliable. Without this
|
|
2430
|
+
* call, `b.contexts()` would return [] and callers would fall back to
|
|
2431
|
+
* `b.newContext()` — which creates an isolated context with NO cookies
|
|
2432
|
+
* shared with the user's existing browser session (causing login failures).
|
|
2433
|
+
*
|
|
2434
|
+
* This method:
|
|
2435
|
+
* 1. Queries Target.getTargets to enumerate all page targets
|
|
2436
|
+
* 2. Groups them by browserContextId
|
|
2437
|
+
* 3. Attaches to each existing page via Target.attachToTarget
|
|
2438
|
+
* 4. Wraps the discovered pages in a XBContextImpl and registers it in
|
|
2439
|
+
* this._contexts so `contexts()` returns the user's actual contexts
|
|
2440
|
+
* 5. Enables Target.setAutoAttach for future pages
|
|
2441
|
+
*
|
|
2442
|
+
* No-op for self-launched browsers (they already populated contexts via
|
|
2443
|
+
* newContext() + childProcess-gated auto-attach).
|
|
2444
|
+
*/
|
|
2445
|
+
async discoverContexts() {
|
|
2446
|
+
if (this._disconnected) return;
|
|
2447
|
+
let targetInfos = [];
|
|
2448
|
+
try {
|
|
2449
|
+
const result = await this.conn.send(
|
|
2450
|
+
"Target.getTargets"
|
|
2451
|
+
);
|
|
2452
|
+
targetInfos = result.targetInfos ?? [];
|
|
2453
|
+
} catch {
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
const pageTargets = targetInfos.filter((t) => t.type === "page");
|
|
2457
|
+
const httpFallbackUrl = this._httpFallbackURL();
|
|
2458
|
+
if (pageTargets.length === 0 && httpFallbackUrl) {
|
|
2459
|
+
console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
|
|
2460
|
+
try {
|
|
2461
|
+
const { getCDPTargets: getCDPTargets2 } = await import("./launcher-L2JNDB2H.js");
|
|
2462
|
+
const httpPages = await getCDPTargets2(httpFallbackUrl);
|
|
2463
|
+
console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
|
|
2464
|
+
for (const p of httpPages) {
|
|
2465
|
+
if (p.type !== "page") continue;
|
|
2466
|
+
if (!p.url || p.url.startsWith("chrome://") || p.url.startsWith("devtools://")) continue;
|
|
2467
|
+
targetInfos.push({
|
|
2468
|
+
targetId: p.id,
|
|
2469
|
+
type: "page",
|
|
2470
|
+
url: p.url,
|
|
2471
|
+
title: p.title
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
console.log(`[discoverContexts] After HTTP fallback: ${targetInfos.length} total targets, ${targetInfos.filter((t) => t.type === "page").length} pages`);
|
|
2475
|
+
} catch (err) {
|
|
2476
|
+
console.log(`[discoverContexts] HTTP fallback failed: ${errMsg(err)}`);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
const pagesByContext = /* @__PURE__ */ new Map();
|
|
2480
|
+
for (const t of targetInfos) {
|
|
2481
|
+
if (t.type !== "page") continue;
|
|
2482
|
+
if (!t.url || t.url.startsWith("chrome://") || t.url.startsWith("devtools://")) {
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2485
|
+
const ctxId = t.browserContextId || "default";
|
|
2486
|
+
if (!pagesByContext.has(ctxId)) pagesByContext.set(ctxId, []);
|
|
2487
|
+
pagesByContext.get(ctxId).push(t);
|
|
2488
|
+
}
|
|
2489
|
+
for (const [ctxId, pages] of pagesByContext) {
|
|
2490
|
+
if (this._contexts.has(ctxId)) continue;
|
|
2491
|
+
const context = new XBContextImpl(this.conn, ctxId, this, {});
|
|
2492
|
+
for (const p of pages) {
|
|
2493
|
+
try {
|
|
2494
|
+
const sessionId = await this._attachToTarget(p.targetId);
|
|
2495
|
+
const page = new XBPageImpl(this.conn, sessionId, p.targetId, context, this);
|
|
2496
|
+
await page._init();
|
|
2497
|
+
context._addDiscoveredPage(page);
|
|
2498
|
+
} catch {
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
this._contexts.set(ctxId, { contextId: ctxId, context });
|
|
2502
|
+
}
|
|
2503
|
+
try {
|
|
2504
|
+
await this.conn.send("Target.setAutoAttach", {
|
|
2505
|
+
autoAttach: true,
|
|
2506
|
+
waitForDebuggerOnStart: false,
|
|
2507
|
+
flatten: true
|
|
2508
|
+
});
|
|
2509
|
+
} catch {
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2231
2512
|
};
|
|
2232
2513
|
|
|
2233
2514
|
// src/cdp-driver/connection.ts
|
|
@@ -2507,7 +2788,8 @@ async function launch(options = {}) {
|
|
|
2507
2788
|
}
|
|
2508
2789
|
const conn = new CDPConnection(wsEndpoint);
|
|
2509
2790
|
await conn.ready();
|
|
2510
|
-
const
|
|
2791
|
+
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
2792
|
+
const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
|
|
2511
2793
|
return { browser, wsEndpoint };
|
|
2512
2794
|
}
|
|
2513
2795
|
|