@xbrowser/cli 1.0.0 → 1.0.2

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