bb-browser 0.10.0 → 0.11.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/README.md +9 -11
- package/README.zh-CN.md +9 -11
- package/dist/{chunk-DBJBHYC7.js → chunk-5WGFUZLM.js} +3 -4
- package/dist/chunk-5WGFUZLM.js.map +1 -0
- package/dist/cli.js +260 -1483
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1835 -262
- package/dist/daemon.js.map +1 -1
- package/dist/mcp.js +173 -31
- package/dist/mcp.js.map +1 -1
- package/dist/openclaw-bridge-Q6EFUQCH.js +106 -0
- package/dist/openclaw-bridge-Q6EFUQCH.js.map +1 -0
- package/extension/manifest.json +1 -1
- package/package.json +3 -1
- package/dist/chunk-DBJBHYC7.js.map +0 -1
- package/dist/chunk-FSL4RNI6.js +0 -53
- package/dist/chunk-FSL4RNI6.js.map +0 -1
- package/dist/openclaw-bridge-HBJH6UFO.js +0 -46
- package/dist/openclaw-bridge-HBJH6UFO.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,1295 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
COMMAND_TIMEOUT,
|
|
4
|
+
DAEMON_PORT,
|
|
4
5
|
generateId
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5WGFUZLM.js";
|
|
6
7
|
import {
|
|
7
8
|
applyJq
|
|
8
9
|
} from "./chunk-AHGAQEFO.js";
|
|
9
|
-
import {
|
|
10
|
-
parseOpenClawJson
|
|
11
|
-
} from "./chunk-FSL4RNI6.js";
|
|
12
10
|
import "./chunk-D4HDZEJT.js";
|
|
13
11
|
|
|
14
12
|
// packages/cli/src/index.ts
|
|
15
|
-
import { fileURLToPath as
|
|
13
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16
14
|
|
|
17
|
-
// packages/cli/src/
|
|
18
|
-
import {
|
|
15
|
+
// packages/cli/src/daemon-manager.ts
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
import { readFile } from "fs/promises";
|
|
19
18
|
import { request as httpRequest } from "http";
|
|
20
|
-
import { request as httpsRequest } from "https";
|
|
21
|
-
import os2 from "os";
|
|
22
|
-
import path2 from "path";
|
|
23
19
|
import { fileURLToPath } from "url";
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
// packages/cli/src/cdp-discovery.ts
|
|
27
|
-
import { execFile, execSync, spawn } from "child_process";
|
|
20
|
+
import { dirname, resolve } from "path";
|
|
28
21
|
import { existsSync } from "fs";
|
|
29
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
30
22
|
import os from "os";
|
|
31
23
|
import path from "path";
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
var
|
|
35
|
-
var
|
|
36
|
-
function
|
|
37
|
-
return new Promise((
|
|
38
|
-
execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
|
|
39
|
-
if (error) {
|
|
40
|
-
reject(error);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
resolve3(stdout.trim());
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function getArgValue(flag) {
|
|
48
|
-
const index = process.argv.indexOf(flag);
|
|
49
|
-
if (index < 0) return void 0;
|
|
50
|
-
return process.argv[index + 1];
|
|
51
|
-
}
|
|
52
|
-
async function tryOpenClaw() {
|
|
53
|
-
try {
|
|
54
|
-
const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 5e3);
|
|
55
|
-
const parsed = parseOpenClawJson(raw);
|
|
56
|
-
const port = Number(parsed?.cdpPort);
|
|
57
|
-
if (Number.isInteger(port) && port > 0) {
|
|
58
|
-
return { host: "127.0.0.1", port };
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
async function canConnect(host, port) {
|
|
65
|
-
try {
|
|
66
|
-
const controller = new AbortController();
|
|
67
|
-
const timeout = setTimeout(() => controller.abort(), 1200);
|
|
68
|
-
const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
|
|
69
|
-
clearTimeout(timeout);
|
|
70
|
-
return response.ok;
|
|
71
|
-
} catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function findBrowserExecutable() {
|
|
76
|
-
if (process.platform === "darwin") {
|
|
77
|
-
const candidates = [
|
|
78
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
79
|
-
"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
|
|
80
|
-
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
81
|
-
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
|
|
82
|
-
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
83
|
-
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
84
|
-
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
85
|
-
];
|
|
86
|
-
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
87
|
-
}
|
|
88
|
-
if (process.platform === "linux") {
|
|
89
|
-
const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
|
|
90
|
-
for (const candidate of candidates) {
|
|
91
|
-
try {
|
|
92
|
-
const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
93
|
-
if (resolved) {
|
|
94
|
-
return resolved;
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
if (process.platform === "win32") {
|
|
102
|
-
const candidates = [
|
|
103
|
-
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
104
|
-
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
105
|
-
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
|
|
106
|
-
];
|
|
107
|
-
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
async function isManagedBrowserRunning() {
|
|
112
|
-
try {
|
|
113
|
-
const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
|
|
114
|
-
const port = Number.parseInt(rawPort.trim(), 10);
|
|
115
|
-
if (!Number.isInteger(port) || port <= 0) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
return await canConnect("127.0.0.1", port);
|
|
119
|
-
} catch {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
124
|
-
const executable = findBrowserExecutable();
|
|
125
|
-
if (!executable) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
|
|
129
|
-
const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
|
|
130
|
-
const prefsPath = path.join(defaultProfileDir, "Preferences");
|
|
131
|
-
await mkdir(defaultProfileDir, { recursive: true });
|
|
132
|
-
try {
|
|
133
|
-
let prefs = {};
|
|
134
|
-
try {
|
|
135
|
-
prefs = JSON.parse(await readFile(prefsPath, "utf8"));
|
|
136
|
-
} catch {
|
|
137
|
-
}
|
|
138
|
-
if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
|
|
139
|
-
prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
|
|
140
|
-
await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
|
|
141
|
-
}
|
|
142
|
-
} catch {
|
|
143
|
-
}
|
|
144
|
-
const args = [
|
|
145
|
-
`--remote-debugging-port=${port}`,
|
|
146
|
-
`--user-data-dir=${MANAGED_USER_DATA_DIR}`,
|
|
147
|
-
"--no-first-run",
|
|
148
|
-
"--no-default-browser-check",
|
|
149
|
-
"--disable-sync",
|
|
150
|
-
"--disable-background-networking",
|
|
151
|
-
"--disable-component-update",
|
|
152
|
-
"--disable-features=Translate,MediaRouter",
|
|
153
|
-
"--disable-session-crashed-bubble",
|
|
154
|
-
"--hide-crash-restore-bubble",
|
|
155
|
-
"about:blank"
|
|
156
|
-
];
|
|
157
|
-
try {
|
|
158
|
-
const child = spawn(executable, args, {
|
|
159
|
-
detached: true,
|
|
160
|
-
stdio: "ignore"
|
|
161
|
-
});
|
|
162
|
-
child.unref();
|
|
163
|
-
} catch {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
|
|
167
|
-
await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
|
|
168
|
-
const deadline = Date.now() + 8e3;
|
|
169
|
-
while (Date.now() < deadline) {
|
|
170
|
-
if (await canConnect("127.0.0.1", port)) {
|
|
171
|
-
return { host: "127.0.0.1", port };
|
|
172
|
-
}
|
|
173
|
-
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
174
|
-
}
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
async function discoverCdpPort() {
|
|
178
|
-
const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
|
|
179
|
-
if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
|
|
180
|
-
return { host: "127.0.0.1", port: explicitPort };
|
|
181
|
-
}
|
|
182
|
-
try {
|
|
183
|
-
const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
|
|
184
|
-
const managedPort = Number.parseInt(rawPort.trim(), 10);
|
|
185
|
-
if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
|
|
186
|
-
return { host: "127.0.0.1", port: managedPort };
|
|
187
|
-
}
|
|
188
|
-
} catch {
|
|
189
|
-
}
|
|
190
|
-
if (process.argv.includes("--openclaw")) {
|
|
191
|
-
const viaOpenClaw = await tryOpenClaw();
|
|
192
|
-
if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
|
|
193
|
-
return viaOpenClaw;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const launched = await launchManagedBrowser();
|
|
197
|
-
if (launched) {
|
|
198
|
-
return launched;
|
|
199
|
-
}
|
|
200
|
-
if (!process.argv.includes("--openclaw")) {
|
|
201
|
-
const detectedOpenClaw = await tryOpenClaw();
|
|
202
|
-
if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
|
|
203
|
-
return detectedOpenClaw;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// packages/cli/src/cdp-client.ts
|
|
210
|
-
var connectionState = null;
|
|
211
|
-
var reconnecting = null;
|
|
212
|
-
var networkRequests = /* @__PURE__ */ new Map();
|
|
213
|
-
var networkEnabled = false;
|
|
214
|
-
var consoleMessages = [];
|
|
215
|
-
var consoleEnabled = false;
|
|
216
|
-
var jsErrors = [];
|
|
217
|
-
var errorsEnabled = false;
|
|
218
|
-
var traceRecording = false;
|
|
219
|
-
var traceEvents = [];
|
|
220
|
-
function getContextFilePath(host, port) {
|
|
221
|
-
const safeHost = host.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
222
|
-
return path2.join(os2.tmpdir(), `bb-browser-cdp-context-${safeHost}-${port}.json`);
|
|
223
|
-
}
|
|
224
|
-
function loadPersistedCurrentTargetId(host, port) {
|
|
225
|
-
try {
|
|
226
|
-
const data = JSON.parse(readFileSync(getContextFilePath(host, port), "utf-8"));
|
|
227
|
-
return typeof data.currentTargetId === "string" && data.currentTargetId ? data.currentTargetId : void 0;
|
|
228
|
-
} catch {
|
|
229
|
-
return void 0;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function persistCurrentTargetId(host, port, currentTargetId) {
|
|
233
|
-
try {
|
|
234
|
-
writeFileSync(getContextFilePath(host, port), JSON.stringify({ currentTargetId }));
|
|
235
|
-
} catch {
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function setCurrentTargetId(targetId) {
|
|
239
|
-
const state = connectionState;
|
|
240
|
-
if (!state) return;
|
|
241
|
-
state.currentTargetId = targetId;
|
|
242
|
-
persistCurrentTargetId(state.host, state.port, targetId);
|
|
243
|
-
}
|
|
244
|
-
function buildRequestError(error) {
|
|
245
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
246
|
-
}
|
|
247
|
-
function fetchJson(url) {
|
|
248
|
-
return new Promise((resolve3, reject) => {
|
|
249
|
-
const requester = url.startsWith("https:") ? httpsRequest : httpRequest;
|
|
250
|
-
const req = requester(url, { method: "GET" }, (res) => {
|
|
251
|
-
const chunks = [];
|
|
252
|
-
res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
253
|
-
res.on("end", () => {
|
|
254
|
-
const raw = Buffer.concat(chunks).toString("utf8");
|
|
255
|
-
if ((res.statusCode ?? 500) >= 400) {
|
|
256
|
-
reject(new Error(`HTTP ${res.statusCode ?? 500}: ${raw}`));
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
try {
|
|
260
|
-
resolve3(JSON.parse(raw));
|
|
261
|
-
} catch (error) {
|
|
262
|
-
reject(error);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
req.on("error", reject);
|
|
267
|
-
req.end();
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
async function getJsonList(host, port) {
|
|
271
|
-
const data = await fetchJson(`http://${host}:${port}/json/list`);
|
|
272
|
-
return Array.isArray(data) ? data : [];
|
|
273
|
-
}
|
|
274
|
-
async function getJsonVersion(host, port) {
|
|
275
|
-
const data = await fetchJson(`http://${host}:${port}/json/version`);
|
|
276
|
-
const url = data.webSocketDebuggerUrl;
|
|
277
|
-
if (typeof url !== "string" || !url) {
|
|
278
|
-
throw new Error("CDP endpoint missing webSocketDebuggerUrl");
|
|
279
|
-
}
|
|
280
|
-
return { webSocketDebuggerUrl: url };
|
|
281
|
-
}
|
|
282
|
-
function connectWebSocket(url) {
|
|
283
|
-
return new Promise((resolve3, reject) => {
|
|
284
|
-
const ws = new WebSocket(url);
|
|
285
|
-
ws.once("open", () => {
|
|
286
|
-
const socket = ws._socket;
|
|
287
|
-
if (socket && typeof socket.unref === "function") {
|
|
288
|
-
socket.unref();
|
|
289
|
-
}
|
|
290
|
-
resolve3(ws);
|
|
291
|
-
});
|
|
292
|
-
ws.once("error", reject);
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
function createState(host, port, browserWsUrl, browserSocket) {
|
|
296
|
-
const state = {
|
|
297
|
-
host,
|
|
298
|
-
port,
|
|
299
|
-
browserWsUrl,
|
|
300
|
-
browserSocket,
|
|
301
|
-
browserPending: /* @__PURE__ */ new Map(),
|
|
302
|
-
nextMessageId: 1,
|
|
303
|
-
sessions: /* @__PURE__ */ new Map(),
|
|
304
|
-
attachedTargets: /* @__PURE__ */ new Map(),
|
|
305
|
-
refsByTarget: /* @__PURE__ */ new Map(),
|
|
306
|
-
currentTargetId: loadPersistedCurrentTargetId(host, port),
|
|
307
|
-
activeFrameIdByTarget: /* @__PURE__ */ new Map(),
|
|
308
|
-
dialogHandlers: /* @__PURE__ */ new Map()
|
|
309
|
-
};
|
|
310
|
-
browserSocket.on("message", (raw) => {
|
|
311
|
-
const message = JSON.parse(raw.toString());
|
|
312
|
-
if (typeof message.id === "number") {
|
|
313
|
-
const pending = state.browserPending.get(message.id);
|
|
314
|
-
if (!pending) return;
|
|
315
|
-
state.browserPending.delete(message.id);
|
|
316
|
-
if (message.error) {
|
|
317
|
-
pending.reject(new Error(`${pending.method}: ${message.error.message ?? "Unknown CDP error"}`));
|
|
318
|
-
} else {
|
|
319
|
-
pending.resolve(message.result);
|
|
320
|
-
}
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
if (message.method === "Target.attachedToTarget") {
|
|
324
|
-
const params = message.params;
|
|
325
|
-
const sessionId = params.sessionId;
|
|
326
|
-
const targetInfo = params.targetInfo;
|
|
327
|
-
if (typeof sessionId === "string" && typeof targetInfo?.targetId === "string") {
|
|
328
|
-
state.sessions.set(targetInfo.targetId, sessionId);
|
|
329
|
-
state.attachedTargets.set(sessionId, targetInfo.targetId);
|
|
330
|
-
}
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (message.method === "Target.detachedFromTarget") {
|
|
334
|
-
const params = message.params;
|
|
335
|
-
const sessionId = params.sessionId;
|
|
336
|
-
if (typeof sessionId === "string") {
|
|
337
|
-
const targetId = state.attachedTargets.get(sessionId);
|
|
338
|
-
if (targetId) {
|
|
339
|
-
state.sessions.delete(targetId);
|
|
340
|
-
state.attachedTargets.delete(sessionId);
|
|
341
|
-
state.activeFrameIdByTarget.delete(targetId);
|
|
342
|
-
state.dialogHandlers.delete(targetId);
|
|
343
|
-
if (state.currentTargetId === targetId) {
|
|
344
|
-
state.currentTargetId = void 0;
|
|
345
|
-
persistCurrentTargetId(state.host, state.port, void 0);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (message.method === "Target.receivedMessageFromTarget") {
|
|
352
|
-
const params = message.params;
|
|
353
|
-
const sessionId = params.sessionId;
|
|
354
|
-
const messageText = params.message;
|
|
355
|
-
if (typeof sessionId === "string" && typeof messageText === "string") {
|
|
356
|
-
const targetId = state.attachedTargets.get(sessionId);
|
|
357
|
-
if (targetId) {
|
|
358
|
-
handleSessionEvent(targetId, JSON.parse(messageText)).catch(() => {
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
if (typeof message.sessionId === "string" && typeof message.method === "string") {
|
|
365
|
-
const targetId = state.attachedTargets.get(message.sessionId);
|
|
366
|
-
if (targetId) {
|
|
367
|
-
handleSessionEvent(targetId, message).catch(() => {
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
browserSocket.on("close", () => {
|
|
373
|
-
if (connectionState === state) {
|
|
374
|
-
connectionState = null;
|
|
375
|
-
}
|
|
376
|
-
for (const pending of state.browserPending.values()) {
|
|
377
|
-
pending.reject(new Error("CDP connection closed"));
|
|
378
|
-
}
|
|
379
|
-
state.browserPending.clear();
|
|
380
|
-
});
|
|
381
|
-
browserSocket.on("error", () => {
|
|
382
|
-
});
|
|
383
|
-
return state;
|
|
384
|
-
}
|
|
385
|
-
async function browserCommand(method, params = {}) {
|
|
386
|
-
const state = connectionState;
|
|
387
|
-
if (!state) throw new Error("CDP connection not initialized");
|
|
388
|
-
const id = state.nextMessageId++;
|
|
389
|
-
const payload = JSON.stringify({ id, method, params });
|
|
390
|
-
const promise = new Promise((resolve3, reject) => {
|
|
391
|
-
state.browserPending.set(id, { resolve: resolve3, reject, method });
|
|
392
|
-
});
|
|
393
|
-
state.browserSocket.send(payload);
|
|
394
|
-
return promise;
|
|
395
|
-
}
|
|
396
|
-
async function sessionCommand(targetId, method, params = {}) {
|
|
397
|
-
const state = connectionState;
|
|
398
|
-
if (!state) throw new Error("CDP connection not initialized");
|
|
399
|
-
const sessionId = state.sessions.get(targetId) ?? await attachTarget(targetId);
|
|
400
|
-
const id = state.nextMessageId++;
|
|
401
|
-
const payload = JSON.stringify({ id, method, params, sessionId });
|
|
402
|
-
return new Promise((resolve3, reject) => {
|
|
403
|
-
const check = (raw) => {
|
|
404
|
-
const msg = JSON.parse(raw.toString());
|
|
405
|
-
if (msg.id === id && msg.sessionId === sessionId) {
|
|
406
|
-
state.browserSocket.off("message", check);
|
|
407
|
-
if (msg.error) reject(new Error(`${method}: ${msg.error.message ?? "Unknown CDP error"}`));
|
|
408
|
-
else resolve3(msg.result);
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
state.browserSocket.on("message", check);
|
|
412
|
-
state.browserSocket.send(payload);
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
function getActiveFrameId(targetId) {
|
|
416
|
-
const frameId = connectionState?.activeFrameIdByTarget.get(targetId);
|
|
417
|
-
return frameId ?? void 0;
|
|
418
|
-
}
|
|
419
|
-
async function pageCommand(targetId, method, params = {}) {
|
|
420
|
-
const frameId = getActiveFrameId(targetId);
|
|
421
|
-
return sessionCommand(targetId, method, frameId ? { ...params, frameId } : params);
|
|
422
|
-
}
|
|
423
|
-
function normalizeHeaders(headers) {
|
|
424
|
-
if (!headers || typeof headers !== "object") return void 0;
|
|
425
|
-
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
|
|
426
|
-
}
|
|
427
|
-
async function handleSessionEvent(targetId, event) {
|
|
428
|
-
const method = event.method;
|
|
429
|
-
const params = event.params ?? {};
|
|
430
|
-
if (typeof method !== "string") return;
|
|
431
|
-
if (method === "Page.javascriptDialogOpening") {
|
|
432
|
-
const handler = connectionState?.dialogHandlers.get(targetId);
|
|
433
|
-
if (handler) {
|
|
434
|
-
await sessionCommand(targetId, "Page.handleJavaScriptDialog", {
|
|
435
|
-
accept: handler.accept,
|
|
436
|
-
...handler.promptText !== void 0 ? { promptText: handler.promptText } : {}
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (method === "Network.requestWillBeSent") {
|
|
442
|
-
const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
|
|
443
|
-
const request = params.request;
|
|
444
|
-
if (!requestId || !request) return;
|
|
445
|
-
networkRequests.set(requestId, {
|
|
446
|
-
requestId,
|
|
447
|
-
url: String(request.url ?? ""),
|
|
448
|
-
method: String(request.method ?? "GET"),
|
|
449
|
-
type: String(params.type ?? "Other"),
|
|
450
|
-
timestamp: Math.round(Number(params.timestamp ?? Date.now()) * 1e3),
|
|
451
|
-
requestHeaders: normalizeHeaders(request.headers),
|
|
452
|
-
requestBody: typeof request.postData === "string" ? request.postData : void 0
|
|
453
|
-
});
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
if (method === "Network.responseReceived") {
|
|
457
|
-
const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
|
|
458
|
-
const response = params.response;
|
|
459
|
-
if (!requestId || !response) return;
|
|
460
|
-
const existing = networkRequests.get(requestId);
|
|
461
|
-
if (!existing) return;
|
|
462
|
-
existing.status = typeof response.status === "number" ? response.status : void 0;
|
|
463
|
-
existing.statusText = typeof response.statusText === "string" ? response.statusText : void 0;
|
|
464
|
-
existing.responseHeaders = normalizeHeaders(response.headers);
|
|
465
|
-
existing.mimeType = typeof response.mimeType === "string" ? response.mimeType : void 0;
|
|
466
|
-
networkRequests.set(requestId, existing);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (method === "Network.loadingFailed") {
|
|
470
|
-
const requestId = typeof params.requestId === "string" ? params.requestId : void 0;
|
|
471
|
-
if (!requestId) return;
|
|
472
|
-
const existing = networkRequests.get(requestId);
|
|
473
|
-
if (!existing) return;
|
|
474
|
-
existing.failed = true;
|
|
475
|
-
existing.failureReason = typeof params.errorText === "string" ? params.errorText : "Unknown error";
|
|
476
|
-
networkRequests.set(requestId, existing);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
if (method === "Runtime.consoleAPICalled") {
|
|
480
|
-
const type = String(params.type ?? "log");
|
|
481
|
-
const args = Array.isArray(params.args) ? params.args : [];
|
|
482
|
-
const text = args.map((arg) => {
|
|
483
|
-
if (typeof arg.value === "string") return arg.value;
|
|
484
|
-
if (arg.value !== void 0) return String(arg.value);
|
|
485
|
-
if (typeof arg.description === "string") return arg.description;
|
|
486
|
-
return "";
|
|
487
|
-
}).filter(Boolean).join(" ");
|
|
488
|
-
const stack = params.stackTrace;
|
|
489
|
-
const firstCallFrame = Array.isArray(stack?.callFrames) ? stack?.callFrames[0] : void 0;
|
|
490
|
-
consoleMessages.push({
|
|
491
|
-
type: ["log", "info", "warn", "error", "debug"].includes(type) ? type : "log",
|
|
492
|
-
text,
|
|
493
|
-
timestamp: Math.round(Number(params.timestamp ?? Date.now())),
|
|
494
|
-
url: typeof firstCallFrame?.url === "string" ? firstCallFrame.url : void 0,
|
|
495
|
-
lineNumber: typeof firstCallFrame?.lineNumber === "number" ? firstCallFrame.lineNumber : void 0
|
|
496
|
-
});
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
if (method === "Runtime.exceptionThrown") {
|
|
500
|
-
const details = params.exceptionDetails;
|
|
501
|
-
if (!details) return;
|
|
502
|
-
const exception = details.exception;
|
|
503
|
-
const stackTrace = details.stackTrace;
|
|
504
|
-
const callFrames = Array.isArray(stackTrace?.callFrames) ? stackTrace.callFrames : [];
|
|
505
|
-
jsErrors.push({
|
|
506
|
-
message: typeof exception?.description === "string" ? exception.description : String(details.text ?? "JavaScript exception"),
|
|
507
|
-
url: typeof details.url === "string" ? details.url : typeof callFrames[0]?.url === "string" ? String(callFrames[0].url) : void 0,
|
|
508
|
-
lineNumber: typeof details.lineNumber === "number" ? details.lineNumber : void 0,
|
|
509
|
-
columnNumber: typeof details.columnNumber === "number" ? details.columnNumber : void 0,
|
|
510
|
-
stackTrace: callFrames.length > 0 ? callFrames.map((frame) => `${String(frame.functionName ?? "<anonymous>")} (${String(frame.url ?? "")}:${String(frame.lineNumber ?? 0)}:${String(frame.columnNumber ?? 0)})`).join("\n") : void 0,
|
|
511
|
-
timestamp: Date.now()
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async function ensureNetworkMonitoring(targetId) {
|
|
516
|
-
if (networkEnabled) return;
|
|
517
|
-
await sessionCommand(targetId, "Network.enable");
|
|
518
|
-
networkEnabled = true;
|
|
519
|
-
}
|
|
520
|
-
async function ensureConsoleMonitoring(targetId) {
|
|
521
|
-
if (consoleEnabled && errorsEnabled) return;
|
|
522
|
-
await sessionCommand(targetId, "Runtime.enable");
|
|
523
|
-
consoleEnabled = true;
|
|
524
|
-
errorsEnabled = true;
|
|
525
|
-
}
|
|
526
|
-
async function attachTarget(targetId) {
|
|
527
|
-
const result = await browserCommand("Target.attachToTarget", {
|
|
528
|
-
targetId,
|
|
529
|
-
flatten: true
|
|
530
|
-
});
|
|
531
|
-
connectionState?.sessions.set(targetId, result.sessionId);
|
|
532
|
-
connectionState?.attachedTargets.set(result.sessionId, targetId);
|
|
533
|
-
connectionState?.activeFrameIdByTarget.set(targetId, connectionState?.activeFrameIdByTarget.get(targetId) ?? null);
|
|
534
|
-
await sessionCommand(targetId, "Page.enable");
|
|
535
|
-
await sessionCommand(targetId, "Runtime.enable");
|
|
536
|
-
await sessionCommand(targetId, "DOM.enable");
|
|
537
|
-
await sessionCommand(targetId, "Accessibility.enable");
|
|
538
|
-
return result.sessionId;
|
|
539
|
-
}
|
|
540
|
-
async function getTargets() {
|
|
541
|
-
const state = connectionState;
|
|
542
|
-
if (!state) throw new Error("CDP connection not initialized");
|
|
543
|
-
try {
|
|
544
|
-
const result = await browserCommand("Target.getTargets");
|
|
545
|
-
return (result.targetInfos || []).map((target) => ({
|
|
546
|
-
id: target.targetId,
|
|
547
|
-
type: target.type,
|
|
548
|
-
title: target.title,
|
|
549
|
-
url: target.url,
|
|
550
|
-
webSocketDebuggerUrl: ""
|
|
551
|
-
}));
|
|
552
|
-
} catch {
|
|
553
|
-
return getJsonList(state.host, state.port);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
async function ensurePageTarget(targetId) {
|
|
557
|
-
const targets = (await getTargets()).filter((target2) => target2.type === "page");
|
|
558
|
-
if (targets.length === 0) throw new Error("No page target found");
|
|
559
|
-
const persistedTargetId = targetId === void 0 ? connectionState?.currentTargetId : void 0;
|
|
560
|
-
let target;
|
|
561
|
-
if (typeof targetId === "number") {
|
|
562
|
-
target = targets[targetId] ?? targets.find((item) => Number(item.id) === targetId);
|
|
563
|
-
} else if (typeof targetId === "string") {
|
|
564
|
-
target = targets.find((item) => item.id === targetId);
|
|
565
|
-
if (!target) {
|
|
566
|
-
const numericTargetId = Number(targetId);
|
|
567
|
-
if (!Number.isNaN(numericTargetId)) {
|
|
568
|
-
target = targets[numericTargetId] ?? targets.find((item) => Number(item.id) === numericTargetId);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
} else if (persistedTargetId) {
|
|
572
|
-
target = targets.find((item) => item.id === persistedTargetId);
|
|
573
|
-
}
|
|
574
|
-
target ??= targets[0];
|
|
575
|
-
setCurrentTargetId(target.id);
|
|
576
|
-
await attachTarget(target.id);
|
|
577
|
-
return target;
|
|
578
|
-
}
|
|
579
|
-
async function resolveBackendNodeIdByXPath(targetId, xpath) {
|
|
580
|
-
await sessionCommand(targetId, "DOM.getDocument", { depth: 0 });
|
|
581
|
-
const search = await sessionCommand(targetId, "DOM.performSearch", {
|
|
582
|
-
query: xpath,
|
|
583
|
-
includeUserAgentShadowDOM: true
|
|
584
|
-
});
|
|
585
|
-
try {
|
|
586
|
-
if (!search.resultCount) {
|
|
587
|
-
throw new Error(`Unknown ref xpath: ${xpath}`);
|
|
588
|
-
}
|
|
589
|
-
const { nodeIds } = await sessionCommand(targetId, "DOM.getSearchResults", {
|
|
590
|
-
searchId: search.searchId,
|
|
591
|
-
fromIndex: 0,
|
|
592
|
-
toIndex: search.resultCount
|
|
593
|
-
});
|
|
594
|
-
for (const nodeId of nodeIds) {
|
|
595
|
-
const described = await sessionCommand(targetId, "DOM.describeNode", {
|
|
596
|
-
nodeId
|
|
597
|
-
});
|
|
598
|
-
if (described.node.backendNodeId) {
|
|
599
|
-
return described.node.backendNodeId;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
throw new Error(`XPath resolved but no backend node id found: ${xpath}`);
|
|
603
|
-
} finally {
|
|
604
|
-
await sessionCommand(targetId, "DOM.discardSearchResults", { searchId: search.searchId }).catch(() => {
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
async function parseRef(ref) {
|
|
609
|
-
const targetId = connectionState?.currentTargetId ?? "";
|
|
610
|
-
let refs = connectionState?.refsByTarget.get(targetId) ?? {};
|
|
611
|
-
if (!refs[ref] && targetId) {
|
|
612
|
-
const persistedRefs = loadPersistedRefs(targetId);
|
|
613
|
-
if (persistedRefs) {
|
|
614
|
-
connectionState?.refsByTarget.set(targetId, persistedRefs);
|
|
615
|
-
refs = persistedRefs;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
const found = refs[ref];
|
|
619
|
-
if (!found) {
|
|
620
|
-
throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
|
|
621
|
-
}
|
|
622
|
-
if (found.backendDOMNodeId) {
|
|
623
|
-
return found.backendDOMNodeId;
|
|
624
|
-
}
|
|
625
|
-
if (targetId && found.xpath) {
|
|
626
|
-
const backendDOMNodeId = await resolveBackendNodeIdByXPath(targetId, found.xpath);
|
|
627
|
-
found.backendDOMNodeId = backendDOMNodeId;
|
|
628
|
-
connectionState?.refsByTarget.set(targetId, refs);
|
|
629
|
-
const pageUrl = await evaluate(targetId, "location.href", true).catch(() => void 0);
|
|
630
|
-
if (pageUrl) {
|
|
631
|
-
persistRefs(targetId, pageUrl, refs);
|
|
632
|
-
}
|
|
633
|
-
return backendDOMNodeId;
|
|
634
|
-
}
|
|
635
|
-
throw new Error(`Unknown ref: ${ref}. Run snapshot first.`);
|
|
636
|
-
}
|
|
637
|
-
function getRefsFilePath(targetId) {
|
|
638
|
-
return path2.join(os2.tmpdir(), `bb-browser-refs-${targetId}.json`);
|
|
639
|
-
}
|
|
640
|
-
function loadPersistedRefs(targetId, expectedUrl) {
|
|
641
|
-
try {
|
|
642
|
-
const data = JSON.parse(readFileSync(getRefsFilePath(targetId), "utf-8"));
|
|
643
|
-
if (data.targetId !== targetId) return null;
|
|
644
|
-
if (expectedUrl !== void 0 && data.url !== expectedUrl) return null;
|
|
645
|
-
if (!data.refs || typeof data.refs !== "object") return null;
|
|
646
|
-
return data.refs;
|
|
647
|
-
} catch {
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
function persistRefs(targetId, url, refs) {
|
|
652
|
-
try {
|
|
653
|
-
writeFileSync(getRefsFilePath(targetId), JSON.stringify({ targetId, url, timestamp: Date.now(), refs }));
|
|
654
|
-
} catch {
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
function clearPersistedRefs(targetId) {
|
|
658
|
-
try {
|
|
659
|
-
unlinkSync(getRefsFilePath(targetId));
|
|
660
|
-
} catch {
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
function loadBuildDomTreeScript() {
|
|
664
|
-
const currentDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
665
|
-
const candidates = [
|
|
666
|
-
path2.resolve(currentDir, "./extension/buildDomTree.js"),
|
|
667
|
-
// npm installed: dist/cli.js → ../extension/buildDomTree.js
|
|
668
|
-
path2.resolve(currentDir, "../extension/buildDomTree.js"),
|
|
669
|
-
path2.resolve(currentDir, "../extension/dist/buildDomTree.js"),
|
|
670
|
-
path2.resolve(currentDir, "../packages/extension/public/buildDomTree.js"),
|
|
671
|
-
path2.resolve(currentDir, "../packages/extension/dist/buildDomTree.js"),
|
|
672
|
-
// dev mode: packages/cli/dist/ → ../../../extension/
|
|
673
|
-
path2.resolve(currentDir, "../../../extension/buildDomTree.js"),
|
|
674
|
-
path2.resolve(currentDir, "../../../extension/dist/buildDomTree.js"),
|
|
675
|
-
// dev mode: packages/cli/src/ → ../../extension/
|
|
676
|
-
path2.resolve(currentDir, "../../extension/buildDomTree.js"),
|
|
677
|
-
path2.resolve(currentDir, "../../../packages/extension/dist/buildDomTree.js"),
|
|
678
|
-
path2.resolve(currentDir, "../../../packages/extension/public/buildDomTree.js")
|
|
679
|
-
];
|
|
680
|
-
for (const candidate of candidates) {
|
|
681
|
-
try {
|
|
682
|
-
return readFileSync(candidate, "utf8");
|
|
683
|
-
} catch {
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
throw new Error("Cannot find buildDomTree.js");
|
|
687
|
-
}
|
|
688
|
-
async function evaluate(targetId, expression, returnByValue = true) {
|
|
689
|
-
const result = await sessionCommand(targetId, "Runtime.evaluate", {
|
|
690
|
-
expression,
|
|
691
|
-
awaitPromise: true,
|
|
692
|
-
returnByValue,
|
|
693
|
-
replMode: true
|
|
694
|
-
});
|
|
695
|
-
if (result.exceptionDetails) {
|
|
696
|
-
throw new Error(
|
|
697
|
-
result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Runtime.evaluate failed"
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
return result.result.value ?? result.result;
|
|
701
|
-
}
|
|
702
|
-
async function focusNode(targetId, backendNodeId) {
|
|
703
|
-
await sessionCommand(targetId, "DOM.focus", { backendNodeId });
|
|
704
|
-
}
|
|
705
|
-
async function insertTextIntoNode(targetId, backendNodeId, text, clearFirst) {
|
|
706
|
-
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
707
|
-
await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
708
|
-
objectId: resolved.object.objectId,
|
|
709
|
-
functionDeclaration: `function(clearFirst) {
|
|
710
|
-
if (typeof this.scrollIntoView === 'function') {
|
|
711
|
-
this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
|
|
712
|
-
}
|
|
713
|
-
if (typeof this.focus === 'function') this.focus();
|
|
714
|
-
if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement) {
|
|
715
|
-
if (clearFirst) {
|
|
716
|
-
this.value = '';
|
|
717
|
-
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
718
|
-
}
|
|
719
|
-
if (typeof this.setSelectionRange === 'function') {
|
|
720
|
-
const end = this.value.length;
|
|
721
|
-
this.setSelectionRange(end, end);
|
|
722
|
-
}
|
|
723
|
-
return true;
|
|
724
|
-
}
|
|
725
|
-
if (this instanceof HTMLElement && this.isContentEditable) {
|
|
726
|
-
if (clearFirst) {
|
|
727
|
-
this.textContent = '';
|
|
728
|
-
this.dispatchEvent(new Event('input', { bubbles: true }));
|
|
729
|
-
}
|
|
730
|
-
const selection = window.getSelection();
|
|
731
|
-
if (selection) {
|
|
732
|
-
const range = document.createRange();
|
|
733
|
-
range.selectNodeContents(this);
|
|
734
|
-
range.collapse(false);
|
|
735
|
-
selection.removeAllRanges();
|
|
736
|
-
selection.addRange(range);
|
|
737
|
-
}
|
|
738
|
-
return true;
|
|
739
|
-
}
|
|
740
|
-
return false;
|
|
741
|
-
}`,
|
|
742
|
-
arguments: [
|
|
743
|
-
{ value: clearFirst }
|
|
744
|
-
],
|
|
745
|
-
returnByValue: true
|
|
746
|
-
});
|
|
747
|
-
if (text) {
|
|
748
|
-
await focusNode(targetId, backendNodeId);
|
|
749
|
-
await sessionCommand(targetId, "Input.insertText", { text });
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
async function getInteractablePoint(targetId, backendNodeId) {
|
|
753
|
-
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
754
|
-
const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
755
|
-
objectId: resolved.object.objectId,
|
|
756
|
-
functionDeclaration: `function() {
|
|
757
|
-
if (!(this instanceof Element)) {
|
|
758
|
-
throw new Error('Ref does not resolve to an element');
|
|
759
|
-
}
|
|
760
|
-
this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
|
|
761
|
-
const rect = this.getBoundingClientRect();
|
|
762
|
-
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
763
|
-
throw new Error('Element is not visible');
|
|
764
|
-
}
|
|
765
|
-
return {
|
|
766
|
-
x: rect.left + rect.width / 2,
|
|
767
|
-
y: rect.top + rect.height / 2,
|
|
768
|
-
};
|
|
769
|
-
}`,
|
|
770
|
-
returnByValue: true
|
|
771
|
-
});
|
|
772
|
-
if (call.exceptionDetails) {
|
|
773
|
-
throw new Error(call.exceptionDetails.text || "Failed to resolve element point");
|
|
774
|
-
}
|
|
775
|
-
const point = call.result.value;
|
|
776
|
-
if (!point || typeof point.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
|
|
777
|
-
throw new Error("Failed to resolve element point");
|
|
778
|
-
}
|
|
779
|
-
return point;
|
|
780
|
-
}
|
|
781
|
-
async function mouseClick(targetId, x, y) {
|
|
782
|
-
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
|
|
783
|
-
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
784
|
-
await sessionCommand(targetId, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
785
|
-
}
|
|
786
|
-
async function getAttributeValue(targetId, backendNodeId, attribute) {
|
|
787
|
-
if (attribute === "text") {
|
|
788
|
-
const resolved = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
789
|
-
const call2 = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
790
|
-
objectId: resolved.object.objectId,
|
|
791
|
-
functionDeclaration: `function() { return (this instanceof HTMLElement ? this.innerText : this.textContent || '').trim(); }`,
|
|
792
|
-
returnByValue: true
|
|
793
|
-
});
|
|
794
|
-
return String(call2.result.value ?? "");
|
|
795
|
-
}
|
|
796
|
-
const result = await sessionCommand(targetId, "DOM.resolveNode", { backendNodeId });
|
|
797
|
-
const call = await sessionCommand(targetId, "Runtime.callFunctionOn", {
|
|
798
|
-
objectId: result.object.objectId,
|
|
799
|
-
functionDeclaration: `function() { if (${JSON.stringify(attribute)} === 'url') return this.href || this.src || location.href; if (${JSON.stringify(attribute)} === 'title') return document.title; return this.getAttribute(${JSON.stringify(attribute)}) || ''; }`,
|
|
800
|
-
returnByValue: true
|
|
801
|
-
});
|
|
802
|
-
return String(call.result.value ?? "");
|
|
803
|
-
}
|
|
804
|
-
async function buildSnapshot(targetId, request) {
|
|
805
|
-
const script = loadBuildDomTreeScript();
|
|
806
|
-
const buildArgs = {
|
|
807
|
-
showHighlightElements: true,
|
|
808
|
-
focusHighlightIndex: -1,
|
|
809
|
-
viewportExpansion: -1,
|
|
810
|
-
debugMode: false,
|
|
811
|
-
startId: 0,
|
|
812
|
-
startHighlightIndex: 0
|
|
813
|
-
};
|
|
814
|
-
const expression = `(() => { ${script}; const fn = globalThis.buildDomTree ?? (typeof window !== 'undefined' ? window.buildDomTree : undefined); if (typeof fn !== 'function') { throw new Error('buildDomTree is not available after script injection'); } return fn(${JSON.stringify({
|
|
815
|
-
...buildArgs
|
|
816
|
-
})}); })()`;
|
|
817
|
-
const value = await evaluate(targetId, expression, true);
|
|
818
|
-
if (!value || !value.map || !value.rootId) {
|
|
819
|
-
const title = await evaluate(targetId, "document.title", true);
|
|
820
|
-
const pageUrl2 = await evaluate(targetId, "location.href", true);
|
|
821
|
-
const fallbackSnapshot = {
|
|
822
|
-
title,
|
|
823
|
-
url: pageUrl2,
|
|
824
|
-
lines: [title || pageUrl2],
|
|
825
|
-
refs: {}
|
|
826
|
-
};
|
|
827
|
-
connectionState?.refsByTarget.set(targetId, {});
|
|
828
|
-
persistRefs(targetId, pageUrl2, {});
|
|
829
|
-
return fallbackSnapshot;
|
|
830
|
-
}
|
|
831
|
-
const snapshot = convertBuildDomTreeResult(value, {
|
|
832
|
-
interactiveOnly: !!request.interactive,
|
|
833
|
-
compact: !!request.compact,
|
|
834
|
-
maxDepth: request.maxDepth,
|
|
835
|
-
selector: request.selector
|
|
836
|
-
});
|
|
837
|
-
const pageUrl = await evaluate(targetId, "location.href", true);
|
|
838
|
-
connectionState?.refsByTarget.set(targetId, snapshot.refs || {});
|
|
839
|
-
persistRefs(targetId, pageUrl, snapshot.refs || {});
|
|
840
|
-
return snapshot;
|
|
841
|
-
}
|
|
842
|
-
function convertBuildDomTreeResult(result, options) {
|
|
843
|
-
const { interactiveOnly, compact, maxDepth, selector } = options;
|
|
844
|
-
const { rootId, map } = result;
|
|
845
|
-
const refs = {};
|
|
846
|
-
const lines = [];
|
|
847
|
-
const getRole = (node) => {
|
|
848
|
-
const tagName = node.tagName.toLowerCase();
|
|
849
|
-
const role = node.attributes?.role;
|
|
850
|
-
if (role) return role;
|
|
851
|
-
const type = node.attributes?.type?.toLowerCase() || "text";
|
|
852
|
-
const inputRoleMap = {
|
|
853
|
-
text: "textbox",
|
|
854
|
-
password: "textbox",
|
|
855
|
-
email: "textbox",
|
|
856
|
-
url: "textbox",
|
|
857
|
-
tel: "textbox",
|
|
858
|
-
search: "searchbox",
|
|
859
|
-
number: "spinbutton",
|
|
860
|
-
range: "slider",
|
|
861
|
-
checkbox: "checkbox",
|
|
862
|
-
radio: "radio",
|
|
863
|
-
button: "button",
|
|
864
|
-
submit: "button",
|
|
865
|
-
reset: "button",
|
|
866
|
-
file: "button"
|
|
867
|
-
};
|
|
868
|
-
const roleMap = {
|
|
869
|
-
a: "link",
|
|
870
|
-
button: "button",
|
|
871
|
-
input: inputRoleMap[type] || "textbox",
|
|
872
|
-
select: "combobox",
|
|
873
|
-
textarea: "textbox",
|
|
874
|
-
img: "image",
|
|
875
|
-
nav: "navigation",
|
|
876
|
-
main: "main",
|
|
877
|
-
header: "banner",
|
|
878
|
-
footer: "contentinfo",
|
|
879
|
-
aside: "complementary",
|
|
880
|
-
form: "form",
|
|
881
|
-
table: "table",
|
|
882
|
-
ul: "list",
|
|
883
|
-
ol: "list",
|
|
884
|
-
li: "listitem",
|
|
885
|
-
h1: "heading",
|
|
886
|
-
h2: "heading",
|
|
887
|
-
h3: "heading",
|
|
888
|
-
h4: "heading",
|
|
889
|
-
h5: "heading",
|
|
890
|
-
h6: "heading",
|
|
891
|
-
dialog: "dialog",
|
|
892
|
-
article: "article",
|
|
893
|
-
section: "region",
|
|
894
|
-
label: "label",
|
|
895
|
-
details: "group",
|
|
896
|
-
summary: "button"
|
|
897
|
-
};
|
|
898
|
-
return roleMap[tagName] || tagName;
|
|
899
|
-
};
|
|
900
|
-
const collectTextContent = (node, nodeMap, depthLimit = 5) => {
|
|
901
|
-
const texts = [];
|
|
902
|
-
const visit = (nodeId, depth) => {
|
|
903
|
-
if (depth > depthLimit) return;
|
|
904
|
-
const currentNode = nodeMap[nodeId];
|
|
905
|
-
if (!currentNode) return;
|
|
906
|
-
if ("type" in currentNode && currentNode.type === "TEXT_NODE") {
|
|
907
|
-
const text = currentNode.text.trim();
|
|
908
|
-
if (text) texts.push(text);
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
for (const childId of currentNode.children || []) visit(childId, depth + 1);
|
|
912
|
-
};
|
|
913
|
-
for (const childId of node.children || []) visit(childId, 0);
|
|
914
|
-
return texts.join(" ").trim();
|
|
915
|
-
};
|
|
916
|
-
const getName = (node) => {
|
|
917
|
-
const attrs = node.attributes || {};
|
|
918
|
-
return attrs["aria-label"] || attrs.title || attrs.placeholder || attrs.alt || attrs.value || collectTextContent(node, map) || attrs.name || void 0;
|
|
919
|
-
};
|
|
920
|
-
const truncateText = (text, length = 50) => text.length <= length ? text : `${text.slice(0, length - 3)}...`;
|
|
921
|
-
const selectorText = selector?.trim().toLowerCase();
|
|
922
|
-
const matchesSelector = (node, role, name) => {
|
|
923
|
-
if (!selectorText) return true;
|
|
924
|
-
const haystack = [node.tagName, role, name, node.xpath || "", ...Object.values(node.attributes || {})].join(" ").toLowerCase();
|
|
925
|
-
return haystack.includes(selectorText);
|
|
926
|
-
};
|
|
927
|
-
if (interactiveOnly) {
|
|
928
|
-
const interactiveNodes = Object.entries(map).filter(([, node]) => !("type" in node) && node.highlightIndex !== void 0 && node.highlightIndex !== null).map(([id, node]) => ({ id, node })).sort((a, b) => (a.node.highlightIndex ?? 0) - (b.node.highlightIndex ?? 0));
|
|
929
|
-
for (const { node } of interactiveNodes) {
|
|
930
|
-
const refId = String(node.highlightIndex);
|
|
931
|
-
const role = getRole(node);
|
|
932
|
-
const name = getName(node);
|
|
933
|
-
if (!matchesSelector(node, role, name)) continue;
|
|
934
|
-
let line = `${role} [ref=${refId}]`;
|
|
935
|
-
if (name) line += ` ${JSON.stringify(truncateText(name))}`;
|
|
936
|
-
lines.push(line);
|
|
937
|
-
refs[refId] = {
|
|
938
|
-
xpath: node.xpath || "",
|
|
939
|
-
role,
|
|
940
|
-
name,
|
|
941
|
-
tagName: node.tagName.toLowerCase()
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
return { snapshot: lines.join("\n"), refs };
|
|
945
|
-
}
|
|
946
|
-
const walk = (nodeId, depth) => {
|
|
947
|
-
if (maxDepth !== void 0 && depth > maxDepth) return;
|
|
948
|
-
const node = map[nodeId];
|
|
949
|
-
if (!node) return;
|
|
950
|
-
if ("type" in node && node.type === "TEXT_NODE") {
|
|
951
|
-
const text = node.text.trim();
|
|
952
|
-
if (!text) return;
|
|
953
|
-
lines.push(`${" ".repeat(depth)}- text ${JSON.stringify(truncateText(text, compact ? 80 : 120))}`);
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
const role = getRole(node);
|
|
957
|
-
const name = getName(node);
|
|
958
|
-
if (!matchesSelector(node, role, name)) {
|
|
959
|
-
for (const childId of node.children || []) walk(childId, depth + 1);
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
const indent = " ".repeat(depth);
|
|
963
|
-
const refId = node.highlightIndex !== void 0 && node.highlightIndex !== null ? String(node.highlightIndex) : null;
|
|
964
|
-
let line = `${indent}- ${role}`;
|
|
965
|
-
if (refId) line += ` [ref=${refId}]`;
|
|
966
|
-
if (name) line += ` ${JSON.stringify(truncateText(name, compact ? 50 : 80))}`;
|
|
967
|
-
if (!compact) line += ` <${node.tagName.toLowerCase()}>`;
|
|
968
|
-
lines.push(line);
|
|
969
|
-
if (refId) {
|
|
970
|
-
refs[refId] = {
|
|
971
|
-
xpath: node.xpath || "",
|
|
972
|
-
role,
|
|
973
|
-
name,
|
|
974
|
-
tagName: node.tagName.toLowerCase()
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
for (const childId of node.children || []) walk(childId, depth + 1);
|
|
978
|
-
};
|
|
979
|
-
walk(rootId, 0);
|
|
980
|
-
return { snapshot: lines.join("\n"), refs };
|
|
981
|
-
}
|
|
982
|
-
function ok(id, data) {
|
|
983
|
-
return { id, success: true, data };
|
|
984
|
-
}
|
|
985
|
-
function fail(id, error) {
|
|
986
|
-
return { id, success: false, error: buildRequestError(error).message };
|
|
987
|
-
}
|
|
988
|
-
async function ensureCdpConnection() {
|
|
989
|
-
if (connectionState) return;
|
|
990
|
-
if (reconnecting) return reconnecting;
|
|
991
|
-
reconnecting = (async () => {
|
|
992
|
-
const discovered = await discoverCdpPort();
|
|
993
|
-
if (!discovered) {
|
|
994
|
-
throw new Error("No browser connection found");
|
|
995
|
-
}
|
|
996
|
-
const version = await getJsonVersion(discovered.host, discovered.port);
|
|
997
|
-
const wsUrl = version.webSocketDebuggerUrl;
|
|
998
|
-
const socket = await connectWebSocket(wsUrl);
|
|
999
|
-
connectionState = createState(discovered.host, discovered.port, wsUrl, socket);
|
|
1000
|
-
})();
|
|
1001
|
-
try {
|
|
1002
|
-
await reconnecting;
|
|
1003
|
-
} finally {
|
|
1004
|
-
reconnecting = null;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
async function sendCommand(request) {
|
|
1008
|
-
try {
|
|
1009
|
-
await ensureCdpConnection();
|
|
1010
|
-
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("\u8BF7\u6C42\u8D85\u65F6")), COMMAND_TIMEOUT));
|
|
1011
|
-
return await Promise.race([dispatchRequest(request), timeout]);
|
|
1012
|
-
} catch (error) {
|
|
1013
|
-
return fail(request.id, error);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
async function dispatchRequest(request) {
|
|
1017
|
-
const target = await ensurePageTarget(request.tabId);
|
|
1018
|
-
switch (request.action) {
|
|
1019
|
-
case "open": {
|
|
1020
|
-
if (!request.url) return fail(request.id, "Missing url parameter");
|
|
1021
|
-
if (request.tabId === void 0) {
|
|
1022
|
-
const created = await browserCommand("Target.createTarget", { url: request.url, background: true });
|
|
1023
|
-
const newTarget = await ensurePageTarget(created.targetId);
|
|
1024
|
-
return ok(request.id, { url: request.url, tabId: newTarget.id });
|
|
1025
|
-
}
|
|
1026
|
-
await pageCommand(target.id, "Page.navigate", { url: request.url });
|
|
1027
|
-
connectionState?.refsByTarget.delete(target.id);
|
|
1028
|
-
clearPersistedRefs(target.id);
|
|
1029
|
-
return ok(request.id, { url: request.url, title: target.title, tabId: target.id });
|
|
1030
|
-
}
|
|
1031
|
-
case "snapshot": {
|
|
1032
|
-
const snapshotData = await buildSnapshot(target.id, request);
|
|
1033
|
-
return ok(request.id, { title: target.title, url: target.url, snapshotData });
|
|
1034
|
-
}
|
|
1035
|
-
case "click":
|
|
1036
|
-
case "hover": {
|
|
1037
|
-
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
1038
|
-
const backendNodeId = await parseRef(request.ref);
|
|
1039
|
-
const point = await getInteractablePoint(target.id, backendNodeId);
|
|
1040
|
-
await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: point.x, y: point.y, button: "none" });
|
|
1041
|
-
if (request.action === "click") await mouseClick(target.id, point.x, point.y);
|
|
1042
|
-
return ok(request.id, {});
|
|
1043
|
-
}
|
|
1044
|
-
case "fill":
|
|
1045
|
-
case "type": {
|
|
1046
|
-
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
1047
|
-
if (request.text == null) return fail(request.id, "Missing text parameter");
|
|
1048
|
-
const backendNodeId = await parseRef(request.ref);
|
|
1049
|
-
await insertTextIntoNode(target.id, backendNodeId, request.text, request.action === "fill");
|
|
1050
|
-
return ok(request.id, { value: request.text });
|
|
1051
|
-
}
|
|
1052
|
-
case "check":
|
|
1053
|
-
case "uncheck": {
|
|
1054
|
-
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
1055
|
-
const backendNodeId = await parseRef(request.ref);
|
|
1056
|
-
const desired = request.action === "check";
|
|
1057
|
-
const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
|
|
1058
|
-
await sessionCommand(target.id, "Runtime.callFunctionOn", {
|
|
1059
|
-
objectId: resolved.object.objectId,
|
|
1060
|
-
functionDeclaration: `function() { this.checked = ${desired}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
|
|
1061
|
-
});
|
|
1062
|
-
return ok(request.id, {});
|
|
1063
|
-
}
|
|
1064
|
-
case "select": {
|
|
1065
|
-
if (!request.ref || request.value == null) return fail(request.id, "Missing ref or value parameter");
|
|
1066
|
-
const backendNodeId = await parseRef(request.ref);
|
|
1067
|
-
const resolved = await sessionCommand(target.id, "DOM.resolveNode", { backendNodeId });
|
|
1068
|
-
await sessionCommand(target.id, "Runtime.callFunctionOn", {
|
|
1069
|
-
objectId: resolved.object.objectId,
|
|
1070
|
-
functionDeclaration: `function() { this.value = ${JSON.stringify(request.value)}; this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }`
|
|
1071
|
-
});
|
|
1072
|
-
return ok(request.id, { value: request.value });
|
|
1073
|
-
}
|
|
1074
|
-
case "get": {
|
|
1075
|
-
if (!request.attribute) return fail(request.id, "Missing attribute parameter");
|
|
1076
|
-
if (request.attribute === "url" && !request.ref) {
|
|
1077
|
-
return ok(request.id, { value: await evaluate(target.id, "location.href", true) });
|
|
1078
|
-
}
|
|
1079
|
-
if (request.attribute === "title" && !request.ref) {
|
|
1080
|
-
return ok(request.id, { value: await evaluate(target.id, "document.title", true) });
|
|
1081
|
-
}
|
|
1082
|
-
if (!request.ref) return fail(request.id, "Missing ref parameter");
|
|
1083
|
-
const value = await getAttributeValue(target.id, await parseRef(request.ref), request.attribute);
|
|
1084
|
-
return ok(request.id, { value });
|
|
1085
|
-
}
|
|
1086
|
-
case "screenshot": {
|
|
1087
|
-
const result = await sessionCommand(target.id, "Page.captureScreenshot", { format: "png", fromSurface: true });
|
|
1088
|
-
return ok(request.id, { dataUrl: `data:image/png;base64,${result.data}` });
|
|
1089
|
-
}
|
|
1090
|
-
case "close": {
|
|
1091
|
-
await browserCommand("Target.closeTarget", { targetId: target.id });
|
|
1092
|
-
connectionState?.refsByTarget.delete(target.id);
|
|
1093
|
-
clearPersistedRefs(target.id);
|
|
1094
|
-
return ok(request.id, {});
|
|
1095
|
-
}
|
|
1096
|
-
case "wait": {
|
|
1097
|
-
await new Promise((resolve3) => setTimeout(resolve3, request.ms ?? 1e3));
|
|
1098
|
-
return ok(request.id, {});
|
|
1099
|
-
}
|
|
1100
|
-
case "press": {
|
|
1101
|
-
if (!request.key) return fail(request.id, "Missing key parameter");
|
|
1102
|
-
await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyDown", key: request.key });
|
|
1103
|
-
if (request.key.length === 1) {
|
|
1104
|
-
await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "char", text: request.key, key: request.key });
|
|
1105
|
-
}
|
|
1106
|
-
await sessionCommand(target.id, "Input.dispatchKeyEvent", { type: "keyUp", key: request.key });
|
|
1107
|
-
return ok(request.id, {});
|
|
1108
|
-
}
|
|
1109
|
-
case "scroll": {
|
|
1110
|
-
const deltaY = request.direction === "up" ? -(request.pixels ?? 300) : request.pixels ?? 300;
|
|
1111
|
-
await sessionCommand(target.id, "Input.dispatchMouseEvent", { type: "mouseWheel", x: 0, y: 0, deltaX: 0, deltaY });
|
|
1112
|
-
return ok(request.id, {});
|
|
1113
|
-
}
|
|
1114
|
-
case "back": {
|
|
1115
|
-
await evaluate(target.id, "history.back(); undefined");
|
|
1116
|
-
return ok(request.id, {});
|
|
1117
|
-
}
|
|
1118
|
-
case "forward": {
|
|
1119
|
-
await evaluate(target.id, "history.forward(); undefined");
|
|
1120
|
-
return ok(request.id, {});
|
|
1121
|
-
}
|
|
1122
|
-
case "refresh": {
|
|
1123
|
-
await sessionCommand(target.id, "Page.reload", { ignoreCache: false });
|
|
1124
|
-
return ok(request.id, {});
|
|
1125
|
-
}
|
|
1126
|
-
case "eval": {
|
|
1127
|
-
if (!request.script) return fail(request.id, "Missing script parameter");
|
|
1128
|
-
const result = await evaluate(target.id, request.script, true);
|
|
1129
|
-
return ok(request.id, { result });
|
|
1130
|
-
}
|
|
1131
|
-
case "tab_list": {
|
|
1132
|
-
const tabs = (await getTargets()).filter((item) => item.type === "page").map((item, index) => ({ index, url: item.url, title: item.title, active: item.id === connectionState?.currentTargetId || !connectionState?.currentTargetId && index === 0, tabId: item.id }));
|
|
1133
|
-
return ok(request.id, { tabs, activeIndex: tabs.findIndex((tab) => tab.active) });
|
|
1134
|
-
}
|
|
1135
|
-
case "tab_new": {
|
|
1136
|
-
const created = await browserCommand("Target.createTarget", { url: request.url ?? "about:blank", background: true });
|
|
1137
|
-
return ok(request.id, { tabId: created.targetId, url: request.url ?? "about:blank" });
|
|
1138
|
-
}
|
|
1139
|
-
case "tab_select": {
|
|
1140
|
-
const tabs = (await getTargets()).filter((item) => item.type === "page");
|
|
1141
|
-
const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
|
|
1142
|
-
if (!selected) return fail(request.id, "Tab not found");
|
|
1143
|
-
setCurrentTargetId(selected.id);
|
|
1144
|
-
await attachTarget(selected.id);
|
|
1145
|
-
return ok(request.id, { tabId: selected.id, url: selected.url, title: selected.title });
|
|
1146
|
-
}
|
|
1147
|
-
case "tab_close": {
|
|
1148
|
-
const tabs = (await getTargets()).filter((item) => item.type === "page");
|
|
1149
|
-
const selected = request.tabId !== void 0 ? tabs.find((item) => item.id === String(request.tabId) || Number(item.id) === request.tabId) : tabs[request.index ?? 0];
|
|
1150
|
-
if (!selected) return fail(request.id, "Tab not found");
|
|
1151
|
-
await browserCommand("Target.closeTarget", { targetId: selected.id });
|
|
1152
|
-
connectionState?.refsByTarget.delete(selected.id);
|
|
1153
|
-
if (connectionState?.currentTargetId === selected.id) {
|
|
1154
|
-
setCurrentTargetId(void 0);
|
|
1155
|
-
}
|
|
1156
|
-
clearPersistedRefs(selected.id);
|
|
1157
|
-
return ok(request.id, { tabId: selected.id });
|
|
1158
|
-
}
|
|
1159
|
-
case "frame": {
|
|
1160
|
-
if (!request.selector) return fail(request.id, "Missing selector parameter");
|
|
1161
|
-
const document = await pageCommand(target.id, "DOM.getDocument", {});
|
|
1162
|
-
const node = await pageCommand(target.id, "DOM.querySelector", { nodeId: document.root.nodeId, selector: request.selector });
|
|
1163
|
-
if (!node.nodeId) return fail(request.id, `\u627E\u4E0D\u5230 iframe: ${request.selector}`);
|
|
1164
|
-
const described = await pageCommand(target.id, "DOM.describeNode", { nodeId: node.nodeId });
|
|
1165
|
-
const frameId = described.node.frameId;
|
|
1166
|
-
const nodeName = String(described.node.nodeName ?? "").toLowerCase();
|
|
1167
|
-
if (!frameId) return fail(request.id, `\u65E0\u6CD5\u83B7\u53D6 iframe frameId: ${request.selector}`);
|
|
1168
|
-
if (nodeName && nodeName !== "iframe" && nodeName !== "frame") return fail(request.id, `\u5143\u7D20\u4E0D\u662F iframe: ${nodeName}`);
|
|
1169
|
-
connectionState?.activeFrameIdByTarget.set(target.id, frameId);
|
|
1170
|
-
const attributes = described.node.attributes ?? [];
|
|
1171
|
-
const attrMap = {};
|
|
1172
|
-
for (let i = 0; i < attributes.length; i += 2) attrMap[String(attributes[i])] = String(attributes[i + 1] ?? "");
|
|
1173
|
-
return ok(request.id, { frameInfo: { selector: request.selector, name: attrMap.name ?? "", url: attrMap.src ?? "", frameId } });
|
|
1174
|
-
}
|
|
1175
|
-
case "frame_main": {
|
|
1176
|
-
connectionState?.activeFrameIdByTarget.set(target.id, null);
|
|
1177
|
-
return ok(request.id, { frameInfo: { frameId: 0 } });
|
|
1178
|
-
}
|
|
1179
|
-
case "dialog": {
|
|
1180
|
-
connectionState?.dialogHandlers.set(target.id, { accept: request.dialogResponse !== "dismiss", ...request.promptText !== void 0 ? { promptText: request.promptText } : {} });
|
|
1181
|
-
await sessionCommand(target.id, "Page.enable");
|
|
1182
|
-
return ok(request.id, { dialog: { armed: true, response: request.dialogResponse ?? "accept" } });
|
|
1183
|
-
}
|
|
1184
|
-
case "network": {
|
|
1185
|
-
const subCommand = request.networkCommand ?? "requests";
|
|
1186
|
-
switch (subCommand) {
|
|
1187
|
-
case "requests": {
|
|
1188
|
-
await ensureNetworkMonitoring(target.id);
|
|
1189
|
-
const requests = Array.from(networkRequests.values()).filter((item) => !request.filter || item.url.includes(request.filter));
|
|
1190
|
-
if (request.withBody) {
|
|
1191
|
-
await Promise.all(requests.map(async (item) => {
|
|
1192
|
-
if (item.failed || item.responseBody !== void 0 || item.bodyError !== void 0) return;
|
|
1193
|
-
try {
|
|
1194
|
-
const body = await sessionCommand(target.id, "Network.getResponseBody", { requestId: item.requestId });
|
|
1195
|
-
item.responseBody = body.body;
|
|
1196
|
-
item.responseBodyBase64 = body.base64Encoded;
|
|
1197
|
-
} catch (error) {
|
|
1198
|
-
item.bodyError = error instanceof Error ? error.message : String(error);
|
|
1199
|
-
}
|
|
1200
|
-
}));
|
|
1201
|
-
}
|
|
1202
|
-
return ok(request.id, { networkRequests: requests });
|
|
1203
|
-
}
|
|
1204
|
-
case "route":
|
|
1205
|
-
return ok(request.id, { routeCount: 0 });
|
|
1206
|
-
case "unroute":
|
|
1207
|
-
return ok(request.id, { routeCount: 0 });
|
|
1208
|
-
case "clear":
|
|
1209
|
-
networkRequests.clear();
|
|
1210
|
-
return ok(request.id, {});
|
|
1211
|
-
default:
|
|
1212
|
-
return fail(request.id, `Unknown network subcommand: ${subCommand}`);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
case "console": {
|
|
1216
|
-
const subCommand = request.consoleCommand ?? "get";
|
|
1217
|
-
await ensureConsoleMonitoring(target.id);
|
|
1218
|
-
switch (subCommand) {
|
|
1219
|
-
case "get":
|
|
1220
|
-
return ok(request.id, { consoleMessages: consoleMessages.filter((item) => !request.filter || item.text.includes(request.filter)) });
|
|
1221
|
-
case "clear":
|
|
1222
|
-
consoleMessages.length = 0;
|
|
1223
|
-
return ok(request.id, {});
|
|
1224
|
-
default:
|
|
1225
|
-
return fail(request.id, `Unknown console subcommand: ${subCommand}`);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
case "errors": {
|
|
1229
|
-
const subCommand = request.errorsCommand ?? "get";
|
|
1230
|
-
await ensureConsoleMonitoring(target.id);
|
|
1231
|
-
switch (subCommand) {
|
|
1232
|
-
case "get":
|
|
1233
|
-
return ok(request.id, { jsErrors: jsErrors.filter((item) => !request.filter || item.message.includes(request.filter) || item.url?.includes(request.filter)) });
|
|
1234
|
-
case "clear":
|
|
1235
|
-
jsErrors.length = 0;
|
|
1236
|
-
return ok(request.id, {});
|
|
1237
|
-
default:
|
|
1238
|
-
return fail(request.id, `Unknown errors subcommand: ${subCommand}`);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
case "trace": {
|
|
1242
|
-
const subCommand = request.traceCommand ?? "status";
|
|
1243
|
-
switch (subCommand) {
|
|
1244
|
-
case "start":
|
|
1245
|
-
traceRecording = true;
|
|
1246
|
-
traceEvents.length = 0;
|
|
1247
|
-
return ok(request.id, { traceStatus: { recording: true, eventCount: 0 } });
|
|
1248
|
-
case "stop": {
|
|
1249
|
-
traceRecording = false;
|
|
1250
|
-
return ok(request.id, { traceEvents: [...traceEvents], traceStatus: { recording: false, eventCount: traceEvents.length } });
|
|
1251
|
-
}
|
|
1252
|
-
case "status":
|
|
1253
|
-
return ok(request.id, { traceStatus: { recording: traceRecording, eventCount: traceEvents.length } });
|
|
1254
|
-
default:
|
|
1255
|
-
return fail(request.id, `Unknown trace subcommand: ${subCommand}`);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
default:
|
|
1259
|
-
return fail(request.id, `Action not yet supported in direct CDP mode: ${request.action}`);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
// packages/cli/src/monitor-manager.ts
|
|
1264
|
-
import { spawn as spawn2 } from "child_process";
|
|
1265
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, unlink } from "fs/promises";
|
|
1266
|
-
import { request as httpRequest2 } from "http";
|
|
1267
|
-
import { randomBytes } from "crypto";
|
|
1268
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1269
|
-
import { dirname, resolve } from "path";
|
|
1270
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1271
|
-
import os3 from "os";
|
|
1272
|
-
import path3 from "path";
|
|
1273
|
-
var MONITOR_DIR = path3.join(os3.homedir(), ".bb-browser");
|
|
1274
|
-
var PID_FILE = path3.join(MONITOR_DIR, "monitor.pid");
|
|
1275
|
-
var PORT_FILE = path3.join(MONITOR_DIR, "monitor.port");
|
|
1276
|
-
var TOKEN_FILE = path3.join(MONITOR_DIR, "monitor.token");
|
|
1277
|
-
var DEFAULT_MONITOR_PORT = 19826;
|
|
1278
|
-
function httpJson(method, url, token, body) {
|
|
1279
|
-
return new Promise((resolve3, reject) => {
|
|
1280
|
-
const parsed = new URL(url);
|
|
24
|
+
var DAEMON_DIR = path.join(os.homedir(), ".bb-browser");
|
|
25
|
+
var TOKEN_FILE = path.join(DAEMON_DIR, "daemon.token");
|
|
26
|
+
var cachedToken = null;
|
|
27
|
+
var daemonReady = false;
|
|
28
|
+
function httpJson(method, urlPath, token, body, timeout = 5e3) {
|
|
29
|
+
return new Promise((resolve2, reject) => {
|
|
1281
30
|
const payload = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
1282
|
-
const req =
|
|
31
|
+
const req = httpRequest(
|
|
1283
32
|
{
|
|
1284
|
-
hostname:
|
|
1285
|
-
port:
|
|
1286
|
-
path:
|
|
33
|
+
hostname: "127.0.0.1",
|
|
34
|
+
port: DAEMON_PORT,
|
|
35
|
+
path: urlPath,
|
|
1287
36
|
method,
|
|
1288
37
|
headers: {
|
|
1289
38
|
Authorization: `Bearer ${token}`,
|
|
1290
|
-
...payload ? {
|
|
39
|
+
...payload ? {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
42
|
+
} : {}
|
|
1291
43
|
},
|
|
1292
|
-
timeout
|
|
44
|
+
timeout
|
|
1293
45
|
},
|
|
1294
46
|
(res) => {
|
|
1295
47
|
const chunks = [];
|
|
@@ -1297,13 +49,13 @@ function httpJson(method, url, token, body) {
|
|
|
1297
49
|
res.on("end", () => {
|
|
1298
50
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1299
51
|
if ((res.statusCode ?? 500) >= 400) {
|
|
1300
|
-
reject(new Error(`
|
|
52
|
+
reject(new Error(`Daemon HTTP ${res.statusCode}: ${raw}`));
|
|
1301
53
|
return;
|
|
1302
54
|
}
|
|
1303
55
|
try {
|
|
1304
|
-
|
|
56
|
+
resolve2(JSON.parse(raw));
|
|
1305
57
|
} catch {
|
|
1306
|
-
reject(new Error(`Invalid JSON from
|
|
58
|
+
reject(new Error(`Invalid JSON from daemon: ${raw}`));
|
|
1307
59
|
}
|
|
1308
60
|
});
|
|
1309
61
|
}
|
|
@@ -1311,64 +63,52 @@ function httpJson(method, url, token, body) {
|
|
|
1311
63
|
req.on("error", reject);
|
|
1312
64
|
req.on("timeout", () => {
|
|
1313
65
|
req.destroy();
|
|
1314
|
-
reject(new Error("
|
|
66
|
+
reject(new Error("Daemon request timed out"));
|
|
1315
67
|
});
|
|
1316
68
|
if (payload) req.write(payload);
|
|
1317
69
|
req.end();
|
|
1318
70
|
});
|
|
1319
71
|
}
|
|
1320
|
-
async function
|
|
72
|
+
async function readToken() {
|
|
1321
73
|
try {
|
|
1322
|
-
|
|
1323
|
-
const port = Number.parseInt(raw.trim(), 10);
|
|
1324
|
-
return Number.isInteger(port) && port > 0 ? port : null;
|
|
74
|
+
return (await readFile(TOKEN_FILE, "utf8")).trim();
|
|
1325
75
|
} catch {
|
|
1326
76
|
return null;
|
|
1327
77
|
}
|
|
1328
78
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
79
|
+
function getDaemonPath() {
|
|
80
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
81
|
+
const currentDir = dirname(currentFile);
|
|
82
|
+
const sameDirPath = resolve(currentDir, "daemon.js");
|
|
83
|
+
if (existsSync(sameDirPath)) {
|
|
84
|
+
return sameDirPath;
|
|
1334
85
|
}
|
|
86
|
+
return resolve(currentDir, "../../daemon/dist/index.js");
|
|
1335
87
|
}
|
|
1336
|
-
async function
|
|
1337
|
-
|
|
1338
|
-
const existingToken = await readTokenFile();
|
|
1339
|
-
if (existingPort && existingToken) {
|
|
88
|
+
async function ensureDaemon() {
|
|
89
|
+
if (daemonReady && cachedToken) {
|
|
1340
90
|
try {
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
91
|
+
await httpJson("GET", "/status", cachedToken, void 0, 2e3);
|
|
92
|
+
return;
|
|
93
|
+
} catch {
|
|
94
|
+
daemonReady = false;
|
|
95
|
+
cachedToken = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
let token = await readToken();
|
|
99
|
+
if (token) {
|
|
100
|
+
try {
|
|
101
|
+
const status = await httpJson("GET", "/status", token, void 0, 2e3);
|
|
1346
102
|
if (status.running) {
|
|
1347
|
-
|
|
103
|
+
cachedToken = token;
|
|
104
|
+
daemonReady = true;
|
|
105
|
+
return;
|
|
1348
106
|
}
|
|
1349
107
|
} catch {
|
|
1350
108
|
}
|
|
1351
109
|
}
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1354
|
-
throw new Error("Cannot start monitor: no browser connection found");
|
|
1355
|
-
}
|
|
1356
|
-
const token = randomBytes(32).toString("hex");
|
|
1357
|
-
const monitorPort = DEFAULT_MONITOR_PORT;
|
|
1358
|
-
const monitorScript = findMonitorScript();
|
|
1359
|
-
await mkdir2(MONITOR_DIR, { recursive: true });
|
|
1360
|
-
await writeFile2(TOKEN_FILE, token, { mode: 384 });
|
|
1361
|
-
const child = spawn2(process.execPath, [
|
|
1362
|
-
monitorScript,
|
|
1363
|
-
"--cdp-host",
|
|
1364
|
-
cdp.host,
|
|
1365
|
-
"--cdp-port",
|
|
1366
|
-
String(cdp.port),
|
|
1367
|
-
"--monitor-port",
|
|
1368
|
-
String(monitorPort),
|
|
1369
|
-
"--token",
|
|
1370
|
-
token
|
|
1371
|
-
], {
|
|
110
|
+
const daemonPath = getDaemonPath();
|
|
111
|
+
const child = spawn(process.execPath, [daemonPath], {
|
|
1372
112
|
detached: true,
|
|
1373
113
|
stdio: "ignore"
|
|
1374
114
|
});
|
|
@@ -1376,51 +116,43 @@ async function ensureMonitorRunning() {
|
|
|
1376
116
|
const deadline = Date.now() + 5e3;
|
|
1377
117
|
while (Date.now() < deadline) {
|
|
1378
118
|
await new Promise((r) => setTimeout(r, 200));
|
|
119
|
+
token = await readToken();
|
|
120
|
+
if (!token) continue;
|
|
1379
121
|
try {
|
|
1380
|
-
const status = await httpJson(
|
|
1381
|
-
"GET",
|
|
1382
|
-
`http://127.0.0.1:${monitorPort}/status`,
|
|
1383
|
-
token
|
|
1384
|
-
);
|
|
122
|
+
const status = await httpJson("GET", "/status", token, void 0, 2e3);
|
|
1385
123
|
if (status.running) {
|
|
1386
|
-
|
|
124
|
+
cachedToken = token;
|
|
125
|
+
daemonReady = true;
|
|
126
|
+
return;
|
|
1387
127
|
}
|
|
1388
128
|
} catch {
|
|
1389
129
|
}
|
|
1390
130
|
}
|
|
1391
|
-
throw new Error(
|
|
1392
|
-
|
|
1393
|
-
async function monitorCommand(request) {
|
|
1394
|
-
const { port, token } = await ensureMonitorRunning();
|
|
1395
|
-
return httpJson(
|
|
1396
|
-
"POST",
|
|
1397
|
-
`http://127.0.0.1:${port}/command`,
|
|
1398
|
-
token,
|
|
1399
|
-
request
|
|
131
|
+
throw new Error(
|
|
132
|
+
"bb-browser: Daemon did not start in time.\n\nMake sure Chrome is installed, then try again."
|
|
1400
133
|
);
|
|
1401
134
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
135
|
+
async function daemonCommand(request) {
|
|
136
|
+
if (!cachedToken) {
|
|
137
|
+
cachedToken = await readToken();
|
|
138
|
+
}
|
|
139
|
+
if (!cachedToken) {
|
|
140
|
+
throw new Error("No daemon token found. Is the daemon running?");
|
|
141
|
+
}
|
|
142
|
+
return httpJson("POST", "/command", cachedToken, request, COMMAND_TIMEOUT);
|
|
143
|
+
}
|
|
144
|
+
async function getDaemonStatus() {
|
|
145
|
+
const token = cachedToken ?? await readToken();
|
|
146
|
+
if (!token) return null;
|
|
147
|
+
try {
|
|
148
|
+
return await httpJson("GET", "/status", token, void 0, 2e3);
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
1418
151
|
}
|
|
1419
|
-
return candidates[0];
|
|
1420
152
|
}
|
|
153
|
+
var ensureDaemonRunning = ensureDaemon;
|
|
1421
154
|
|
|
1422
155
|
// packages/cli/src/client.ts
|
|
1423
|
-
var MONITOR_ACTIONS = /* @__PURE__ */ new Set(["network", "console", "errors", "trace"]);
|
|
1424
156
|
var jqExpression;
|
|
1425
157
|
function setJqExpression(expression) {
|
|
1426
158
|
jqExpression = expression;
|
|
@@ -1438,43 +170,14 @@ function handleJqResponse(response) {
|
|
|
1438
170
|
printJqResults(response);
|
|
1439
171
|
}
|
|
1440
172
|
}
|
|
1441
|
-
async function
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return await monitorCommand(request);
|
|
1445
|
-
} catch {
|
|
1446
|
-
return sendCommand(request);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
return sendCommand(request);
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// packages/cli/src/daemon-manager.ts
|
|
1453
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1454
|
-
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
1455
|
-
import { existsSync as existsSync3 } from "fs";
|
|
1456
|
-
async function isDaemonRunning() {
|
|
1457
|
-
return await isManagedBrowserRunning();
|
|
1458
|
-
}
|
|
1459
|
-
async function ensureDaemonRunning() {
|
|
1460
|
-
try {
|
|
1461
|
-
await ensureCdpConnection();
|
|
1462
|
-
} catch (error) {
|
|
1463
|
-
if (error instanceof Error && error.message.includes("No browser connection found")) {
|
|
1464
|
-
throw new Error([
|
|
1465
|
-
"bb-browser: Could not start browser.",
|
|
1466
|
-
"",
|
|
1467
|
-
"Make sure Chrome is installed, then try again.",
|
|
1468
|
-
"Or specify a CDP port manually: bb-browser --port 9222"
|
|
1469
|
-
].join("\n"));
|
|
1470
|
-
}
|
|
1471
|
-
throw error;
|
|
1472
|
-
}
|
|
173
|
+
async function sendCommand(request) {
|
|
174
|
+
await ensureDaemon();
|
|
175
|
+
return daemonCommand(request);
|
|
1473
176
|
}
|
|
1474
177
|
|
|
1475
178
|
// packages/cli/src/history-sqlite.ts
|
|
1476
|
-
import { copyFileSync, existsSync as
|
|
1477
|
-
import { execSync
|
|
179
|
+
import { copyFileSync, existsSync as existsSync2, unlinkSync } from "fs";
|
|
180
|
+
import { execSync } from "child_process";
|
|
1478
181
|
import { homedir, tmpdir } from "os";
|
|
1479
182
|
import { join } from "path";
|
|
1480
183
|
function getHistoryPathCandidates() {
|
|
@@ -1497,7 +200,7 @@ function getHistoryPathCandidates() {
|
|
|
1497
200
|
}
|
|
1498
201
|
function findHistoryPath() {
|
|
1499
202
|
for (const historyPath of getHistoryPathCandidates()) {
|
|
1500
|
-
if (
|
|
203
|
+
if (existsSync2(historyPath)) {
|
|
1501
204
|
return historyPath;
|
|
1502
205
|
}
|
|
1503
206
|
}
|
|
@@ -1522,7 +225,7 @@ function runHistoryQuery(sql, mapRow) {
|
|
|
1522
225
|
copyFileSync(historyPath, tmpPath);
|
|
1523
226
|
const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
|
|
1524
227
|
const escapedSql = sql.replace(/"/g, '\\"');
|
|
1525
|
-
const output =
|
|
228
|
+
const output = execSync(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
|
|
1526
229
|
encoding: "utf-8",
|
|
1527
230
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1528
231
|
});
|
|
@@ -1531,7 +234,7 @@ function runHistoryQuery(sql, mapRow) {
|
|
|
1531
234
|
return [];
|
|
1532
235
|
} finally {
|
|
1533
236
|
try {
|
|
1534
|
-
|
|
237
|
+
unlinkSync(tmpPath);
|
|
1535
238
|
} catch {
|
|
1536
239
|
}
|
|
1537
240
|
}
|
|
@@ -1617,18 +320,18 @@ function getHistoryDomains(days) {
|
|
|
1617
320
|
}
|
|
1618
321
|
|
|
1619
322
|
// packages/cli/src/commands/site.ts
|
|
1620
|
-
import { readFileSync
|
|
323
|
+
import { readFileSync, readdirSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
1621
324
|
import { join as join2, relative } from "path";
|
|
1622
325
|
import { homedir as homedir2 } from "os";
|
|
1623
|
-
import { execSync as
|
|
326
|
+
import { execSync as execSync2 } from "child_process";
|
|
1624
327
|
var BB_DIR = join2(homedir2(), ".bb-browser");
|
|
1625
328
|
var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
|
|
1626
329
|
var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
|
|
1627
330
|
var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
|
|
1628
331
|
function checkCliUpdate() {
|
|
1629
332
|
try {
|
|
1630
|
-
const current =
|
|
1631
|
-
const latest =
|
|
333
|
+
const current = execSync2("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
334
|
+
const latest = execSync2("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
1632
335
|
if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
|
|
1633
336
|
console.log(`
|
|
1634
337
|
\u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
|
|
@@ -1643,7 +346,7 @@ function exitJsonError(error, extra = {}) {
|
|
|
1643
346
|
function parseSiteMeta(filePath, source) {
|
|
1644
347
|
let content;
|
|
1645
348
|
try {
|
|
1646
|
-
content =
|
|
349
|
+
content = readFileSync(filePath, "utf-8");
|
|
1647
350
|
} catch {
|
|
1648
351
|
return null;
|
|
1649
352
|
}
|
|
@@ -1703,7 +406,7 @@ function parseSiteMeta(filePath, source) {
|
|
|
1703
406
|
return meta;
|
|
1704
407
|
}
|
|
1705
408
|
function scanSites(dir, source) {
|
|
1706
|
-
if (!
|
|
409
|
+
if (!existsSync3(dir)) return [];
|
|
1707
410
|
const sites = [];
|
|
1708
411
|
function walk(currentDir) {
|
|
1709
412
|
let entries;
|
|
@@ -1825,13 +528,13 @@ function siteSearch(query, options) {
|
|
|
1825
528
|
}
|
|
1826
529
|
function siteUpdate(options = {}) {
|
|
1827
530
|
mkdirSync(BB_DIR, { recursive: true });
|
|
1828
|
-
const updateMode =
|
|
531
|
+
const updateMode = existsSync3(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
|
|
1829
532
|
if (updateMode === "pull") {
|
|
1830
533
|
if (!options.json) {
|
|
1831
534
|
console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
|
|
1832
535
|
}
|
|
1833
536
|
try {
|
|
1834
|
-
|
|
537
|
+
execSync2("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
|
|
1835
538
|
if (!options.json) {
|
|
1836
539
|
console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
|
|
1837
540
|
console.log("");
|
|
@@ -1852,7 +555,7 @@ function siteUpdate(options = {}) {
|
|
|
1852
555
|
console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
|
|
1853
556
|
}
|
|
1854
557
|
try {
|
|
1855
|
-
|
|
558
|
+
execSync2(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
|
|
1856
559
|
if (!options.json) {
|
|
1857
560
|
console.log("\u514B\u9686\u5B8C\u6210\u3002");
|
|
1858
561
|
console.log("");
|
|
@@ -2061,12 +764,12 @@ async function siteRun(name, args, options) {
|
|
|
2061
764
|
process.exit(1);
|
|
2062
765
|
}
|
|
2063
766
|
}
|
|
2064
|
-
const jsContent =
|
|
767
|
+
const jsContent = readFileSync(site.filePath, "utf-8");
|
|
2065
768
|
const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
|
|
2066
769
|
const argsJson = JSON.stringify(argMap);
|
|
2067
770
|
const script = `(${jsBody})(${argsJson})`;
|
|
2068
771
|
if (options.openclaw) {
|
|
2069
|
-
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-
|
|
772
|
+
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-Q6EFUQCH.js");
|
|
2070
773
|
let targetId;
|
|
2071
774
|
if (site.domain) {
|
|
2072
775
|
const tabs = ocGetTabs();
|
|
@@ -2075,7 +778,7 @@ async function siteRun(name, args, options) {
|
|
|
2075
778
|
targetId = existing.targetId;
|
|
2076
779
|
} else {
|
|
2077
780
|
targetId = ocOpenTab(`https://${site.domain}`);
|
|
2078
|
-
await new Promise((
|
|
781
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
2079
782
|
}
|
|
2080
783
|
} else {
|
|
2081
784
|
const tabs = ocGetTabs();
|
|
@@ -2121,7 +824,7 @@ async function siteRun(name, args, options) {
|
|
|
2121
824
|
let targetTabId = options.tabId;
|
|
2122
825
|
if (!targetTabId && site.domain) {
|
|
2123
826
|
const listReq = { id: generateId(), action: "tab_list" };
|
|
2124
|
-
const listResp = await
|
|
827
|
+
const listResp = await sendCommand(listReq);
|
|
2125
828
|
if (listResp.success && listResp.data?.tabs) {
|
|
2126
829
|
const matchingTab = listResp.data.tabs.find(
|
|
2127
830
|
(tab) => matchTabOrigin(tab.url, site.domain)
|
|
@@ -2131,17 +834,17 @@ async function siteRun(name, args, options) {
|
|
|
2131
834
|
}
|
|
2132
835
|
}
|
|
2133
836
|
if (!targetTabId) {
|
|
2134
|
-
const newResp = await
|
|
837
|
+
const newResp = await sendCommand({
|
|
2135
838
|
id: generateId(),
|
|
2136
839
|
action: "tab_new",
|
|
2137
840
|
url: `https://${site.domain}`
|
|
2138
841
|
});
|
|
2139
842
|
targetTabId = newResp.data?.tabId;
|
|
2140
|
-
await new Promise((
|
|
843
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
2141
844
|
}
|
|
2142
845
|
}
|
|
2143
846
|
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
2144
|
-
const evalResp = await
|
|
847
|
+
const evalResp = await sendCommand(evalReq);
|
|
2145
848
|
if (!evalResp.success) {
|
|
2146
849
|
const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
|
|
2147
850
|
if (options.json) {
|
|
@@ -2277,9 +980,9 @@ async function siteCommand(args, options = {}) {
|
|
|
2277
980
|
}
|
|
2278
981
|
function silentUpdate() {
|
|
2279
982
|
const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
|
|
2280
|
-
if (!
|
|
2281
|
-
import("child_process").then(({ spawn:
|
|
2282
|
-
const child =
|
|
983
|
+
if (!existsSync3(gitDir)) return;
|
|
984
|
+
import("child_process").then(({ spawn: spawn2 }) => {
|
|
985
|
+
const child = spawn2("git", ["pull", "--ff-only"], {
|
|
2283
986
|
cwd: COMMUNITY_SITES_DIR,
|
|
2284
987
|
stdio: "ignore",
|
|
2285
988
|
detached: true
|
|
@@ -2315,7 +1018,7 @@ async function openCommand(url, options = {}) {
|
|
|
2315
1018
|
request.tabId = tabId;
|
|
2316
1019
|
}
|
|
2317
1020
|
}
|
|
2318
|
-
const response = await
|
|
1021
|
+
const response = await sendCommand(request);
|
|
2319
1022
|
if (options.json) {
|
|
2320
1023
|
console.log(JSON.stringify(response, null, 2));
|
|
2321
1024
|
} else {
|
|
@@ -2351,7 +1054,7 @@ async function snapshotCommand(options = {}) {
|
|
|
2351
1054
|
selector: options.selector,
|
|
2352
1055
|
tabId: options.tabId
|
|
2353
1056
|
};
|
|
2354
|
-
const response = await
|
|
1057
|
+
const response = await sendCommand(request);
|
|
2355
1058
|
if (options.json) {
|
|
2356
1059
|
console.log(JSON.stringify(response, null, 2));
|
|
2357
1060
|
} else {
|
|
@@ -2370,7 +1073,7 @@ async function snapshotCommand(options = {}) {
|
|
|
2370
1073
|
}
|
|
2371
1074
|
|
|
2372
1075
|
// packages/cli/src/commands/click.ts
|
|
2373
|
-
function
|
|
1076
|
+
function parseRef(ref) {
|
|
2374
1077
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2375
1078
|
}
|
|
2376
1079
|
async function clickCommand(ref, options = {}) {
|
|
@@ -2378,14 +1081,14 @@ async function clickCommand(ref, options = {}) {
|
|
|
2378
1081
|
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2379
1082
|
}
|
|
2380
1083
|
await ensureDaemonRunning();
|
|
2381
|
-
const parsedRef =
|
|
1084
|
+
const parsedRef = parseRef(ref);
|
|
2382
1085
|
const request = {
|
|
2383
1086
|
id: generateId(),
|
|
2384
1087
|
action: "click",
|
|
2385
1088
|
ref: parsedRef,
|
|
2386
1089
|
tabId: options.tabId
|
|
2387
1090
|
};
|
|
2388
|
-
const response = await
|
|
1091
|
+
const response = await sendCommand(request);
|
|
2389
1092
|
if (options.json) {
|
|
2390
1093
|
console.log(JSON.stringify(response, null, 2));
|
|
2391
1094
|
} else {
|
|
@@ -2405,7 +1108,7 @@ async function clickCommand(ref, options = {}) {
|
|
|
2405
1108
|
}
|
|
2406
1109
|
|
|
2407
1110
|
// packages/cli/src/commands/hover.ts
|
|
2408
|
-
function
|
|
1111
|
+
function parseRef2(ref) {
|
|
2409
1112
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2410
1113
|
}
|
|
2411
1114
|
async function hoverCommand(ref, options = {}) {
|
|
@@ -2413,14 +1116,14 @@ async function hoverCommand(ref, options = {}) {
|
|
|
2413
1116
|
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2414
1117
|
}
|
|
2415
1118
|
await ensureDaemonRunning();
|
|
2416
|
-
const parsedRef =
|
|
1119
|
+
const parsedRef = parseRef2(ref);
|
|
2417
1120
|
const request = {
|
|
2418
1121
|
id: generateId(),
|
|
2419
1122
|
action: "hover",
|
|
2420
1123
|
ref: parsedRef,
|
|
2421
1124
|
tabId: options.tabId
|
|
2422
1125
|
};
|
|
2423
|
-
const response = await
|
|
1126
|
+
const response = await sendCommand(request);
|
|
2424
1127
|
if (options.json) {
|
|
2425
1128
|
console.log(JSON.stringify(response, null, 2));
|
|
2426
1129
|
} else {
|
|
@@ -2440,7 +1143,7 @@ async function hoverCommand(ref, options = {}) {
|
|
|
2440
1143
|
}
|
|
2441
1144
|
|
|
2442
1145
|
// packages/cli/src/commands/fill.ts
|
|
2443
|
-
function
|
|
1146
|
+
function parseRef3(ref) {
|
|
2444
1147
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2445
1148
|
}
|
|
2446
1149
|
async function fillCommand(ref, text, options = {}) {
|
|
@@ -2451,7 +1154,7 @@ async function fillCommand(ref, text, options = {}) {
|
|
|
2451
1154
|
throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
|
|
2452
1155
|
}
|
|
2453
1156
|
await ensureDaemonRunning();
|
|
2454
|
-
const parsedRef =
|
|
1157
|
+
const parsedRef = parseRef3(ref);
|
|
2455
1158
|
const request = {
|
|
2456
1159
|
id: generateId(),
|
|
2457
1160
|
action: "fill",
|
|
@@ -2459,7 +1162,7 @@ async function fillCommand(ref, text, options = {}) {
|
|
|
2459
1162
|
text,
|
|
2460
1163
|
tabId: options.tabId
|
|
2461
1164
|
};
|
|
2462
|
-
const response = await
|
|
1165
|
+
const response = await sendCommand(request);
|
|
2463
1166
|
if (options.json) {
|
|
2464
1167
|
console.log(JSON.stringify(response, null, 2));
|
|
2465
1168
|
} else {
|
|
@@ -2480,7 +1183,7 @@ async function fillCommand(ref, text, options = {}) {
|
|
|
2480
1183
|
}
|
|
2481
1184
|
|
|
2482
1185
|
// packages/cli/src/commands/type.ts
|
|
2483
|
-
function
|
|
1186
|
+
function parseRef4(ref) {
|
|
2484
1187
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2485
1188
|
}
|
|
2486
1189
|
async function typeCommand(ref, text, options = {}) {
|
|
@@ -2491,7 +1194,7 @@ async function typeCommand(ref, text, options = {}) {
|
|
|
2491
1194
|
throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
|
|
2492
1195
|
}
|
|
2493
1196
|
await ensureDaemonRunning();
|
|
2494
|
-
const parsedRef =
|
|
1197
|
+
const parsedRef = parseRef4(ref);
|
|
2495
1198
|
const request = {
|
|
2496
1199
|
id: generateId(),
|
|
2497
1200
|
action: "type",
|
|
@@ -2499,7 +1202,7 @@ async function typeCommand(ref, text, options = {}) {
|
|
|
2499
1202
|
text,
|
|
2500
1203
|
tabId: options.tabId
|
|
2501
1204
|
};
|
|
2502
|
-
const response = await
|
|
1205
|
+
const response = await sendCommand(request);
|
|
2503
1206
|
if (options.json) {
|
|
2504
1207
|
console.log(JSON.stringify(response, null, 2));
|
|
2505
1208
|
} else {
|
|
@@ -2527,7 +1230,7 @@ async function closeCommand(options = {}) {
|
|
|
2527
1230
|
action: "close",
|
|
2528
1231
|
tabId: options.tabId
|
|
2529
1232
|
};
|
|
2530
|
-
const response = await
|
|
1233
|
+
const response = await sendCommand(request);
|
|
2531
1234
|
if (options.json) {
|
|
2532
1235
|
console.log(JSON.stringify(response, null, 2));
|
|
2533
1236
|
} else {
|
|
@@ -2546,7 +1249,7 @@ async function closeCommand(options = {}) {
|
|
|
2546
1249
|
}
|
|
2547
1250
|
|
|
2548
1251
|
// packages/cli/src/commands/get.ts
|
|
2549
|
-
function
|
|
1252
|
+
function parseRef5(ref) {
|
|
2550
1253
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2551
1254
|
}
|
|
2552
1255
|
async function getCommand(attribute, ref, options = {}) {
|
|
@@ -2558,10 +1261,10 @@ async function getCommand(attribute, ref, options = {}) {
|
|
|
2558
1261
|
id: generateId(),
|
|
2559
1262
|
action: "get",
|
|
2560
1263
|
attribute,
|
|
2561
|
-
ref: ref ?
|
|
1264
|
+
ref: ref ? parseRef5(ref) : void 0,
|
|
2562
1265
|
tabId: options.tabId
|
|
2563
1266
|
};
|
|
2564
|
-
const response = await
|
|
1267
|
+
const response = await sendCommand(request);
|
|
2565
1268
|
if (options.json) {
|
|
2566
1269
|
console.log(JSON.stringify(response, null, 2));
|
|
2567
1270
|
} else {
|
|
@@ -2577,17 +1280,17 @@ async function getCommand(attribute, ref, options = {}) {
|
|
|
2577
1280
|
|
|
2578
1281
|
// packages/cli/src/commands/screenshot.ts
|
|
2579
1282
|
import fs from "fs";
|
|
2580
|
-
import
|
|
2581
|
-
import
|
|
1283
|
+
import path2 from "path";
|
|
1284
|
+
import os2 from "os";
|
|
2582
1285
|
function getDefaultPath() {
|
|
2583
1286
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2584
1287
|
const filename = `bb-screenshot-${timestamp}.png`;
|
|
2585
|
-
return
|
|
1288
|
+
return path2.join(os2.tmpdir(), filename);
|
|
2586
1289
|
}
|
|
2587
1290
|
function saveBase64Image(dataUrl, filePath) {
|
|
2588
1291
|
const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
2589
1292
|
const buffer = Buffer.from(base64Data, "base64");
|
|
2590
|
-
const dir =
|
|
1293
|
+
const dir = path2.dirname(filePath);
|
|
2591
1294
|
if (!fs.existsSync(dir)) {
|
|
2592
1295
|
fs.mkdirSync(dir, { recursive: true });
|
|
2593
1296
|
}
|
|
@@ -2595,13 +1298,13 @@ function saveBase64Image(dataUrl, filePath) {
|
|
|
2595
1298
|
}
|
|
2596
1299
|
async function screenshotCommand(outputPath, options = {}) {
|
|
2597
1300
|
await ensureDaemonRunning();
|
|
2598
|
-
const filePath = outputPath ?
|
|
1301
|
+
const filePath = outputPath ? path2.resolve(outputPath) : getDefaultPath();
|
|
2599
1302
|
const request = {
|
|
2600
1303
|
id: generateId(),
|
|
2601
1304
|
action: "screenshot",
|
|
2602
1305
|
tabId: options.tabId
|
|
2603
1306
|
};
|
|
2604
|
-
const response = await
|
|
1307
|
+
const response = await sendCommand(request);
|
|
2605
1308
|
if (response.success && response.data?.dataUrl) {
|
|
2606
1309
|
const dataUrl = response.data.dataUrl;
|
|
2607
1310
|
saveBase64Image(dataUrl, filePath);
|
|
@@ -2628,7 +1331,7 @@ async function screenshotCommand(outputPath, options = {}) {
|
|
|
2628
1331
|
function isTimeWait(target) {
|
|
2629
1332
|
return /^\d+$/.test(target);
|
|
2630
1333
|
}
|
|
2631
|
-
function
|
|
1334
|
+
function parseRef6(ref) {
|
|
2632
1335
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2633
1336
|
}
|
|
2634
1337
|
async function waitCommand(target, options = {}) {
|
|
@@ -2647,7 +1350,7 @@ async function waitCommand(target, options = {}) {
|
|
|
2647
1350
|
tabId: options.tabId
|
|
2648
1351
|
};
|
|
2649
1352
|
} else {
|
|
2650
|
-
const ref =
|
|
1353
|
+
const ref = parseRef6(target);
|
|
2651
1354
|
request = {
|
|
2652
1355
|
id: generateId(),
|
|
2653
1356
|
action: "wait",
|
|
@@ -2656,7 +1359,7 @@ async function waitCommand(target, options = {}) {
|
|
|
2656
1359
|
tabId: options.tabId
|
|
2657
1360
|
};
|
|
2658
1361
|
}
|
|
2659
|
-
const response = await
|
|
1362
|
+
const response = await sendCommand(request);
|
|
2660
1363
|
if (options.json) {
|
|
2661
1364
|
console.log(JSON.stringify(response, null, 2));
|
|
2662
1365
|
} else {
|
|
@@ -2664,7 +1367,7 @@ async function waitCommand(target, options = {}) {
|
|
|
2664
1367
|
if (isTimeWait(target)) {
|
|
2665
1368
|
console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
|
|
2666
1369
|
} else {
|
|
2667
|
-
console.log(`\u5143\u7D20 @${
|
|
1370
|
+
console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
|
|
2668
1371
|
}
|
|
2669
1372
|
} else {
|
|
2670
1373
|
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
@@ -2704,7 +1407,7 @@ async function pressCommand(keyString, options = {}) {
|
|
|
2704
1407
|
modifiers,
|
|
2705
1408
|
tabId: options.tabId
|
|
2706
1409
|
};
|
|
2707
|
-
const response = await
|
|
1410
|
+
const response = await sendCommand(request);
|
|
2708
1411
|
if (options.json) {
|
|
2709
1412
|
console.log(JSON.stringify(response, null, 2));
|
|
2710
1413
|
} else {
|
|
@@ -2745,7 +1448,7 @@ async function scrollCommand(direction, pixels, options = {}) {
|
|
|
2745
1448
|
pixels: pixelValue,
|
|
2746
1449
|
tabId: options.tabId
|
|
2747
1450
|
};
|
|
2748
|
-
const response = await
|
|
1451
|
+
const response = await sendCommand(request);
|
|
2749
1452
|
if (options.json) {
|
|
2750
1453
|
console.log(JSON.stringify(response, null, 2));
|
|
2751
1454
|
} else {
|
|
@@ -2766,7 +1469,7 @@ async function backCommand(options = {}) {
|
|
|
2766
1469
|
action: "back",
|
|
2767
1470
|
tabId: options.tabId
|
|
2768
1471
|
};
|
|
2769
|
-
const response = await
|
|
1472
|
+
const response = await sendCommand(request);
|
|
2770
1473
|
if (options.json) {
|
|
2771
1474
|
console.log(JSON.stringify(response, null, 2));
|
|
2772
1475
|
} else {
|
|
@@ -2790,7 +1493,7 @@ async function forwardCommand(options = {}) {
|
|
|
2790
1493
|
action: "forward",
|
|
2791
1494
|
tabId: options.tabId
|
|
2792
1495
|
};
|
|
2793
|
-
const response = await
|
|
1496
|
+
const response = await sendCommand(request);
|
|
2794
1497
|
if (options.json) {
|
|
2795
1498
|
console.log(JSON.stringify(response, null, 2));
|
|
2796
1499
|
} else {
|
|
@@ -2814,7 +1517,7 @@ async function refreshCommand(options = {}) {
|
|
|
2814
1517
|
action: "refresh",
|
|
2815
1518
|
tabId: options.tabId
|
|
2816
1519
|
};
|
|
2817
|
-
const response = await
|
|
1520
|
+
const response = await sendCommand(request);
|
|
2818
1521
|
if (options.json) {
|
|
2819
1522
|
console.log(JSON.stringify(response, null, 2));
|
|
2820
1523
|
} else {
|
|
@@ -2833,7 +1536,7 @@ async function refreshCommand(options = {}) {
|
|
|
2833
1536
|
}
|
|
2834
1537
|
|
|
2835
1538
|
// packages/cli/src/commands/check.ts
|
|
2836
|
-
function
|
|
1539
|
+
function parseRef7(ref) {
|
|
2837
1540
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2838
1541
|
}
|
|
2839
1542
|
async function checkCommand(ref, options = {}) {
|
|
@@ -2841,14 +1544,14 @@ async function checkCommand(ref, options = {}) {
|
|
|
2841
1544
|
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2842
1545
|
}
|
|
2843
1546
|
await ensureDaemonRunning();
|
|
2844
|
-
const parsedRef =
|
|
1547
|
+
const parsedRef = parseRef7(ref);
|
|
2845
1548
|
const request = {
|
|
2846
1549
|
id: generateId(),
|
|
2847
1550
|
action: "check",
|
|
2848
1551
|
ref: parsedRef,
|
|
2849
1552
|
tabId: options.tabId
|
|
2850
1553
|
};
|
|
2851
|
-
const response = await
|
|
1554
|
+
const response = await sendCommand(request);
|
|
2852
1555
|
if (options.json) {
|
|
2853
1556
|
console.log(JSON.stringify(response, null, 2));
|
|
2854
1557
|
} else {
|
|
@@ -2880,14 +1583,14 @@ async function uncheckCommand(ref, options = {}) {
|
|
|
2880
1583
|
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2881
1584
|
}
|
|
2882
1585
|
await ensureDaemonRunning();
|
|
2883
|
-
const parsedRef =
|
|
1586
|
+
const parsedRef = parseRef7(ref);
|
|
2884
1587
|
const request = {
|
|
2885
1588
|
id: generateId(),
|
|
2886
1589
|
action: "uncheck",
|
|
2887
1590
|
ref: parsedRef,
|
|
2888
1591
|
tabId: options.tabId
|
|
2889
1592
|
};
|
|
2890
|
-
const response = await
|
|
1593
|
+
const response = await sendCommand(request);
|
|
2891
1594
|
if (options.json) {
|
|
2892
1595
|
console.log(JSON.stringify(response, null, 2));
|
|
2893
1596
|
} else {
|
|
@@ -2916,7 +1619,7 @@ async function uncheckCommand(ref, options = {}) {
|
|
|
2916
1619
|
}
|
|
2917
1620
|
|
|
2918
1621
|
// packages/cli/src/commands/select.ts
|
|
2919
|
-
function
|
|
1622
|
+
function parseRef8(ref) {
|
|
2920
1623
|
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
2921
1624
|
}
|
|
2922
1625
|
async function selectCommand(ref, value, options = {}) {
|
|
@@ -2927,7 +1630,7 @@ async function selectCommand(ref, value, options = {}) {
|
|
|
2927
1630
|
throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
|
|
2928
1631
|
}
|
|
2929
1632
|
await ensureDaemonRunning();
|
|
2930
|
-
const parsedRef =
|
|
1633
|
+
const parsedRef = parseRef8(ref);
|
|
2931
1634
|
const request = {
|
|
2932
1635
|
id: generateId(),
|
|
2933
1636
|
action: "select",
|
|
@@ -2935,7 +1638,7 @@ async function selectCommand(ref, value, options = {}) {
|
|
|
2935
1638
|
value,
|
|
2936
1639
|
tabId: options.tabId
|
|
2937
1640
|
};
|
|
2938
|
-
const response = await
|
|
1641
|
+
const response = await sendCommand(request);
|
|
2939
1642
|
if (options.json) {
|
|
2940
1643
|
console.log(JSON.stringify(response, null, 2));
|
|
2941
1644
|
} else {
|
|
@@ -2973,7 +1676,7 @@ async function evalCommand(script, options = {}) {
|
|
|
2973
1676
|
script,
|
|
2974
1677
|
tabId: options.tabId
|
|
2975
1678
|
};
|
|
2976
|
-
const response = await
|
|
1679
|
+
const response = await sendCommand(request);
|
|
2977
1680
|
if (options.json) {
|
|
2978
1681
|
console.log(JSON.stringify(response, null, 2));
|
|
2979
1682
|
} else {
|
|
@@ -3056,6 +1759,12 @@ function formatTabList(tabs, activeIndex) {
|
|
|
3056
1759
|
async function tabCommand(args, options = {}) {
|
|
3057
1760
|
await ensureDaemonRunning();
|
|
3058
1761
|
const parsed = parseTabSubcommand(args, process.argv);
|
|
1762
|
+
if (options.globalTabId && parsed.tabId === void 0 && parsed.index === void 0) {
|
|
1763
|
+
if (parsed.action === "tab_close" || parsed.action === "tab_select") {
|
|
1764
|
+
const numId = parseInt(options.globalTabId, 10);
|
|
1765
|
+
parsed.tabId = isNaN(numId) ? options.globalTabId : numId;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
3059
1768
|
const request = {
|
|
3060
1769
|
id: generateId(),
|
|
3061
1770
|
action: parsed.action,
|
|
@@ -3063,7 +1772,7 @@ async function tabCommand(args, options = {}) {
|
|
|
3063
1772
|
index: parsed.index,
|
|
3064
1773
|
tabId: parsed.tabId
|
|
3065
1774
|
};
|
|
3066
|
-
const response = await
|
|
1775
|
+
const response = await sendCommand(request);
|
|
3067
1776
|
if (options.json) {
|
|
3068
1777
|
console.log(JSON.stringify(response, null, 2));
|
|
3069
1778
|
} else {
|
|
@@ -3112,7 +1821,7 @@ async function frameCommand(selector, options = {}) {
|
|
|
3112
1821
|
selector,
|
|
3113
1822
|
tabId: options.tabId
|
|
3114
1823
|
};
|
|
3115
|
-
const response = await
|
|
1824
|
+
const response = await sendCommand(request);
|
|
3116
1825
|
if (options.json) {
|
|
3117
1826
|
console.log(JSON.stringify(response, null, 2));
|
|
3118
1827
|
} else {
|
|
@@ -3136,7 +1845,7 @@ async function frameMainCommand(options = {}) {
|
|
|
3136
1845
|
action: "frame_main",
|
|
3137
1846
|
tabId: options.tabId
|
|
3138
1847
|
};
|
|
3139
|
-
const response = await
|
|
1848
|
+
const response = await sendCommand(request);
|
|
3140
1849
|
if (options.json) {
|
|
3141
1850
|
console.log(JSON.stringify(response, null, 2));
|
|
3142
1851
|
} else {
|
|
@@ -3162,7 +1871,7 @@ async function dialogCommand(subCommand, promptText, options = {}) {
|
|
|
3162
1871
|
promptText: subCommand === "accept" ? promptText : void 0,
|
|
3163
1872
|
tabId: options.tabId
|
|
3164
1873
|
};
|
|
3165
|
-
const response = await
|
|
1874
|
+
const response = await sendCommand(request);
|
|
3166
1875
|
if (options.json) {
|
|
3167
1876
|
console.log(JSON.stringify(response, null, 2));
|
|
3168
1877
|
} else {
|
|
@@ -3183,8 +1892,13 @@ async function dialogCommand(subCommand, promptText, options = {}) {
|
|
|
3183
1892
|
|
|
3184
1893
|
// packages/cli/src/commands/network.ts
|
|
3185
1894
|
async function networkCommand(subCommand, urlOrFilter, options = {}) {
|
|
3186
|
-
|
|
3187
|
-
|
|
1895
|
+
let since;
|
|
1896
|
+
if (subCommand === "requests" && options.since) {
|
|
1897
|
+
const num = parseInt(options.since, 10);
|
|
1898
|
+
since = !isNaN(num) && String(num) === options.since ? num : options.since;
|
|
1899
|
+
}
|
|
1900
|
+
const request = {
|
|
1901
|
+
id: generateId(),
|
|
3188
1902
|
action: "network",
|
|
3189
1903
|
networkCommand: subCommand,
|
|
3190
1904
|
url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
|
|
@@ -3194,8 +1908,12 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
|
|
|
3194
1908
|
body: options.body
|
|
3195
1909
|
} : void 0,
|
|
3196
1910
|
withBody: subCommand === "requests" ? options.withBody : void 0,
|
|
1911
|
+
since,
|
|
1912
|
+
method: subCommand === "requests" ? options.method : void 0,
|
|
1913
|
+
status: subCommand === "requests" ? options.status : void 0,
|
|
3197
1914
|
tabId: options.tabId
|
|
3198
|
-
}
|
|
1915
|
+
};
|
|
1916
|
+
const response = await sendCommand(request);
|
|
3199
1917
|
if (options.json) {
|
|
3200
1918
|
console.log(JSON.stringify(response));
|
|
3201
1919
|
return;
|
|
@@ -3270,12 +1988,19 @@ async function networkCommand(subCommand, urlOrFilter, options = {}) {
|
|
|
3270
1988
|
|
|
3271
1989
|
// packages/cli/src/commands/console.ts
|
|
3272
1990
|
async function consoleCommand(options = {}) {
|
|
3273
|
-
|
|
3274
|
-
|
|
1991
|
+
let since;
|
|
1992
|
+
if (options.since) {
|
|
1993
|
+
const num = parseInt(options.since, 10);
|
|
1994
|
+
since = !isNaN(num) && String(num) === options.since ? num : options.since;
|
|
1995
|
+
}
|
|
1996
|
+
const request = {
|
|
1997
|
+
id: generateId(),
|
|
3275
1998
|
action: "console",
|
|
3276
1999
|
consoleCommand: options.clear ? "clear" : "get",
|
|
3277
|
-
tabId: options.tabId
|
|
3278
|
-
|
|
2000
|
+
tabId: options.tabId,
|
|
2001
|
+
since
|
|
2002
|
+
};
|
|
2003
|
+
const response = await sendCommand(request);
|
|
3279
2004
|
if (options.json) {
|
|
3280
2005
|
console.log(JSON.stringify(response));
|
|
3281
2006
|
return;
|
|
@@ -3315,12 +2040,19 @@ async function consoleCommand(options = {}) {
|
|
|
3315
2040
|
|
|
3316
2041
|
// packages/cli/src/commands/errors.ts
|
|
3317
2042
|
async function errorsCommand(options = {}) {
|
|
3318
|
-
|
|
3319
|
-
|
|
2043
|
+
let since;
|
|
2044
|
+
if (options.since) {
|
|
2045
|
+
const num = parseInt(options.since, 10);
|
|
2046
|
+
since = !isNaN(num) && String(num) === options.since ? num : options.since;
|
|
2047
|
+
}
|
|
2048
|
+
const request = {
|
|
2049
|
+
id: generateId(),
|
|
3320
2050
|
action: "errors",
|
|
3321
2051
|
errorsCommand: options.clear ? "clear" : "get",
|
|
3322
|
-
tabId: options.tabId
|
|
3323
|
-
|
|
2052
|
+
tabId: options.tabId,
|
|
2053
|
+
since
|
|
2054
|
+
};
|
|
2055
|
+
const response = await sendCommand(request);
|
|
3324
2056
|
if (options.json) {
|
|
3325
2057
|
console.log(JSON.stringify(response));
|
|
3326
2058
|
return;
|
|
@@ -3355,8 +2087,8 @@ async function errorsCommand(options = {}) {
|
|
|
3355
2087
|
|
|
3356
2088
|
// packages/cli/src/commands/trace.ts
|
|
3357
2089
|
async function traceCommand(subCommand, options = {}) {
|
|
3358
|
-
const response = await
|
|
3359
|
-
id:
|
|
2090
|
+
const response = await sendCommand({
|
|
2091
|
+
id: generateId(),
|
|
3360
2092
|
action: "trace",
|
|
3361
2093
|
traceCommand: subCommand,
|
|
3362
2094
|
tabId: options.tabId
|
|
@@ -3445,7 +2177,7 @@ function matchTabOrigin2(tabUrl, targetHostname) {
|
|
|
3445
2177
|
}
|
|
3446
2178
|
async function ensureTabForOrigin(origin, hostname) {
|
|
3447
2179
|
const listReq = { id: generateId(), action: "tab_list" };
|
|
3448
|
-
const listResp = await
|
|
2180
|
+
const listResp = await sendCommand(listReq);
|
|
3449
2181
|
if (listResp.success && listResp.data?.tabs) {
|
|
3450
2182
|
const matchingTab = listResp.data.tabs.find(
|
|
3451
2183
|
(tab) => matchTabOrigin2(tab.url, hostname)
|
|
@@ -3454,11 +2186,11 @@ async function ensureTabForOrigin(origin, hostname) {
|
|
|
3454
2186
|
return matchingTab.tabId;
|
|
3455
2187
|
}
|
|
3456
2188
|
}
|
|
3457
|
-
const newResp = await
|
|
2189
|
+
const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
|
|
3458
2190
|
if (!newResp.success) {
|
|
3459
2191
|
throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
|
|
3460
2192
|
}
|
|
3461
|
-
await new Promise((
|
|
2193
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
3462
2194
|
return newResp.data?.tabId;
|
|
3463
2195
|
}
|
|
3464
2196
|
function buildFetchScript(url, options) {
|
|
@@ -3523,7 +2255,7 @@ async function fetchCommand(url, options = {}) {
|
|
|
3523
2255
|
}
|
|
3524
2256
|
const script = buildFetchScript(url, options);
|
|
3525
2257
|
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
3526
|
-
const evalResp = await
|
|
2258
|
+
const evalResp = await sendCommand(evalReq);
|
|
3527
2259
|
if (!evalResp.success) {
|
|
3528
2260
|
throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
|
|
3529
2261
|
}
|
|
@@ -3542,9 +2274,9 @@ async function fetchCommand(url, options = {}) {
|
|
|
3542
2274
|
throw new Error(`Fetch error: ${result.error}`);
|
|
3543
2275
|
}
|
|
3544
2276
|
if (options.output) {
|
|
3545
|
-
const { writeFileSync
|
|
2277
|
+
const { writeFileSync } = await import("fs");
|
|
3546
2278
|
const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
|
|
3547
|
-
|
|
2279
|
+
writeFileSync(options.output, content, "utf-8");
|
|
3548
2280
|
console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
|
|
3549
2281
|
return;
|
|
3550
2282
|
}
|
|
@@ -3561,7 +2293,7 @@ async function historyCommand(subCommand, options = {}) {
|
|
|
3561
2293
|
const data = subCommand === "search" ? { historyItems: searchHistory(options.query, days) } : { historyDomains: getHistoryDomains(days) };
|
|
3562
2294
|
if (options.json) {
|
|
3563
2295
|
console.log(JSON.stringify({
|
|
3564
|
-
id:
|
|
2296
|
+
id: generateId(),
|
|
3565
2297
|
success: true,
|
|
3566
2298
|
data
|
|
3567
2299
|
}));
|
|
@@ -3606,16 +2338,49 @@ async function historyCommand(subCommand, options = {}) {
|
|
|
3606
2338
|
|
|
3607
2339
|
// packages/cli/src/commands/daemon.ts
|
|
3608
2340
|
async function statusCommand(options = {}) {
|
|
3609
|
-
const
|
|
2341
|
+
const status = await getDaemonStatus();
|
|
2342
|
+
if (!status) {
|
|
2343
|
+
if (options.json) {
|
|
2344
|
+
console.log(JSON.stringify({ running: false }));
|
|
2345
|
+
} else {
|
|
2346
|
+
console.log("Daemon not running");
|
|
2347
|
+
}
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
3610
2350
|
if (options.json) {
|
|
3611
|
-
console.log(JSON.stringify(
|
|
2351
|
+
console.log(JSON.stringify(status, null, 2));
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
console.log(`Daemon running: ${status.running ? "yes" : "no"}`);
|
|
2355
|
+
console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
|
|
2356
|
+
console.log(`Uptime: ${formatUptime(status.uptime)}`);
|
|
2357
|
+
console.log(`Global seq: ${status.currentSeq ?? "N/A"}`);
|
|
2358
|
+
const tabs = status.tabs;
|
|
2359
|
+
if (tabs && tabs.length > 0) {
|
|
2360
|
+
console.log(`
|
|
2361
|
+
Tabs (${tabs.length}):`);
|
|
2362
|
+
for (const tab of tabs) {
|
|
2363
|
+
const active = tab.targetId === status.currentTargetId ? " *" : "";
|
|
2364
|
+
console.log(
|
|
2365
|
+
` ${tab.shortId}${active} net:${tab.networkRequests} console:${tab.consoleMessages} err:${tab.jsErrors} seq:${tab.lastActionSeq}`
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
3612
2368
|
} else {
|
|
3613
|
-
console.log(
|
|
2369
|
+
console.log("\nNo tabs");
|
|
3614
2370
|
}
|
|
3615
2371
|
}
|
|
2372
|
+
function formatUptime(ms) {
|
|
2373
|
+
if (!ms || ms <= 0) return "0s";
|
|
2374
|
+
const s = Math.floor(ms / 1e3);
|
|
2375
|
+
if (s < 60) return `${s}s`;
|
|
2376
|
+
const m = Math.floor(s / 60);
|
|
2377
|
+
if (m < 60) return `${m}m ${s % 60}s`;
|
|
2378
|
+
const h = Math.floor(m / 60);
|
|
2379
|
+
return `${h}h ${m % 60}m`;
|
|
2380
|
+
}
|
|
3616
2381
|
|
|
3617
2382
|
// packages/cli/src/index.ts
|
|
3618
|
-
var VERSION = "0.
|
|
2383
|
+
var VERSION = "0.11.0";
|
|
3619
2384
|
var HELP_TEXT = `
|
|
3620
2385
|
bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
3621
2386
|
|
|
@@ -3751,6 +2516,12 @@ function parseArgs(argv) {
|
|
|
3751
2516
|
skipNext = true;
|
|
3752
2517
|
} else if (arg === "--tab") {
|
|
3753
2518
|
skipNext = true;
|
|
2519
|
+
} else if (arg === "--since") {
|
|
2520
|
+
skipNext = true;
|
|
2521
|
+
} else if (arg === "--method") {
|
|
2522
|
+
skipNext = true;
|
|
2523
|
+
} else if (arg === "--status") {
|
|
2524
|
+
skipNext = true;
|
|
3754
2525
|
} else if (arg.startsWith("-")) {
|
|
3755
2526
|
} else if (result.command === null) {
|
|
3756
2527
|
result.command = arg;
|
|
@@ -3764,15 +2535,17 @@ async function main() {
|
|
|
3764
2535
|
const parsed = parseArgs(process.argv);
|
|
3765
2536
|
setJqExpression(parsed.flags.jq);
|
|
3766
2537
|
const tabArgIdx = process.argv.indexOf("--tab");
|
|
3767
|
-
const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ?
|
|
2538
|
+
const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? process.argv[tabArgIdx + 1] : void 0;
|
|
2539
|
+
const sinceArgIdx = process.argv.indexOf("--since");
|
|
2540
|
+
const globalSince = sinceArgIdx >= 0 && process.argv[sinceArgIdx + 1] ? process.argv[sinceArgIdx + 1] : void 0;
|
|
3768
2541
|
if (parsed.flags.version) {
|
|
3769
2542
|
console.log(VERSION);
|
|
3770
2543
|
return;
|
|
3771
2544
|
}
|
|
3772
2545
|
if (process.argv.includes("--mcp")) {
|
|
3773
|
-
const mcpPath =
|
|
3774
|
-
const { spawn:
|
|
3775
|
-
const child =
|
|
2546
|
+
const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
|
|
2547
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
2548
|
+
const child = spawn2(process.execPath, [mcpPath], { stdio: "inherit" });
|
|
3776
2549
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
3777
2550
|
return;
|
|
3778
2551
|
}
|
|
@@ -3992,7 +2765,7 @@ async function main() {
|
|
|
3992
2765
|
break;
|
|
3993
2766
|
}
|
|
3994
2767
|
case "tab": {
|
|
3995
|
-
await tabCommand(parsed.args, { json: parsed.flags.json });
|
|
2768
|
+
await tabCommand(parsed.args, { json: parsed.flags.json, globalTabId });
|
|
3996
2769
|
break;
|
|
3997
2770
|
}
|
|
3998
2771
|
case "status": {
|
|
@@ -4036,17 +2809,21 @@ async function main() {
|
|
|
4036
2809
|
const withBody = process.argv.includes("--with-body");
|
|
4037
2810
|
const bodyIndex = process.argv.findIndex((a) => a === "--body");
|
|
4038
2811
|
const body = bodyIndex >= 0 ? process.argv[bodyIndex + 1] : void 0;
|
|
4039
|
-
|
|
2812
|
+
const methodIndex = process.argv.findIndex((a) => a === "--method");
|
|
2813
|
+
const method = methodIndex >= 0 ? process.argv[methodIndex + 1] : void 0;
|
|
2814
|
+
const statusIndex = process.argv.findIndex((a) => a === "--status");
|
|
2815
|
+
const statusFilter = statusIndex >= 0 ? process.argv[statusIndex + 1] : void 0;
|
|
2816
|
+
await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter });
|
|
4040
2817
|
break;
|
|
4041
2818
|
}
|
|
4042
2819
|
case "console": {
|
|
4043
2820
|
const clear = process.argv.includes("--clear");
|
|
4044
|
-
await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId });
|
|
2821
|
+
await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
|
|
4045
2822
|
break;
|
|
4046
2823
|
}
|
|
4047
2824
|
case "errors": {
|
|
4048
2825
|
const clear = process.argv.includes("--clear");
|
|
4049
|
-
await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId });
|
|
2826
|
+
await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
|
|
4050
2827
|
break;
|
|
4051
2828
|
}
|
|
4052
2829
|
case "trace": {
|
|
@@ -4116,9 +2893,9 @@ async function main() {
|
|
|
4116
2893
|
break;
|
|
4117
2894
|
}
|
|
4118
2895
|
case "star": {
|
|
4119
|
-
const { execSync:
|
|
2896
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
4120
2897
|
try {
|
|
4121
|
-
|
|
2898
|
+
execSync3("gh auth status", { stdio: "pipe" });
|
|
4122
2899
|
} catch {
|
|
4123
2900
|
console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
|
|
4124
2901
|
console.error(" brew install gh && gh auth login");
|
|
@@ -4127,7 +2904,7 @@ async function main() {
|
|
|
4127
2904
|
const repos = ["epiral/bb-browser", "epiral/bb-sites"];
|
|
4128
2905
|
for (const repo of repos) {
|
|
4129
2906
|
try {
|
|
4130
|
-
|
|
2907
|
+
execSync3(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
|
|
4131
2908
|
console.log(`\u2B50 Starred ${repo}`);
|
|
4132
2909
|
} catch {
|
|
4133
2910
|
console.log(`Already starred or failed: ${repo}`);
|