@xbrowser/cli 1.9.8 → 1.10.0

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 (39) hide show
  1. package/dist/{anti-bot-MCVM6IFB.js → anti-bot-DR56Y63V.js} +1 -1
  2. package/dist/{browser-SJWPJFN7.js → browser-HBL72GPZ.js} +2 -2
  3. package/dist/{browser-GT4BMX6X.js → browser-T3V3JWVH.js} +1 -1
  4. package/dist/{browser-43YXUIML.js → browser-WQZ3D6AE.js} +2 -2
  5. package/dist/{cdp-driver-WAFYSIIW.js → cdp-driver-2T4P4ZE2.js} +87 -1
  6. package/dist/{cdp-driver-PUZLDSQU.js → cdp-driver-J3YQ4LJV.js} +1 -1
  7. package/dist/chunk-3OD76SUE.js +20 -0
  8. package/dist/chunk-43TEMG4U.js +9 -0
  9. package/dist/{chunk-C3LIGO4V.js → chunk-4452SPFI.js} +906 -16
  10. package/dist/{chunk-VEWTHYDG.js → chunk-5UGR6MUK.js} +906 -16
  11. package/dist/{chunk-ENCEMM7Z.js → chunk-BAHSRZIX.js} +82 -29
  12. package/dist/{chunk-7ZDWM6T2.js → chunk-BNO7OKO4.js} +5 -6
  13. package/dist/{chunk-24SY6PE5.js → chunk-CG3D3CHY.js} +1 -8
  14. package/dist/{chunk-ZJHQPMCY.js → chunk-DWDXEGVK.js} +5 -6
  15. package/dist/{chunk-DPJNCMLC.js → chunk-INTQPBYF.js} +7 -0
  16. package/dist/{human-interaction-OU7LZ6OZ.js → chunk-MWFHZUIY.js} +6 -7
  17. package/dist/{chunk-A4YHJW3Z.js → chunk-NITFVWWS.js} +168 -29
  18. package/dist/chunk-Q2DFJQGS.js +128 -0
  19. package/dist/{chunk-GM3HWQ5U.js → chunk-RRBXV7KE.js} +87 -1
  20. package/dist/{human-interaction-HJTXFVQS.js → chunk-SQHSZENE.js} +2 -2
  21. package/dist/chunk-TZPKFUBT.js +20 -0
  22. package/dist/{chunk-PBDID7SE.js → chunk-WSCP7QCJ.js} +82 -29
  23. package/dist/{chunk-YAUG3NSI.js → chunk-YMUSHPU4.js} +16 -128
  24. package/dist/cli.js +48 -31
  25. package/dist/{daemon-client-4HJENICO.js → daemon-client-GKEPT4NY.js} +15 -3
  26. package/dist/{daemon-client-CMRAWXTN.js → daemon-client-O6BYVRXV.js} +4 -1
  27. package/dist/daemon-main.js +649 -58
  28. package/dist/human-interaction-5AO42MBA.js +8 -0
  29. package/dist/{human-interaction-2DZK4MW7.js → human-interaction-C5OEGXGO.js} +1 -1
  30. package/dist/human-interaction-ISXZTAYY.js +8 -0
  31. package/dist/index.d.ts +167 -2
  32. package/dist/index.js +194 -67
  33. package/dist/recovery-EF33LKRJ.js +77 -0
  34. package/dist/recovery-J2ISVGUL.js +77 -0
  35. package/dist/recovery-ZJVVHP7N.js +76 -0
  36. package/dist/{session-recorder-K3K63Q33.js → session-recorder-H3KEYU26.js} +1 -1
  37. package/dist/{session-recorder-AG6CH6EI.js → session-recorder-QRZMKFVL.js} +1 -1
  38. package/dist/{session-replayer-V4MP6M6I.js → session-replayer-LJUC4TI7.js} +24 -7
  39. package/package.json +1 -1
