@nntoan/bb-browser 0.13.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.
@@ -0,0 +1,3258 @@
1
+ const DEFAULT_DAEMON_PORT = 19824;
2
+ const DEFAULT_DAEMON_HOST = "localhost";
3
+ const DEFAULT_DAEMON_BASE_URL = `http://${DEFAULT_DAEMON_HOST}:${DEFAULT_DAEMON_PORT}`;
4
+ const SSE_RECONNECT_DELAY = 3e3;
5
+ const STORAGE_KEY = "upstreamUrl";
6
+ async function getUpstreamUrl() {
7
+ try {
8
+ const result = await chrome.storage.sync.get(STORAGE_KEY);
9
+ const url = result[STORAGE_KEY];
10
+ if (url && typeof url === "string" && url.trim()) {
11
+ return url.trim().replace(/\/+$/, "");
12
+ }
13
+ } catch {
14
+ }
15
+ return DEFAULT_DAEMON_BASE_URL;
16
+ }
17
+
18
+ class SSEClient {
19
+ constructor() {
20
+ this.abortController = null;
21
+ this.reconnectAttempts = 0;
22
+ this.isConnectedFlag = false;
23
+ this.onCommandHandler = null;
24
+ }
25
+ /**
26
+ * 连接到 Daemon SSE 端点
27
+ */
28
+ async connect() {
29
+ if (this.abortController) {
30
+ console.warn("[SSEClient] Already connected");
31
+ return;
32
+ }
33
+ const baseUrl = await getUpstreamUrl();
34
+ const sseUrl = `${baseUrl}/sse`;
35
+ console.log("[SSEClient] Connecting to:", sseUrl);
36
+ this.abortController = new AbortController();
37
+ try {
38
+ const response = await fetch(sseUrl, {
39
+ signal: this.abortController.signal,
40
+ headers: {
41
+ Accept: "text/event-stream",
42
+ "Cache-Control": "no-cache"
43
+ },
44
+ keepalive: true
45
+ });
46
+ if (!response.ok) {
47
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
48
+ }
49
+ if (!response.body) {
50
+ throw new Error("Response body is null");
51
+ }
52
+ const contentType = response.headers.get("Content-Type");
53
+ console.log("[SSEClient] Connection established, Content-Type:", contentType);
54
+ this.isConnectedFlag = true;
55
+ this.reconnectAttempts = 0;
56
+ await this.readStream(response.body);
57
+ } catch (error) {
58
+ if (error instanceof Error && error.name === "AbortError") {
59
+ console.log("[SSEClient] Connection aborted");
60
+ return;
61
+ }
62
+ this.isConnectedFlag = false;
63
+ this.reconnect();
64
+ }
65
+ }
66
+ /**
67
+ * 读取并解析 SSE 流
68
+ */
69
+ async readStream(body) {
70
+ const reader = body.getReader();
71
+ const decoder = new TextDecoder();
72
+ let buffer = "";
73
+ let event = "";
74
+ let data = "";
75
+ try {
76
+ while (true) {
77
+ const { done, value } = await reader.read();
78
+ if (done) {
79
+ console.log("[SSEClient] Stream ended");
80
+ this.isConnectedFlag = false;
81
+ this.reconnect();
82
+ break;
83
+ }
84
+ buffer += decoder.decode(value, { stream: true });
85
+ const lines = buffer.split("\n");
86
+ buffer = lines.pop() || "";
87
+ for (const line of lines) {
88
+ const trimmedLine = line.trim();
89
+ if (trimmedLine.startsWith("event:")) {
90
+ event = trimmedLine.substring(6).trim();
91
+ } else if (trimmedLine.startsWith("data:")) {
92
+ data = trimmedLine.substring(5).trim();
93
+ } else if (trimmedLine === "") {
94
+ if (event && data) {
95
+ this.handleMessage(event, data).catch(
96
+ (err) => console.error("[SSEClient] handleMessage error:", err)
97
+ );
98
+ event = "";
99
+ data = "";
100
+ }
101
+ }
102
+ }
103
+ }
104
+ } catch (error) {
105
+ if (error instanceof Error && error.name === "AbortError") {
106
+ console.log("[SSEClient] Stream reading aborted");
107
+ return;
108
+ }
109
+ console.error("[SSEClient] Stream reading error:", error);
110
+ this.isConnectedFlag = false;
111
+ this.reconnect();
112
+ } finally {
113
+ reader.releaseLock();
114
+ }
115
+ }
116
+ /**
117
+ * 处理 SSE 消息
118
+ */
119
+ async handleMessage(event, data) {
120
+ try {
121
+ const parsed = JSON.parse(data);
122
+ switch (event) {
123
+ case "connected":
124
+ console.log("[SSEClient] Connection confirmed:", parsed);
125
+ break;
126
+ case "heartbeat":
127
+ console.log("[SSEClient] Heartbeat:", new Date(parsed.time * 1e3).toISOString());
128
+ break;
129
+ case "command":
130
+ console.log("[SSEClient] Command received:", parsed.id, parsed.action);
131
+ if (this.onCommandHandler) {
132
+ await this.onCommandHandler(parsed);
133
+ } else {
134
+ console.warn("[SSEClient] No command handler registered");
135
+ }
136
+ break;
137
+ default:
138
+ console.log("[SSEClient] Unknown event type:", event);
139
+ }
140
+ } catch (error) {
141
+ console.error("[SSEClient] Error handling message:", error);
142
+ }
143
+ }
144
+ /**
145
+ * 指数退避重连
146
+ */
147
+ reconnect() {
148
+ this.reconnectAttempts++;
149
+ const delay = Math.min(SSE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts - 1), 6e4);
150
+ setTimeout(() => {
151
+ this.disconnect();
152
+ this.connect();
153
+ }, delay);
154
+ }
155
+ /**
156
+ * 注册命令处理器
157
+ */
158
+ onCommand(handler) {
159
+ this.onCommandHandler = handler;
160
+ }
161
+ /**
162
+ * 断开连接
163
+ */
164
+ disconnect() {
165
+ if (this.abortController) {
166
+ console.log("[SSEClient] Disconnecting...");
167
+ this.abortController.abort();
168
+ this.abortController = null;
169
+ this.isConnectedFlag = false;
170
+ }
171
+ }
172
+ /**
173
+ * 检查连接状态
174
+ */
175
+ isConnected() {
176
+ return this.isConnectedFlag;
177
+ }
178
+ }
179
+
180
+ async function sendResult(result) {
181
+ const baseUrl = await getUpstreamUrl();
182
+ const url = `${baseUrl}/result`;
183
+ console.log("[APIClient] Sending result:", result.id, result.success);
184
+ try {
185
+ const response = await fetch(url, {
186
+ method: "POST",
187
+ headers: {
188
+ "Content-Type": "application/json"
189
+ },
190
+ body: JSON.stringify(result)
191
+ });
192
+ if (!response.ok) {
193
+ console.error("[APIClient] Failed to send result:", response.status, response.statusText);
194
+ return;
195
+ }
196
+ const data = await response.json();
197
+ console.log("[APIClient] Result sent successfully:", data);
198
+ } catch (error) {
199
+ console.error("[APIClient] Error sending result:", error);
200
+ }
201
+ }
202
+
203
+ const attachedTabs = /* @__PURE__ */ new Set();
204
+ const pendingDialogs = /* @__PURE__ */ new Map();
205
+ const networkRequests = /* @__PURE__ */ new Map();
206
+ const consoleMessages = /* @__PURE__ */ new Map();
207
+ const jsErrors = /* @__PURE__ */ new Map();
208
+ const networkRoutes = /* @__PURE__ */ new Map();
209
+ const networkEnabledTabs = /* @__PURE__ */ new Set();
210
+ const networkBodyBytes = /* @__PURE__ */ new Map();
211
+ const MAX_REQUESTS = 500;
212
+ const MAX_REQUEST_BODY_BYTES = 64 * 1024;
213
+ const MAX_RESPONSE_BODY_BYTES = 256 * 1024;
214
+ const MAX_TAB_BODY_BYTES = 8 * 1024 * 1024;
215
+ const MAX_CONSOLE_MESSAGES = 500;
216
+ const MAX_ERRORS = 100;
217
+ async function ensureAttached(tabId) {
218
+ if (attachedTabs.has(tabId)) {
219
+ return;
220
+ }
221
+ try {
222
+ await chrome.debugger.attach({ tabId }, "1.3");
223
+ attachedTabs.add(tabId);
224
+ await chrome.debugger.sendCommand({ tabId }, "Page.enable");
225
+ await chrome.debugger.sendCommand({ tabId }, "DOM.enable");
226
+ await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
227
+ console.log("[CDPService] Attached to tab:", tabId);
228
+ } catch (error) {
229
+ const msg = error instanceof Error ? error.message : String(error);
230
+ if (msg.includes("Another debugger is already attached")) {
231
+ attachedTabs.add(tabId);
232
+ return;
233
+ }
234
+ throw error;
235
+ }
236
+ }
237
+ async function sendCommand(tabId, method, params) {
238
+ await ensureAttached(tabId);
239
+ const result = await chrome.debugger.sendCommand({ tabId }, method, params);
240
+ return result;
241
+ }
242
+ async function evaluate(tabId, expression, options = {}) {
243
+ const result = await sendCommand(tabId, "Runtime.evaluate", {
244
+ expression,
245
+ returnByValue: options.returnByValue ?? true,
246
+ awaitPromise: options.awaitPromise ?? true,
247
+ replMode: true
248
+ });
249
+ if (result.exceptionDetails) {
250
+ const errorMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Unknown error";
251
+ throw new Error(`Eval error: ${errorMsg}`);
252
+ }
253
+ return result.result?.value;
254
+ }
255
+ async function callFunctionOn(tabId, objectId, functionDeclaration, args = []) {
256
+ const result = await sendCommand(tabId, "Runtime.callFunctionOn", {
257
+ objectId,
258
+ functionDeclaration,
259
+ arguments: args.map((arg) => ({ value: arg })),
260
+ returnByValue: true,
261
+ awaitPromise: true
262
+ });
263
+ if (result.exceptionDetails) {
264
+ throw new Error(result.exceptionDetails.exception?.description || "Call failed");
265
+ }
266
+ return result.result?.value;
267
+ }
268
+ async function getDocument(tabId, options = {}) {
269
+ const result = await sendCommand(tabId, "DOM.getDocument", {
270
+ depth: options.depth ?? -1,
271
+ // -1 表示获取整个树
272
+ pierce: options.pierce ?? true
273
+ // 穿透 shadow DOM 和 iframe
274
+ });
275
+ return result.root;
276
+ }
277
+ async function querySelector(tabId, nodeId, selector) {
278
+ const result = await sendCommand(tabId, "DOM.querySelector", {
279
+ nodeId,
280
+ selector
281
+ });
282
+ return result.nodeId;
283
+ }
284
+ async function resolveNodeByBackendId(tabId, backendNodeId) {
285
+ const result = await sendCommand(tabId, "DOM.resolveNode", {
286
+ backendNodeId
287
+ });
288
+ return result.object.objectId;
289
+ }
290
+ async function dispatchMouseEvent(tabId, type, x, y, options = {}) {
291
+ await sendCommand(tabId, "Input.dispatchMouseEvent", {
292
+ type,
293
+ x,
294
+ y,
295
+ button: options.button ?? "left",
296
+ clickCount: options.clickCount ?? 1,
297
+ deltaX: options.deltaX ?? 0,
298
+ deltaY: options.deltaY ?? 0,
299
+ modifiers: options.modifiers ?? 0
300
+ });
301
+ }
302
+ async function click(tabId, x, y) {
303
+ await dispatchMouseEvent(tabId, "mousePressed", x, y, { button: "left", clickCount: 1 });
304
+ await dispatchMouseEvent(tabId, "mouseReleased", x, y, { button: "left", clickCount: 1 });
305
+ }
306
+ async function moveMouse(tabId, x, y) {
307
+ await dispatchMouseEvent(tabId, "mouseMoved", x, y);
308
+ }
309
+ async function scroll(tabId, x, y, deltaX, deltaY) {
310
+ await dispatchMouseEvent(tabId, "mouseWheel", x, y, { deltaX, deltaY });
311
+ }
312
+ async function dispatchKeyEvent(tabId, type, options = {}) {
313
+ await sendCommand(tabId, "Input.dispatchKeyEvent", {
314
+ type,
315
+ ...options
316
+ });
317
+ }
318
+ async function pressKey$1(tabId, key, options = {}) {
319
+ const keyCodeMap = {
320
+ Enter: 13,
321
+ Tab: 9,
322
+ Backspace: 8,
323
+ Escape: 27,
324
+ ArrowUp: 38,
325
+ ArrowDown: 40,
326
+ ArrowLeft: 37,
327
+ ArrowRight: 39,
328
+ Delete: 46,
329
+ Home: 36,
330
+ End: 35,
331
+ PageUp: 33,
332
+ PageDown: 34
333
+ };
334
+ const keyCode = keyCodeMap[key] || key.charCodeAt(0);
335
+ await dispatchKeyEvent(tabId, "rawKeyDown", {
336
+ key,
337
+ code: key,
338
+ windowsVirtualKeyCode: keyCode,
339
+ nativeVirtualKeyCode: keyCode,
340
+ modifiers: options.modifiers
341
+ });
342
+ if (key.length === 1) {
343
+ await dispatchKeyEvent(tabId, "char", {
344
+ text: key,
345
+ key,
346
+ modifiers: options.modifiers
347
+ });
348
+ }
349
+ await dispatchKeyEvent(tabId, "keyUp", {
350
+ key,
351
+ code: key,
352
+ windowsVirtualKeyCode: keyCode,
353
+ nativeVirtualKeyCode: keyCode,
354
+ modifiers: options.modifiers
355
+ });
356
+ }
357
+ async function insertText(tabId, text) {
358
+ await sendCommand(tabId, "Input.insertText", { text });
359
+ }
360
+ async function handleJavaScriptDialog(tabId, accept, promptText) {
361
+ await sendCommand(tabId, "Page.handleJavaScriptDialog", {
362
+ accept,
363
+ promptText
364
+ });
365
+ }
366
+ function getPendingDialog(tabId) {
367
+ return pendingDialogs.get(tabId);
368
+ }
369
+ async function getFullAccessibilityTree(tabId, options = {}) {
370
+ await sendCommand(tabId, "Accessibility.enable");
371
+ const result = await sendCommand(
372
+ tabId,
373
+ "Accessibility.getFullAXTree",
374
+ {
375
+ depth: options.depth,
376
+ frameId: options.frameId
377
+ }
378
+ );
379
+ return result.nodes;
380
+ }
381
+ async function getPartialAccessibilityTree(tabId, nodeId, backendNodeId, options = {}) {
382
+ await sendCommand(tabId, "Accessibility.enable");
383
+ const result = await sendCommand(
384
+ tabId,
385
+ "Accessibility.getPartialAXTree",
386
+ {
387
+ nodeId,
388
+ backendNodeId,
389
+ fetchRelatives: options.fetchRelatives ?? true,
390
+ depth: options.depth
391
+ }
392
+ );
393
+ return result.nodes;
394
+ }
395
+ async function enableNetwork(tabId) {
396
+ if (networkEnabledTabs.has(tabId)) return;
397
+ await ensureAttached(tabId);
398
+ await sendCommand(tabId, "Network.enable");
399
+ await sendCommand(tabId, "Fetch.enable", {
400
+ patterns: [{ urlPattern: "*" }]
401
+ });
402
+ networkEnabledTabs.add(tabId);
403
+ if (!networkRequests.has(tabId)) {
404
+ networkRequests.set(tabId, []);
405
+ }
406
+ if (!networkBodyBytes.has(tabId)) {
407
+ networkBodyBytes.set(tabId, 0);
408
+ }
409
+ console.log("[CDPService] Network enabled for tab:", tabId);
410
+ }
411
+ function getNetworkRequests(tabId, filter, withBody = false) {
412
+ const requests = networkRequests.get(tabId) || [];
413
+ const filtered = !filter ? requests : requests.filter(
414
+ (r) => r.url.toLowerCase().includes(filter.toLowerCase()) || r.method.toLowerCase().includes(filter.toLowerCase()) || r.type.toLowerCase().includes(filter.toLowerCase())
415
+ );
416
+ if (withBody) return filtered;
417
+ return filtered.map((r) => ({
418
+ requestId: r.requestId,
419
+ url: r.url,
420
+ method: r.method,
421
+ type: r.type,
422
+ timestamp: r.timestamp,
423
+ response: r.response ? {
424
+ status: r.response.status,
425
+ statusText: r.response.statusText
426
+ } : void 0,
427
+ failed: r.failed,
428
+ failureReason: r.failureReason
429
+ }));
430
+ }
431
+ function clearNetworkRequests(tabId) {
432
+ networkRequests.set(tabId, []);
433
+ networkBodyBytes.set(tabId, 0);
434
+ }
435
+ async function addNetworkRoute(tabId, urlPattern, options = {}) {
436
+ await enableNetwork(tabId);
437
+ const route = {
438
+ urlPattern,
439
+ action: options.abort ? "abort" : options.body ? "fulfill" : "continue",
440
+ body: options.body,
441
+ status: options.status ?? 200,
442
+ headers: options.headers
443
+ };
444
+ const routes = networkRoutes.get(tabId) || [];
445
+ const filtered = routes.filter((r) => r.urlPattern !== urlPattern);
446
+ filtered.push(route);
447
+ networkRoutes.set(tabId, filtered);
448
+ console.log("[CDPService] Added network route:", route);
449
+ }
450
+ function removeNetworkRoute(tabId, urlPattern) {
451
+ if (!urlPattern) {
452
+ networkRoutes.delete(tabId);
453
+ console.log("[CDPService] Removed all network routes for tab:", tabId);
454
+ } else {
455
+ const routes = networkRoutes.get(tabId) || [];
456
+ networkRoutes.set(tabId, routes.filter((r) => r.urlPattern !== urlPattern));
457
+ console.log("[CDPService] Removed network route:", urlPattern);
458
+ }
459
+ }
460
+ function getNetworkRoutes(tabId) {
461
+ return networkRoutes.get(tabId) || [];
462
+ }
463
+ async function enableConsole(tabId) {
464
+ await ensureAttached(tabId);
465
+ await sendCommand(tabId, "Runtime.enable");
466
+ await sendCommand(tabId, "Log.enable");
467
+ if (!consoleMessages.has(tabId)) {
468
+ consoleMessages.set(tabId, []);
469
+ }
470
+ if (!jsErrors.has(tabId)) {
471
+ jsErrors.set(tabId, []);
472
+ }
473
+ console.log("[CDPService] Console enabled for tab:", tabId);
474
+ }
475
+ function getConsoleMessages(tabId) {
476
+ return consoleMessages.get(tabId) || [];
477
+ }
478
+ function clearConsoleMessages(tabId) {
479
+ consoleMessages.set(tabId, []);
480
+ }
481
+ function getJSErrors(tabId) {
482
+ return jsErrors.get(tabId) || [];
483
+ }
484
+ function clearJSErrors(tabId) {
485
+ jsErrors.set(tabId, []);
486
+ }
487
+ function initEventListeners() {
488
+ chrome.debugger.onEvent.addListener((source, method, params) => {
489
+ const tabId = source.tabId;
490
+ if (!tabId) return;
491
+ if (method === "Page.javascriptDialogOpening") {
492
+ const dialogParams = params;
493
+ console.log("[CDPService] Dialog opened:", dialogParams);
494
+ pendingDialogs.set(tabId, dialogParams);
495
+ } else if (method === "Page.javascriptDialogClosed") {
496
+ console.log("[CDPService] Dialog closed");
497
+ pendingDialogs.delete(tabId);
498
+ } else if (method === "Network.requestWillBeSent") {
499
+ handleNetworkRequest(tabId, params);
500
+ } else if (method === "Network.responseReceived") {
501
+ handleNetworkResponse(tabId, params);
502
+ } else if (method === "Network.loadingFailed") {
503
+ handleNetworkFailed(tabId, params);
504
+ } else if (method === "Network.loadingFinished") {
505
+ void handleNetworkLoadingFinished(tabId, params);
506
+ } else if (method === "Fetch.requestPaused") {
507
+ handleFetchPaused(tabId, params);
508
+ } else if (method === "Runtime.consoleAPICalled") {
509
+ handleConsoleAPI(tabId, params);
510
+ } else if (method === "Log.entryAdded") {
511
+ handleLogEntry(tabId, params);
512
+ } else if (method === "Runtime.exceptionThrown") {
513
+ handleException(tabId, params);
514
+ }
515
+ });
516
+ chrome.debugger.onDetach.addListener((source) => {
517
+ if (source.tabId) {
518
+ cleanupTab$2(source.tabId);
519
+ console.log("[CDPService] Debugger detached from tab:", source.tabId);
520
+ }
521
+ });
522
+ chrome.tabs.onRemoved.addListener((tabId) => {
523
+ cleanupTab$2(tabId);
524
+ });
525
+ }
526
+ function cleanupTab$2(tabId) {
527
+ attachedTabs.delete(tabId);
528
+ pendingDialogs.delete(tabId);
529
+ networkRequests.delete(tabId);
530
+ networkRoutes.delete(tabId);
531
+ networkEnabledTabs.delete(tabId);
532
+ networkBodyBytes.delete(tabId);
533
+ consoleMessages.delete(tabId);
534
+ jsErrors.delete(tabId);
535
+ }
536
+ function estimateBodyBytes(value) {
537
+ return value ? value.length * 2 : 0;
538
+ }
539
+ function truncateBody(value, maxBytes) {
540
+ const maxChars = Math.max(0, Math.floor(maxBytes / 2));
541
+ if (value.length <= maxChars) {
542
+ return { body: value, truncated: false };
543
+ }
544
+ return { body: value.slice(0, maxChars), truncated: true };
545
+ }
546
+ function getStoredBodyBytes(request) {
547
+ return estimateBodyBytes(request.requestBody) + estimateBodyBytes(request.response?.body);
548
+ }
549
+ function updateTabBodyBytes(tabId) {
550
+ const requests = networkRequests.get(tabId) || [];
551
+ let total = 0;
552
+ for (const request of requests) {
553
+ total += getStoredBodyBytes(request);
554
+ }
555
+ networkBodyBytes.set(tabId, total);
556
+ }
557
+ function enforceBodyBudget(tabId) {
558
+ const requests = networkRequests.get(tabId) || [];
559
+ let total = networkBodyBytes.get(tabId) || 0;
560
+ for (const request of requests) {
561
+ if (total <= MAX_TAB_BODY_BYTES) break;
562
+ if (request.requestBody) {
563
+ total -= estimateBodyBytes(request.requestBody);
564
+ delete request.requestBody;
565
+ request.requestBodyTruncated = true;
566
+ }
567
+ if (total <= MAX_TAB_BODY_BYTES) break;
568
+ if (request.response?.body) {
569
+ total -= estimateBodyBytes(request.response.body);
570
+ delete request.response.body;
571
+ request.response.bodyTruncated = true;
572
+ }
573
+ }
574
+ networkBodyBytes.set(tabId, Math.max(0, total));
575
+ }
576
+ function handleNetworkRequest(tabId, params) {
577
+ const requests = networkRequests.get(tabId) || [];
578
+ if (requests.length >= MAX_REQUESTS) {
579
+ requests.shift();
580
+ }
581
+ const truncatedRequestBody = params.request.postData ? truncateBody(params.request.postData, MAX_REQUEST_BODY_BYTES) : void 0;
582
+ requests.push({
583
+ requestId: params.requestId,
584
+ url: params.request.url,
585
+ method: params.request.method,
586
+ type: params.type,
587
+ timestamp: params.timestamp * 1e3,
588
+ requestHeaders: params.request.headers,
589
+ requestBody: truncatedRequestBody?.body,
590
+ requestBodyTruncated: truncatedRequestBody?.truncated
591
+ });
592
+ networkRequests.set(tabId, requests);
593
+ updateTabBodyBytes(tabId);
594
+ enforceBodyBudget(tabId);
595
+ }
596
+ function handleNetworkResponse(tabId, params) {
597
+ const requests = networkRequests.get(tabId) || [];
598
+ const request = requests.find((r) => r.requestId === params.requestId);
599
+ if (request) {
600
+ request.response = {
601
+ status: params.response.status,
602
+ statusText: params.response.statusText,
603
+ headers: params.response.headers,
604
+ mimeType: params.response.mimeType,
605
+ body: request.response?.body,
606
+ bodyBase64: request.response?.bodyBase64,
607
+ bodyTruncated: request.response?.bodyTruncated
608
+ };
609
+ }
610
+ }
611
+ async function handleNetworkLoadingFinished(tabId, params) {
612
+ const requests = networkRequests.get(tabId) || [];
613
+ const request = requests.find((r) => r.requestId === params.requestId);
614
+ if (!request || request.failed) {
615
+ return;
616
+ }
617
+ try {
618
+ const result = await sendCommand(tabId, "Network.getResponseBody", { requestId: params.requestId });
619
+ const truncatedResponseBody = truncateBody(result.body, MAX_RESPONSE_BODY_BYTES);
620
+ request.response = {
621
+ status: request.response?.status ?? 0,
622
+ statusText: request.response?.statusText ?? "",
623
+ headers: request.response?.headers,
624
+ mimeType: request.response?.mimeType,
625
+ body: truncatedResponseBody.body,
626
+ bodyBase64: result.base64Encoded,
627
+ bodyTruncated: truncatedResponseBody.truncated
628
+ };
629
+ request.bodyError = void 0;
630
+ updateTabBodyBytes(tabId);
631
+ enforceBodyBudget(tabId);
632
+ } catch (error) {
633
+ request.bodyError = error instanceof Error ? error.message : String(error);
634
+ }
635
+ }
636
+ function handleNetworkFailed(tabId, params) {
637
+ const requests = networkRequests.get(tabId) || [];
638
+ const request = requests.find((r) => r.requestId === params.requestId);
639
+ if (request) {
640
+ request.failed = true;
641
+ request.failureReason = params.errorText;
642
+ }
643
+ }
644
+ async function handleFetchPaused(tabId, params) {
645
+ const routes = networkRoutes.get(tabId) || [];
646
+ const url = params.request.url;
647
+ const matchedRoute = routes.find((route) => {
648
+ if (route.urlPattern === "*") return true;
649
+ if (route.urlPattern.includes("*")) {
650
+ const regex = new RegExp(route.urlPattern.replace(/\*/g, ".*"));
651
+ return regex.test(url);
652
+ }
653
+ return url.includes(route.urlPattern);
654
+ });
655
+ try {
656
+ if (matchedRoute) {
657
+ if (matchedRoute.action === "abort") {
658
+ await sendCommand(tabId, "Fetch.failRequest", {
659
+ requestId: params.requestId,
660
+ errorReason: "BlockedByClient"
661
+ });
662
+ console.log("[CDPService] Blocked request:", url);
663
+ } else if (matchedRoute.action === "fulfill") {
664
+ await sendCommand(tabId, "Fetch.fulfillRequest", {
665
+ requestId: params.requestId,
666
+ responseCode: matchedRoute.status || 200,
667
+ responseHeaders: Object.entries(matchedRoute.headers || {}).map(([name, value]) => ({ name, value })),
668
+ body: matchedRoute.body ? btoa(matchedRoute.body) : void 0
669
+ });
670
+ console.log("[CDPService] Fulfilled request with mock:", url);
671
+ } else {
672
+ await sendCommand(tabId, "Fetch.continueRequest", {
673
+ requestId: params.requestId
674
+ });
675
+ }
676
+ } else {
677
+ await sendCommand(tabId, "Fetch.continueRequest", {
678
+ requestId: params.requestId
679
+ });
680
+ }
681
+ } catch (error) {
682
+ console.error("[CDPService] Fetch handling error:", error);
683
+ try {
684
+ await sendCommand(tabId, "Fetch.continueRequest", {
685
+ requestId: params.requestId
686
+ });
687
+ } catch {
688
+ }
689
+ }
690
+ }
691
+ function handleConsoleAPI(tabId, params) {
692
+ const messages = consoleMessages.get(tabId) || [];
693
+ if (messages.length >= MAX_CONSOLE_MESSAGES) {
694
+ messages.shift();
695
+ }
696
+ const text = params.args.map((arg) => arg.value !== void 0 ? String(arg.value) : arg.description || "").join(" ");
697
+ const typeMap = {
698
+ log: "log",
699
+ info: "info",
700
+ warning: "warn",
701
+ error: "error",
702
+ debug: "debug"
703
+ };
704
+ messages.push({
705
+ type: typeMap[params.type] || "log",
706
+ text,
707
+ timestamp: params.timestamp,
708
+ url: params.stackTrace?.callFrames[0]?.url,
709
+ lineNumber: params.stackTrace?.callFrames[0]?.lineNumber
710
+ });
711
+ consoleMessages.set(tabId, messages);
712
+ }
713
+ function handleLogEntry(tabId, params) {
714
+ const messages = consoleMessages.get(tabId) || [];
715
+ if (messages.length >= MAX_CONSOLE_MESSAGES) {
716
+ messages.shift();
717
+ }
718
+ const typeMap = {
719
+ verbose: "debug",
720
+ info: "info",
721
+ warning: "warn",
722
+ error: "error"
723
+ };
724
+ messages.push({
725
+ type: typeMap[params.entry.level] || "log",
726
+ text: params.entry.text,
727
+ timestamp: params.entry.timestamp,
728
+ url: params.entry.url,
729
+ lineNumber: params.entry.lineNumber
730
+ });
731
+ consoleMessages.set(tabId, messages);
732
+ }
733
+ function handleException(tabId, params) {
734
+ const errors = jsErrors.get(tabId) || [];
735
+ if (errors.length >= MAX_ERRORS) {
736
+ errors.shift();
737
+ }
738
+ const details = params.exceptionDetails;
739
+ const stackTrace = details.stackTrace?.callFrames.map((f) => ` at ${f.url}:${f.lineNumber}:${f.columnNumber}`).join("\n");
740
+ errors.push({
741
+ message: details.exception?.description || details.text,
742
+ url: details.url,
743
+ lineNumber: details.lineNumber,
744
+ columnNumber: details.columnNumber,
745
+ stackTrace,
746
+ timestamp: params.timestamp
747
+ });
748
+ jsErrors.set(tabId, errors);
749
+ }
750
+
751
+ const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
752
+ "button",
753
+ "link",
754
+ "textbox",
755
+ "searchbox",
756
+ "combobox",
757
+ "listbox",
758
+ "checkbox",
759
+ "radio",
760
+ "slider",
761
+ "spinbutton",
762
+ "switch",
763
+ "tab",
764
+ "menuitem",
765
+ "menuitemcheckbox",
766
+ "menuitemradio",
767
+ "option",
768
+ "treeitem"
769
+ ]);
770
+ const SKIP_ROLES = /* @__PURE__ */ new Set([
771
+ "none",
772
+ "InlineTextBox",
773
+ "LineBreak",
774
+ "Ignored"
775
+ ]);
776
+ const CONTENT_ROLES_WITH_REF = /* @__PURE__ */ new Set([
777
+ "heading",
778
+ "img",
779
+ "cell",
780
+ "columnheader",
781
+ "rowheader"
782
+ ]);
783
+ function createRoleNameTracker() {
784
+ const counts = /* @__PURE__ */ new Map();
785
+ const refsByKey = /* @__PURE__ */ new Map();
786
+ return {
787
+ counts,
788
+ refsByKey,
789
+ getKey(role, name) {
790
+ return `${role}:${name ?? ""}`;
791
+ },
792
+ getNextIndex(role, name) {
793
+ const key = this.getKey(role, name);
794
+ const current = counts.get(key) ?? 0;
795
+ counts.set(key, current + 1);
796
+ return current;
797
+ },
798
+ trackRef(role, name, ref) {
799
+ const key = this.getKey(role, name);
800
+ const refs = refsByKey.get(key) ?? [];
801
+ refs.push(ref);
802
+ refsByKey.set(key, refs);
803
+ },
804
+ getDuplicateKeys() {
805
+ const duplicates = /* @__PURE__ */ new Set();
806
+ for (const [key, refs] of refsByKey) {
807
+ if (refs.length > 1) duplicates.add(key);
808
+ }
809
+ return duplicates;
810
+ }
811
+ };
812
+ }
813
+ function removeNthFromNonDuplicates(refs, tracker) {
814
+ const duplicateKeys = tracker.getDuplicateKeys();
815
+ for (const refInfo of Object.values(refs)) {
816
+ const key = tracker.getKey(refInfo.role, refInfo.name);
817
+ if (!duplicateKeys.has(key)) {
818
+ delete refInfo.nth;
819
+ }
820
+ }
821
+ }
822
+ function getProperty(node, propName) {
823
+ const prop = node.properties?.find((p) => p.name === propName);
824
+ return prop?.value?.value;
825
+ }
826
+ function truncate(text, max = 80) {
827
+ if (text.length <= max) return text;
828
+ return text.slice(0, max - 3) + "...";
829
+ }
830
+ function indent(depth) {
831
+ return " ".repeat(depth);
832
+ }
833
+ function getIndentLevel(line) {
834
+ const match = line.match(/^(\s*)/);
835
+ return match ? Math.floor(match[1].length / 2) : 0;
836
+ }
837
+ function formatAXTree(nodes, urlMap, options = {}) {
838
+ const nodeMap = /* @__PURE__ */ new Map();
839
+ for (const node of nodes) {
840
+ nodeMap.set(node.nodeId, node);
841
+ }
842
+ const rootNode = nodes[0];
843
+ if (!rootNode) {
844
+ return { snapshot: "(empty)", refs: {} };
845
+ }
846
+ const lines = [];
847
+ const refs = {};
848
+ const tracker = createRoleNameTracker();
849
+ let refCounter = 0;
850
+ function nextRef() {
851
+ return String(refCounter++);
852
+ }
853
+ function shouldAssignRef(role) {
854
+ if (options.interactive) {
855
+ return INTERACTIVE_ROLES.has(role);
856
+ }
857
+ return INTERACTIVE_ROLES.has(role) || CONTENT_ROLES_WITH_REF.has(role);
858
+ }
859
+ function traverse(nodeId, depth) {
860
+ const node = nodeMap.get(nodeId);
861
+ if (!node) return;
862
+ if (node.ignored) {
863
+ for (const childId of node.childIds || []) {
864
+ traverse(childId, depth);
865
+ }
866
+ return;
867
+ }
868
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) return;
869
+ const role = node.role?.value || "";
870
+ if (SKIP_ROLES.has(role)) {
871
+ for (const childId of node.childIds || []) {
872
+ traverse(childId, depth);
873
+ }
874
+ return;
875
+ }
876
+ const name = node.name?.value?.trim() || "";
877
+ const isInteractive = INTERACTIVE_ROLES.has(role);
878
+ if (options.interactive && !isInteractive) {
879
+ for (const childId of node.childIds || []) {
880
+ traverse(childId, depth);
881
+ }
882
+ return;
883
+ }
884
+ if (role === "StaticText") {
885
+ if (name) {
886
+ const displayText = truncate(name, 100);
887
+ lines.push(`${indent(depth)}- text: ${displayText}`);
888
+ }
889
+ return;
890
+ }
891
+ if ((role === "GenericContainer" || role === "generic") && !name) {
892
+ for (const childId of node.childIds || []) {
893
+ traverse(childId, depth);
894
+ }
895
+ return;
896
+ }
897
+ const displayRole = role.charAt(0).toLowerCase() + role.slice(1);
898
+ let line = `${indent(depth)}- ${displayRole}`;
899
+ if (name) {
900
+ line += ` "${truncate(name, 50)}"`;
901
+ }
902
+ const level = getProperty(node, "level");
903
+ if (level !== void 0) {
904
+ line += ` [level=${level}]`;
905
+ }
906
+ const hasBackendId = node.backendDOMNodeId !== void 0;
907
+ if (shouldAssignRef(role) && hasBackendId) {
908
+ const ref = nextRef();
909
+ const nth = tracker.getNextIndex(role, name || void 0);
910
+ tracker.trackRef(role, name || void 0, ref);
911
+ line += ` [ref=${ref}]`;
912
+ if (nth > 0) line += ` [nth=${nth}]`;
913
+ refs[ref] = {
914
+ backendDOMNodeId: node.backendDOMNodeId,
915
+ role: displayRole,
916
+ name: name || void 0,
917
+ nth
918
+ };
919
+ }
920
+ if (!options.interactive && role === "link" && node.backendDOMNodeId !== void 0) {
921
+ const url = urlMap.get(node.backendDOMNodeId);
922
+ if (url) {
923
+ lines.push(line);
924
+ lines.push(`${indent(depth + 1)}- /url: ${url}`);
925
+ for (const childId of node.childIds || []) {
926
+ traverse(childId, depth + 1);
927
+ }
928
+ return;
929
+ }
930
+ }
931
+ lines.push(line);
932
+ if (options.interactive) return;
933
+ for (const childId of node.childIds || []) {
934
+ traverse(childId, depth + 1);
935
+ }
936
+ }
937
+ traverse(rootNode.nodeId, 0);
938
+ removeNthFromNonDuplicates(refs, tracker);
939
+ const duplicateKeys = tracker.getDuplicateKeys();
940
+ const cleanedLines = lines.map((line) => {
941
+ const nthMatch = line.match(/\[nth=0\]/);
942
+ if (nthMatch) {
943
+ return line.replace(" [nth=0]", "");
944
+ }
945
+ const refMatch = line.match(/\[ref=(\d+)\].*\[nth=\d+\]/);
946
+ if (refMatch) {
947
+ const refId = refMatch[1];
948
+ const refInfo = refs[refId];
949
+ if (refInfo) {
950
+ const key = tracker.getKey(refInfo.role, refInfo.name);
951
+ if (!duplicateKeys.has(key)) {
952
+ return line.replace(/\s*\[nth=\d+\]/, "");
953
+ }
954
+ }
955
+ }
956
+ return line;
957
+ });
958
+ let snapshot = cleanedLines.join("\n");
959
+ if (options.compact) {
960
+ snapshot = compactTree(snapshot);
961
+ }
962
+ return { snapshot: snapshot || "(empty)", refs };
963
+ }
964
+ function compactTree(tree) {
965
+ const lines = tree.split("\n");
966
+ const result = [];
967
+ for (let i = 0; i < lines.length; i++) {
968
+ const line = lines[i];
969
+ if (line.includes("[ref=")) {
970
+ result.push(line);
971
+ continue;
972
+ }
973
+ if (line.includes("- text:") || line.includes("- /url:")) {
974
+ result.push(line);
975
+ continue;
976
+ }
977
+ if (line.includes('"')) {
978
+ result.push(line);
979
+ continue;
980
+ }
981
+ const currentIndent = getIndentLevel(line);
982
+ let hasRelevantChildren = false;
983
+ for (let j = i + 1; j < lines.length; j++) {
984
+ const childIndent = getIndentLevel(lines[j]);
985
+ if (childIndent <= currentIndent) break;
986
+ if (lines[j].includes("[ref=") || lines[j].includes('"') || lines[j].includes("- text:")) {
987
+ hasRelevantChildren = true;
988
+ break;
989
+ }
990
+ }
991
+ if (hasRelevantChildren) {
992
+ result.push(line);
993
+ }
994
+ }
995
+ return result.join("\n");
996
+ }
997
+
998
+ const tabSnapshotRefs$1 = /* @__PURE__ */ new Map();
999
+ const tabActiveFrameId$2 = /* @__PURE__ */ new Map();
1000
+ async function loadRefsFromStorage() {
1001
+ try {
1002
+ const result = await chrome.storage.session.get("tabSnapshotRefs");
1003
+ if (result.tabSnapshotRefs) {
1004
+ const stored = result.tabSnapshotRefs;
1005
+ for (const [tabIdStr, refs] of Object.entries(stored)) {
1006
+ tabSnapshotRefs$1.set(Number(tabIdStr), refs);
1007
+ }
1008
+ console.log("[CDPDOMService] Loaded refs from storage:", tabSnapshotRefs$1.size, "tabs");
1009
+ }
1010
+ } catch (e) {
1011
+ console.warn("[CDPDOMService] Failed to load refs from storage:", e);
1012
+ }
1013
+ }
1014
+ async function saveRefsToStorage(tabId, refs) {
1015
+ try {
1016
+ const result = await chrome.storage.session.get("tabSnapshotRefs");
1017
+ const stored = result.tabSnapshotRefs || {};
1018
+ stored[String(tabId)] = refs;
1019
+ await chrome.storage.session.set({ tabSnapshotRefs: stored });
1020
+ } catch (e) {
1021
+ console.warn("[CDPDOMService] Failed to save refs to storage:", e);
1022
+ }
1023
+ }
1024
+ loadRefsFromStorage();
1025
+ async function buildURLMap(tabId, linkBackendIds) {
1026
+ if (linkBackendIds.size === 0) return /* @__PURE__ */ new Map();
1027
+ const urlMap = /* @__PURE__ */ new Map();
1028
+ try {
1029
+ let walk = function(node) {
1030
+ if (linkBackendIds.has(node.backendNodeId)) {
1031
+ const attrs = node.attributes || [];
1032
+ for (let i = 0; i < attrs.length; i += 2) {
1033
+ if (attrs[i] === "href") {
1034
+ urlMap.set(node.backendNodeId, attrs[i + 1]);
1035
+ break;
1036
+ }
1037
+ }
1038
+ }
1039
+ for (const child of node.children || []) walk(child);
1040
+ if (node.contentDocument) walk(node.contentDocument);
1041
+ for (const shadow of node.shadowRoots || []) walk(shadow);
1042
+ };
1043
+ const doc = await getDocument(tabId, { depth: -1, pierce: true });
1044
+ walk(doc);
1045
+ } catch (e) {
1046
+ console.warn("[CDPDOMService] Failed to build URL map:", e);
1047
+ }
1048
+ return urlMap;
1049
+ }
1050
+ async function getSnapshot(tabId, options = {}) {
1051
+ console.log("[CDPDOMService] Getting snapshot via AX tree for tab:", tabId, options);
1052
+ let axNodes;
1053
+ if (options.selector) {
1054
+ try {
1055
+ const doc = await getDocument(tabId, { depth: 0 });
1056
+ const nodeId = await querySelector(tabId, doc.nodeId, options.selector);
1057
+ if (!nodeId) throw new Error(`Selector "${options.selector}" not found`);
1058
+ axNodes = await getPartialAccessibilityTree(tabId, nodeId);
1059
+ } catch (e) {
1060
+ throw new Error(`Selector "${options.selector}" failed: ${e instanceof Error ? e.message : String(e)}`);
1061
+ }
1062
+ } else {
1063
+ axNodes = await getFullAccessibilityTree(tabId);
1064
+ }
1065
+ const linkBackendIds = /* @__PURE__ */ new Set();
1066
+ for (const node of axNodes) {
1067
+ if (node.role?.value === "link" && node.backendDOMNodeId !== void 0) {
1068
+ linkBackendIds.add(node.backendDOMNodeId);
1069
+ }
1070
+ }
1071
+ const urlMap = await buildURLMap(tabId, linkBackendIds);
1072
+ const result = formatAXTree(axNodes, urlMap, {
1073
+ interactive: options.interactive,
1074
+ compact: options.compact,
1075
+ maxDepth: options.maxDepth
1076
+ });
1077
+ const convertedRefs = {};
1078
+ for (const [refId, axRef] of Object.entries(result.refs)) {
1079
+ convertedRefs[refId] = {
1080
+ backendDOMNodeId: axRef.backendDOMNodeId,
1081
+ role: axRef.role,
1082
+ name: axRef.name
1083
+ };
1084
+ }
1085
+ tabSnapshotRefs$1.set(tabId, convertedRefs);
1086
+ await saveRefsToStorage(tabId, convertedRefs);
1087
+ console.log("[CDPDOMService] Snapshot complete:", {
1088
+ linesCount: result.snapshot.split("\n").length,
1089
+ refsCount: Object.keys(convertedRefs).length
1090
+ });
1091
+ return { snapshot: result.snapshot, refs: convertedRefs };
1092
+ }
1093
+ async function getElementCenter(tabId, backendNodeId) {
1094
+ const objectId = await resolveNodeByBackendId(tabId, backendNodeId);
1095
+ if (!objectId) throw new Error("Failed to resolve node");
1096
+ const result = await callFunctionOn(tabId, objectId, `function() {
1097
+ this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
1098
+ const rect = this.getBoundingClientRect();
1099
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
1100
+ }`);
1101
+ if (!result || typeof result !== "object") throw new Error("Failed to get element center");
1102
+ return result;
1103
+ }
1104
+ async function evaluateOnElement(tabId, backendNodeId, fn, args = []) {
1105
+ const objectId = await resolveNodeByBackendId(tabId, backendNodeId);
1106
+ if (!objectId) throw new Error("Failed to resolve node");
1107
+ return callFunctionOn(tabId, objectId, fn, args);
1108
+ }
1109
+ function getBackendNodeId(refInfo) {
1110
+ return refInfo.backendDOMNodeId ?? null;
1111
+ }
1112
+ async function getElementCenterByXPath(tabId, xpath) {
1113
+ const result = await evaluate(tabId, `
1114
+ (function() {
1115
+ const result = document.evaluate(
1116
+ ${JSON.stringify(xpath)},
1117
+ document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
1118
+ );
1119
+ const element = result.singleNodeValue;
1120
+ if (!element) return null;
1121
+ element.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
1122
+ const rect = element.getBoundingClientRect();
1123
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
1124
+ })()
1125
+ `, { returnByValue: true });
1126
+ if (!result) throw new Error(`Element not found by xpath: ${xpath}`);
1127
+ return result;
1128
+ }
1129
+ async function getRefInfo(tabId, ref) {
1130
+ const refId = ref.startsWith("@") ? ref.slice(1) : ref;
1131
+ const refs = tabSnapshotRefs$1.get(tabId);
1132
+ if (refs?.[refId]) return refs[refId];
1133
+ if (!tabSnapshotRefs$1.has(tabId)) {
1134
+ await loadRefsFromStorage();
1135
+ const loaded = tabSnapshotRefs$1.get(tabId);
1136
+ if (loaded?.[refId]) return loaded[refId];
1137
+ }
1138
+ return null;
1139
+ }
1140
+ function cleanupTab$1(tabId) {
1141
+ tabSnapshotRefs$1.delete(tabId);
1142
+ tabActiveFrameId$2.delete(tabId);
1143
+ }
1144
+ async function clickElement(tabId, ref) {
1145
+ const refInfo = await getRefInfo(tabId, ref);
1146
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1147
+ const { role, name } = refInfo;
1148
+ const backendNodeId = getBackendNodeId(refInfo);
1149
+ let x, y;
1150
+ if (backendNodeId !== null) {
1151
+ ({ x, y } = await getElementCenter(tabId, backendNodeId));
1152
+ } else if (refInfo.xpath) {
1153
+ ({ x, y } = await getElementCenterByXPath(tabId, refInfo.xpath));
1154
+ } else {
1155
+ throw new Error(`No locator for ref "${ref}"`);
1156
+ }
1157
+ await click(tabId, x, y);
1158
+ console.log("[CDPDOMService] Clicked element:", { ref, role, name, x, y });
1159
+ return { role, name };
1160
+ }
1161
+ async function hoverElement(tabId, ref) {
1162
+ const refInfo = await getRefInfo(tabId, ref);
1163
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1164
+ const { role, name } = refInfo;
1165
+ const backendNodeId = getBackendNodeId(refInfo);
1166
+ let x, y;
1167
+ if (backendNodeId !== null) {
1168
+ ({ x, y } = await getElementCenter(tabId, backendNodeId));
1169
+ } else if (refInfo.xpath) {
1170
+ ({ x, y } = await getElementCenterByXPath(tabId, refInfo.xpath));
1171
+ } else {
1172
+ throw new Error(`No locator for ref "${ref}"`);
1173
+ }
1174
+ await moveMouse(tabId, x, y);
1175
+ console.log("[CDPDOMService] Hovered element:", { ref, role, name, x, y });
1176
+ return { role, name };
1177
+ }
1178
+ async function fillElement(tabId, ref, text) {
1179
+ const refInfo = await getRefInfo(tabId, ref);
1180
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1181
+ const { role, name } = refInfo;
1182
+ const backendNodeId = getBackendNodeId(refInfo);
1183
+ if (backendNodeId !== null) {
1184
+ await evaluateOnElement(tabId, backendNodeId, `function() {
1185
+ this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
1186
+ this.focus();
1187
+ if (this.tagName === 'INPUT' || this.tagName === 'TEXTAREA') {
1188
+ this.value = '';
1189
+ } else if (this.isContentEditable) {
1190
+ this.textContent = '';
1191
+ }
1192
+ }`);
1193
+ } else if (refInfo.xpath) {
1194
+ await evaluate(tabId, `
1195
+ (function() {
1196
+ const result = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1197
+ const element = result.singleNodeValue;
1198
+ if (!element) throw new Error('Element not found');
1199
+ element.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
1200
+ element.focus();
1201
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { element.value = ''; }
1202
+ else if (element.isContentEditable) { element.textContent = ''; }
1203
+ })()
1204
+ `);
1205
+ } else {
1206
+ throw new Error(`No locator for ref "${ref}"`);
1207
+ }
1208
+ await insertText(tabId, text);
1209
+ console.log("[CDPDOMService] Filled element:", { ref, role, name, textLength: text.length });
1210
+ return { role, name };
1211
+ }
1212
+ async function typeElement(tabId, ref, text) {
1213
+ const refInfo = await getRefInfo(tabId, ref);
1214
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1215
+ const { role, name } = refInfo;
1216
+ const backendNodeId = getBackendNodeId(refInfo);
1217
+ if (backendNodeId !== null) {
1218
+ await evaluateOnElement(tabId, backendNodeId, `function() {
1219
+ this.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
1220
+ this.focus();
1221
+ }`);
1222
+ } else if (refInfo.xpath) {
1223
+ await evaluate(tabId, `
1224
+ (function() {
1225
+ const result = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1226
+ const element = result.singleNodeValue;
1227
+ if (!element) throw new Error('Element not found');
1228
+ element.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
1229
+ element.focus();
1230
+ })()
1231
+ `);
1232
+ } else {
1233
+ throw new Error(`No locator for ref "${ref}"`);
1234
+ }
1235
+ for (const char of text) {
1236
+ await pressKey$1(tabId, char);
1237
+ }
1238
+ console.log("[CDPDOMService] Typed in element:", { ref, role, name, textLength: text.length });
1239
+ return { role, name };
1240
+ }
1241
+ async function getElementText(tabId, ref) {
1242
+ const refInfo = await getRefInfo(tabId, ref);
1243
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1244
+ const backendNodeId = getBackendNodeId(refInfo);
1245
+ let text;
1246
+ if (backendNodeId !== null) {
1247
+ text = await evaluateOnElement(tabId, backendNodeId, `function() {
1248
+ return (this.textContent || '').trim();
1249
+ }`);
1250
+ } else if (refInfo.xpath) {
1251
+ text = await evaluate(tabId, `
1252
+ (function() {
1253
+ const result = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1254
+ const element = result.singleNodeValue;
1255
+ if (!element) return '';
1256
+ return (element.textContent || '').trim();
1257
+ })()
1258
+ `);
1259
+ } else {
1260
+ throw new Error(`No locator for ref "${ref}"`);
1261
+ }
1262
+ return text || "";
1263
+ }
1264
+ async function checkElement(tabId, ref) {
1265
+ const refInfo = await getRefInfo(tabId, ref);
1266
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1267
+ const { role, name } = refInfo;
1268
+ const backendNodeId = getBackendNodeId(refInfo);
1269
+ let wasChecked;
1270
+ if (backendNodeId !== null) {
1271
+ wasChecked = await evaluateOnElement(tabId, backendNodeId, `function() {
1272
+ if (this.type !== 'checkbox' && this.type !== 'radio') throw new Error('Element is not a checkbox or radio');
1273
+ const was = this.checked;
1274
+ if (!was) { this.checked = true; this.dispatchEvent(new Event('change', { bubbles: true })); }
1275
+ return was;
1276
+ }`);
1277
+ } else if (refInfo.xpath) {
1278
+ wasChecked = await evaluate(tabId, `
1279
+ (function() {
1280
+ const result = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1281
+ const element = result.singleNodeValue;
1282
+ if (!element) throw new Error('Element not found');
1283
+ if (element.type !== 'checkbox' && element.type !== 'radio') throw new Error('Element is not a checkbox or radio');
1284
+ const was = element.checked;
1285
+ if (!was) { element.checked = true; element.dispatchEvent(new Event('change', { bubbles: true })); }
1286
+ return was;
1287
+ })()
1288
+ `);
1289
+ } else {
1290
+ throw new Error(`No locator for ref "${ref}"`);
1291
+ }
1292
+ return { role, name, wasAlreadyChecked: wasChecked };
1293
+ }
1294
+ async function uncheckElement(tabId, ref) {
1295
+ const refInfo = await getRefInfo(tabId, ref);
1296
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1297
+ const { role, name } = refInfo;
1298
+ const backendNodeId = getBackendNodeId(refInfo);
1299
+ let wasUnchecked;
1300
+ if (backendNodeId !== null) {
1301
+ wasUnchecked = await evaluateOnElement(tabId, backendNodeId, `function() {
1302
+ if (this.type !== 'checkbox' && this.type !== 'radio') throw new Error('Element is not a checkbox or radio');
1303
+ const was = !this.checked;
1304
+ if (!was) { this.checked = false; this.dispatchEvent(new Event('change', { bubbles: true })); }
1305
+ return was;
1306
+ }`);
1307
+ } else if (refInfo.xpath) {
1308
+ wasUnchecked = await evaluate(tabId, `
1309
+ (function() {
1310
+ const result = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1311
+ const element = result.singleNodeValue;
1312
+ if (!element) throw new Error('Element not found');
1313
+ if (element.type !== 'checkbox' && element.type !== 'radio') throw new Error('Element is not a checkbox or radio');
1314
+ const was = !element.checked;
1315
+ if (!was) { element.checked = false; element.dispatchEvent(new Event('change', { bubbles: true })); }
1316
+ return was;
1317
+ })()
1318
+ `);
1319
+ } else {
1320
+ throw new Error(`No locator for ref "${ref}"`);
1321
+ }
1322
+ return { role, name, wasAlreadyUnchecked: wasUnchecked };
1323
+ }
1324
+ async function selectOption(tabId, ref, value) {
1325
+ const refInfo = await getRefInfo(tabId, ref);
1326
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1327
+ const { role, name } = refInfo;
1328
+ const backendNodeId = getBackendNodeId(refInfo);
1329
+ const selectFn = `function(selectValue) {
1330
+ if (this.tagName !== 'SELECT') throw new Error('Element is not a <select> element');
1331
+ let matched = null;
1332
+ for (const opt of this.options) {
1333
+ if (opt.value === selectValue || opt.textContent.trim() === selectValue) { matched = opt; break; }
1334
+ }
1335
+ if (!matched) {
1336
+ const lower = selectValue.toLowerCase();
1337
+ for (const opt of this.options) {
1338
+ if (opt.value.toLowerCase() === lower || opt.textContent.trim().toLowerCase() === lower) { matched = opt; break; }
1339
+ }
1340
+ }
1341
+ if (!matched) {
1342
+ const available = Array.from(this.options).map(o => ({ value: o.value, label: o.textContent.trim() }));
1343
+ throw new Error('Option not found: ' + selectValue + '. Available: ' + JSON.stringify(available));
1344
+ }
1345
+ this.value = matched.value;
1346
+ this.dispatchEvent(new Event('change', { bubbles: true }));
1347
+ return { selectedValue: matched.value, selectedLabel: matched.textContent.trim() };
1348
+ }`;
1349
+ let result;
1350
+ if (backendNodeId !== null) {
1351
+ result = await evaluateOnElement(tabId, backendNodeId, selectFn, [value]);
1352
+ } else if (refInfo.xpath) {
1353
+ result = await evaluate(tabId, `
1354
+ (function() {
1355
+ const selectValue = ${JSON.stringify(value)};
1356
+ const xpathResult = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1357
+ const element = xpathResult.singleNodeValue;
1358
+ if (!element) throw new Error('Element not found');
1359
+ return (${selectFn}).call(element, selectValue);
1360
+ })()
1361
+ `);
1362
+ } else {
1363
+ throw new Error(`No locator for ref "${ref}"`);
1364
+ }
1365
+ const { selectedValue, selectedLabel } = result;
1366
+ return { role, name, selectedValue, selectedLabel };
1367
+ }
1368
+ async function waitForElement(tabId, ref, maxWait = 1e4, interval = 200) {
1369
+ const refInfo = await getRefInfo(tabId, ref);
1370
+ if (!refInfo) throw new Error(`Ref "${ref}" not found. Run snapshot first to get available refs.`);
1371
+ const backendNodeId = getBackendNodeId(refInfo);
1372
+ let elapsed = 0;
1373
+ while (elapsed < maxWait) {
1374
+ try {
1375
+ if (backendNodeId !== null) {
1376
+ const objectId = await resolveNodeByBackendId(tabId, backendNodeId);
1377
+ if (objectId) return;
1378
+ } else if (refInfo.xpath) {
1379
+ const found = await evaluate(tabId, `
1380
+ (function() {
1381
+ const result = document.evaluate(${JSON.stringify(refInfo.xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
1382
+ return result.singleNodeValue !== null;
1383
+ })()
1384
+ `);
1385
+ if (found) return;
1386
+ }
1387
+ } catch {
1388
+ }
1389
+ await new Promise((resolve) => setTimeout(resolve, interval));
1390
+ elapsed += interval;
1391
+ }
1392
+ throw new Error(`Timeout waiting for element @${ref} after ${maxWait}ms`);
1393
+ }
1394
+ function setActiveFrameId(tabId, frameId) {
1395
+ tabActiveFrameId$2.set(tabId, frameId);
1396
+ }
1397
+ async function pressKey(tabId, key, modifiers = []) {
1398
+ let modifierFlags = 0;
1399
+ if (modifiers.includes("Alt")) modifierFlags |= 1;
1400
+ if (modifiers.includes("Control")) modifierFlags |= 2;
1401
+ if (modifiers.includes("Meta")) modifierFlags |= 4;
1402
+ if (modifiers.includes("Shift")) modifierFlags |= 8;
1403
+ await pressKey$1(tabId, key, { modifiers: modifierFlags });
1404
+ }
1405
+ async function scrollPage(tabId, direction, pixels) {
1406
+ const result = await evaluate(
1407
+ tabId,
1408
+ "JSON.stringify({ width: window.innerWidth, height: window.innerHeight })"
1409
+ );
1410
+ const { width, height } = JSON.parse(result);
1411
+ const x = width / 2;
1412
+ const y = height / 2;
1413
+ let deltaX = 0;
1414
+ let deltaY = 0;
1415
+ switch (direction) {
1416
+ case "up":
1417
+ deltaY = -pixels;
1418
+ break;
1419
+ case "down":
1420
+ deltaY = pixels;
1421
+ break;
1422
+ case "left":
1423
+ deltaX = -pixels;
1424
+ break;
1425
+ case "right":
1426
+ deltaX = pixels;
1427
+ break;
1428
+ }
1429
+ await scroll(tabId, x, y, deltaX, deltaY);
1430
+ }
1431
+
1432
+ const tabSnapshotRefs = /* @__PURE__ */ new Map();
1433
+ const tabActiveFrameId$1 = /* @__PURE__ */ new Map();
1434
+ function cleanupTab(tabId) {
1435
+ tabSnapshotRefs.delete(tabId);
1436
+ tabActiveFrameId$1.delete(tabId);
1437
+ }
1438
+
1439
+ let isRecording = false;
1440
+ let recordingTabId = null;
1441
+ let events = [];
1442
+ async function startRecording(tabId) {
1443
+ console.log("[TraceService] Starting recording on tab:", tabId);
1444
+ isRecording = true;
1445
+ recordingTabId = tabId;
1446
+ events = [];
1447
+ try {
1448
+ const tab = await chrome.tabs.get(tabId);
1449
+ if (tab.url) {
1450
+ events.push({
1451
+ type: "navigation",
1452
+ timestamp: Date.now(),
1453
+ url: tab.url,
1454
+ elementRole: "document",
1455
+ elementName: tab.title || "",
1456
+ elementTag: "document"
1457
+ });
1458
+ }
1459
+ } catch (error) {
1460
+ console.error("[TraceService] Error getting tab info:", error);
1461
+ }
1462
+ try {
1463
+ await chrome.tabs.sendMessage(tabId, { type: "TRACE_START" });
1464
+ } catch (error) {
1465
+ console.log("[TraceService] Content script not ready, will record on next event");
1466
+ }
1467
+ }
1468
+ async function stopRecording() {
1469
+ console.log("[TraceService] Stopping recording, events:", events.length);
1470
+ const recordedEvents = [...events];
1471
+ if (recordingTabId !== null) {
1472
+ try {
1473
+ await chrome.tabs.sendMessage(recordingTabId, { type: "TRACE_STOP" });
1474
+ } catch (error) {
1475
+ console.log("[TraceService] Could not notify content script:", error);
1476
+ }
1477
+ }
1478
+ isRecording = false;
1479
+ recordingTabId = null;
1480
+ events = [];
1481
+ return recordedEvents;
1482
+ }
1483
+ function getStatus() {
1484
+ return {
1485
+ recording: isRecording,
1486
+ eventCount: events.length,
1487
+ tabId: recordingTabId ?? void 0
1488
+ };
1489
+ }
1490
+ function addEvent(event) {
1491
+ if (!isRecording) return;
1492
+ console.log("[TraceService] Adding event:", event.type, event);
1493
+ events.push(event);
1494
+ }
1495
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1496
+ if (message.type === "TRACE_EVENT") {
1497
+ if (isRecording && sender.tab?.id === recordingTabId) {
1498
+ addEvent(message.payload);
1499
+ }
1500
+ sendResponse({ received: true });
1501
+ return true;
1502
+ }
1503
+ if (message.type === "GET_TRACE_STATUS") {
1504
+ sendResponse({
1505
+ recording: isRecording && sender.tab?.id === recordingTabId,
1506
+ tabId: recordingTabId
1507
+ });
1508
+ return true;
1509
+ }
1510
+ return false;
1511
+ });
1512
+ chrome.tabs.onRemoved.addListener((tabId) => {
1513
+ if (tabId === recordingTabId) {
1514
+ console.log("[TraceService] Recording tab closed, stopping recording");
1515
+ isRecording = false;
1516
+ recordingTabId = null;
1517
+ }
1518
+ });
1519
+ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, _tab) => {
1520
+ if (tabId === recordingTabId && isRecording) {
1521
+ if (changeInfo.url) {
1522
+ events.push({
1523
+ type: "navigation",
1524
+ timestamp: Date.now(),
1525
+ url: changeInfo.url,
1526
+ elementRole: "document",
1527
+ elementName: _tab.title || "",
1528
+ elementTag: "document"
1529
+ });
1530
+ console.log("[TraceService] Navigation event:", changeInfo.url);
1531
+ }
1532
+ if (changeInfo.status === "complete") {
1533
+ console.log("[TraceService] Page loaded, notifying content script to start recording");
1534
+ try {
1535
+ await chrome.tabs.sendMessage(tabId, { type: "TRACE_START" });
1536
+ } catch (error) {
1537
+ console.log("[TraceService] Could not notify content script:", error);
1538
+ }
1539
+ }
1540
+ }
1541
+ });
1542
+ console.log("[TraceService] Initialized");
1543
+
1544
+ initEventListeners();
1545
+ const tabActiveFrameId = /* @__PURE__ */ new Map();
1546
+ async function resolveTab(command) {
1547
+ if (command.tabId !== void 0 && typeof command.tabId === "number") {
1548
+ return chrome.tabs.get(command.tabId);
1549
+ }
1550
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1551
+ if (!tab?.id) {
1552
+ throw new Error("No active tab found");
1553
+ }
1554
+ return tab;
1555
+ }
1556
+ async function handleCommand(command) {
1557
+ console.log("[CommandHandler] Processing command:", command.id, command.action);
1558
+ let result;
1559
+ try {
1560
+ switch (command.action) {
1561
+ case "open":
1562
+ result = await handleOpen(command);
1563
+ break;
1564
+ case "snapshot":
1565
+ result = await handleSnapshot(command);
1566
+ break;
1567
+ case "click":
1568
+ result = await handleClick(command);
1569
+ break;
1570
+ case "hover":
1571
+ result = await handleHover(command);
1572
+ break;
1573
+ case "fill":
1574
+ result = await handleFill(command);
1575
+ break;
1576
+ case "type":
1577
+ result = await handleType(command);
1578
+ break;
1579
+ case "check":
1580
+ result = await handleCheck(command);
1581
+ break;
1582
+ case "uncheck":
1583
+ result = await handleUncheck(command);
1584
+ break;
1585
+ case "close":
1586
+ result = await handleClose(command);
1587
+ break;
1588
+ case "get":
1589
+ result = await handleGet(command);
1590
+ break;
1591
+ case "screenshot":
1592
+ result = await handleScreenshot(command);
1593
+ break;
1594
+ case "wait":
1595
+ result = await handleWait(command);
1596
+ break;
1597
+ case "press":
1598
+ result = await handlePress(command);
1599
+ break;
1600
+ case "scroll":
1601
+ result = await handleScroll(command);
1602
+ break;
1603
+ case "back":
1604
+ result = await handleBack(command);
1605
+ break;
1606
+ case "forward":
1607
+ result = await handleForward(command);
1608
+ break;
1609
+ case "refresh":
1610
+ result = await handleRefresh(command);
1611
+ break;
1612
+ case "eval":
1613
+ result = await handleEval(command);
1614
+ break;
1615
+ case "select":
1616
+ result = await handleSelect(command);
1617
+ break;
1618
+ case "tab_list":
1619
+ result = await handleTabList(command);
1620
+ break;
1621
+ case "tab_new":
1622
+ result = await handleTabNew(command);
1623
+ break;
1624
+ case "tab_select":
1625
+ result = await handleTabSelect(command);
1626
+ break;
1627
+ case "tab_close":
1628
+ result = await handleTabClose(command);
1629
+ break;
1630
+ case "frame":
1631
+ result = await handleFrame(command);
1632
+ break;
1633
+ case "frame_main":
1634
+ result = await handleFrameMain(command);
1635
+ break;
1636
+ case "dialog":
1637
+ result = await handleDialog(command);
1638
+ break;
1639
+ case "network":
1640
+ result = await handleNetwork(command);
1641
+ break;
1642
+ case "console":
1643
+ result = await handleConsole(command);
1644
+ break;
1645
+ case "errors":
1646
+ result = await handleErrors(command);
1647
+ break;
1648
+ case "trace":
1649
+ result = await handleTrace(command);
1650
+ break;
1651
+ case "history":
1652
+ result = await handleHistory(command);
1653
+ break;
1654
+ default:
1655
+ result = {
1656
+ id: command.id,
1657
+ success: false,
1658
+ error: `Unknown action: ${command.action}`
1659
+ };
1660
+ }
1661
+ } catch (error) {
1662
+ result = {
1663
+ id: command.id,
1664
+ success: false,
1665
+ error: error instanceof Error ? error.message : String(error)
1666
+ };
1667
+ }
1668
+ await sendResult(result);
1669
+ }
1670
+ async function handleOpen(command) {
1671
+ const url = command.url;
1672
+ const tabIdParam = command.tabId;
1673
+ if (!url) {
1674
+ return {
1675
+ id: command.id,
1676
+ success: false,
1677
+ error: "Missing url parameter"
1678
+ };
1679
+ }
1680
+ console.log("[CommandHandler] Opening URL:", url, "tabId:", tabIdParam);
1681
+ let tab;
1682
+ if (tabIdParam === void 0) {
1683
+ tab = await chrome.tabs.create({ url, active: true });
1684
+ } else if (tabIdParam === "current") {
1685
+ const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
1686
+ if (activeTab && activeTab.id) {
1687
+ tab = await chrome.tabs.update(activeTab.id, { url });
1688
+ } else {
1689
+ tab = await chrome.tabs.create({ url, active: true });
1690
+ }
1691
+ } else {
1692
+ const targetTabId = typeof tabIdParam === "number" ? tabIdParam : parseInt(String(tabIdParam), 10);
1693
+ if (isNaN(targetTabId)) {
1694
+ return {
1695
+ id: command.id,
1696
+ success: false,
1697
+ error: `Invalid tabId: ${tabIdParam}`
1698
+ };
1699
+ }
1700
+ try {
1701
+ tab = await chrome.tabs.update(targetTabId, { url, active: true });
1702
+ } catch (error) {
1703
+ return {
1704
+ id: command.id,
1705
+ success: false,
1706
+ error: `Tab ${targetTabId} not found or cannot be updated`
1707
+ };
1708
+ }
1709
+ }
1710
+ await waitForTabLoad(tab.id);
1711
+ const updatedTab = await chrome.tabs.get(tab.id);
1712
+ return {
1713
+ id: command.id,
1714
+ success: true,
1715
+ data: {
1716
+ tabId: tab.id,
1717
+ title: updatedTab.title || "",
1718
+ url: updatedTab.url || url
1719
+ }
1720
+ };
1721
+ }
1722
+ async function handleSnapshot(command) {
1723
+ const activeTab = await resolveTab(command);
1724
+ if (!activeTab.id) {
1725
+ return {
1726
+ id: command.id,
1727
+ success: false,
1728
+ error: "No active tab found"
1729
+ };
1730
+ }
1731
+ const url = activeTab.url || "";
1732
+ if (url.startsWith("chrome://") || url.startsWith("about:") || url.startsWith("chrome-extension://")) {
1733
+ return {
1734
+ id: command.id,
1735
+ success: false,
1736
+ error: `Cannot take snapshot of restricted page: ${url}`
1737
+ };
1738
+ }
1739
+ const interactive = command.interactive;
1740
+ const compact = command.compact;
1741
+ const maxDepth = command.maxDepth;
1742
+ const selector = command.selector;
1743
+ console.log("[CommandHandler] Taking snapshot of tab:", activeTab.id, activeTab.url, { interactive, compact, maxDepth, selector });
1744
+ try {
1745
+ const snapshotResult = await getSnapshot(activeTab.id, { interactive, compact, maxDepth, selector });
1746
+ return {
1747
+ id: command.id,
1748
+ success: true,
1749
+ data: {
1750
+ title: activeTab.title || "",
1751
+ url: activeTab.url || "",
1752
+ snapshotData: snapshotResult
1753
+ }
1754
+ };
1755
+ } catch (error) {
1756
+ console.error("[CommandHandler] Snapshot failed:", error);
1757
+ return {
1758
+ id: command.id,
1759
+ success: false,
1760
+ error: `Snapshot failed: ${error instanceof Error ? error.message : String(error)}`
1761
+ };
1762
+ }
1763
+ }
1764
+ async function handleClick(command) {
1765
+ const ref = command.ref;
1766
+ if (!ref) {
1767
+ return {
1768
+ id: command.id,
1769
+ success: false,
1770
+ error: "Missing ref parameter"
1771
+ };
1772
+ }
1773
+ const activeTab = await resolveTab(command);
1774
+ if (!activeTab.id) {
1775
+ return {
1776
+ id: command.id,
1777
+ success: false,
1778
+ error: "No active tab found"
1779
+ };
1780
+ }
1781
+ console.log("[CommandHandler] Clicking element:", ref);
1782
+ try {
1783
+ const elementInfo = await clickElement(activeTab.id, ref);
1784
+ return {
1785
+ id: command.id,
1786
+ success: true,
1787
+ data: {
1788
+ role: elementInfo.role,
1789
+ name: elementInfo.name
1790
+ }
1791
+ };
1792
+ } catch (error) {
1793
+ console.error("[CommandHandler] Click failed:", error);
1794
+ return {
1795
+ id: command.id,
1796
+ success: false,
1797
+ error: `Click failed: ${error instanceof Error ? error.message : String(error)}`
1798
+ };
1799
+ }
1800
+ }
1801
+ async function handleHover(command) {
1802
+ const ref = command.ref;
1803
+ if (!ref) {
1804
+ return {
1805
+ id: command.id,
1806
+ success: false,
1807
+ error: "Missing ref parameter"
1808
+ };
1809
+ }
1810
+ const activeTab = await resolveTab(command);
1811
+ if (!activeTab.id) {
1812
+ return {
1813
+ id: command.id,
1814
+ success: false,
1815
+ error: "No active tab found"
1816
+ };
1817
+ }
1818
+ console.log("[CommandHandler] Hovering element:", ref);
1819
+ try {
1820
+ const elementInfo = await hoverElement(activeTab.id, ref);
1821
+ return {
1822
+ id: command.id,
1823
+ success: true,
1824
+ data: {
1825
+ role: elementInfo.role,
1826
+ name: elementInfo.name
1827
+ }
1828
+ };
1829
+ } catch (error) {
1830
+ console.error("[CommandHandler] Hover failed:", error);
1831
+ return {
1832
+ id: command.id,
1833
+ success: false,
1834
+ error: `Hover failed: ${error instanceof Error ? error.message : String(error)}`
1835
+ };
1836
+ }
1837
+ }
1838
+ async function handleFill(command) {
1839
+ const ref = command.ref;
1840
+ const text = command.text;
1841
+ if (!ref) {
1842
+ return {
1843
+ id: command.id,
1844
+ success: false,
1845
+ error: "Missing ref parameter"
1846
+ };
1847
+ }
1848
+ if (text === void 0 || text === null) {
1849
+ return {
1850
+ id: command.id,
1851
+ success: false,
1852
+ error: "Missing text parameter"
1853
+ };
1854
+ }
1855
+ const activeTab = await resolveTab(command);
1856
+ if (!activeTab.id) {
1857
+ return {
1858
+ id: command.id,
1859
+ success: false,
1860
+ error: "No active tab found"
1861
+ };
1862
+ }
1863
+ console.log("[CommandHandler] Filling element:", ref, "with text length:", text.length);
1864
+ try {
1865
+ const elementInfo = await fillElement(activeTab.id, ref, text);
1866
+ return {
1867
+ id: command.id,
1868
+ success: true,
1869
+ data: {
1870
+ role: elementInfo.role,
1871
+ name: elementInfo.name,
1872
+ filledText: text
1873
+ }
1874
+ };
1875
+ } catch (error) {
1876
+ console.error("[CommandHandler] Fill failed:", error);
1877
+ return {
1878
+ id: command.id,
1879
+ success: false,
1880
+ error: `Fill failed: ${error instanceof Error ? error.message : String(error)}`
1881
+ };
1882
+ }
1883
+ }
1884
+ async function handleType(command) {
1885
+ const ref = command.ref;
1886
+ const text = command.text;
1887
+ if (!ref) {
1888
+ return {
1889
+ id: command.id,
1890
+ success: false,
1891
+ error: "Missing ref parameter"
1892
+ };
1893
+ }
1894
+ if (text === void 0 || text === null) {
1895
+ return {
1896
+ id: command.id,
1897
+ success: false,
1898
+ error: "Missing text parameter"
1899
+ };
1900
+ }
1901
+ const activeTab = await resolveTab(command);
1902
+ if (!activeTab.id) {
1903
+ return {
1904
+ id: command.id,
1905
+ success: false,
1906
+ error: "No active tab found"
1907
+ };
1908
+ }
1909
+ console.log("[CommandHandler] Typing in element:", ref, "text length:", text.length);
1910
+ try {
1911
+ const elementInfo = await typeElement(activeTab.id, ref, text);
1912
+ return {
1913
+ id: command.id,
1914
+ success: true,
1915
+ data: {
1916
+ role: elementInfo.role,
1917
+ name: elementInfo.name,
1918
+ typedText: text
1919
+ }
1920
+ };
1921
+ } catch (error) {
1922
+ console.error("[CommandHandler] Type failed:", error);
1923
+ return {
1924
+ id: command.id,
1925
+ success: false,
1926
+ error: `Type failed: ${error instanceof Error ? error.message : String(error)}`
1927
+ };
1928
+ }
1929
+ }
1930
+ async function handleCheck(command) {
1931
+ const ref = command.ref;
1932
+ if (!ref) {
1933
+ return {
1934
+ id: command.id,
1935
+ success: false,
1936
+ error: "Missing ref parameter"
1937
+ };
1938
+ }
1939
+ const activeTab = await resolveTab(command);
1940
+ if (!activeTab.id) {
1941
+ return {
1942
+ id: command.id,
1943
+ success: false,
1944
+ error: "No active tab found"
1945
+ };
1946
+ }
1947
+ console.log("[CommandHandler] Checking element:", ref);
1948
+ try {
1949
+ const elementInfo = await checkElement(activeTab.id, ref);
1950
+ return {
1951
+ id: command.id,
1952
+ success: true,
1953
+ data: {
1954
+ role: elementInfo.role,
1955
+ name: elementInfo.name,
1956
+ wasAlreadyChecked: elementInfo.wasAlreadyChecked
1957
+ }
1958
+ };
1959
+ } catch (error) {
1960
+ console.error("[CommandHandler] Check failed:", error);
1961
+ return {
1962
+ id: command.id,
1963
+ success: false,
1964
+ error: `Check failed: ${error instanceof Error ? error.message : String(error)}`
1965
+ };
1966
+ }
1967
+ }
1968
+ async function handleUncheck(command) {
1969
+ const ref = command.ref;
1970
+ if (!ref) {
1971
+ return {
1972
+ id: command.id,
1973
+ success: false,
1974
+ error: "Missing ref parameter"
1975
+ };
1976
+ }
1977
+ const activeTab = await resolveTab(command);
1978
+ if (!activeTab.id) {
1979
+ return {
1980
+ id: command.id,
1981
+ success: false,
1982
+ error: "No active tab found"
1983
+ };
1984
+ }
1985
+ console.log("[CommandHandler] Unchecking element:", ref);
1986
+ try {
1987
+ const elementInfo = await uncheckElement(activeTab.id, ref);
1988
+ return {
1989
+ id: command.id,
1990
+ success: true,
1991
+ data: {
1992
+ role: elementInfo.role,
1993
+ name: elementInfo.name,
1994
+ wasAlreadyUnchecked: elementInfo.wasAlreadyUnchecked
1995
+ }
1996
+ };
1997
+ } catch (error) {
1998
+ console.error("[CommandHandler] Uncheck failed:", error);
1999
+ return {
2000
+ id: command.id,
2001
+ success: false,
2002
+ error: `Uncheck failed: ${error instanceof Error ? error.message : String(error)}`
2003
+ };
2004
+ }
2005
+ }
2006
+ async function handleSelect(command) {
2007
+ const ref = command.ref;
2008
+ const value = command.value;
2009
+ if (!ref) {
2010
+ return {
2011
+ id: command.id,
2012
+ success: false,
2013
+ error: "Missing ref parameter"
2014
+ };
2015
+ }
2016
+ if (value === void 0 || value === null) {
2017
+ return {
2018
+ id: command.id,
2019
+ success: false,
2020
+ error: "Missing value parameter"
2021
+ };
2022
+ }
2023
+ const activeTab = await resolveTab(command);
2024
+ if (!activeTab.id) {
2025
+ return {
2026
+ id: command.id,
2027
+ success: false,
2028
+ error: "No active tab found"
2029
+ };
2030
+ }
2031
+ console.log("[CommandHandler] Selecting option:", ref, "value:", value);
2032
+ try {
2033
+ const result = await selectOption(activeTab.id, ref, value);
2034
+ return {
2035
+ id: command.id,
2036
+ success: true,
2037
+ data: {
2038
+ role: result.role,
2039
+ name: result.name,
2040
+ selectedValue: result.selectedValue,
2041
+ selectedLabel: result.selectedLabel
2042
+ }
2043
+ };
2044
+ } catch (error) {
2045
+ console.error("[CommandHandler] Select failed:", error);
2046
+ return {
2047
+ id: command.id,
2048
+ success: false,
2049
+ error: `Select failed: ${error instanceof Error ? error.message : String(error)}`
2050
+ };
2051
+ }
2052
+ }
2053
+ async function handleClose(command) {
2054
+ const activeTab = await resolveTab(command);
2055
+ if (!activeTab.id) {
2056
+ return {
2057
+ id: command.id,
2058
+ success: false,
2059
+ error: "No active tab found"
2060
+ };
2061
+ }
2062
+ const tabId = activeTab.id;
2063
+ const title = activeTab.title || "";
2064
+ const url = activeTab.url || "";
2065
+ console.log("[CommandHandler] Closing tab:", tabId, url);
2066
+ try {
2067
+ await chrome.tabs.remove(tabId);
2068
+ cleanupTab(tabId);
2069
+ cleanupTab$1(tabId);
2070
+ tabActiveFrameId.delete(tabId);
2071
+ return {
2072
+ id: command.id,
2073
+ success: true,
2074
+ data: {
2075
+ tabId,
2076
+ title,
2077
+ url
2078
+ }
2079
+ };
2080
+ } catch (error) {
2081
+ console.error("[CommandHandler] Close failed:", error);
2082
+ return {
2083
+ id: command.id,
2084
+ success: false,
2085
+ error: `Close failed: ${error instanceof Error ? error.message : String(error)}`
2086
+ };
2087
+ }
2088
+ }
2089
+ async function handleGet(command) {
2090
+ const attribute = command.attribute;
2091
+ if (!attribute) {
2092
+ return {
2093
+ id: command.id,
2094
+ success: false,
2095
+ error: "Missing attribute parameter"
2096
+ };
2097
+ }
2098
+ const activeTab = await resolveTab(command);
2099
+ if (!activeTab.id) {
2100
+ return {
2101
+ id: command.id,
2102
+ success: false,
2103
+ error: "No active tab found"
2104
+ };
2105
+ }
2106
+ console.log("[CommandHandler] Getting:", attribute);
2107
+ try {
2108
+ let value;
2109
+ switch (attribute) {
2110
+ case "url":
2111
+ value = activeTab.url || "";
2112
+ break;
2113
+ case "title":
2114
+ value = activeTab.title || "";
2115
+ break;
2116
+ case "text": {
2117
+ const ref = command.ref;
2118
+ if (!ref) {
2119
+ return {
2120
+ id: command.id,
2121
+ success: false,
2122
+ error: "Missing ref parameter for get text"
2123
+ };
2124
+ }
2125
+ value = await getElementText(activeTab.id, ref);
2126
+ break;
2127
+ }
2128
+ default:
2129
+ return {
2130
+ id: command.id,
2131
+ success: false,
2132
+ error: `Unknown attribute: ${attribute}`
2133
+ };
2134
+ }
2135
+ return {
2136
+ id: command.id,
2137
+ success: true,
2138
+ data: {
2139
+ value
2140
+ }
2141
+ };
2142
+ } catch (error) {
2143
+ console.error("[CommandHandler] Get failed:", error);
2144
+ return {
2145
+ id: command.id,
2146
+ success: false,
2147
+ error: `Get failed: ${error instanceof Error ? error.message : String(error)}`
2148
+ };
2149
+ }
2150
+ }
2151
+ async function handleScreenshot(command) {
2152
+ const activeTab = await resolveTab(command);
2153
+ if (!activeTab.id || !activeTab.windowId) {
2154
+ return {
2155
+ id: command.id,
2156
+ success: false,
2157
+ error: "No active tab found"
2158
+ };
2159
+ }
2160
+ console.log("[CommandHandler] Taking screenshot of tab:", activeTab.id, activeTab.url);
2161
+ try {
2162
+ const dataUrl = await chrome.tabs.captureVisibleTab(activeTab.windowId, { format: "png" });
2163
+ return {
2164
+ id: command.id,
2165
+ success: true,
2166
+ data: {
2167
+ dataUrl,
2168
+ title: activeTab.title || "",
2169
+ url: activeTab.url || ""
2170
+ }
2171
+ };
2172
+ } catch (error) {
2173
+ console.error("[CommandHandler] Screenshot failed:", error);
2174
+ return {
2175
+ id: command.id,
2176
+ success: false,
2177
+ error: `Screenshot failed: ${error instanceof Error ? error.message : String(error)}`
2178
+ };
2179
+ }
2180
+ }
2181
+ async function handleWait(command) {
2182
+ const waitType = command.waitType;
2183
+ if (waitType === "time") {
2184
+ const ms = command.ms;
2185
+ if (!ms || ms < 0) {
2186
+ return {
2187
+ id: command.id,
2188
+ success: false,
2189
+ error: "Invalid ms parameter"
2190
+ };
2191
+ }
2192
+ console.log("[CommandHandler] Waiting for", ms, "ms");
2193
+ await new Promise((resolve) => setTimeout(resolve, ms));
2194
+ return {
2195
+ id: command.id,
2196
+ success: true,
2197
+ data: { waited: ms }
2198
+ };
2199
+ } else if (waitType === "element") {
2200
+ const ref = command.ref;
2201
+ if (!ref) {
2202
+ return {
2203
+ id: command.id,
2204
+ success: false,
2205
+ error: "Missing ref parameter"
2206
+ };
2207
+ }
2208
+ const activeTab = await resolveTab(command);
2209
+ if (!activeTab.id) {
2210
+ return {
2211
+ id: command.id,
2212
+ success: false,
2213
+ error: "No active tab found"
2214
+ };
2215
+ }
2216
+ console.log("[CommandHandler] Waiting for element:", ref);
2217
+ try {
2218
+ await waitForElement(activeTab.id, ref);
2219
+ return {
2220
+ id: command.id,
2221
+ success: true,
2222
+ data: { ref }
2223
+ };
2224
+ } catch (error) {
2225
+ console.error("[CommandHandler] Wait failed:", error);
2226
+ return {
2227
+ id: command.id,
2228
+ success: false,
2229
+ error: `Wait failed: ${error instanceof Error ? error.message : String(error)}`
2230
+ };
2231
+ }
2232
+ } else {
2233
+ return {
2234
+ id: command.id,
2235
+ success: false,
2236
+ error: `Unknown wait type: ${waitType}`
2237
+ };
2238
+ }
2239
+ }
2240
+ async function handlePress(command) {
2241
+ const key = command.key;
2242
+ const modifiers = command.modifiers || [];
2243
+ if (!key) {
2244
+ return {
2245
+ id: command.id,
2246
+ success: false,
2247
+ error: "Missing key parameter"
2248
+ };
2249
+ }
2250
+ const activeTab = await resolveTab(command);
2251
+ if (!activeTab.id) {
2252
+ return {
2253
+ id: command.id,
2254
+ success: false,
2255
+ error: "No active tab found"
2256
+ };
2257
+ }
2258
+ const url = activeTab.url || "";
2259
+ if (url.startsWith("chrome://") || url.startsWith("about:") || url.startsWith("chrome-extension://")) {
2260
+ return {
2261
+ id: command.id,
2262
+ success: false,
2263
+ error: `Cannot send keys to restricted page: ${url}`
2264
+ };
2265
+ }
2266
+ console.log("[CommandHandler] Pressing key:", key, "modifiers:", modifiers);
2267
+ try {
2268
+ await pressKey(activeTab.id, key, modifiers);
2269
+ const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
2270
+ return {
2271
+ id: command.id,
2272
+ success: true,
2273
+ data: {
2274
+ key: displayKey
2275
+ }
2276
+ };
2277
+ } catch (error) {
2278
+ console.error("[CommandHandler] Press failed:", error);
2279
+ return {
2280
+ id: command.id,
2281
+ success: false,
2282
+ error: `Press failed: ${error instanceof Error ? error.message : String(error)}`
2283
+ };
2284
+ }
2285
+ }
2286
+ async function handleScroll(command) {
2287
+ const direction = command.direction;
2288
+ const pixels = command.pixels || 300;
2289
+ if (!direction) {
2290
+ return {
2291
+ id: command.id,
2292
+ success: false,
2293
+ error: "Missing direction parameter"
2294
+ };
2295
+ }
2296
+ if (!["up", "down", "left", "right"].includes(direction)) {
2297
+ return {
2298
+ id: command.id,
2299
+ success: false,
2300
+ error: `Invalid direction: ${direction}`
2301
+ };
2302
+ }
2303
+ const activeTab = await resolveTab(command);
2304
+ if (!activeTab.id) {
2305
+ return {
2306
+ id: command.id,
2307
+ success: false,
2308
+ error: "No active tab found"
2309
+ };
2310
+ }
2311
+ console.log("[CommandHandler] Scrolling:", direction, pixels, "px");
2312
+ try {
2313
+ await scrollPage(activeTab.id, direction, pixels);
2314
+ return {
2315
+ id: command.id,
2316
+ success: true,
2317
+ data: {
2318
+ direction,
2319
+ pixels
2320
+ }
2321
+ };
2322
+ } catch (error) {
2323
+ console.error("[CommandHandler] Scroll failed:", error);
2324
+ return {
2325
+ id: command.id,
2326
+ success: false,
2327
+ error: `Scroll failed: ${error instanceof Error ? error.message : String(error)}`
2328
+ };
2329
+ }
2330
+ }
2331
+ async function handleBack(command) {
2332
+ const activeTab = await resolveTab(command);
2333
+ if (!activeTab.id) {
2334
+ return {
2335
+ id: command.id,
2336
+ success: false,
2337
+ error: "No active tab found"
2338
+ };
2339
+ }
2340
+ const tabId = activeTab.id;
2341
+ console.log("[CommandHandler] Going back in tab:", tabId);
2342
+ try {
2343
+ const canGoBack = await evaluate(tabId, "window.history.length > 1");
2344
+ if (!canGoBack) {
2345
+ return {
2346
+ id: command.id,
2347
+ success: false,
2348
+ error: "No previous page in history"
2349
+ };
2350
+ }
2351
+ await evaluate(tabId, "window.history.back()");
2352
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2353
+ const updatedTab = await chrome.tabs.get(tabId);
2354
+ return {
2355
+ id: command.id,
2356
+ success: true,
2357
+ data: {
2358
+ url: updatedTab.url || "",
2359
+ title: updatedTab.title || ""
2360
+ }
2361
+ };
2362
+ } catch (error) {
2363
+ console.error("[CommandHandler] Back failed:", error);
2364
+ return {
2365
+ id: command.id,
2366
+ success: false,
2367
+ error: `Back failed: ${error instanceof Error ? error.message : String(error)}`
2368
+ };
2369
+ }
2370
+ }
2371
+ async function handleForward(command) {
2372
+ const activeTab = await resolveTab(command);
2373
+ if (!activeTab.id) {
2374
+ return {
2375
+ id: command.id,
2376
+ success: false,
2377
+ error: "No active tab found"
2378
+ };
2379
+ }
2380
+ const tabId = activeTab.id;
2381
+ console.log("[CommandHandler] Going forward in tab:", tabId);
2382
+ try {
2383
+ await evaluate(tabId, "window.history.forward()");
2384
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2385
+ const updatedTab = await chrome.tabs.get(tabId);
2386
+ return {
2387
+ id: command.id,
2388
+ success: true,
2389
+ data: {
2390
+ url: updatedTab.url || "",
2391
+ title: updatedTab.title || ""
2392
+ }
2393
+ };
2394
+ } catch (error) {
2395
+ console.error("[CommandHandler] Forward failed:", error);
2396
+ return {
2397
+ id: command.id,
2398
+ success: false,
2399
+ error: `Forward failed: ${error instanceof Error ? error.message : String(error)}`
2400
+ };
2401
+ }
2402
+ }
2403
+ async function handleRefresh(command) {
2404
+ const activeTab = await resolveTab(command);
2405
+ if (!activeTab.id) {
2406
+ return {
2407
+ id: command.id,
2408
+ success: false,
2409
+ error: "No active tab found"
2410
+ };
2411
+ }
2412
+ console.log("[CommandHandler] Refreshing tab:", activeTab.id);
2413
+ try {
2414
+ await chrome.tabs.reload(activeTab.id);
2415
+ await waitForTabLoad(activeTab.id);
2416
+ const updatedTab = await chrome.tabs.get(activeTab.id);
2417
+ return {
2418
+ id: command.id,
2419
+ success: true,
2420
+ data: {
2421
+ url: updatedTab.url || "",
2422
+ title: updatedTab.title || ""
2423
+ }
2424
+ };
2425
+ } catch (error) {
2426
+ console.error("[CommandHandler] Refresh failed:", error);
2427
+ return {
2428
+ id: command.id,
2429
+ success: false,
2430
+ error: `Refresh failed: ${error instanceof Error ? error.message : String(error)}`
2431
+ };
2432
+ }
2433
+ }
2434
+ async function handleEval(command) {
2435
+ const script = command.script;
2436
+ if (!script) {
2437
+ return {
2438
+ id: command.id,
2439
+ success: false,
2440
+ error: "Missing script parameter"
2441
+ };
2442
+ }
2443
+ const activeTab = await resolveTab(command);
2444
+ if (!activeTab.id) {
2445
+ return {
2446
+ id: command.id,
2447
+ success: false,
2448
+ error: "No active tab found"
2449
+ };
2450
+ }
2451
+ const url = activeTab.url || "";
2452
+ if (url.startsWith("chrome://") || url.startsWith("about:") || url.startsWith("chrome-extension://")) {
2453
+ return {
2454
+ id: command.id,
2455
+ success: false,
2456
+ error: `Cannot execute script on restricted page: ${url}`
2457
+ };
2458
+ }
2459
+ console.log("[CommandHandler] Evaluating script:", script.substring(0, 100));
2460
+ const tabId = activeTab.id;
2461
+ try {
2462
+ const result = await evaluate(tabId, script);
2463
+ console.log("[CommandHandler] Eval result:", JSON.stringify(result));
2464
+ return {
2465
+ id: command.id,
2466
+ success: true,
2467
+ data: {
2468
+ result
2469
+ }
2470
+ };
2471
+ } catch (error) {
2472
+ console.error("[CommandHandler] Eval failed:", error);
2473
+ return {
2474
+ id: command.id,
2475
+ success: false,
2476
+ error: `Eval failed: ${error instanceof Error ? error.message : String(error)}`
2477
+ };
2478
+ }
2479
+ }
2480
+ async function handleTabList(command) {
2481
+ console.log("[CommandHandler] Listing all tabs");
2482
+ try {
2483
+ const tabs = await chrome.tabs.query({ currentWindow: true });
2484
+ const tabInfos = tabs.map((tab) => ({
2485
+ index: tab.index,
2486
+ url: tab.url || "",
2487
+ title: tab.title || "",
2488
+ active: tab.active || false,
2489
+ tabId: tab.id || 0
2490
+ }));
2491
+ const activeTab = tabInfos.find((t) => t.active);
2492
+ const activeIndex = activeTab?.index ?? 0;
2493
+ return {
2494
+ id: command.id,
2495
+ success: true,
2496
+ data: {
2497
+ tabs: tabInfos,
2498
+ activeIndex
2499
+ }
2500
+ };
2501
+ } catch (error) {
2502
+ console.error("[CommandHandler] Tab list failed:", error);
2503
+ return {
2504
+ id: command.id,
2505
+ success: false,
2506
+ error: `Tab list failed: ${error instanceof Error ? error.message : String(error)}`
2507
+ };
2508
+ }
2509
+ }
2510
+ async function handleTabNew(command) {
2511
+ const url = command.url;
2512
+ console.log("[CommandHandler] Creating new tab:", url || "about:blank");
2513
+ try {
2514
+ const createOptions = { active: true };
2515
+ if (url) {
2516
+ createOptions.url = url;
2517
+ }
2518
+ const tab = await chrome.tabs.create(createOptions);
2519
+ if (url && tab.id) {
2520
+ await waitForTabLoad(tab.id);
2521
+ }
2522
+ const updatedTab = tab.id ? await chrome.tabs.get(tab.id) : tab;
2523
+ return {
2524
+ id: command.id,
2525
+ success: true,
2526
+ data: {
2527
+ tabId: updatedTab.id,
2528
+ title: updatedTab.title || "",
2529
+ url: updatedTab.url || ""
2530
+ }
2531
+ };
2532
+ } catch (error) {
2533
+ console.error("[CommandHandler] Tab new failed:", error);
2534
+ return {
2535
+ id: command.id,
2536
+ success: false,
2537
+ error: `Tab new failed: ${error instanceof Error ? error.message : String(error)}`
2538
+ };
2539
+ }
2540
+ }
2541
+ async function handleTabSelect(command) {
2542
+ const index = command.index;
2543
+ const tabIdParam = command.tabId;
2544
+ if (index === void 0 && tabIdParam === void 0) {
2545
+ return {
2546
+ id: command.id,
2547
+ success: false,
2548
+ error: "Missing index or tabId parameter"
2549
+ };
2550
+ }
2551
+ console.log("[CommandHandler] Selecting tab:", tabIdParam !== void 0 ? `tabId=${tabIdParam}` : `index=${index}`);
2552
+ try {
2553
+ let targetTab;
2554
+ if (tabIdParam !== void 0) {
2555
+ targetTab = await chrome.tabs.get(tabIdParam);
2556
+ } else {
2557
+ const tabs = await chrome.tabs.query({ currentWindow: true });
2558
+ const found = tabs.find((t) => t.index === index);
2559
+ if (!found || !found.id) {
2560
+ return {
2561
+ id: command.id,
2562
+ success: false,
2563
+ error: `No tab found at index ${index} (total tabs: ${tabs.length})`
2564
+ };
2565
+ }
2566
+ targetTab = found;
2567
+ }
2568
+ await chrome.tabs.update(targetTab.id, { active: true });
2569
+ return {
2570
+ id: command.id,
2571
+ success: true,
2572
+ data: {
2573
+ tabId: targetTab.id,
2574
+ title: targetTab.title || "",
2575
+ url: targetTab.url || ""
2576
+ }
2577
+ };
2578
+ } catch (error) {
2579
+ console.error("[CommandHandler] Tab select failed:", error);
2580
+ return {
2581
+ id: command.id,
2582
+ success: false,
2583
+ error: `Tab select failed: ${error instanceof Error ? error.message : String(error)}`
2584
+ };
2585
+ }
2586
+ }
2587
+ async function handleTabClose(command) {
2588
+ const index = command.index;
2589
+ const tabIdParam = command.tabId;
2590
+ console.log("[CommandHandler] Closing tab:", tabIdParam !== void 0 ? `tabId=${tabIdParam}` : index !== void 0 ? `index=${index}` : "current");
2591
+ try {
2592
+ let targetTab;
2593
+ if (tabIdParam !== void 0) {
2594
+ targetTab = await chrome.tabs.get(tabIdParam);
2595
+ } else if (index !== void 0) {
2596
+ const tabs = await chrome.tabs.query({ currentWindow: true });
2597
+ const found = tabs.find((t) => t.index === index);
2598
+ if (!found || !found.id) {
2599
+ return {
2600
+ id: command.id,
2601
+ success: false,
2602
+ error: `No tab found at index ${index} (total tabs: ${tabs.length})`
2603
+ };
2604
+ }
2605
+ targetTab = found;
2606
+ } else {
2607
+ const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
2608
+ if (!activeTab || !activeTab.id) {
2609
+ return {
2610
+ id: command.id,
2611
+ success: false,
2612
+ error: "No active tab found"
2613
+ };
2614
+ }
2615
+ targetTab = activeTab;
2616
+ }
2617
+ const tabId = targetTab.id;
2618
+ const title = targetTab.title || "";
2619
+ const url = targetTab.url || "";
2620
+ await chrome.tabs.remove(tabId);
2621
+ cleanupTab(tabId);
2622
+ cleanupTab$1(tabId);
2623
+ tabActiveFrameId.delete(tabId);
2624
+ return {
2625
+ id: command.id,
2626
+ success: true,
2627
+ data: {
2628
+ tabId,
2629
+ title,
2630
+ url
2631
+ }
2632
+ };
2633
+ } catch (error) {
2634
+ console.error("[CommandHandler] Tab close failed:", error);
2635
+ return {
2636
+ id: command.id,
2637
+ success: false,
2638
+ error: `Tab close failed: ${error instanceof Error ? error.message : String(error)}`
2639
+ };
2640
+ }
2641
+ }
2642
+ async function handleFrame(command) {
2643
+ const selector = command.selector;
2644
+ if (!selector) {
2645
+ return {
2646
+ id: command.id,
2647
+ success: false,
2648
+ error: "Missing selector parameter"
2649
+ };
2650
+ }
2651
+ const activeTab = await resolveTab(command);
2652
+ if (!activeTab.id) {
2653
+ return {
2654
+ id: command.id,
2655
+ success: false,
2656
+ error: "No active tab found"
2657
+ };
2658
+ }
2659
+ const tabId = activeTab.id;
2660
+ console.log("[CommandHandler] Switching to frame:", selector);
2661
+ try {
2662
+ const iframeInfoResults = await chrome.scripting.executeScript({
2663
+ target: { tabId, frameIds: tabActiveFrameId.get(tabId) !== null && tabActiveFrameId.get(tabId) !== void 0 ? [tabActiveFrameId.get(tabId)] : [0] },
2664
+ func: (sel) => {
2665
+ const iframe = document.querySelector(sel);
2666
+ if (!iframe) {
2667
+ return { found: false, error: `找不到 iframe: ${sel}` };
2668
+ }
2669
+ if (iframe.tagName.toLowerCase() !== "iframe" && iframe.tagName.toLowerCase() !== "frame") {
2670
+ return { found: false, error: `元素不是 iframe: ${iframe.tagName}` };
2671
+ }
2672
+ return {
2673
+ found: true,
2674
+ name: iframe.name || "",
2675
+ src: iframe.src || "",
2676
+ // 获取 iframe 在页面中的位置用于匹配
2677
+ rect: iframe.getBoundingClientRect()
2678
+ };
2679
+ },
2680
+ args: [selector]
2681
+ });
2682
+ const iframeInfo = iframeInfoResults[0]?.result;
2683
+ if (!iframeInfo || !iframeInfo.found) {
2684
+ return {
2685
+ id: command.id,
2686
+ success: false,
2687
+ error: iframeInfo?.error || `找不到 iframe: ${selector}`
2688
+ };
2689
+ }
2690
+ const frames = await chrome.webNavigation.getAllFrames({ tabId });
2691
+ if (!frames || frames.length === 0) {
2692
+ return {
2693
+ id: command.id,
2694
+ success: false,
2695
+ error: "无法获取页面 frames"
2696
+ };
2697
+ }
2698
+ let targetFrameId = null;
2699
+ if (iframeInfo.src) {
2700
+ const matchedFrame = frames.find(
2701
+ (f) => f.url === iframeInfo.src || f.url.includes(iframeInfo.src) || iframeInfo.src.includes(f.url)
2702
+ );
2703
+ if (matchedFrame) {
2704
+ targetFrameId = matchedFrame.frameId;
2705
+ }
2706
+ }
2707
+ if (targetFrameId === null) {
2708
+ const childFrames = frames.filter((f) => f.frameId !== 0);
2709
+ if (childFrames.length === 1) {
2710
+ targetFrameId = childFrames[0].frameId;
2711
+ } else if (childFrames.length > 1) {
2712
+ if (iframeInfo.name) {
2713
+ console.log("[CommandHandler] Multiple frames found, using URL matching");
2714
+ }
2715
+ if (targetFrameId === null) {
2716
+ return {
2717
+ id: command.id,
2718
+ success: false,
2719
+ error: `找到多个子 frame,无法确定目标。请使用更精确的 selector 或确保 iframe 有 src 属性。`
2720
+ };
2721
+ }
2722
+ } else {
2723
+ return {
2724
+ id: command.id,
2725
+ success: false,
2726
+ error: "页面中没有子 frame"
2727
+ };
2728
+ }
2729
+ }
2730
+ try {
2731
+ await chrome.scripting.executeScript({
2732
+ target: { tabId, frameIds: [targetFrameId] },
2733
+ func: () => true
2734
+ });
2735
+ } catch (e) {
2736
+ return {
2737
+ id: command.id,
2738
+ success: false,
2739
+ error: `无法访问 frame (frameId: ${targetFrameId}),可能是跨域 iframe`
2740
+ };
2741
+ }
2742
+ tabActiveFrameId.set(tabId, targetFrameId);
2743
+ setActiveFrameId(tabId, String(targetFrameId));
2744
+ const matchedFrameInfo = frames.find((f) => f.frameId === targetFrameId);
2745
+ return {
2746
+ id: command.id,
2747
+ success: true,
2748
+ data: {
2749
+ frameInfo: {
2750
+ selector,
2751
+ name: iframeInfo.name,
2752
+ url: matchedFrameInfo?.url || iframeInfo.src,
2753
+ frameId: targetFrameId
2754
+ }
2755
+ }
2756
+ };
2757
+ } catch (error) {
2758
+ console.error("[CommandHandler] Frame switch failed:", error);
2759
+ return {
2760
+ id: command.id,
2761
+ success: false,
2762
+ error: `Frame switch failed: ${error instanceof Error ? error.message : String(error)}`
2763
+ };
2764
+ }
2765
+ }
2766
+ async function handleFrameMain(command) {
2767
+ console.log("[CommandHandler] Switching to main frame");
2768
+ const activeTab = await resolveTab(command);
2769
+ if (!activeTab.id) {
2770
+ return {
2771
+ id: command.id,
2772
+ success: false,
2773
+ error: "No active tab found"
2774
+ };
2775
+ }
2776
+ const tabId = activeTab.id;
2777
+ tabActiveFrameId.set(tabId, null);
2778
+ setActiveFrameId(tabId, null);
2779
+ return {
2780
+ id: command.id,
2781
+ success: true,
2782
+ data: {
2783
+ frameInfo: {
2784
+ frameId: 0
2785
+ }
2786
+ }
2787
+ };
2788
+ }
2789
+ async function handleDialog(command) {
2790
+ const dialogResponse = command.dialogResponse;
2791
+ const promptText = command.promptText;
2792
+ if (!dialogResponse || !["accept", "dismiss"].includes(dialogResponse)) {
2793
+ return {
2794
+ id: command.id,
2795
+ success: false,
2796
+ error: "Missing or invalid dialogResponse parameter (accept/dismiss)"
2797
+ };
2798
+ }
2799
+ const activeTab = await resolveTab(command);
2800
+ if (!activeTab.id) {
2801
+ return {
2802
+ id: command.id,
2803
+ success: false,
2804
+ error: "No active tab found"
2805
+ };
2806
+ }
2807
+ const tabId = activeTab.id;
2808
+ console.log("[CommandHandler] Handling dialog:", dialogResponse, "promptText:", promptText);
2809
+ try {
2810
+ const pendingDialog = getPendingDialog(tabId);
2811
+ if (!pendingDialog) {
2812
+ return {
2813
+ id: command.id,
2814
+ success: false,
2815
+ error: "没有待处理的对话框"
2816
+ };
2817
+ }
2818
+ await handleJavaScriptDialog(
2819
+ tabId,
2820
+ dialogResponse === "accept",
2821
+ dialogResponse === "accept" ? promptText : void 0
2822
+ );
2823
+ const dialogInfo = {
2824
+ type: pendingDialog.type,
2825
+ message: pendingDialog.message,
2826
+ handled: true
2827
+ };
2828
+ return {
2829
+ id: command.id,
2830
+ success: true,
2831
+ data: {
2832
+ dialogInfo
2833
+ }
2834
+ };
2835
+ } catch (error) {
2836
+ console.error("[CommandHandler] Dialog handling failed:", error);
2837
+ return {
2838
+ id: command.id,
2839
+ success: false,
2840
+ error: `Dialog failed: ${error instanceof Error ? error.message : String(error)}`
2841
+ };
2842
+ }
2843
+ }
2844
+ function waitForTabLoad(tabId, timeout = 3e4) {
2845
+ return new Promise((resolve, reject) => {
2846
+ const timeoutId = setTimeout(() => {
2847
+ chrome.tabs.onUpdated.removeListener(listener);
2848
+ reject(new Error("Tab load timeout"));
2849
+ }, timeout);
2850
+ const listener = (updatedTabId, changeInfo) => {
2851
+ if (updatedTabId === tabId && changeInfo.status === "complete") {
2852
+ clearTimeout(timeoutId);
2853
+ chrome.tabs.onUpdated.removeListener(listener);
2854
+ resolve();
2855
+ }
2856
+ };
2857
+ chrome.tabs.onUpdated.addListener(listener);
2858
+ });
2859
+ }
2860
+ async function handleNetwork(command) {
2861
+ const activeTab = await resolveTab(command);
2862
+ if (!activeTab.id) {
2863
+ return {
2864
+ id: command.id,
2865
+ success: false,
2866
+ error: "No active tab found"
2867
+ };
2868
+ }
2869
+ const tabId = activeTab.id;
2870
+ const subCommand = command.networkCommand;
2871
+ const urlPattern = command.url;
2872
+ console.log("[CommandHandler] Network command:", subCommand, urlPattern);
2873
+ try {
2874
+ switch (subCommand) {
2875
+ case "requests": {
2876
+ await enableNetwork(tabId);
2877
+ const filter = command.filter;
2878
+ const withBody = command.withBody === true;
2879
+ const requests = getNetworkRequests(tabId, filter, withBody);
2880
+ const networkRequests = requests.map((r) => ({
2881
+ requestId: r.requestId,
2882
+ url: r.url,
2883
+ method: r.method,
2884
+ type: r.type,
2885
+ timestamp: r.timestamp,
2886
+ status: r.response?.status,
2887
+ statusText: r.response?.statusText,
2888
+ failed: r.failed,
2889
+ failureReason: r.failureReason,
2890
+ ...withBody ? {
2891
+ requestHeaders: r.requestHeaders,
2892
+ requestBody: r.requestBody,
2893
+ requestBodyTruncated: r.requestBodyTruncated,
2894
+ responseHeaders: r.response?.headers,
2895
+ responseBody: r.response?.body,
2896
+ responseBodyBase64: r.response?.bodyBase64,
2897
+ responseBodyTruncated: r.response?.bodyTruncated,
2898
+ mimeType: r.response?.mimeType,
2899
+ bodyError: r.bodyError
2900
+ } : {}
2901
+ }));
2902
+ return {
2903
+ id: command.id,
2904
+ success: true,
2905
+ data: {
2906
+ networkRequests
2907
+ }
2908
+ };
2909
+ }
2910
+ case "route": {
2911
+ if (!urlPattern) {
2912
+ return {
2913
+ id: command.id,
2914
+ success: false,
2915
+ error: "URL pattern required for route command"
2916
+ };
2917
+ }
2918
+ const options = command.routeOptions || {};
2919
+ await addNetworkRoute(tabId, urlPattern, options);
2920
+ const routeCount = getNetworkRoutes(tabId).length;
2921
+ return {
2922
+ id: command.id,
2923
+ success: true,
2924
+ data: {
2925
+ routeCount
2926
+ }
2927
+ };
2928
+ }
2929
+ case "unroute": {
2930
+ removeNetworkRoute(tabId, urlPattern);
2931
+ const routeCount = getNetworkRoutes(tabId).length;
2932
+ return {
2933
+ id: command.id,
2934
+ success: true,
2935
+ data: {
2936
+ routeCount
2937
+ }
2938
+ };
2939
+ }
2940
+ case "clear": {
2941
+ clearNetworkRequests(tabId);
2942
+ return {
2943
+ id: command.id,
2944
+ success: true,
2945
+ data: {}
2946
+ };
2947
+ }
2948
+ default:
2949
+ return {
2950
+ id: command.id,
2951
+ success: false,
2952
+ error: `Unknown network subcommand: ${subCommand}`
2953
+ };
2954
+ }
2955
+ } catch (error) {
2956
+ console.error("[CommandHandler] Network command failed:", error);
2957
+ return {
2958
+ id: command.id,
2959
+ success: false,
2960
+ error: `Network command failed: ${error instanceof Error ? error.message : String(error)}`
2961
+ };
2962
+ }
2963
+ }
2964
+ async function handleConsole(command) {
2965
+ const activeTab = await resolveTab(command);
2966
+ if (!activeTab.id) {
2967
+ return {
2968
+ id: command.id,
2969
+ success: false,
2970
+ error: "No active tab found"
2971
+ };
2972
+ }
2973
+ const tabId = activeTab.id;
2974
+ const subCommand = command.consoleCommand || "get";
2975
+ console.log("[CommandHandler] Console command:", subCommand);
2976
+ try {
2977
+ await enableConsole(tabId);
2978
+ switch (subCommand) {
2979
+ case "get": {
2980
+ const messages = getConsoleMessages(tabId);
2981
+ return {
2982
+ id: command.id,
2983
+ success: true,
2984
+ data: {
2985
+ consoleMessages: messages
2986
+ }
2987
+ };
2988
+ }
2989
+ case "clear": {
2990
+ clearConsoleMessages(tabId);
2991
+ return {
2992
+ id: command.id,
2993
+ success: true,
2994
+ data: {}
2995
+ };
2996
+ }
2997
+ default:
2998
+ return {
2999
+ id: command.id,
3000
+ success: false,
3001
+ error: `Unknown console subcommand: ${subCommand}`
3002
+ };
3003
+ }
3004
+ } catch (error) {
3005
+ console.error("[CommandHandler] Console command failed:", error);
3006
+ return {
3007
+ id: command.id,
3008
+ success: false,
3009
+ error: `Console command failed: ${error instanceof Error ? error.message : String(error)}`
3010
+ };
3011
+ }
3012
+ }
3013
+ async function handleErrors(command) {
3014
+ const activeTab = await resolveTab(command);
3015
+ if (!activeTab.id) {
3016
+ return {
3017
+ id: command.id,
3018
+ success: false,
3019
+ error: "No active tab found"
3020
+ };
3021
+ }
3022
+ const tabId = activeTab.id;
3023
+ const subCommand = command.errorsCommand || "get";
3024
+ console.log("[CommandHandler] Errors command:", subCommand);
3025
+ try {
3026
+ await enableConsole(tabId);
3027
+ switch (subCommand) {
3028
+ case "get": {
3029
+ const errors = getJSErrors(tabId);
3030
+ return {
3031
+ id: command.id,
3032
+ success: true,
3033
+ data: {
3034
+ jsErrors: errors
3035
+ }
3036
+ };
3037
+ }
3038
+ case "clear": {
3039
+ clearJSErrors(tabId);
3040
+ return {
3041
+ id: command.id,
3042
+ success: true,
3043
+ data: {}
3044
+ };
3045
+ }
3046
+ default:
3047
+ return {
3048
+ id: command.id,
3049
+ success: false,
3050
+ error: `Unknown errors subcommand: ${subCommand}`
3051
+ };
3052
+ }
3053
+ } catch (error) {
3054
+ console.error("[CommandHandler] Errors command failed:", error);
3055
+ return {
3056
+ id: command.id,
3057
+ success: false,
3058
+ error: `Errors command failed: ${error instanceof Error ? error.message : String(error)}`
3059
+ };
3060
+ }
3061
+ }
3062
+ async function handleTrace(command) {
3063
+ const subCommand = command.traceCommand || "status";
3064
+ console.log("[CommandHandler] Trace command:", subCommand);
3065
+ try {
3066
+ switch (subCommand) {
3067
+ case "start": {
3068
+ const activeTab = await resolveTab(command);
3069
+ if (!activeTab.id) {
3070
+ return {
3071
+ id: command.id,
3072
+ success: false,
3073
+ error: "No active tab found"
3074
+ };
3075
+ }
3076
+ const url = activeTab.url || "";
3077
+ if (url.startsWith("chrome://") || url.startsWith("about:") || url.startsWith("chrome-extension://")) {
3078
+ return {
3079
+ id: command.id,
3080
+ success: false,
3081
+ error: `Cannot record on restricted page: ${url}`
3082
+ };
3083
+ }
3084
+ await startRecording(activeTab.id);
3085
+ const status = getStatus();
3086
+ return {
3087
+ id: command.id,
3088
+ success: true,
3089
+ data: {
3090
+ traceStatus: status
3091
+ }
3092
+ };
3093
+ }
3094
+ case "stop": {
3095
+ const events = await stopRecording();
3096
+ return {
3097
+ id: command.id,
3098
+ success: true,
3099
+ data: {
3100
+ traceEvents: events,
3101
+ traceStatus: {
3102
+ recording: false,
3103
+ eventCount: events.length
3104
+ }
3105
+ }
3106
+ };
3107
+ }
3108
+ case "status": {
3109
+ const status = getStatus();
3110
+ return {
3111
+ id: command.id,
3112
+ success: true,
3113
+ data: {
3114
+ traceStatus: status
3115
+ }
3116
+ };
3117
+ }
3118
+ default:
3119
+ return {
3120
+ id: command.id,
3121
+ success: false,
3122
+ error: `Unknown trace subcommand: ${subCommand}`
3123
+ };
3124
+ }
3125
+ } catch (error) {
3126
+ console.error("[CommandHandler] Trace command failed:", error);
3127
+ return {
3128
+ id: command.id,
3129
+ success: false,
3130
+ error: `Trace command failed: ${error instanceof Error ? error.message : String(error)}`
3131
+ };
3132
+ }
3133
+ }
3134
+ async function handleHistory(command) {
3135
+ const subCommand = command.historyCommand || "search";
3136
+ const days = typeof command.ms === "number" && command.ms > 0 ? command.ms : 30;
3137
+ const startTime = Date.now() - days * 24 * 60 * 60 * 1e3;
3138
+ console.log("[CommandHandler] History command:", subCommand, "days:", days);
3139
+ try {
3140
+ switch (subCommand) {
3141
+ case "search": {
3142
+ const items = await chrome.history.search({
3143
+ text: command.text || "",
3144
+ maxResults: command.maxResults || 100,
3145
+ startTime
3146
+ });
3147
+ return {
3148
+ id: command.id,
3149
+ success: true,
3150
+ data: {
3151
+ historyItems: items.map((item) => ({
3152
+ url: item.url || "",
3153
+ title: item.title || "",
3154
+ visitCount: item.visitCount || 0,
3155
+ lastVisitTime: item.lastVisitTime || 0
3156
+ }))
3157
+ }
3158
+ };
3159
+ }
3160
+ case "domains": {
3161
+ const items = await chrome.history.search({
3162
+ text: "",
3163
+ maxResults: 5e3,
3164
+ startTime
3165
+ });
3166
+ const domainMap = /* @__PURE__ */ new Map();
3167
+ for (const item of items) {
3168
+ if (!item.url) continue;
3169
+ let domain;
3170
+ try {
3171
+ domain = new URL(item.url).hostname;
3172
+ } catch {
3173
+ continue;
3174
+ }
3175
+ const current = domainMap.get(domain) || { visits: 0, titles: /* @__PURE__ */ new Set() };
3176
+ current.visits += item.visitCount || 0;
3177
+ if (item.title) {
3178
+ current.titles.add(item.title);
3179
+ }
3180
+ domainMap.set(domain, current);
3181
+ }
3182
+ const historyDomains = Array.from(domainMap.entries()).map(([domain, value]) => ({
3183
+ domain,
3184
+ visits: value.visits,
3185
+ titles: Array.from(value.titles)
3186
+ })).sort((a, b) => b.visits - a.visits);
3187
+ return {
3188
+ id: command.id,
3189
+ success: true,
3190
+ data: {
3191
+ historyDomains
3192
+ }
3193
+ };
3194
+ }
3195
+ default:
3196
+ return {
3197
+ id: command.id,
3198
+ success: false,
3199
+ error: `Unknown history subcommand: ${subCommand}`
3200
+ };
3201
+ }
3202
+ } catch (error) {
3203
+ console.error("[CommandHandler] History command failed:", error);
3204
+ return {
3205
+ id: command.id,
3206
+ success: false,
3207
+ error: `History command failed: ${error instanceof Error ? error.message : String(error)}`
3208
+ };
3209
+ }
3210
+ }
3211
+
3212
+ const KEEPALIVE_ALARM = "bb-browser-keepalive";
3213
+ const sseClient = new SSEClient();
3214
+ sseClient.onCommand(handleCommand);
3215
+ chrome.storage.onChanged.addListener((changes, area) => {
3216
+ if (area === "sync" && changes.upstreamUrl) {
3217
+ const newUrl = changes.upstreamUrl.newValue || "default";
3218
+ console.log("[bb-browser] Upstream URL changed to:", newUrl, "— reconnecting...");
3219
+ sseClient.disconnect();
3220
+ sseClient.connect();
3221
+ }
3222
+ });
3223
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
3224
+ console.log("[bb-browser] Message from content script:", message, "sender:", sender.tab?.id);
3225
+ sendResponse({ received: true });
3226
+ return true;
3227
+ });
3228
+ async function setupKeepaliveAlarm() {
3229
+ await chrome.alarms.clear(KEEPALIVE_ALARM);
3230
+ await chrome.alarms.create(KEEPALIVE_ALARM, {
3231
+ periodInMinutes: 0.4
3232
+ // 24 秒
3233
+ });
3234
+ console.log("[bb-browser] Keepalive alarm set (every 24s)");
3235
+ }
3236
+ chrome.alarms.onAlarm.addListener((alarm) => {
3237
+ if (alarm.name === KEEPALIVE_ALARM) {
3238
+ console.log("[bb-browser] Keepalive alarm triggered, checking connection...");
3239
+ if (!sseClient.isConnected()) {
3240
+ console.log("[bb-browser] SSE disconnected, reconnecting...");
3241
+ sseClient.connect();
3242
+ }
3243
+ }
3244
+ });
3245
+ chrome.runtime.onInstalled.addListener((details) => {
3246
+ console.log("[bb-browser] Extension installed/updated:", details.reason);
3247
+ sseClient.connect();
3248
+ setupKeepaliveAlarm();
3249
+ });
3250
+ chrome.runtime.onStartup.addListener(() => {
3251
+ console.log("[bb-browser] Browser started, connecting to daemon...");
3252
+ sseClient.connect();
3253
+ setupKeepaliveAlarm();
3254
+ });
3255
+ console.log("[bb-browser] Background service worker started, connecting to daemon...");
3256
+ sseClient.connect();
3257
+ setupKeepaliveAlarm();
3258
+ //# sourceMappingURL=background.js.map