@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/browser.ts
10
10
  import { randomUUID } from "crypto";
@@ -250,36 +250,36 @@ function resolveKeyMapping(key) {
250
250
  return { key, code: key };
251
251
  }
252
252
  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" }
253
+ Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
254
+ Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
255
+ Escape: { key: "Escape", code: "Escape", keyCode: 27 },
256
+ Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
257
+ Delete: { key: "Delete", code: "Delete", keyCode: 46 },
258
+ Space: { key: " ", code: "Space", text: " ", keyCode: 32 },
259
+ ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
260
+ ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
261
+ ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
262
+ ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
263
+ Home: { key: "Home", code: "Home", keyCode: 36 },
264
+ End: { key: "End", code: "End", keyCode: 35 },
265
+ PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
266
+ PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
267
+ Control: { key: "Control", code: "ControlLeft", keyCode: 17 },
268
+ Shift: { key: "Shift", code: "ShiftLeft", keyCode: 16 },
269
+ Alt: { key: "Alt", code: "AltLeft", keyCode: 18 },
270
+ Meta: { key: "Meta", code: "MetaLeft", keyCode: 91 },
271
+ F1: { key: "F1", code: "F1", keyCode: 112 },
272
+ F2: { key: "F2", code: "F2", keyCode: 113 },
273
+ F3: { key: "F3", code: "F3", keyCode: 114 },
274
+ F4: { key: "F4", code: "F4", keyCode: 115 },
275
+ F5: { key: "F5", code: "F5", keyCode: 116 },
276
+ F6: { key: "F6", code: "F6", keyCode: 117 },
277
+ F7: { key: "F7", code: "F7", keyCode: 118 },
278
+ F8: { key: "F8", code: "F8", keyCode: 119 },
279
+ F9: { key: "F9", code: "F9", keyCode: 120 },
280
+ F10: { key: "F10", code: "F10", keyCode: 121 },
281
+ F11: { key: "F11", code: "F11", keyCode: 122 },
282
+ F12: { key: "F12", code: "F12", keyCode: 123 }
283
283
  };
284
284
  function sleep2(ms) {
285
285
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -974,7 +974,7 @@ function createXBRequest(page, data) {
974
974
  }
975
975
  };
976
976
  }
977
- function createXBRouteFetch(conn, sessionId, params) {
977
+ function createXBRouteFetch(conn, sessionId, params, emitter) {
978
978
  const request = createXBRequest(null, {
979
979
  requestId: params.requestId,
980
980
  url: params.request.url,
@@ -1012,6 +1012,16 @@ function createXBRouteFetch(conn, sessionId, params) {
1012
1012
  responseHeaders: Object.entries(headers).map(([k, v]) => ({ name: k, value: v })),
1013
1013
  body: bodyBytes.toString("base64")
1014
1014
  }, sessionId);
1015
+ if (emitter) {
1016
+ const responseData = {
1017
+ requestId: params.requestId,
1018
+ status: opts.status ?? 200,
1019
+ url: params.request.url,
1020
+ headers
1021
+ };
1022
+ const response = createXBResponse(responseData, conn, sessionId);
1023
+ emitter.emit("response", response);
1024
+ }
1015
1025
  }
1016
1026
  };
1017
1027
  }
