@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.
- package/dist/{anti-bot-MCVM6IFB.js → anti-bot-DR56Y63V.js} +1 -1
- package/dist/{browser-SJWPJFN7.js → browser-HBL72GPZ.js} +2 -2
- package/dist/{browser-GT4BMX6X.js → browser-T3V3JWVH.js} +1 -1
- package/dist/{browser-43YXUIML.js → browser-WQZ3D6AE.js} +2 -2
- package/dist/{cdp-driver-WAFYSIIW.js → cdp-driver-2T4P4ZE2.js} +87 -1
- package/dist/{cdp-driver-PUZLDSQU.js → cdp-driver-J3YQ4LJV.js} +1 -1
- package/dist/chunk-3OD76SUE.js +20 -0
- package/dist/chunk-43TEMG4U.js +9 -0
- package/dist/{chunk-C3LIGO4V.js → chunk-4452SPFI.js} +906 -16
- package/dist/{chunk-VEWTHYDG.js → chunk-5UGR6MUK.js} +906 -16
- package/dist/{chunk-ENCEMM7Z.js → chunk-BAHSRZIX.js} +82 -29
- package/dist/{chunk-7ZDWM6T2.js → chunk-BNO7OKO4.js} +5 -6
- package/dist/{chunk-24SY6PE5.js → chunk-CG3D3CHY.js} +1 -8
- package/dist/{chunk-ZJHQPMCY.js → chunk-DWDXEGVK.js} +5 -6
- package/dist/{chunk-DPJNCMLC.js → chunk-INTQPBYF.js} +7 -0
- package/dist/{human-interaction-OU7LZ6OZ.js → chunk-MWFHZUIY.js} +6 -7
- package/dist/{chunk-A4YHJW3Z.js → chunk-NITFVWWS.js} +168 -29
- package/dist/chunk-Q2DFJQGS.js +128 -0
- package/dist/{chunk-GM3HWQ5U.js → chunk-RRBXV7KE.js} +87 -1
- package/dist/{human-interaction-HJTXFVQS.js → chunk-SQHSZENE.js} +2 -2
- package/dist/chunk-TZPKFUBT.js +20 -0
- package/dist/{chunk-PBDID7SE.js → chunk-WSCP7QCJ.js} +82 -29
- package/dist/{chunk-YAUG3NSI.js → chunk-YMUSHPU4.js} +16 -128
- package/dist/cli.js +48 -31
- package/dist/{daemon-client-4HJENICO.js → daemon-client-GKEPT4NY.js} +15 -3
- package/dist/{daemon-client-CMRAWXTN.js → daemon-client-O6BYVRXV.js} +4 -1
- package/dist/daemon-main.js +649 -58
- package/dist/human-interaction-5AO42MBA.js +8 -0
- package/dist/{human-interaction-2DZK4MW7.js → human-interaction-C5OEGXGO.js} +1 -1
- package/dist/human-interaction-ISXZTAYY.js +8 -0
- package/dist/index.d.ts +167 -2
- package/dist/index.js +194 -67
- package/dist/recovery-EF33LKRJ.js +77 -0
- package/dist/recovery-J2ISVGUL.js +77 -0
- package/dist/recovery-ZJVVHP7N.js +76 -0
- package/dist/{session-recorder-K3K63Q33.js → session-recorder-H3KEYU26.js} +1 -1
- package/dist/{session-recorder-AG6CH6EI.js → session-recorder-QRZMKFVL.js} +1 -1
- package/dist/{session-replayer-V4MP6M6I.js → session-replayer-LJUC4TI7.js} +24 -7
- 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
|
-
|
|
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-
|
|
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-
|
|
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)
|
|
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
|
|
560
|
-
if (
|
|
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
|
|
573
|
-
if (
|
|
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-
|
|
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 (
|
|
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
|
-
|
|
682
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|