@@ -0,0 +1,128 @@
1
+ // src/daemon/daemon.ts
2
+ import { spawn } from "child_process";
3
+ import { join, dirname } from "path";
4
+ import { homedir } from "os";
5
+ import { fileURLToPath } from "url";
6
+ import { writeFileSync, unlinkSync, mkdirSync } from "fs";
7
+ import { stopDaemon as xcliStopDaemon, isDaemonRunning, getDaemonStatus, killAllDaemon } from "@dyyz1993/xcli-core";
8
+ var CONFIG_DIR = join(homedir(), ".xbrowser");
9
+ var __dirname = dirname(fileURLToPath(import.meta.url));
10
+ var WORKER_PATH = join(__dirname, "daemon-main.js");
11
+ function getDaemonConfig() {
12
+ return {
13
+ configDir: CONFIG_DIR,
14
+ workerEntryPath: WORKER_PATH,
15
+ basePort: 9224
16
+ };
17
+ }
18
+ async function startDaemonProcess(port = 9224) {
19
+ const config = getDaemonConfig();
20
+ if (isDaemonRunning(config)) {
21
+ const status = getDaemonStatus(config);
22
+ if (status.port === port && status.pid) {
23
+ return { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
24
+ }
25
+ await xcliStopDaemon(config);
26
+ }
27
+ const lockFile = join(CONFIG_DIR, "daemon.lock");
28
+ try {
29
+ mkdirSync(CONFIG_DIR, { recursive: true });
30
+ writeFileSync(lockFile, String(process.pid), { flag: "wx" });
31
+ } catch {
32
+ console.error("Another process is starting the daemon, waiting...");
33
+ for (let i = 0; i < 50; i++) {
34
+ await new Promise((r) => setTimeout(r, 200));
35
+ if (isDaemonRunning(config)) {
36
+ const s = getDaemonStatus(config);
37
+ if (s.port === port && s.pid) {
38
+ return { pid: s.pid, port: s.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
39
+ }
40
+ }
41
+ }
42
+ try {
43
+ unlinkSync(lockFile);
44
+ } catch {
45
+ }
46
+ try {
47
+ writeFileSync(lockFile, String(process.pid), { flag: "wx" });
48
+ } catch {
49
+ }
50
+ }
51
+ const child = spawn("node", [WORKER_PATH], {
52
+ detached: true,
53
+ stdio: "ignore",
54
+ env: {
55
+ ...process.env,
56
+ XBROWSER_DAEMON_PORT: String(port)
57
+ }
58
+ });
59
+ child.unref();
60
+ return new Promise((resolve, reject) => {
61
+ let resolved = false;
62
+ const timeout = setTimeout(() => {
63
+ if (!resolved) {
64
+ resolved = true;
65
+ try {
66
+ unlinkSync(lockFile);
67
+ } catch {
68
+ }
69
+ reject(new Error("Daemon start timeout after 15s"));
70
+ }
71
+ }, 15e3);
72
+ const checkInterval = setInterval(() => {
73
+ if (isDaemonRunning(config)) {
74
+ const s = getDaemonStatus(config);
75
+ if (s.port === port && s.pid) {
76
+ resolved = true;
77
+ clearTimeout(timeout);
78
+ clearInterval(checkInterval);
79
+ try {
80
+ unlinkSync(lockFile);
81
+ } catch {
82
+ }
83
+ resolve({ pid: s.pid, port: s.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
84
+ }
85
+ }
86
+ }, 200);
87
+ child.on("error", (err) => {
88
+ if (!resolved) {
89
+ resolved = true;
90
+ clearTimeout(timeout);
91
+ clearInterval(checkInterval);
92
+ try {
93
+ unlinkSync(lockFile);
94
+ } catch {
95
+ }
96
+ reject(err);
97
+ }
98
+ });
99
+ });
100
+ }
101
+ async function stopDaemonProcess() {
102
+ await xcliStopDaemon(getDaemonConfig());
103
+ }
104
+ async function killAllDaemonProcesses() {
105
+ await killAllDaemon(getDaemonConfig());
106
+ }
107
+ function getDaemonProcessStatus() {
108
+ const config = getDaemonConfig();
109
+ const running = isDaemonRunning(config);
110
+ if (!running) {
111
+ return { running: false, pid: 0, port: 0, info: null };
112
+ }
113
+ const status = getDaemonStatus(config);
114
+ return {
115
+ running: true,
116
+ pid: status.pid,
117
+ port: status.port,
118
+ info: { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
119
+ };
120
+ }
121
+
122
+ export {
123
+ getDaemonConfig,
124
+ startDaemonProcess,
125
+ stopDaemonProcess,
126
+ killAllDaemonProcesses,
127
+ getDaemonProcessStatus
128
+ };
@@ -288,6 +288,36 @@ function queryJS(selector) {
288
288
  const xpath = JSON.stringify(selector.slice(6));
289
289
  return `document.evaluate(${xpath}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue`;
290
290
  }
291
+ if (selector.startsWith("text=")) {
292
+ const raw = selector.slice(5);
293
+ const exact = raw.startsWith('"') && raw.endsWith('"');
294
+ const text = exact ? raw.slice(1, -1) : raw;
295
+ return `(() => {
296
+ const target = ${JSON.stringify(text)};
297
+ const exact = ${exact};
298
+ const els = [...document.querySelectorAll('*')].filter(e => {
299
+ if (e.children.length > 0) return false;
300
+ if (e.offsetParent === null) return false;
301
+ const t = (e.textContent || '').trim();
302
+ if (!t) return false;
303
+ return exact ? t === target : t.toLowerCase().includes(target.toLowerCase());
304
+ });
305
+ return els[0] || null;
306
+ })()`;
307
+ }
308
+ if (selector.startsWith("popup-text=")) {
309
+ const text = selector.slice("popup-text=".length);
310
+ return `(() => {
311
+ const target = ${JSON.stringify(text)};
312
+ const els = [...document.querySelectorAll('*')].filter(e => {
313
+ if (e.children.length > 0) return false;
314
+ if (e.offsetParent === null) return false;
315
+ if ((e.textContent || '').trim() !== target) return false;
316
+ return true;
317
+ });
318
+ return els[0] || null;
319
+ })()`;
320
+ }
291
321
  return `document.querySelector(${JSON.stringify(selector)})`;
292
322
  }
293
323
  function queryAllJS(selector) {
@@ -295,6 +325,9 @@ function queryAllJS(selector) {
295
325
  const xpath = JSON.stringify(selector.slice(6));
296
326
  return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()`;
297
327
  }
328
+ if (selector.startsWith("text=") || selector.startsWith("popup-text=")) {
329
+ return `(() => { const el = ${queryJS(selector)}; return el ? [el] : []; })()`;
330
+ }
298
331
  return `document.querySelectorAll(${JSON.stringify(selector)})`;
299
332
  }
300
333
 
@@ -2757,11 +2790,52 @@ var CDPConnection = class extends EventEmitter4 {
2757
2790
  this.setMaxListeners(0);
2758
2791
  this.defaultSessionId = sessionId;
2759
2792
  if (typeof wsOrUrl === "string") {
2760
- this.ws = new WebSocket(wsOrUrl);
2793
+ const wsOptions = /^wss:\/\/\d+\.\d+\.\d+\.\d+/.test(wsOrUrl) ? { rejectUnauthorized: false } : void 0;
2794
+ this.ws = new WebSocket(wsOrUrl, wsOptions);
2761
2795
  } else {
2762
2796
  this.ws = wsOrUrl;
2763
2797
  }
2764
2798
  this.bindWebSocket();
2799
+ this.startKeepalive();
2800
+ }
2801
+ /** Send periodic WS pings to prevent idle-timeout disconnects (e.g. CF's 100s).
2802
+ * Also detects dead connections: if a pong isn't received within 10s of a
2803
+ * ping, the connection is considered dead and forcibly closed. */
2804
+ keepaliveTimer = null;
2805
+ pongTimer = null;
2806
+ startKeepalive() {
2807
+ this.ws.on("pong", () => {
2808
+ if (this.pongTimer) {
2809
+ clearTimeout(this.pongTimer);
2810
+ this.pongTimer = null;
2811
+ }
2812
+ });
2813
+ this.keepaliveTimer = setInterval(() => {
2814
+ if (this.ws.readyState === WebSocket.OPEN) {
2815
+ if (!this.pongTimer) {
2816
+ this.pongTimer = setTimeout(() => {
2817
+ if (!this.closed) {
2818
+ this.closed = true;
2819
+ this.closeReason = "keepalive timeout (no pong in 10s)";
2820
+ try {
2821
+ this.ws.terminate();
2822
+ } catch {
2823
+ }
2824
+ for (const [, pending] of this.pending) {
2825
+ clearTimeout(pending.timeout);
2826
+ pending.reject(new Error("Connection dead: keepalive timeout"));
2827
+ }
2828
+ this.pending.clear();
2829
+ this.emit("disconnect");
2830
+ }
2831
+ }, 1e4);
2832
+ }
2833
+ this.ws.ping?.();
2834
+ } else if (this.closed) {
2835
+ if (this.keepaliveTimer) clearInterval(this.keepaliveTimer);
2836
+ this.keepaliveTimer = null;
2837
+ }
2838
+ }, 3e4);
2765
2839
  }
2766
2840
  /** Wait for the connection to be fully open */
2767
2841
  async ready() {
@@ -2870,6 +2944,14 @@ var CDPConnection = class extends EventEmitter4 {
2870
2944
  if (this.closed) return;
2871
2945
  this.closed = true;
2872
2946
  this.closeReason = "closed by caller";
2947
+ if (this.keepaliveTimer) {
2948
+ clearInterval(this.keepaliveTimer);
2949
+ this.keepaliveTimer = null;
2950
+ }
2951
+ if (this.pongTimer) {
2952
+ clearTimeout(this.pongTimer);
2953
+ this.pongTimer = null;
2954
+ }
2873
2955
  for (const [id, pending] of this.pending) {
2874
2956
  clearTimeout(pending.timeout);
2875
2957
  pending.reject(new Error(`Connection closed: ${pending.method}`));
@@ -2911,6 +2993,10 @@ var CDPConnection = class extends EventEmitter4 {
2911
2993
  if (this.closed) return;
2912
2994
  this.closed = true;
2913
2995
  this.closeReason = `WebSocket closed: ${code} ${reason?.toString() ?? ""}`.trim();
2996
+ if (this.keepaliveTimer) {
2997
+ clearInterval(this.keepaliveTimer);
2998
+ this.keepaliveTimer = null;
2999
+ }
2914
3000
  for (const [id, pending] of this.pending) {
2915
3001
  clearTimeout(pending.timeout);
2916
3002
  pending.reject(new Error(`Connection closed: ${pending.method}`));
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  ScreencastCapturer
3
- } from "./chunk-7ZDWM6T2.js";
4
- import "./chunk-KFQGP6VL.js";
3
+ } from "./chunk-BNO7OKO4.js";
5
4
 
6
5
  // src/human-interaction.ts
7
6
  import { execSync } from "child_process";
@@ -307,6 +306,7 @@ var HumanInteractionManager = class {
307
306
  });
308
307
  }
309
308
  };
309
+
310
310
  export {
311
311
  HumanInteractionManager
312
312
  };
@@ -0,0 +1,20 @@
1
+ import {
2
+ getDaemonConfig,
3
+ getDaemonProcessStatus
4
+ } from "./chunk-CG3D3CHY.js";
5
+
6
+ // src/utils/viewer-url.ts
7
+ function buildViewerUrl(sessionName = "default") {
8
+ try {
9
+ const status = getDaemonProcessStatus();
10
+ if (!status.running) return void 0;
11
+ const port = status.port || getDaemonConfig().basePort;
12
+ return `http://localhost:${port}/preview/${encodeURIComponent(sessionName)}`;
13
+ } catch {
14
+ return void 0;
15
+ }
16
+ }
17
+
18
+ export {
19
+ buildViewerUrl
20
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-GM3HWQ5U.js";
3
+ } from "./chunk-RRBXV7KE.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -134,7 +134,7 @@ process.on("exit", () => {
134
134
  if (session.isCDP) {
135
135
  logSessionEvent("process_exit", `Session "${session.name}": CDP connection (not closing external browser).`);
136
136
  } else {
137
- logSessionEvent("process_exit", `Session "${session.name}": Closing browser.`);
137
+ logSessionEvent("process_exit", `Session "${session.name}": Closing self-launched browser.`);
138
138
  try {
139
139
  session.browser?.close();
140
140
  } catch {
@@ -142,7 +142,7 @@ process.on("exit", () => {
142
142
  }
143
143
  }
144
144
  if (_sharedBrowser) {
145
- logSessionEvent("process_exit", "Closing shared browser.");
145
+ logSessionEvent("process_exit", "Closing shared browser (self-launched only, external CDP is safe).");
146
146
  try {
147
147
  _sharedBrowser.close();
148
148
  } catch {
@@ -272,9 +272,29 @@ function deleteSessionDiskMeta(name) {
272
272
  } catch {
273
273
  }
274
274
  }
275
+ async function isSessionPageAlive(session) {
276
+ const page = session.page;
277
+ if (!page || typeof page.evaluate !== "function") return false;
278
+ try {
279
+ await Promise.race([
280
+ page.evaluate("1"),
281
+ new Promise((_, reject) => setTimeout(() => reject(new Error("liveness probe timeout")), 1500))
282
+ ]);
283
+ return true;
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
275
288
  async function findOrRestoreSession(name, cdpEndpoint) {
276
289
  const inMem = findSession(name);
277
- if (inMem) return inMem;
290
+ if (inMem) {
291
+ if (await isSessionPageAlive(inMem)) return inMem;
292
+ logSessionEvent("stale_session", `name="${name}" \u2014 page \u63A2\u9488\u5931\u8D25\uFF08\u6D4F\u89C8\u5668\u53EF\u80FD\u5DF2\u91CD\u542F\uFF09\uFF0C\u5C31\u5730\u91CD\u5EFA`);
293
+ try {
294
+ await closeSessionByName(name);
295
+ } catch {
296
+ }
297
+ }
278
298
  const meta = readSessionDiskMeta(name);
279
299
  if (!meta) return void 0;
280
300
  const ep = cdpEndpoint || meta.cdpEndpoint;
@@ -537,14 +557,6 @@ async function createSession(name, url, options) {
537
557
  let context;
538
558
  let page;
539
559
  if (isCDP) {
540
- await new Promise((r) => setTimeout(r, 500));
541
- let contexts = b.contexts();
542
- if (contexts.length === 0) {
543
- await new Promise((r) => setTimeout(r, 500));
544
- contexts = b.contexts();
545
- }
546
- context = contexts[0] || await b.newContext();
547
- let targetPage = null;
548
560
  const targetHostname = url ? (() => {
549
561
  try {
550
562
  return new URL(url).hostname;
@@ -552,12 +564,54 @@ async function createSession(name, url, options) {
552
564
  return "";
553
565
  }
554
566
  })() : "";
567
+ const isRealPageUrl = (u) => !!u && u !== "about:blank" && !u.startsWith("chrome://") && !u.startsWith("chrome-untrusted://") && !u.startsWith("chrome-error://");
568
+ const resolvePageUrl = async (p) => {
569
+ const u = p.url();
570
+ if (u && u !== "about:blank") return u;
571
+ try {
572
+ return await p.evaluate("location.href");
573
+ } catch {
574
+ return u ?? "";
575
+ }
576
+ };
577
+ const pollStart = Date.now();
578
+ for (; ; ) {
579
+ const ctxs = b.contexts();
580
+ let hostHit = false;
581
+ let anyHit = false;
582
+ for (const ctx of ctxs) {
583
+ for (const p of ctx.pages()) {
584
+ const u = await resolvePageUrl(p);
585
+ if (!isRealPageUrl(u)) continue;
586
+ anyHit = true;
587
+ if (targetHostname && u.includes(targetHostname)) {
588
+ hostHit = true;
589
+ break;
590
+ }
591
+ }
592
+ if (hostHit) break;
593
+ }
594
+ if (process.env.XB_DEBUG_SESSION_POLL) {
595
+ console.error(`[poll] t=${Date.now() - pollStart}ms ctxs=${ctxs.length} hostHit=${hostHit} anyHit=${anyHit}`);
596
+ }
597
+ if (hostHit) break;
598
+ if (anyHit && Date.now() - pollStart >= 2e3) break;
599
+ if (Date.now() - pollStart >= 5e3) break;
600
+ await new Promise((r) => setTimeout(r, 200));
601
+ }
602
+ let contexts = b.contexts();
603
+ if (contexts.length === 0) {
604
+ await new Promise((r) => setTimeout(r, 500));
605
+ contexts = b.contexts();
606
+ }
607
+ context = contexts[0] || await b.newContext();
608
+ let targetPage = null;
555
609
  if (targetHostname) {
556
610
  for (const ctx of contexts) {
557
611
  const pages = ctx.pages();
558
612
  for (const p of pages) {
559
- const pUrl = p.url();
560
- if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && !pUrl.startsWith("chrome-untrusted://") && !pUrl.startsWith("chrome-error://") && pUrl.includes(targetHostname)) {
613
+ const pUrl = await resolvePageUrl(p);
614
+ if (isRealPageUrl(pUrl) && pUrl.includes(targetHostname)) {
561
615
  targetPage = p;
562
616
  break;
563
617
  }
@@ -569,8 +623,8 @@ async function createSession(name, url, options) {
569
623
  for (const ctx of contexts) {
570
624
  const pages = ctx.pages();
571
625
  for (const p of pages) {
572
- const pUrl = p.url();
573
- if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && !pUrl.startsWith("chrome-untrusted://") && !pUrl.startsWith("chrome-error://")) {
626
+ const pUrl = await resolvePageUrl(p);
627
+ if (isRealPageUrl(pUrl)) {
574
628
  targetPage = p;
575
629
  break;
576
630
  }
@@ -628,10 +682,6 @@ async function closeSessionByName(name) {
628
682
  if (session.name === name || session.id === name) {
629
683
  logSessionEvent("close_session", `name="${session.name}" id="${session.id}" url="${session.page.url()}"`);
630
684
  if (session.isCDP) {
631
- try {
632
- await session.page.close();
633
- } catch {
634
- }
635
685
  if (session.browser) {
636
686
  await session.browser.close().catch(() => {
637
687
  });
@@ -656,7 +706,7 @@ async function closeSessionByName(name) {
656
706
  } catch {
657
707
  }
658
708
  try {
659
- const { SessionRecorder } = await import("./session-recorder-AG6CH6EI.js");
709
+ const { SessionRecorder } = await import("./session-recorder-QRZMKFVL.js");
660
710
  SessionRecorder.cleanup(session.name);
661
711
  } catch {
662
712
  }
@@ -675,12 +725,17 @@ async function closeAllSessions() {
675
725
  if (names) logSessionEvent("close_all_sessions", `Closing ${sessions.size} sessions: ${names}`);
676
726
  for (const session of sessions.list()) {
677
727
  try {
678
- if (!session.isCDP) {
728
+ if (session.isCDP) {
729
+ if (session.browser) {
730
+ await session.browser.close().catch(() => {
731
+ });
732
+ }
733
+ } else {
679
734
  await session.context.close();
680
- }
681
- if (session.browser) {
682
- await session.browser.close().catch(() => {
683
- });
735
+ if (session.browser) {
736
+ await session.browser.close().catch(() => {
737
+ });
738
+ }
684
739
  }
685
740
  sessions.removeById(session.id);
686
741
  } catch {
@@ -689,7 +744,7 @@ async function closeAllSessions() {
689
744
  }
690
745
  }
691
746
  async function destroyBrowser() {
692
- logSessionEvent("destroy_browser", `Sessions count: ${sessions.size}. Clearing idle timer and closing all browsers.`);
747
+ logSessionEvent("destroy_browser", `Sessions count: ${sessions.size}. Clearing idle timer and closing all sessions.`);
693
748
  if (idleTimer) {
694
749
  clearTimeout(idleTimer);
695
750
  idleTimer = null;
@@ -725,8 +780,6 @@ async function ensureProcessCanExit() {
725
780
  for (const session of sessions.list()) {
726
781
  if (session.browser) {
727
782
  if (session.isCDP) {
728
- await session.browser.close().catch(() => {
729
- });
730
783
  } else {
731
784
  await session.browser.close().catch(() => {
732
785
  });
@@ -1,128 +1,11 @@
1
+ import {
2
+ startDaemonProcess,
3
+ stopDaemonProcess
4
+ } from "./chunk-Q2DFJQGS.js";
1
5
  import {
2
6
  errMsg
3
7
  } from "./chunk-GDKLH7ZY.js";
4
8
 
5
- // src/daemon/daemon.ts
6
- import { spawn } from "child_process";
7
- import { join, dirname } from "path";
8
- import { homedir } from "os";
9
- import { fileURLToPath } from "url";
10
- import { writeFileSync, unlinkSync, mkdirSync } from "fs";
11
- import { stopDaemon as xcliStopDaemon, isDaemonRunning, getDaemonStatus, killAllDaemon } from "@dyyz1993/xcli-core";
12
- var CONFIG_DIR = join(homedir(), ".xbrowser");
13
- var __dirname = dirname(fileURLToPath(import.meta.url));
14
- var WORKER_PATH = join(__dirname, "daemon-main.js");
15
- function getDaemonConfig() {
16
- return {
17
- configDir: CONFIG_DIR,
18
- workerEntryPath: WORKER_PATH,
19
- basePort: 9224
20
- };
21
- }
22
- async function startDaemonProcess(port = 9224) {
23
- const config = getDaemonConfig();
24
- if (isDaemonRunning(config)) {
25
- const status = getDaemonStatus(config);
26
- if (status.port === port && status.pid) {
27
- return { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
28
- }
29
- await xcliStopDaemon(config);
30
- }
31
- const lockFile = join(CONFIG_DIR, "daemon.lock");
32
- try {
33
- mkdirSync(CONFIG_DIR, { recursive: true });
34
- writeFileSync(lockFile, String(process.pid), { flag: "wx" });
35
- } catch {
36
- console.error("Another process is starting the daemon, waiting...");
37
- for (let i = 0; i < 50; i++) {
38
- await new Promise((r) => setTimeout(r, 200));
39
- if (isDaemonRunning(config)) {
40
- const s = getDaemonStatus(config);
41
- if (s.port === port && s.pid) {
42
- return { pid: s.pid, port: s.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
43
- }
44
- }
45
- }
46
- try {
47
- unlinkSync(lockFile);
48
- } catch {
49
- }
50
- try {
51
- writeFileSync(lockFile, String(process.pid), { flag: "wx" });
52
- } catch {
53
- }
54
- }
55
- const child = spawn("node", [WORKER_PATH], {
56
- detached: true,
57
- stdio: "ignore",
58
- env: {
59
- ...process.env,
60
- XBROWSER_DAEMON_PORT: String(port)
61
- }
62
- });
63
- child.unref();
64
- return new Promise((resolve, reject) => {
65
- let resolved = false;
66
- const timeout = setTimeout(() => {
67
- if (!resolved) {
68
- resolved = true;
69
- try {
70
- unlinkSync(lockFile);
71
- } catch {
72
- }
73
- reject(new Error("Daemon start timeout after 15s"));
74
- }
75
- }, 15e3);
76
- const checkInterval = setInterval(() => {
77
- if (isDaemonRunning(config)) {
78
- const s = getDaemonStatus(config);
79
- if (s.port === port && s.pid) {
80
- resolved = true;
81
- clearTimeout(timeout);
82
- clearInterval(checkInterval);
83
- try {
84
- unlinkSync(lockFile);
85
- } catch {
86
- }
87
- resolve({ pid: s.pid, port: s.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
88
- }
89
- }
90
- }, 200);
91
- child.on("error", (err) => {
92
- if (!resolved) {
93
- resolved = true;
94
- clearTimeout(timeout);
95
- clearInterval(checkInterval);
96
- try {
97
- unlinkSync(lockFile);
98
- } catch {
99
- }
100
- reject(err);
101
- }
102
- });
103
- });
104
- }
105
- async function stopDaemonProcess() {
106
- await xcliStopDaemon(getDaemonConfig());
107
- }
108
- async function killAllDaemonProcesses() {
109
- await killAllDaemon(getDaemonConfig());
110
- }
111
- function getDaemonProcessStatus() {
112
- const config = getDaemonConfig();
113
- const running = isDaemonRunning(config);
114
- if (!running) {
115
- return { running: false, pid: 0, port: 0, info: null };
116
- }
117
- const status = getDaemonStatus(config);
118
- return {
119
- running: true,
120
- pid: status.pid,
121
- port: status.port,
122
- info: { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
123
- };
124
- }
125
-
126
9
  // src/version.ts
127
10
  import { createRequire } from "module";
128
11
  var require2 = createRequire(import.meta.url);
@@ -201,7 +84,7 @@ async function rpcCall(method, params = {}, timeoutMs = 1e4) {
201
84
  }
202
85
  return resp.json();
203
86
  }
204
- async function isDaemonRunning2() {
87
+ async function isDaemonRunning() {
205
88
  try {
206
89
  const resp = await fetch(`${DAEMON_BASE}/health`, { signal: AbortSignal.timeout(2e3) });
207
90
  if (!resp.ok) return false;
@@ -238,6 +121,15 @@ async function forwardExec(command, params, session = "default", cdpEndpoint, ti
238
121
  return { success: false, data: null, message: errMsg(e), duration: 0 };
239
122
  }
240
123
  }
124
+ async function forwardPluginExec(command, params, session = "default", cdpEndpoint, timeoutMs = 12e4) {
125
+ const rpcParams = { command, params, session };
126
+ if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
127
+ try {
128
+ return await rpcCall("plugin:exec", rpcParams, timeoutMs);
129
+ } catch (e) {
130
+ return { success: false, data: null, message: errMsg(e), duration: 0 };
131
+ }
132
+ }
241
133
  async function forwardChain(input, session = "default", cdpEndpoint) {
242
134
  const params = { chain: input, session };
243
135
  if (cdpEndpoint) params.cdpEndpoint = cdpEndpoint;
@@ -329,17 +221,13 @@ async function forwardViewerCheckSelector(name, selector) {
329
221
 
330
222
  export {
331
223
  version,
332
- getDaemonConfig,
333
- startDaemonProcess,
334
- stopDaemonProcess,
335
- killAllDaemonProcesses,
336
- getDaemonProcessStatus,
337
- isDaemonRunning2 as isDaemonRunning,
224
+ isDaemonRunning,
338
225
  daemonPing,
339
226
  forwardSessionCreate,
340
227
  forwardSessionClose,
341
228
  forwardSessionList,
342
229
  forwardExec,
230
+ forwardPluginExec,
343
231
  forwardChain,
344
232
  forwardAgentObserve,
345
233
  forwardAgentAct,