@@ -1233,6 +1243,25 @@ Last error: ${lastError.message}` : "";
1233
1243
  }
1234
1244
  return result.result?.value;
1235
1245
  }
1246
+ /** evaluateHandle — evaluates fn and returns a handle for element bounding box */
1247
+ async evaluateHandle(fn, ...args) {
1248
+ let expression;
1249
+ if (typeof fn === "string") {
1250
+ expression = fn;
1251
+ } else {
1252
+ const argStr = args.length > 0 ? `...${JSON.stringify(args)}` : "";
1253
+ 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;})()`;
1254
+ }
1255
+ const result = await this.conn.send("Runtime.evaluate", { expression, returnByValue: true }).catch(() => ({ result: { value: null } }));
1256
+ let box = null;
1257
+ try {
1258
+ box = JSON.parse(result.result?.value);
1259
+ } catch {
1260
+ }
1261
+ return {
1262
+ asElement: () => box ? { boundingBox: async () => box } : null
1263
+ };
1264
+ }
1236
1265
  async $eval(selector, fn, ...args) {
1237
1266
  const fnBody = typeof fn === "function" ? fn.toString() : fn;
1238
1267
  return this.evaluate(
@@ -1448,6 +1477,26 @@ Last error: ${lastError.message}` : "";
1448
1477
  off(event, handler) {
1449
1478
  this._emitter.off(event, handler);
1450
1479
  }
1480
+ /**
1481
+ * Wait for a one-shot event (Playwright-compatible subset).
1482
+ * Used to listen for 'filechooser', 'dialog', 'popup', 'framenavigated', etc.
1483
+ */
1484
+ async waitForEvent(event, opts = {}) {
1485
+ const timeout = opts.timeout ?? 3e4;
1486
+ return new Promise((resolve, reject) => {
1487
+ const timer = setTimeout(() => {
1488
+ this._emitter.off(event, handler);
1489
+ reject(new Error(`waitForEvent('${event}') timeout after ${timeout}ms`));
1490
+ }, timeout);
1491
+ const handler = (...args) => {
1492
+ if (opts.predicate && !opts.predicate(...args)) return;
1493
+ clearTimeout(timer);
1494
+ this._emitter.off(event, handler);
1495
+ resolve(args.length === 1 ? args[0] : args);
1496
+ };
1497
+ this._emitter.on(event, handler);
1498
+ });
1499
+ }
1451
1500
  // ── Lifecycle ───────────────────────────────────────────────
1452
1501
  async close() {
1453
1502
  if (this._closed) return;
@@ -1611,6 +1660,40 @@ Last error: ${lastError.message}` : "";
1611
1660
  this._emit("dialog", dialog);
1612
1661
  })
1613
1662
  );
1663
+ this._subscriptions.push(
1664
+ this.conn.subscribe("Page.fileChooserOpened", this.sessionId, async (params) => {
1665
+ const p = params;
1666
+ let selector = "";
1667
+ try {
1668
+ const result = await this.conn.send("DOM.describeNode", { backendNodeId: p.backendNodeId }, this.sessionId);
1669
+ const attrs = result.node?.attributes || [];
1670
+ const idIdx = attrs.indexOf("id");
1671
+ if (idIdx >= 0) selector = "#" + attrs[idIdx + 1];
1672
+ } catch {
1673
+ }
1674
+ if (!selector) {
1675
+ try {
1676
+ const result = await this.conn.send("DOM.resolveNode", { backendNodeId: p.backendNodeId }, this.sessionId);
1677
+ const evalResult = await this.conn.send("Runtime.callFunctionOn", {
1678
+ objectId: result.objectId,
1679
+ functionDeclaration: 'function() { return this.id || this.name || "" }',
1680
+ returnByValue: true
1681
+ }, this.sessionId);
1682
+ if (evalResult.result?.value) selector = "#" + evalResult.result.value;
1683
+ } catch {
1684
+ }
1685
+ }
1686
+ const fileChooser = {
1687
+ selector,
1688
+ isMultiple: p.mode === "selectMultiple",
1689
+ setFiles: async (files) => {
1690
+ const fileArray = Array.isArray(files) ? files : [files];
1691
+ await this.setInputFiles(selector || 'input[type="file"]', fileArray);
1692
+ }
1693
+ };
1694
+ this._emit("filechooser", fileChooser);
1695
+ })
1696
+ );
1614
1697
  }
1615
1698
  setupNetworkEvents() {
1616
1699
  this._subscriptions.push(
@@ -1624,7 +1707,17 @@ Last error: ${lastError.message}` : "";
1624
1707
  postData: p.request.postData ?? null,
1625
1708
  resourceType: p.type
1626
1709
  });
1627
- this._emit("request", p);
1710
+ this._emit("request", createXBRequest(
1711
+ null,
1712
+ {
1713
+ requestId: p.requestId,
1714
+ url: p.request.url,
1715
+ method: p.request.method,
1716
+ headers: p.request.headers,
1717
+ postData: p.request.postData ?? null,
1718
+ resourceType: p.type
1719
+ }
1720
+ ));
1628
1721
  this.checkNetworkIdle();
1629
1722
  })
1630
1723
  );
@@ -1636,7 +1729,11 @@ Last error: ${lastError.message}` : "";
1636
1729
  url: p.response.url,
1637
1730
  headers: p.response.headers
1638
1731
  });
1639
- this._emit("response", p);
1732
+ this._emit("response", createXBResponse(
1733
+ { requestId: p.requestId, status: p.response.status, url: p.response.url, headers: p.response.headers },
1734
+ this.conn,
1735
+ this.sessionId
1736
+ ));
1640
1737
  })
