@xbrowser/cli 0.16.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.
- package/README.md +17 -26
- package/dist/{browser-R7B255ML.js → browser-GITRHHFO.js} +4 -1
- package/dist/{browser-GWBH6OJK.js → browser-R56O3CW6.js} +3 -1
- package/dist/{browser-I2HJZ7IP.js → browser-ZJOZB5CR.js} +4 -2
- package/dist/cdp-driver-BE3FOMRN.js +2803 -0
- package/dist/cdp-driver-TOPYJIFL.js +47 -0
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-KDYXFLAC.js → chunk-ACFE6PKF.js} +1015 -121
- package/dist/chunk-BBMRDUYQ.js +260 -0
- package/dist/chunk-CAFNSGYM.js +4834 -0
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/{chunk-RS6YYWTK.js → chunk-JPA2ZT2R.js} +140 -72
- package/dist/chunk-JPHCY4TC.js +260 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/chunk-PPG4D2EW.js +2796 -0
- package/dist/{chunk-ATFTAKMN.js → chunk-Q4IGYTKR.js} +39 -7
- package/dist/{chunk-F3ZWFCJJ.js → chunk-QIK2I3VQ.js} +141 -72
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +2358 -1086
- package/dist/{convert-4DUWZIKH.js → convert-LB3GJTLR.js} +4 -2
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-GX2UYIW4.js → daemon-client-DRCUMNHK.js} +45 -72
- package/dist/{daemon-client-XWSSQBEA.js → daemon-client-UZZEHHIV.js} +8 -1
- package/dist/daemon-main.js +3067 -1688
- package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
- package/dist/{extract-EGRXZSSK.js → extract-BSYBM4MR.js} +2 -0
- package/dist/{filter-OLAE26HN.js → filter-KCFO4RSV.js} +2 -0
- 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 +745 -148
- package/dist/index.js +3488 -1719
- package/dist/launcher-QUJ4M2VS.js +19 -0
- package/dist/launcher-YARP45UY.js +19 -0
- package/dist/{network-store-YAF5OIBH.js → network-store-XGZ25FFC.js} +1 -0
- 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-LV4BJ5RC.js} +1 -1
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-GLTUICSD.js +276 -0
- package/dist/site-knowledge-SYC6VCDB.js +23 -0
- package/package.json +6 -6
- package/dist/chunk-2ONMTDLK.js +0 -2050
- package/dist/daemon-client-3IJD6X4B.js +0 -59
- package/dist/network-store-2S5HATEV.js +0 -194
- package/dist/parse-action-dsl-DRSPBALP.js +0 -72
- package/dist/screenshot-CWAWMXVA.js +0 -28
- package/dist/screenshot-MB6R7RSS.js +0 -26
- package/dist/session-recorder-ILSSV2UC.js +0 -6
- package/dist/session-recorder-XET3DNML.js +0 -7
|
@@ -110,6 +110,12 @@ async function ensureDaemonRunning() {
|
|
|
110
110
|
_ensurePromise = null;
|
|
111
111
|
throw new Error("Daemon not available");
|
|
112
112
|
}
|
|
113
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
114
|
+
const ready = await fetch(`${DAEMON_BASE}/health`, { signal: AbortSignal.timeout(1e3) }).then((r) => r.ok ? r.json() : null).then((d) => d?.status === "ok").catch(() => false);
|
|
115
|
+
if (ready) return;
|
|
116
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
117
|
+
}
|
|
118
|
+
throw new Error("Daemon HTTP server not ready after 4s");
|
|
113
119
|
}
|
|
114
120
|
async function rpcCall(method, params = {}, timeoutMs = 1e4) {
|
|
115
121
|
await ensureDaemonRunning();
|
|
@@ -120,7 +126,13 @@ async function rpcCall(method, params = {}, timeoutMs = 1e4) {
|
|
|
120
126
|
signal: AbortSignal.timeout(timeoutMs)
|
|
121
127
|
});
|
|
122
128
|
if (!resp.ok) {
|
|
123
|
-
|
|
129
|
+
let detail = resp.statusText;
|
|
130
|
+
try {
|
|
131
|
+
const body = await resp.json();
|
|
132
|
+
if (body?.error) detail = body.error;
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Daemon error: ${detail}`);
|
|
124
136
|
}
|
|
125
137
|
return resp.json();
|
|
126
138
|
}
|
|
@@ -157,8 +169,8 @@ async function forwardExec(command, params, session = "default", cdpEndpoint, ti
|
|
|
157
169
|
if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
|
|
158
170
|
try {
|
|
159
171
|
return await rpcCall("exec", rpcParams, timeoutMs);
|
|
160
|
-
} catch {
|
|
161
|
-
return { success: false, data: null, message:
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return { success: false, data: null, message: e.message, duration: 0 };
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
176
|
async function forwardChain(input, session = "default", cdpEndpoint) {
|
|
@@ -166,10 +178,27 @@ async function forwardChain(input, session = "default", cdpEndpoint) {
|
|
|
166
178
|
if (cdpEndpoint) params.cdpEndpoint = cdpEndpoint;
|
|
167
179
|
try {
|
|
168
180
|
return await rpcCall("chain", params, 12e4);
|
|
169
|
-
} catch {
|
|
170
|
-
return { success: false, steps: [], totalDuration: 0, stoppedReason:
|
|
181
|
+
} catch (e) {
|
|
182
|
+
return { success: false, steps: [], totalDuration: 0, stoppedReason: e.message };
|
|
171
183
|
}
|
|
172
184
|
}
|
|
185
|
+
async function forwardAgentObserve(session = "default", options) {
|
|
186
|
+
const params = { session };
|
|
187
|
+
if (options?.cdpEndpoint) params.cdpEndpoint = options.cdpEndpoint;
|
|
188
|
+
if (options?.includeHidden !== void 0) params.includeHidden = options.includeHidden;
|
|
189
|
+
if (options?.limit !== void 0) params.limit = options.limit;
|
|
190
|
+
return rpcCall("agent:observe", params, 3e4);
|
|
191
|
+
}
|
|
192
|
+
async function forwardAgentAct(session = "default", params, cdpEndpoint) {
|
|
193
|
+
const rpcParams = { ...params, session };
|
|
194
|
+
if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
|
|
195
|
+
return rpcCall("agent:act", rpcParams, 3e4);
|
|
196
|
+
}
|
|
197
|
+
async function forwardAgentWait(session = "default", params, cdpEndpoint, timeoutMs = 3e4) {
|
|
198
|
+
const rpcParams = { ...params, session };
|
|
199
|
+
if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
|
|
200
|
+
return rpcCall("agent:wait", rpcParams, timeoutMs + 5e3);
|
|
201
|
+
}
|
|
173
202
|
async function forwardNetworkList(sessionName, options) {
|
|
174
203
|
return rpcCall("network:list", { session: sessionName, ...options }, 3e4);
|
|
175
204
|
}
|
|
@@ -206,8 +235,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
|
|
|
206
235
|
async function forwardNetworkInspect(sessionName, id) {
|
|
207
236
|
return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
|
|
208
237
|
}
|
|
209
|
-
async function forwardRecordStart(session, url) {
|
|
210
|
-
return rpcCall("record:start", { session, url }, 15e3);
|
|
238
|
+
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
239
|
+
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
211
240
|
}
|
|
212
241
|
async function forwardRecordStop(session) {
|
|
213
242
|
return rpcCall("record:stop", { session }, 1e4);
|
|
@@ -244,6 +273,9 @@ export {
|
|
|
244
273
|
forwardSessionList,
|
|
245
274
|
forwardExec,
|
|
246
275
|
forwardChain,
|
|
276
|
+
forwardAgentObserve,
|
|
277
|
+
forwardAgentAct,
|
|
278
|
+
forwardAgentWait,
|
|
247
279
|
forwardNetworkList,
|
|
248
280
|
forwardNetworkClear,
|
|
249
281
|
forwardNetworkTop,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
launch
|
|
3
|
+
} from "./chunk-PPG4D2EW.js";
|
|
4
|
+
|
|
1
5
|
// src/browser.ts
|
|
2
6
|
import { randomUUID } from "crypto";
|
|
3
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
4
8
|
import { join } from "path";
|
|
5
9
|
import { homedir } from "os";
|
|
6
|
-
import { chromium } from "playwright";
|
|
7
10
|
|
|
8
11
|
// src/cdp-interceptor/proxy.ts
|
|
9
12
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -1427,6 +1430,7 @@ async function resolveCDPEndpoint(raw) {
|
|
|
1427
1430
|
}
|
|
1428
1431
|
|
|
1429
1432
|
// src/browser.ts
|
|
1433
|
+
import { SessionStore } from "@dyyz1993/xcli-core";
|
|
1430
1434
|
function logSessionEvent(event, details) {
|
|
1431
1435
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
1432
1436
|
const pid = process.pid;
|
|
@@ -1439,7 +1443,7 @@ function sessionFile(name) {
|
|
|
1439
1443
|
function ensureSessionDir() {
|
|
1440
1444
|
mkdirSync(SESSION_DIR, { recursive: true });
|
|
1441
1445
|
}
|
|
1442
|
-
var sessions =
|
|
1446
|
+
var sessions = new SessionStore();
|
|
1443
1447
|
var _sharedBrowser = null;
|
|
1444
1448
|
var _sharedCdpProxy = null;
|
|
1445
1449
|
var IDLE_TIMEOUT_MS = (process.env.XBROWSER_IDLE_TIMEOUT ? parseInt(process.env.XBROWSER_IDLE_TIMEOUT, 10) : 30) * 60 * 1e3;
|
|
@@ -1450,7 +1454,7 @@ function resetIdleTimer() {
|
|
|
1450
1454
|
const now = Date.now();
|
|
1451
1455
|
let allIdle = true;
|
|
1452
1456
|
const idleSessions = [];
|
|
1453
|
-
for (const
|
|
1457
|
+
for (const s of sessions) {
|
|
1454
1458
|
if (now - s.lastActivityAt < IDLE_TIMEOUT_MS) {
|
|
1455
1459
|
allIdle = false;
|
|
1456
1460
|
} else {
|
|
@@ -1473,7 +1477,7 @@ function touchSession(id) {
|
|
|
1473
1477
|
resetIdleTimer();
|
|
1474
1478
|
}
|
|
1475
1479
|
process.on("exit", () => {
|
|
1476
|
-
for (const session of sessions.
|
|
1480
|
+
for (const session of sessions.list()) {
|
|
1477
1481
|
if (session.isCDP) {
|
|
1478
1482
|
logSessionEvent("process_exit", `Session "${session.name}": CDP connection (not closing external browser).`);
|
|
1479
1483
|
} else {
|
|
@@ -1556,15 +1560,21 @@ async function createBrowser(options) {
|
|
|
1556
1560
|
const realEndpoint = await resolveCDPEndpoint(options.cdpEndpoint);
|
|
1557
1561
|
if (options.intercept) {
|
|
1558
1562
|
const config = typeof options.intercept === "object" ? { ...options.intercept, cdpEndpoint: realEndpoint } : { cdpEndpoint: realEndpoint };
|
|
1559
|
-
|
|
1560
|
-
const proxyPort = await
|
|
1563
|
+
_sharedCdpProxy = new CDPInterceptorProxy(config);
|
|
1564
|
+
const proxyPort = await _sharedCdpProxy.start();
|
|
1561
1565
|
console.error(`[CDP Interceptor] Proxy running on ws://localhost:${proxyPort}, forwarding to ${realEndpoint}`);
|
|
1562
|
-
|
|
1566
|
+
const { browser: browser3 } = await launch({ cdpEndpoint: `ws://localhost:${proxyPort}` });
|
|
1567
|
+
return browser3;
|
|
1563
1568
|
}
|
|
1564
|
-
|
|
1569
|
+
const { browser: browser2 } = await launch({ cdpEndpoint: realEndpoint });
|
|
1570
|
+
await browser2.discoverContexts().catch((err) => {
|
|
1571
|
+
console.error(`[browser] discoverContexts failed: ${err.message}`);
|
|
1572
|
+
});
|
|
1573
|
+
return browser2;
|
|
1565
1574
|
}
|
|
1566
1575
|
const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
|
|
1567
|
-
|
|
1576
|
+
const { browser } = await launch({ executablePath, headless: options?.headless ?? true });
|
|
1577
|
+
return browser;
|
|
1568
1578
|
}
|
|
1569
1579
|
async function getBrowser(options) {
|
|
1570
1580
|
if (_sharedBrowser) return _sharedBrowser;
|
|
@@ -1574,10 +1584,7 @@ async function getBrowser(options) {
|
|
|
1574
1584
|
return _sharedBrowser;
|
|
1575
1585
|
}
|
|
1576
1586
|
function findSession(name) {
|
|
1577
|
-
|
|
1578
|
-
if (session.name === name) return session;
|
|
1579
|
-
}
|
|
1580
|
-
return void 0;
|
|
1587
|
+
return sessions.find(name);
|
|
1581
1588
|
}
|
|
1582
1589
|
function getSessionById(id) {
|
|
1583
1590
|
return sessions.get(id);
|
|
@@ -1681,9 +1688,14 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
1681
1688
|
return void 0;
|
|
1682
1689
|
}
|
|
1683
1690
|
const targetUrl = meta.conversationUrl || meta.url;
|
|
1684
|
-
if (targetUrl && page.url() !== targetUrl
|
|
1685
|
-
|
|
1686
|
-
|
|
1691
|
+
if (targetUrl && page.url() !== targetUrl) {
|
|
1692
|
+
try {
|
|
1693
|
+
if (!page.url().includes(new URL(targetUrl).hostname)) {
|
|
1694
|
+
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
} catch {
|
|
1698
|
+
}
|
|
1687
1699
|
}
|
|
1688
1700
|
const session = {
|
|
1689
1701
|
id: meta.id || randomUUID(),
|
|
@@ -1696,13 +1708,13 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
1696
1708
|
isCDP: true,
|
|
1697
1709
|
cdpEndpoint: ep
|
|
1698
1710
|
};
|
|
1699
|
-
for (const
|
|
1711
|
+
for (const existingSession of sessions.list()) {
|
|
1700
1712
|
if (existingSession.name === name) {
|
|
1701
|
-
logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${
|
|
1702
|
-
sessions.
|
|
1713
|
+
logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingSession.id}" during restore`);
|
|
1714
|
+
sessions.removeById(existingSession.id);
|
|
1703
1715
|
}
|
|
1704
1716
|
}
|
|
1705
|
-
sessions.set(session
|
|
1717
|
+
sessions.set(session);
|
|
1706
1718
|
resetIdleTimer();
|
|
1707
1719
|
await installNetworkCapture(page, name);
|
|
1708
1720
|
return session;
|
|
@@ -1715,10 +1727,15 @@ async function findOrRestoreSession(name, cdpEndpoint) {
|
|
|
1715
1727
|
async function createEphemeralContext(options) {
|
|
1716
1728
|
if (options?.cdpEndpoint) {
|
|
1717
1729
|
const endpoint = await resolveCDPEndpoint(options.cdpEndpoint);
|
|
1718
|
-
const b2 = await
|
|
1730
|
+
const { browser: b2 } = await launch({ cdpEndpoint: endpoint });
|
|
1719
1731
|
const contexts = b2.contexts();
|
|
1720
1732
|
const ctx = contexts[0] || await b2.newContext();
|
|
1721
|
-
const
|
|
1733
|
+
const allPages = ctx.pages();
|
|
1734
|
+
const existingPages = allPages.filter((p) => {
|
|
1735
|
+
const url = p.url();
|
|
1736
|
+
return url !== "about:blank" && !url.startsWith("chrome://");
|
|
1737
|
+
});
|
|
1738
|
+
const page2 = existingPages.length > 0 ? existingPages[0] : allPages.length > 0 ? allPages[0] : await ctx.newPage();
|
|
1722
1739
|
resetIdleTimer();
|
|
1723
1740
|
ephemeralConnections.set(page2, b2);
|
|
1724
1741
|
return { context: ctx, page: page2 };
|
|
@@ -1750,38 +1767,59 @@ async function closeEphemeralContext(context) {
|
|
|
1750
1767
|
}
|
|
1751
1768
|
}
|
|
1752
1769
|
function getAllSessions() {
|
|
1753
|
-
return
|
|
1770
|
+
return sessions.list();
|
|
1754
1771
|
}
|
|
1755
1772
|
async function installNetworkCapture(page, sessionName) {
|
|
1756
1773
|
if (process.env.XBROWSER_DAEMON_WORKER !== "1") return;
|
|
1757
|
-
const { networkStore } = await import("./network-store-
|
|
1758
|
-
|
|
1774
|
+
const { networkStore } = await import("./network-store-XGZ25FFC.js");
|
|
1775
|
+
const requestData = /* @__PURE__ */ new Map();
|
|
1776
|
+
const responseMeta = /* @__PURE__ */ new Map();
|
|
1777
|
+
const xbPage = page;
|
|
1778
|
+
xbPage.on("request", (params) => {
|
|
1759
1779
|
try {
|
|
1760
|
-
const
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1780
|
+
const p = params;
|
|
1781
|
+
requestData.set(p.requestId, {
|
|
1782
|
+
method: p.request.method,
|
|
1783
|
+
headers: p.request.headers,
|
|
1784
|
+
postData: p.request.postData ?? null,
|
|
1785
|
+
resourceType: p.type
|
|
1786
|
+
});
|
|
1787
|
+
} catch {
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
xbPage.on("response", (params) => {
|
|
1791
|
+
try {
|
|
1792
|
+
const p = params;
|
|
1793
|
+
responseMeta.set(p.requestId, {
|
|
1794
|
+
status: p.response.status,
|
|
1795
|
+
url: p.response.url,
|
|
1796
|
+
headers: p.response.headers,
|
|
1797
|
+
mimeType: p.response.mimeType,
|
|
1798
|
+
type: p.type
|
|
1799
|
+
});
|
|
1800
|
+
} catch {
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
xbPage.on("requestfinished", async (params) => {
|
|
1804
|
+
try {
|
|
1805
|
+
const p = params;
|
|
1806
|
+
const meta = responseMeta.get(p.requestId);
|
|
1807
|
+
if (!meta) return;
|
|
1808
|
+
const req = requestData.get(p.requestId);
|
|
1809
|
+
const method = req?.method ?? "GET";
|
|
1810
|
+
const contentType = meta.headers["content-type"] || meta.headers["Content-Type"] || "";
|
|
1811
|
+
const resourceType = req?.resourceType ?? meta.type;
|
|
1812
|
+
const requestHeaders = req?.headers ?? {};
|
|
1771
1813
|
let requestBody = void 0;
|
|
1772
|
-
const method = request.method();
|
|
1773
1814
|
const isPostLike = ["POST", "PATCH", "PUT"].includes(method);
|
|
1774
1815
|
if (isPostLike && requestHeaders["content-type"]?.includes("application/json")) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
requestBody = postData;
|
|
1782
|
-
}
|
|
1816
|
+
const postData = req?.postData;
|
|
1817
|
+
if (postData) {
|
|
1818
|
+
try {
|
|
1819
|
+
requestBody = JSON.parse(postData);
|
|
1820
|
+
} catch {
|
|
1821
|
+
requestBody = postData;
|
|
1783
1822
|
}
|
|
1784
|
-
} catch {
|
|
1785
1823
|
}
|
|
1786
1824
|
}
|
|
1787
1825
|
let responseBody = void 0;
|
|
@@ -1789,7 +1827,11 @@ async function installNetworkCapture(page, sessionName) {
|
|
|
1789
1827
|
const isJsonish = contentType.includes("json") || contentType.includes("javascript") || contentType.includes("text/");
|
|
1790
1828
|
if (isJsonish) {
|
|
1791
1829
|
try {
|
|
1792
|
-
const
|
|
1830
|
+
const bodyResult = await xbPage._cdpSend(
|
|
1831
|
+
"Network.getResponseBody",
|
|
1832
|
+
{ requestId: p.requestId }
|
|
1833
|
+
);
|
|
1834
|
+
const text = bodyResult.body ?? "";
|
|
1793
1835
|
size = text.length;
|
|
1794
1836
|
if (size <= 10240) {
|
|
1795
1837
|
try {
|
|
@@ -1802,8 +1844,11 @@ async function installNetworkCapture(page, sessionName) {
|
|
|
1802
1844
|
}
|
|
1803
1845
|
} else {
|
|
1804
1846
|
try {
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1847
|
+
const bodyResult = await xbPage._cdpSend(
|
|
1848
|
+
"Network.getResponseBody",
|
|
1849
|
+
{ requestId: p.requestId }
|
|
1850
|
+
);
|
|
1851
|
+
size = bodyResult.body?.length ?? 0;
|
|
1807
1852
|
} catch {
|
|
1808
1853
|
size = 0;
|
|
1809
1854
|
}
|
|
@@ -1811,17 +1856,19 @@ async function installNetworkCapture(page, sessionName) {
|
|
|
1811
1856
|
networkStore.add(sessionName, {
|
|
1812
1857
|
timestamp: Date.now(),
|
|
1813
1858
|
method,
|
|
1814
|
-
url,
|
|
1815
|
-
path: new URL(url).pathname,
|
|
1816
|
-
status:
|
|
1859
|
+
url: meta.url,
|
|
1860
|
+
path: new URL(meta.url).pathname,
|
|
1861
|
+
status: meta.status,
|
|
1817
1862
|
contentType,
|
|
1818
1863
|
size,
|
|
1819
|
-
headers,
|
|
1864
|
+
headers: meta.headers,
|
|
1820
1865
|
body: responseBody,
|
|
1821
1866
|
requestHeaders,
|
|
1822
1867
|
requestBody,
|
|
1823
|
-
resourceType
|
|
1868
|
+
resourceType
|
|
1824
1869
|
});
|
|
1870
|
+
requestData.delete(p.requestId);
|
|
1871
|
+
responseMeta.delete(p.requestId);
|
|
1825
1872
|
} catch {
|
|
1826
1873
|
}
|
|
1827
1874
|
});
|
|
@@ -1845,16 +1892,38 @@ async function createSession(name, url, options) {
|
|
|
1845
1892
|
}
|
|
1846
1893
|
context = contexts[0] || await b.newContext();
|
|
1847
1894
|
let targetPage = null;
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1895
|
+
const targetHostname = url ? (() => {
|
|
1896
|
+
try {
|
|
1897
|
+
return new URL(url).hostname;
|
|
1898
|
+
} catch {
|
|
1899
|
+
return "";
|
|
1900
|
+
}
|
|
1901
|
+
})() : "";
|
|
1902
|
+
if (targetHostname) {
|
|
1903
|
+
for (const ctx of contexts) {
|
|
1904
|
+
const pages = ctx.pages();
|
|
1905
|
+
for (const p of pages) {
|
|
1906
|
+
const pUrl = p.url();
|
|
1907
|
+
if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && pUrl.includes(targetHostname)) {
|
|
1908
|
+
targetPage = p;
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (targetPage) break;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (!targetPage) {
|
|
1916
|
+
for (const ctx of contexts) {
|
|
1917
|
+
const pages = ctx.pages();
|
|
1918
|
+
for (const p of pages) {
|
|
1919
|
+
const pUrl = p.url();
|
|
1920
|
+
if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
|
|
1921
|
+
targetPage = p;
|
|
1922
|
+
break;
|
|
1923
|
+
}
|
|
1855
1924
|
}
|
|
1925
|
+
if (targetPage) break;
|
|
1856
1926
|
}
|
|
1857
|
-
if (targetPage) break;
|
|
1858
1927
|
}
|
|
1859
1928
|
if (!targetPage && options?.cdpEndpoint) {
|
|
1860
1929
|
const targets = await getCDPTargets(options.cdpEndpoint);
|
|
@@ -1895,14 +1964,14 @@ async function createSession(name, url, options) {
|
|
|
1895
1964
|
isCDP,
|
|
1896
1965
|
cdpEndpoint: options?.cdpEndpoint
|
|
1897
1966
|
};
|
|
1898
|
-
sessions.set(session
|
|
1967
|
+
sessions.set(session);
|
|
1899
1968
|
logSessionEvent("create_session", `name="${name}" id="${session.id}" url="${url || "(no url)"}" isCDP=${isCDP} cdpEndpoint=${options?.cdpEndpoint || "(none)"}`);
|
|
1900
1969
|
resetIdleTimer();
|
|
1901
1970
|
await installNetworkCapture(page, name);
|
|
1902
1971
|
return session;
|
|
1903
1972
|
}
|
|
1904
1973
|
async function closeSessionByName(name) {
|
|
1905
|
-
for (const
|
|
1974
|
+
for (const session of sessions) {
|
|
1906
1975
|
if (session.name === name || session.id === name) {
|
|
1907
1976
|
logSessionEvent("close_session", `name="${session.name}" id="${session.id}" url="${session.page.url()}"`);
|
|
1908
1977
|
if (session.isCDP) {
|
|
@@ -1921,20 +1990,20 @@ async function closeSessionByName(name) {
|
|
|
1921
1990
|
});
|
|
1922
1991
|
}
|
|
1923
1992
|
}
|
|
1924
|
-
sessions.
|
|
1993
|
+
sessions.removeById(session.id);
|
|
1925
1994
|
const file2 = sessionFile(session.name);
|
|
1926
1995
|
try {
|
|
1927
1996
|
unlinkSync(file2);
|
|
1928
1997
|
} catch {
|
|
1929
1998
|
}
|
|
1930
1999
|
try {
|
|
1931
|
-
const { networkStore, commandLogStore } = await import("./network-store-
|
|
2000
|
+
const { networkStore, commandLogStore } = await import("./network-store-XGZ25FFC.js");
|
|
1932
2001
|
networkStore.clear(session.name);
|
|
1933
2002
|
commandLogStore.clear(session.name);
|
|
1934
2003
|
} catch {
|
|
1935
2004
|
}
|
|
1936
2005
|
try {
|
|
1937
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
2006
|
+
const { SessionRecorder } = await import("./session-recorder-YI7YYM36.js");
|
|
1938
2007
|
SessionRecorder.cleanup(session.name);
|
|
1939
2008
|
} catch {
|
|
1940
2009
|
}
|
|
@@ -1949,9 +2018,9 @@ async function closeSessionByName(name) {
|
|
|
1949
2018
|
return false;
|
|
1950
2019
|
}
|
|
1951
2020
|
async function closeAllSessions() {
|
|
1952
|
-
const names =
|
|
2021
|
+
const names = sessions.list().map((s) => `${s.name}(${s.page.url()})`).join(", ");
|
|
1953
2022
|
if (names) logSessionEvent("close_all_sessions", `Closing ${sessions.size} sessions: ${names}`);
|
|
1954
|
-
for (const
|
|
2023
|
+
for (const session of sessions.list()) {
|
|
1955
2024
|
try {
|
|
1956
2025
|
if (!session.isCDP) {
|
|
1957
2026
|
await session.context.close();
|
|
@@ -1960,9 +2029,9 @@ async function closeAllSessions() {
|
|
|
1960
2029
|
await session.browser.close().catch(() => {
|
|
1961
2030
|
});
|
|
1962
2031
|
}
|
|
1963
|
-
sessions.
|
|
2032
|
+
sessions.removeById(session.id);
|
|
1964
2033
|
} catch {
|
|
1965
|
-
sessions.
|
|
2034
|
+
sessions.removeById(session.id);
|
|
1966
2035
|
}
|
|
1967
2036
|
}
|
|
1968
2037
|
}
|
|
@@ -2000,7 +2069,7 @@ async function ensureProcessCanExit() {
|
|
|
2000
2069
|
clearTimeout(idleTimer);
|
|
2001
2070
|
idleTimer = null;
|
|
2002
2071
|
}
|
|
2003
|
-
for (const session of sessions.
|
|
2072
|
+
for (const session of sessions.list()) {
|
|
2004
2073
|
if (session.browser) {
|
|
2005
2074
|
if (session.isCDP) {
|
|
2006
2075
|
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
|
+
};
|