@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.
Files changed (48) hide show
  1. package/README.md +17 -26
  2. package/dist/{browser-DSVV4GHS.js → browser-5CTOA2WS.js} +4 -3
  3. package/dist/{browser-53KUFEEM.js → browser-ITLZZDHJ.js} +5 -5
  4. package/dist/{browser-GURRY444.js → browser-IUJXXNBT.js} +6 -3
  5. package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-4X3DK6PS.js} +339 -59
  6. package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-D6WMSMWX.js} +4 -3
  7. package/dist/chunk-2SVQTI2O.js +2794 -0
  8. package/dist/{chunk-IDVD44ED.js → chunk-6WOSXSCQ.js} +23 -7
  9. package/dist/{chunk-ZZ2TFWIV.js → chunk-ABXMBNQ6.js} +1 -1
  10. package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
  11. package/dist/chunk-AMI64BSD.js +268 -0
  12. package/dist/{chunk-E4O5ZU3H.js → chunk-DKWR54XQ.js} +412 -98
  13. package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
  14. package/dist/chunk-GDKLH7ZY.js +8 -0
  15. package/dist/chunk-KFQGP6VL.js +33 -0
  16. package/dist/{chunk-2BQZIT3S.js → chunk-LRBSUKUZ.js} +85 -2497
  17. package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
  18. package/dist/{chunk-42RPMJ76.js → chunk-N2JFPWMI.js} +342 -60
  19. package/dist/chunk-OZKD3W4X.js +417 -0
  20. package/dist/{chunk-T4J4C2NZ.js → chunk-TNEN6VQ2.js} +17 -4
  21. package/dist/{chunk-YKOHDEFV.js → chunk-TWWOIJM7.js} +74 -38
  22. package/dist/chunk-WJRE55TN.js +83 -0
  23. package/dist/cli.js +1558 -1122
  24. package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
  25. package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
  26. package/dist/{daemon-client-YAVQ343A.js → daemon-client-3JOKX2L2.js} +3 -2
  27. package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DIEHGP5B.js} +28 -74
  28. package/dist/daemon-main.js +2296 -1722
  29. package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
  30. package/dist/{extract-L2IW3IUB.js → extract-BSYBM4MR.js} +1 -1
  31. package/dist/{filter-HC4RA7JY.js → filter-KCFO4RSV.js} +1 -1
  32. package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
  33. package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
  34. package/dist/index.d.ts +166 -109
  35. package/dist/index.js +2668 -1742
  36. package/dist/launcher-L2JNDB2H.js +20 -0
  37. package/dist/{launcher-KA7J32K5.js → launcher-OZXJQPNG.js} +1 -1
  38. package/dist/{network-store-66A2RATI.js → network-store-XGZ25FFC.js} +1 -1
  39. package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
  40. package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
  41. package/dist/{proxy-WKGUCH2C.js → proxy-C6CK3UH5.js} +2 -2
  42. package/dist/session-recorder-RTDGURIJ.js +8 -0
  43. package/dist/session-recorder-YI7YYM36.js +7 -0
  44. package/dist/session-replayer-MY27H4DX.js +276 -0
  45. package/dist/site-knowledge-SYC6VCDB.js +23 -0
  46. package/package.json +5 -4
  47. package/dist/screenshot-CWAWMXVA.js +0 -28
  48. 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
- // src/utils/json-file.ts
284
- import { readFileSync, writeFileSync } from "fs";
285
- function readJsonFile(filePath, defaultValue) {
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
- const configFile = getConfigFile();
300
- if (!existsSync(configFile)) return {};
301
- return readJsonFile(configFile, {});
286
+ return coreLoadConfig(getConfigSource());
302
287
  }
303
288
  function saveConfig(config) {
304
- const dir = join(homedir() || tmpdir(), ".xbrowser");
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-T4J4C2NZ.js";
4
+ } from "./chunk-TNEN6VQ2.js";
5
+ import {
6
+ errMsg
7
+ } from "./chunk-GDKLH7ZY.js";
5
8
  import {
6
9
  __require
7
- } from "./chunk-3RG5ZIWI.js";
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", p);
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", p);
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
- const p = params;
1720
- const data = {
1721
- requestId: p.requestId,
1722
- status: p.response.status,
1723
- url: p.response.url,
1724
- headers: p.response.headers
1725
- };
1726
- const response = createXBResponse(data, this.conn, this.sessionId);
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
- const p = params;
1751
- const data = {
1752
- requestId: p.requestId,
1753
- url: p.request.url,
1754
- method: p.request.method,
1755
- headers: p.request.headers,
1756
- postData: p.request.postData ?? null,
1757
- resourceType: p.type
1758
- };
1759
- const request = createXBRequest(this, data);
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
- const route = createXBRouteFetch(this.conn, this.sessionId, params);
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(_page) {
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
- constructor(conn, childProcess, tmpDir) {
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-KA7J32K5.js");
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: true,
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 browser = new XBBrowserImpl(conn, childProcess, tmpDir);
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