1641
1738
  );
1642
1739
  this._subscriptions.push(
@@ -1722,14 +1819,21 @@ Last error: ${lastError.message}` : "";
1722
1819
  reject(new Error(`waitForResponse timed out after ${timeout}ms`));
1723
1820
  }, timeout);
1724
1821
  const handler = (params) => {
1725
- const p = params;
1726
- const data = {
1727
- requestId: p.requestId,
1728
- status: p.response.status,
1729
- url: p.response.url,
1730
- headers: p.response.headers
1731
- };
1732
- const response = createXBResponse(data, this.conn, this.sessionId);
1822
+ let response;
1823
+ const respObj = params;
1824
+ if (respObj.response) {
1825
+ const data = {
1826
+ requestId: respObj.requestId || "",
1827
+ status: respObj.response.status || 0,
1828
+ url: respObj.response.url || "",
1829
+ headers: respObj.response.headers || {}
1830
+ };
1831
+ response = createXBResponse(data, this.conn, this.sessionId);
1832
+ } else if (typeof params.status === "function") {
1833
+ response = params;
1834
+ } else {
1835
+ return;
1836
+ }
1733
1837
  if (predicate(response)) {
1734
1838
  clearTimeout(timer);
1735
1839
  this._emitter.removeListener("response", handler);
@@ -1753,16 +1857,22 @@ Last error: ${lastError.message}` : "";
1753
1857
  reject(new Error(`waitForRequest timed out after ${timeout}ms`));
1754
1858
  }, timeout);
