@xbrowser/cli 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -26
- package/dist/{browser-DSVV4GHS.js → browser-5CTOA2WS.js} +4 -3
- package/dist/{browser-53KUFEEM.js → browser-ITLZZDHJ.js} +5 -5
- package/dist/{browser-GURRY444.js → browser-IUJXXNBT.js} +6 -3
- package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-4X3DK6PS.js} +339 -59
- package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-D6WMSMWX.js} +4 -3
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-IDVD44ED.js → chunk-6WOSXSCQ.js} +23 -7
- package/dist/{chunk-ZZ2TFWIV.js → chunk-ABXMBNQ6.js} +1 -1
- package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
- package/dist/chunk-AMI64BSD.js +268 -0
- package/dist/{chunk-E4O5ZU3H.js → chunk-DKWR54XQ.js} +412 -98
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/chunk-GDKLH7ZY.js +8 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-2BQZIT3S.js → chunk-LRBSUKUZ.js} +85 -2497
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/{chunk-42RPMJ76.js → chunk-N2JFPWMI.js} +342 -60
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/{chunk-T4J4C2NZ.js → chunk-TNEN6VQ2.js} +17 -4
- package/dist/{chunk-YKOHDEFV.js → chunk-TWWOIJM7.js} +74 -38
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +1558 -1122
- package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-YAVQ343A.js → daemon-client-3JOKX2L2.js} +3 -2
- package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DIEHGP5B.js} +28 -74
- package/dist/daemon-main.js +2296 -1722
- package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
- package/dist/{extract-L2IW3IUB.js → extract-BSYBM4MR.js} +1 -1
- package/dist/{filter-HC4RA7JY.js → filter-KCFO4RSV.js} +1 -1
- package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
- package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
- package/dist/index.d.ts +166 -109
- package/dist/index.js +2668 -1742
- package/dist/launcher-L2JNDB2H.js +20 -0
- package/dist/{launcher-KA7J32K5.js → launcher-OZXJQPNG.js} +1 -1
- package/dist/{network-store-66A2RATI.js → network-store-XGZ25FFC.js} +1 -1
- package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
- package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
- package/dist/{proxy-WKGUCH2C.js → proxy-C6CK3UH5.js} +2 -2
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-MY27H4DX.js +276 -0
- package/dist/site-knowledge-SYC6VCDB.js +23 -0
- package/package.json +5 -4
- package/dist/screenshot-CWAWMXVA.js +0 -28
- package/dist/session-recorder-MA75PKTQ.js +0 -7
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
launch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-N2JFPWMI.js";
|
|
4
|
+
import {
|
|
5
|
+
errMsg
|
|
6
|
+
} from "./chunk-GDKLH7ZY.js";
|
|
4
7
|
import {
|
|
5
8
|
CDPInterceptorProxy
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ABXMBNQ6.js";
|
|
7
10
|
|
|
8
11
|
// src/browser.ts
|
|
9
12
|
import { randomUUID } from "crypto";
|
|
@@ -65,6 +68,7 @@ async function resolveCDPEndpoint(raw) {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
// src/browser.ts
|
|
71
|
+
import { SessionStore } from "@dyyz1993/xcli-core";
|
|
68
72
|
function logSessionEvent(event, details) {
|
|
69
73
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
70
74
|
const pid = process.pid;
|
|
@@ -77,7 +81,7 @@ function sessionFile(name) {
|
|
|
77
81
|
function ensureSessionDir() {
|
|
78
82
|
mkdirSync(SESSION_DIR, { recursive: true });
|
|
79
83
|
}
|
|
80
|
-
var sessions =
|
|
84
|
+
var sessions = new SessionStore();
|
|
81
85
|
var _sharedBrowser = null;
|
|
82
86
|
var _sharedCdpProxy = null;
|
|
83
87
|
var IDLE_TIMEOUT_MS = (process.env.XBROWSER_IDLE_TIMEOUT ? parseInt(process.env.XBROWSER_IDLE_TIMEOUT, 10) : 30) * 60 * 1e3;
|
|
@@ -88,7 +92,7 @@ function resetIdleTimer() {
|
|
|
88
92
|
const now = Date.now();
|
|
89
93
|
let allIdle = true;
|
|
90
94
|
const idleSessions = [];
|
|
91
|
-
for (const
|
|
95
|
+
for (const s of sessions) {
|
|
92
96
|
if (now - s.lastActivityAt < IDLE_TIMEOUT_MS) {
|
|
93
97
|
allIdle = false;
|
|
94
98
|
} else {
|
|
@@ -111,7 +115,7 @@ function touchSession(id) {
|
|
|
111
115
|
resetIdleTimer();
|
|
112
116
|
}
|
|
113
117
|
process.on("exit", () => {
|
|
114
|
-
for (const session of sessions.
|
|
118
|
+
for (const session of sessions.list()) {
|
|
115
119
|
if (session.isCDP) {
|
|
116
120
|
logSessionEvent("process_exit", `Session "${session.name}": CDP connection (not closing external browser).`);
|
|
117
121
|
} else {
|
|
@@ -201,6 +205,9 @@ async function createBrowser(options) {
|
|
|
201
205
|
return browser3;
|
|
202
206
|
}
|
|
203
207
|
const { browser: browser2 } = await launch({ cdpEndpoint: realEndpoint });
|
|
208
|
+
await browser2.discoverContexts().catch((err) => {
|
|
209
|
+
console.error(`[browser] discoverContexts failed: ${errMsg(err)}`);
|
|
210
|
+
});
|
|
204
211
|
return browser2;
|
|
205
212
|
}
|
|
206
213
|
const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
|
|
@@ -215,10 +222,7 @@ async function getBrowser(options) {
|
|
|
215
222
|
return _sharedBrowser;
|
|
216
223
|
}
|
|
217
224
|
function findSession(name) {
|
|
218
|
-
|
|
219
|
-
if (session.name === name) return session;
|
|
220
|
-
}
|
|
221
|
-
return void 0;
|
|
225
|
+
return sessions.find(name);
|
|
222
226
|
}
|
|
223
227
|
function getSessionById(id) {
|
|
224
228
|
return sessions.get(id);
|
|
@@ -322,9 +326,14 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
322
326
|
return void 0;
|
|
323
327
|
}
|
|
324
328
|
const targetUrl = meta.conversationUrl || meta.url;
|
|
325
|
-
if (targetUrl && page.url() !== targetUrl
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
if (targetUrl && page.url() !== targetUrl) {
|
|
330
|
+
try {
|
|
331
|
+
if (!page.url().includes(new URL(targetUrl).hostname)) {
|
|
332
|
+
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
328
337
|
}
|
|
329
338
|
const session = {
|
|
330
339
|
id: meta.id || randomUUID(),
|
|
@@ -337,18 +346,18 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
337
346
|
isCDP: true,
|
|
338
347
|
cdpEndpoint: ep
|
|
339
348
|
};
|
|
340
|
-
for (const
|
|
349
|
+
for (const existingSession of sessions.list()) {
|
|
341
350
|
if (existingSession.name === name) {
|
|
342
|
-
logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${
|
|
343
|
-
sessions.
|
|
351
|
+
logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingSession.id}" during restore`);
|
|
352
|
+
sessions.removeById(existingSession.id);
|
|
344
353
|
}
|
|
345
354
|
}
|
|
346
|
-
sessions.set(session
|
|
355
|
+
sessions.set(session);
|
|
347
356
|
resetIdleTimer();
|
|
348
357
|
await installNetworkCapture(page, name);
|
|
349
358
|
return session;
|
|
350
359
|
} catch (e) {
|
|
351
|
-
console.error(`[Session Restore] Failed for "${name}":`, e
|
|
360
|
+
console.error(`[Session Restore] Failed for "${name}":`, errMsg(e));
|
|
352
361
|
deleteSessionDiskMeta(name);
|
|
353
362
|
return void 0;
|
|
354
363
|
}
|
|
@@ -359,7 +368,12 @@ async function createEphemeralContext(options) {
|
|
|
359
368
|
const { browser: b2 } = await launch({ cdpEndpoint: endpoint });
|
|
360
369
|
const contexts = b2.contexts();
|
|
361
370
|
const ctx = contexts[0] || await b2.newContext();
|
|
362
|
-
const
|
|
371
|
+
const allPages = ctx.pages();
|
|
372
|
+
const existingPages = allPages.filter((p) => {
|
|
373
|
+
const url = p.url();
|
|
374
|
+
return url !== "about:blank" && !url.startsWith("chrome://");
|
|
375
|
+
});
|
|
376
|
+
const page2 = existingPages.length > 0 ? existingPages[0] : allPages.length > 0 ? allPages[0] : await ctx.newPage();
|
|
363
377
|
resetIdleTimer();
|
|
364
378
|
ephemeralConnections.set(page2, b2);
|
|
365
379
|
return { context: ctx, page: page2 };
|
|
@@ -391,11 +405,11 @@ async function closeEphemeralContext(context) {
|
|
|
391
405
|
}
|
|
392
406
|
}
|
|
393
407
|
function getAllSessions() {
|
|
394
|
-
return
|
|
408
|
+
return sessions.list();
|
|
395
409
|
}
|
|
396
410
|
async function installNetworkCapture(page, sessionName) {
|
|
397
411
|
if (process.env.XBROWSER_DAEMON_WORKER !== "1") return;
|
|
398
|
-
const { networkStore } = await import("./network-store-
|
|
412
|
+
const { networkStore } = await import("./network-store-YVDNUREI.js");
|
|
399
413
|
const requestData = /* @__PURE__ */ new Map();
|
|
400
414
|
const responseMeta = /* @__PURE__ */ new Map();
|
|
401
415
|
const xbPage = page;
|
|
@@ -516,16 +530,38 @@ async function createSession(name, url, options) {
|
|
|
516
530
|
}
|
|
517
531
|
context = contexts[0] || await b.newContext();
|
|
518
532
|
let targetPage = null;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
533
|
+
const targetHostname = url ? (() => {
|
|
534
|
+
try {
|
|
535
|
+
return new URL(url).hostname;
|
|
536
|
+
} catch {
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
539
|
+
})() : "";
|
|
540
|
+
if (targetHostname) {
|
|
541
|
+
for (const ctx of contexts) {
|
|
542
|
+
const pages = ctx.pages();
|
|
543
|
+
for (const p of pages) {
|
|
544
|
+
const pUrl = p.url();
|
|
545
|
+
if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && pUrl.includes(targetHostname)) {
|
|
546
|
+
targetPage = p;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (targetPage) break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (!targetPage) {
|
|
554
|
+
for (const ctx of contexts) {
|
|
555
|
+
const pages = ctx.pages();
|
|
556
|
+
for (const p of pages) {
|
|
557
|
+
const pUrl = p.url();
|
|
558
|
+
if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
|
|
559
|
+
targetPage = p;
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
526
562
|
}
|
|
563
|
+
if (targetPage) break;
|
|
527
564
|
}
|
|
528
|
-
if (targetPage) break;
|
|
529
565
|
}
|
|
530
566
|
if (!targetPage && options?.cdpEndpoint) {
|
|
531
567
|
const targets = await getCDPTargets(options.cdpEndpoint);
|
|
@@ -566,14 +602,14 @@ async function createSession(name, url, options) {
|
|
|
566
602
|
isCDP,
|
|
567
603
|
cdpEndpoint: options?.cdpEndpoint
|
|
568
604
|
};
|
|
569
|
-
sessions.set(session
|
|
605
|
+
sessions.set(session);
|
|
570
606
|
logSessionEvent("create_session", `name="${name}" id="${session.id}" url="${url || "(no url)"}" isCDP=${isCDP} cdpEndpoint=${options?.cdpEndpoint || "(none)"}`);
|
|
571
607
|
resetIdleTimer();
|
|
572
608
|
await installNetworkCapture(page, name);
|
|
573
609
|
return session;
|
|
574
610
|
}
|
|
575
611
|
async function closeSessionByName(name) {
|
|
576
|
-
for (const
|
|
612
|
+
for (const session of sessions) {
|
|
577
613
|
if (session.name === name || session.id === name) {
|
|
578
614
|
logSessionEvent("close_session", `name="${session.name}" id="${session.id}" url="${session.page.url()}"`);
|
|
579
615
|
if (session.isCDP) {
|
|
@@ -592,20 +628,20 @@ async function closeSessionByName(name) {
|
|
|
592
628
|
});
|
|
593
629
|
}
|
|
594
630
|
}
|
|
595
|
-
sessions.
|
|
631
|
+
sessions.removeById(session.id);
|
|
596
632
|
const file2 = sessionFile(session.name);
|
|
597
633
|
try {
|
|
598
634
|
unlinkSync(file2);
|
|
599
635
|
} catch {
|
|
600
636
|
}
|
|
601
637
|
try {
|
|
602
|
-
const { networkStore, commandLogStore } = await import("./network-store-
|
|
638
|
+
const { networkStore, commandLogStore } = await import("./network-store-YVDNUREI.js");
|
|
603
639
|
networkStore.clear(session.name);
|
|
604
640
|
commandLogStore.clear(session.name);
|
|
605
641
|
} catch {
|
|
606
642
|
}
|
|
607
643
|
try {
|
|
608
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
644
|
+
const { SessionRecorder } = await import("./session-recorder-RTDGURIJ.js");
|
|
609
645
|
SessionRecorder.cleanup(session.name);
|
|
610
646
|
} catch {
|
|
611
647
|
}
|
|
@@ -620,9 +656,9 @@ async function closeSessionByName(name) {
|
|
|
620
656
|
return false;
|
|
621
657
|
}
|
|
622
658
|
async function closeAllSessions() {
|
|
623
|
-
const names =
|
|
659
|
+
const names = sessions.list().map((s) => `${s.name}(${s.page.url()})`).join(", ");
|
|
624
660
|
if (names) logSessionEvent("close_all_sessions", `Closing ${sessions.size} sessions: ${names}`);
|
|
625
|
-
for (const
|
|
661
|
+
for (const session of sessions.list()) {
|
|
626
662
|
try {
|
|
627
663
|
if (!session.isCDP) {
|
|
628
664
|
await session.context.close();
|
|
@@ -631,9 +667,9 @@ async function closeAllSessions() {
|
|
|
631
667
|
await session.browser.close().catch(() => {
|
|
632
668
|
});
|
|
633
669
|
}
|
|
634
|
-
sessions.
|
|
670
|
+
sessions.removeById(session.id);
|
|
635
671
|
} catch {
|
|
636
|
-
sessions.
|
|
672
|
+
sessions.removeById(session.id);
|
|
637
673
|
}
|
|
638
674
|
}
|
|
639
675
|
}
|
|
@@ -671,7 +707,7 @@ async function ensureProcessCanExit() {
|
|
|
671
707
|
clearTimeout(idleTimer);
|
|
672
708
|
idleTimer = null;
|
|
673
709
|
}
|
|
674
|
-
for (const session of sessions.
|
|
710
|
+
for (const session of sessions.list()) {
|
|
675
711
|
if (session.browser) {
|
|
676
712
|
if (session.isCDP) {
|
|
677
713
|
await session.browser.close().catch(() => {
|
|
@@ -0,0 +1,83 @@
|
|
|
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 { stopDaemon as xcliStopDaemon, isDaemonRunning, getDaemonStatus, killAllDaemon } from "@dyyz1993/xcli-core";
|
|
7
|
+
var CONFIG_DIR = join(homedir(), ".xbrowser");
|
|
8
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
var WORKER_PATH = join(__dirname, "daemon-main.js");
|
|
10
|
+
function getDaemonConfig() {
|
|
11
|
+
return {
|
|
12
|
+
configDir: CONFIG_DIR,
|
|
13
|
+
workerEntryPath: WORKER_PATH,
|
|
14
|
+
basePort: 9224
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async function startDaemonProcess(port = 9224) {
|
|
18
|
+
const config = getDaemonConfig();
|
|
19
|
+
if (isDaemonRunning(config)) {
|
|
20
|
+
const status = getDaemonStatus(config);
|
|
21
|
+
if (status.port === port && status.pid) {
|
|
22
|
+
return { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
23
|
+
}
|
|
24
|
+
await xcliStopDaemon(config);
|
|
25
|
+
}
|
|
26
|
+
const child = spawn("node", [WORKER_PATH], {
|
|
27
|
+
detached: true,
|
|
28
|
+
stdio: "ignore",
|
|
29
|
+
env: {
|
|
30
|
+
...process.env,
|
|
31
|
+
XBROWSER_DAEMON_PORT: String(port)
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
child.unref();
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
let resolved = false;
|
|
37
|
+
const timeout = setTimeout(() => {
|
|
38
|
+
if (!resolved) {
|
|
39
|
+
resolved = true;
|
|
40
|
+
reject(new Error("Daemon start timeout after 15s"));
|
|
41
|
+
}
|
|
42
|
+
}, 15e3);
|
|
43
|
+
const checkInterval = setInterval(() => {
|
|
44
|
+
if (isDaemonRunning(config)) {
|
|
45
|
+
const s = getDaemonStatus(config);
|
|
46
|
+
if (s.port === port && s.pid) {
|
|
47
|
+
resolved = true;
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
clearInterval(checkInterval);
|
|
50
|
+
resolve({ pid: s.pid, port: s.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, 200);
|
|
54
|
+
child.on("error", (err) => {
|
|
55
|
+
if (!resolved) {
|
|
56
|
+
resolved = true;
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
clearInterval(checkInterval);
|
|
59
|
+
reject(err);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function getDaemonProcessStatus() {
|
|
65
|
+
const config = getDaemonConfig();
|
|
66
|
+
const running = isDaemonRunning(config);
|
|
67
|
+
if (!running) {
|
|
68
|
+
return { running: false, pid: 0, port: 0, info: null };
|
|
69
|
+
}
|
|
70
|
+
const status = getDaemonStatus(config);
|
|
71
|
+
return {
|
|
72
|
+
running: true,
|
|
73
|
+
pid: status.pid,
|
|
74
|
+
port: status.port,
|
|
75
|
+
info: { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
getDaemonConfig,
|
|
81
|
+
startDaemonProcess,
|
|
82
|
+
getDaemonProcessStatus
|
|
83
|
+
};
|