1755
1859
  const handler = (params) => {
1756
- const p = params;
1757
- const data = {
1758
- requestId: p.requestId,
1759
- url: p.request.url,
1760
- method: p.request.method,
1761
- headers: p.request.headers,
1762
- postData: p.request.postData ?? null,
1763
- resourceType: p.type
1764
- };
1765
- const request = createXBRequest(this, data);
1860
+ let request;
1861
+ const reqObj = params;
1862
+ if (reqObj.request) {
1863
+ request = createXBRequest(this, {
1864
+ requestId: reqObj.requestId || "",
1865
+ url: reqObj.request.url || "",
1866
+ method: reqObj.request.method || "",
1867
+ headers: reqObj.request.headers || {},
1868
+ postData: reqObj.request.postData ?? null,
1869
+ resourceType: reqObj.type || ""
1870
+ });
1871
+ } else if (typeof params.url === "function") {
1872
+ request = params;
1873
+ } else {
1874
+ return;
1875
+ }
1766
1876
  if (predicate(request)) {
1767
1877
  clearTimeout(timer);
1768
1878
  this._emitter.removeListener("request", handler);
@@ -1823,7 +1933,18 @@ Last error: ${lastError.message}` : "";
1823
1933
  const requestUrl = params.request.url;
1824
1934
  for (const { regex, handler } of this._routeHandlers) {
1825
1935
  if (regex.test(requestUrl)) {
1826
- const route = createXBRouteFetch(this.conn, this.sessionId, params);
1936
+ this._emit("request", createXBRequest(
1937
+ null,
1938
+ {
1939
+ requestId: params.requestId,
1940
+ url: params.request.url,
1941
+ method: params.request.method,
1942
+ headers: params.request.headers,
1943
+ postData: params.request.postData ?? null,
1944
+ resourceType: params.resourceType
1945
+ }
1946
+ ));
1947
+ const route = createXBRouteFetch(this.conn, this.sessionId, params, this._emitter);
1827
1948
  try {
1828
1949
  await handler(route);
1829
1950
  } catch {
@@ -1990,6 +2111,7 @@ var XBContextImpl = class {
1990
2111
  const page = new XBPageImpl(this.conn, sessionId, targetId, this, this._browser);
1991
2112
  await page._init();
1992
2113
  this._pages.push(page);
2114
+ this.forwardPageEvents(page);
1993
2115
  if (this.options.viewport) {
1994
2116
  await page.setViewportSize(this.options.viewport).catch(() => {
1995
2117
  });
@@ -2031,7 +2153,10 @@ var XBContextImpl = class {
2031
2153
  }
2032
2154
  this._browser._removeContext(this.contextId);
2033
2155
  }
2034
- async newCDPSession(_page) {
2156
+ async newCDPSession(page) {
2157
+ if (page instanceof XBPageImpl) {
2158
+ return new XBCDPSessionImpl(this.conn, page.sessionId);
2159
+ }
2035
2160
  return new XBCDPSessionImpl(this.conn);
2036
2161
  }
2037
2162
  async addInitScript(script) {
@@ -2068,7 +2193,31 @@ var XBContextImpl = class {
2068
2193
  off(event, handler) {
2069
2194
  this._emitter.off(event, handler);
2070
2195
  }
2196
+ /**
2197
+ * Register a page that was attached to an existing target (discovered via
2198
+ * Target.getTargets). Used by XBBrowserImpl.discoverContexts() to wire up
2199
+ * pages from the user's existing browser session into the context wrapper
2200
+ * so they appear in `context.pages()` and can be reused by plugins.
2201
+ */
2202
+ _addDiscoveredPage(page) {
2203
+ const exists = this._pages.some((p) => p._targetId === page._targetId);
2204
+ if (exists) return;
2205
+ this._pages.push(page);
2206
+ this.forwardPageEvents(page);
2207
+ }
2071
2208
  // ── Private ─────────────────────────────────────────────────
2209
+ /** Forward page-level events (request, response, etc.) to context listeners */
2210
+ forwardPageEvents(page) {
2211
+ const forward = (event) => {
2212
+ page.on(event, (...args) => {
2213
+ this._emitter.emit(event, ...args);
2214
+ });
2215
+ };
2216
+ forward("request");
2217
+ forward("response");
2218
+ forward("requestfailed");
2219
+ forward("requestfinished");
2220
+ }
2072
2221
  setupAutoAttach() {
2073
2222
  this.targetAttachedHandler = (paramsRaw) => {
2074
2223
  const params = paramsRaw;
@@ -2093,6 +2242,7 @@ var XBContextImpl = class {
2093
2242
  });
2094
2243
  }
2095
2244
  this._pages.push(page);
2245
+ this.forwardPageEvents(page);
2096
2246
  this._emitter.emit("page", page);
2097
2247
  });
2098
2248
  };
@@ -2109,10 +2259,17 @@ var XBBrowserImpl = class {
2109
2259
  childProcess = null;
2110
2260
  tmpDir;
2111
2261
  _exitHandler = null;
2112
- constructor(conn, childProcess, tmpDir) {
2262
+ /**
2263
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
2264
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
2265
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
2266
+ */
2267
+ cdpEndpoint;
2268
+ constructor(conn, childProcess, tmpDir, cdpEndpoint) {
2113
2269
  this.conn = conn;
2114
2270
  this.childProcess = childProcess ?? null;
2115
2271
  this.tmpDir = tmpDir;
2272
+ this.cdpEndpoint = cdpEndpoint;
2116
2273
  conn.on("disconnect", () => {
2117
2274
  this._disconnected = true;
2118
2275
  this._emitter.emit("disconnected");
@@ -2156,7 +2313,7 @@ var XBBrowserImpl = class {
2156
2313
  this._exitHandler = null;
2157
2314
  }
2158
2315
  if (this.childProcess) {
2159
- const { killChrome: killChrome2 } = await import("./launcher-KA7J32K5.js");
2316
+ const { killChrome: killChrome2 } = await import("./launcher-QUJ4M2VS.js");
2160
2317
  await killChrome2(this.childProcess, this.tmpDir);
2161
2318
  }
2162
2319
  await this.conn.close();
@@ -2186,6 +2343,10 @@ var XBBrowserImpl = class {
2186
2343
  contextId,
2187
2344
  context
2188
2345
  });
2346
+ if (this.childProcess) {
2347
+ this._enableAutoAttach().catch(() => {
2348
+ });
2349
+ }
2189
2350
  return context;
2190
2351
  }
2191
2352
  contexts() {
@@ -2214,6 +2375,23 @@ var XBBrowserImpl = class {
2214
2375
  async _detachFromTarget(sessionId) {
2215
2376
  await this.conn.send("Target.detachFromTarget", { sessionId });
2216
2377
  }
2378
+ /**
2379
+ * Derive the HTTP /json base URL from the original cdpEndpoint for use
2380
+ * as a fallback when Target.getTargets doesn't return page targets.
2381
+ * Supports both http:// and ws:// input formats.
2382
+ */
2383
+ _httpFallbackURL() {
2384
+ if (!this.cdpEndpoint) return void 0;
2385
+ if (this.cdpEndpoint.startsWith("http://") || this.cdpEndpoint.startsWith("https://")) {
2386
+ return this.cdpEndpoint;
2387
+ }
2388
+ if (this.cdpEndpoint.startsWith("ws://") || this.cdpEndpoint.startsWith("wss://")) {
2389
+ const url = this.cdpEndpoint.replace(/^ws/, "http");
2390
+ const slashIdx = url.indexOf("/", url.indexOf("//") + 2);
2391
+ return slashIdx >= 0 ? url.substring(0, slashIdx) : url;
2392
+ }
2393
+ return void 0;
2394
+ }
2217
2395
  /** Create a new page target within a browser context */
2218
2396
  async _createTarget(contextId, url = "about:blank") {
2219
2397
  const params = { url };
@@ -2230,10 +2408,97 @@ var XBBrowserImpl = class {
2230
2408
  async _enableAutoAttach() {
2231
2409
  await this.conn.send("Target.setAutoAttach", {
2232
2410
  autoAttach: true,
2233
- waitForDebuggerOnStart: true,
2411
+ waitForDebuggerOnStart: false,
2234
2412
  flatten: true
2235
2413
  });
2236
2414
  }
2415
+ /**
2416
+ * Discover existing browser contexts and pages via Target.getTargets.
2417
+ *
2418
+ * For CDP tunnel connections (cdp-tunnel, attach scenarios), the
2419
+ * Target.attachedToTarget auto-attach flow is unreliable. Without this
2420
+ * call, `b.contexts()` would return [] and callers would fall back to
2421
+ * `b.newContext()` — which creates an isolated context with NO cookies
2422
+ * shared with the user's existing browser session (causing login failures).
2423
+ *
2424
+ * This method:
2425
+ * 1. Queries Target.getTargets to enumerate all page targets
2426
+ * 2. Groups them by browserContextId
2427
+ * 3. Attaches to each existing page via Target.attachToTarget
2428
+ * 4. Wraps the discovered pages in a XBContextImpl and registers it in
2429
+ * this._contexts so `contexts()` returns the user's actual contexts
2430
+ * 5. Enables Target.setAutoAttach for future pages
2431
+ *
2432
+ * No-op for self-launched browsers (they already populated contexts via
2433
+ * newContext() + childProcess-gated auto-attach).
2434
+ */
2435
+ async discoverContexts() {
2436
+ if (this._disconnected) return;
2437
+ let targetInfos = [];
2438
+ try {
2439
+ const result = await this.conn.send(
2440
+ "Target.getTargets"
2441
+ );
2442
+ targetInfos = result.targetInfos ?? [];
2443
+ } catch {
2444
+ return;
2445
+ }
2446
+ const pageTargets = targetInfos.filter((t) => t.type === "page");
2447
+ const httpFallbackUrl = this._httpFallbackURL();
2448
+ if (pageTargets.length === 0 && httpFallbackUrl) {
2449
+ console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
2450
+ try {
2451
+ const { getCDPTargets: getCDPTargets3 } = await import("./launcher-QUJ4M2VS.js");
2452
+ const httpPages = await getCDPTargets3(httpFallbackUrl);
2453
+ console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
2454
+ for (const p of httpPages) {
2455
+ if (p.type !== "page") continue;
2456
+ if (!p.url || p.url.startsWith("chrome://") || p.url.startsWith("devtools://")) continue;
2457
+ targetInfos.push({
2458
+ targetId: p.id,
2459
+ type: "page",
2460
+ url: p.url,
2461
+ title: p.title
2462
+ });
2463
+ }
2464
+ console.log(`[discoverContexts] After HTTP fallback: ${targetInfos.length} total targets, ${targetInfos.filter((t) => t.type === "page").length} pages`);
2465
+ } catch (err) {
2466
+ console.log(`[discoverContexts] HTTP fallback failed: ${err.message}`);
2467
+ }
2468
+ }
2469
+ const pagesByContext = /* @__PURE__ */ new Map();
2470
+ for (const t of targetInfos) {
2471
+ if (t.type !== "page") continue;
2472
+ if (!t.url || t.url.startsWith("chrome://") || t.url.startsWith("devtools://")) {
2473
+ continue;
2474
+ }
2475
+ const ctxId = t.browserContextId || "default";
2476
+ if (!pagesByContext.has(ctxId)) pagesByContext.set(ctxId, []);
2477
+ pagesByContext.get(ctxId).push(t);
2478
+ }
2479
+ for (const [ctxId, pages] of pagesByContext) {
2480
+ if (this._contexts.has(ctxId)) continue;
2481
+ const context = new XBContextImpl(this.conn, ctxId, this, {});
2482
+ for (const p of pages) {
2483
+ try {
2484
+ const sessionId = await this._attachToTarget(p.targetId);
2485
+ const page = new XBPageImpl(this.conn, sessionId, p.targetId, context, this);
2486
+ await page._init();
2487
+ context._addDiscoveredPage(page);
2488
+ } catch {
2489
+ }
2490
+ }
2491
+ this._contexts.set(ctxId, { contextId: ctxId, context });
2492
+ }
2493
+ try {
2494
+ await this.conn.send("Target.setAutoAttach", {
2495
+ autoAttach: true,
2496
+ waitForDebuggerOnStart: false,
2497
+ flatten: true
2498
+ });
2499
+ } catch {
2500
+ }
2501
+ }
2237
2502
  };
2238
2503
 
2239
2504
  // src/cdp-driver/connection.ts
@@ -2453,7 +2718,8 @@ async function launch(options = {}) {
2453
2718
  }
2454
2719
  const conn = new CDPConnection(wsEndpoint);
2455
2720
  await conn.ready();
2456
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir);
2721
+ const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
2722
+ const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
2457
2723
  return { browser, wsEndpoint };
2458
2724
  }
2459
2725
 
@@ -3879,6 +4145,7 @@ async function resolveCDPEndpoint(raw) {
3879
4145
  }
3880
4146
 
3881
4147
  // src/browser.ts
4148
+ import { SessionStore } from "@dyyz1993/xcli-core";
3882
4149
  function logSessionEvent(event, details) {
3883
4150
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
3884
4151
  const pid = process.pid;
@@ -3891,7 +4158,7 @@ function sessionFile(name) {
3891
4158
  function ensureSessionDir() {
3892
4159
  mkdirSync(SESSION_DIR, { recursive: true });
3893
4160
  }
3894
- var sessions = /* @__PURE__ */ new Map();
4161
+ var sessions = new SessionStore();
3895
4162
  var _sharedBrowser = null;
3896
4163
  var _sharedCdpProxy = null;
3897
4164
  var IDLE_TIMEOUT_MS = (process.env.XBROWSER_IDLE_TIMEOUT ? parseInt(process.env.XBROWSER_IDLE_TIMEOUT, 10) : 30) * 60 * 1e3;
@@ -3902,7 +4169,7 @@ function resetIdleTimer() {
3902
4169
  const now = Date.now();
3903
4170
  let allIdle = true;
3904
4171
  const idleSessions = [];
3905
- for (const [, s] of sessions) {
4172
+ for (const s of sessions) {
3906
4173
  if (now - s.lastActivityAt < IDLE_TIMEOUT_MS) {
3907
4174
  allIdle = false;
3908
4175
  } else {
@@ -3925,7 +4192,7 @@ function touchSession(id) {
3925
4192
  resetIdleTimer();
3926
4193
  }
3927
4194
  process.on("exit", () => {
3928
- for (const session of sessions.values()) {
4195
+ for (const session of sessions.list()) {
3929
4196
  if (session.isCDP) {
3930
4197
  logSessionEvent("process_exit", `Session "${session.name}": CDP connection (not closing external browser).`);
3931
4198
  } else {
@@ -4015,6 +4282,9 @@ async function createBrowser(options) {
4015
4282
  return browser3;
4016
4283
  }
4017
4284
  const { browser: browser2 } = await launch({ cdpEndpoint: realEndpoint });
4285
+ await browser2.discoverContexts().catch((err) => {
4286
+ console.error(`[browser] discoverContexts failed: ${err.message}`);
4287
+ });
4018
4288
  return browser2;
4019
4289
  }
4020
4290
  const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
@@ -4029,10 +4299,7 @@ async function getBrowser(options) {
4029
4299
  return _sharedBrowser;
4030
4300
  }
4031
4301
  function findSession(name) {
4032
- for (const [, session] of sessions) {
4033
- if (session.name === name) return session;
4034
- }
4035
- return void 0;
4302
+ return sessions.find(name);
4036
4303
  }
4037
4304
  function getSessionById(id) {
4038
4305
  return sessions.get(id);
@@ -4136,9 +4403,14 @@ async function findOrRestoreSession(name, cdpEndpoint) {
4136
4403
  return void 0;
4137
4404
  }
4138
4405
  const targetUrl = meta.conversationUrl || meta.url;
4139
- if (targetUrl && page.url() !== targetUrl && !page.url().includes(new URL(targetUrl).hostname)) {
4140
- await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
4141
- });
4406
+ if (targetUrl && page.url() !== targetUrl) {
4407
+ try {
4408
+ if (!page.url().includes(new URL(targetUrl).hostname)) {
4409
+ await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
4410
+ });
4411
+ }
4412
+ } catch {
4413
+ }
4142
4414
  }
4143
4415
  const session = {
4144
4416
  id: meta.id || randomUUID(),
@@ -4151,13 +4423,13 @@ async function findOrRestoreSession(name, cdpEndpoint) {
4151
4423
  isCDP: true,
4152
4424
  cdpEndpoint: ep
4153
4425
  };
4154
- for (const [existingId, existingSession] of sessions) {
4426
+ for (const existingSession of sessions.list()) {
4155
4427
  if (existingSession.name === name) {
4156
- logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingId}" during restore`);
4157
- sessions.delete(existingId);
4428
+ logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingSession.id}" during restore`);
4429
+ sessions.removeById(existingSession.id);
4158
4430
  }
4159
4431
  }
4160
- sessions.set(session.id, session);
4432
+ sessions.set(session);
4161
4433
  resetIdleTimer();
4162
4434
  await installNetworkCapture(page, name);
4163
4435
  return session;
@@ -4173,7 +4445,12 @@ async function createEphemeralContext(options) {
4173
4445
  const { browser: b2 } = await launch({ cdpEndpoint: endpoint });
4174
4446
  const contexts = b2.contexts();
4175
4447
  const ctx = contexts[0] || await b2.newContext();
4176
- const page2 = await ctx.newPage();
4448
+ const allPages = ctx.pages();
4449
+ const existingPages = allPages.filter((p) => {
4450
+ const url = p.url();
4451
+ return url !== "about:blank" && !url.startsWith("chrome://");
4452
+ });
4453
+ const page2 = existingPages.length > 0 ? existingPages[0] : allPages.length > 0 ? allPages[0] : await ctx.newPage();
4177
4454
  resetIdleTimer();
4178
4455
  ephemeralConnections.set(page2, b2);
4179
4456
  return { context: ctx, page: page2 };
@@ -4205,11 +4482,11 @@ async function closeEphemeralContext(context) {
4205
4482
  }
4206
4483
  }
4207
4484
  function getAllSessions() {
4208
- return Array.from(sessions.values());
4485
+ return sessions.list();
4209
4486
  }
4210
4487
  async function installNetworkCapture(page, sessionName) {
4211
4488
  if (process.env.XBROWSER_DAEMON_WORKER !== "1") return;
4212
- const { networkStore } = await import("./network-store-66A2RATI.js");
4489
+ const { networkStore } = await import("./network-store-YVDNUREI.js");
4213
4490
  const requestData = /* @__PURE__ */ new Map();
4214
4491
  const responseMeta = /* @__PURE__ */ new Map();
4215
4492
  const xbPage = page;
@@ -4330,16 +4607,38 @@ async function createSession(name, url, options) {
4330
4607
  }
4331
4608
  context = contexts[0] || await b.newContext();
4332
4609
  let targetPage = null;
4333
- for (const ctx of contexts) {
4334
- const pages = ctx.pages();
4335
- for (const p of pages) {
4336
- const pUrl = p.url();
4337
- if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
4338
- targetPage = p;
4339
- break;
4610
+ const targetHostname = url ? (() => {
4611
+ try {
4612
+ return new URL(url).hostname;
4613
+ } catch {
4614
+ return "";
4615
+ }
4616
+ })() : "";
4617
+ if (targetHostname) {
4618
+ for (const ctx of contexts) {
4619
+ const pages = ctx.pages();
4620
+ for (const p of pages) {
4621
+ const pUrl = p.url();
4622
+ if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && pUrl.includes(targetHostname)) {
4623
+ targetPage = p;
4624
+ break;
4625
+ }
4626
+ }
4627
+ if (targetPage) break;
4628
+ }
4629
+ }
4630
+ if (!targetPage) {
4631
+ for (const ctx of contexts) {
4632
+ const pages = ctx.pages();
4633
+ for (const p of pages) {
4634
+ const pUrl = p.url();
4635
+ if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
4636
+ targetPage = p;
4637
+ break;
4638
+ }
4340
4639
  }
4640
+ if (targetPage) break;
4341
4641
  }
4342
- if (targetPage) break;
4343
4642
  }
4344
4643
  if (!targetPage && options?.cdpEndpoint) {
4345
4644
  const targets = await getCDPTargets2(options.cdpEndpoint);
@@ -4380,14 +4679,14 @@ async function createSession(name, url, options) {
4380
4679
  isCDP,
4381
4680
  cdpEndpoint: options?.cdpEndpoint
4382
4681
  };
4383
- sessions.set(session.id, session);
4682
+ sessions.set(session);
4384
4683
  logSessionEvent("create_session", `name="${name}" id="${session.id}" url="${url || "(no url)"}" isCDP=${isCDP} cdpEndpoint=${options?.cdpEndpoint || "(none)"}`);
4385
4684
  resetIdleTimer();
4386
4685
  await installNetworkCapture(page, name);
4387
4686
  return session;
4388
4687
  }
4389
4688
  async function closeSessionByName(name) {
4390
- for (const [id, session] of sessions) {
4689
+ for (const session of sessions) {
4391
4690
  if (session.name === name || session.id === name) {
4392
4691
  logSessionEvent("close_session", `name="${session.name}" id="${session.id}" url="${session.page.url()}"`);
4393
4692
  if (session.isCDP) {
@@ -4406,20 +4705,20 @@ async function closeSessionByName(name) {
4406
4705
  });
4407
4706
  }
4408
4707
  }
4409
- sessions.delete(id);
4708
+ sessions.removeById(session.id);
4410
4709
  const file2 = sessionFile(session.name);
4411
4710
  try {
4412
4711
  unlinkSync(file2);
4413
4712
  } catch {
4414
4713
  }
4415
4714
  try {
4416
- const { networkStore, commandLogStore } = await import("./network-store-66A2RATI.js");
4715
+ const { networkStore, commandLogStore } = await import("./network-store-YVDNUREI.js");
4417
4716
  networkStore.clear(session.name);
4418
4717
  commandLogStore.clear(session.name);
4419
4718
  } catch {
4420
4719
  }
4421
4720
  try {
4422
- const { SessionRecorder } = await import("./session-recorder-MA75PKTQ.js");
4721
+ const { SessionRecorder } = await import("./session-recorder-RTDGURIJ.js");
4423
4722
  SessionRecorder.cleanup(session.name);
4424
4723
  } catch {
4425
4724
  }
@@ -4434,9 +4733,9 @@ async function closeSessionByName(name) {
4434
4733
  return false;
4435
4734
  }
4436
4735
  async function closeAllSessions() {
4437
- const names = [...sessions.values()].map((s) => `${s.name}(${s.page.url()})`).join(", ");
4736
+ const names = sessions.list().map((s) => `${s.name}(${s.page.url()})`).join(", ");
4438
4737
  if (names) logSessionEvent("close_all_sessions", `Closing ${sessions.size} sessions: ${names}`);
4439
- for (const [id, session] of sessions) {
4738
+ for (const session of sessions.list()) {
4440
4739
  try {
4441
4740
  if (!session.isCDP) {
4442
4741
  await session.context.close();
@@ -4445,9 +4744,9 @@ async function closeAllSessions() {
4445
4744
  await session.browser.close().catch(() => {
4446
4745
  });
4447
4746
  }
4448
- sessions.delete(id);
4747
+ sessions.removeById(session.id);
4449
4748
  } catch {
4450
- sessions.delete(id);
4749
+ sessions.removeById(session.id);
4451
4750
  }
4452
4751
  }
4453
4752
  }
@@ -4485,7 +4784,7 @@ async function ensureProcessCanExit() {
4485
4784
  clearTimeout(idleTimer);
4486
4785
  idleTimer = null;
4487
4786
  }
4488
- for (const session of sessions.values()) {
4787
+ for (const session of sessions.list()) {
4489
4788
  if (session.browser) {
4490
4789
  if (session.isCDP) {
4491
4790
  await session.browser.close().catch(() => {
@@ -4511,7 +4810,6 @@ async function ensureProcessCanExit() {
4511
4810
 
4512
4811
  export {
4513
4812
  createRuleEngine,
4514
- resolveCDPEndpoint,
4515
4813
  touchSession,
4516
4814
  findTargetPage,
4517
4815
  resolveLaunchOpts,