@fairfox/polly 0.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/cli/polly.ts +564 -0
  4. package/dist/background/api-client.d.ts +7 -0
  5. package/dist/background/context-menu.d.ts +7 -0
  6. package/dist/background/index.d.ts +31 -0
  7. package/dist/background/index.js +1309 -0
  8. package/dist/background/index.js.map +25 -0
  9. package/dist/background/log-store.d.ts +22 -0
  10. package/dist/background/message-router.d.ts +30 -0
  11. package/dist/background/message-router.js +1300 -0
  12. package/dist/background/message-router.js.map +24 -0
  13. package/dist/background/offscreen-manager.d.ts +10 -0
  14. package/dist/index.d.ts +18 -0
  15. package/dist/index.js +1471 -0
  16. package/dist/index.js.map +27 -0
  17. package/dist/shared/adapters/chrome/context-menus.chrome.d.ts +8 -0
  18. package/dist/shared/adapters/chrome/offscreen.chrome.d.ts +6 -0
  19. package/dist/shared/adapters/chrome/runtime.chrome.d.ts +13 -0
  20. package/dist/shared/adapters/chrome/storage.chrome.d.ts +8 -0
  21. package/dist/shared/adapters/chrome/tabs.chrome.d.ts +16 -0
  22. package/dist/shared/adapters/chrome/window.chrome.d.ts +6 -0
  23. package/dist/shared/adapters/context-menus.adapter.d.ts +22 -0
  24. package/dist/shared/adapters/fetch.adapter.d.ts +6 -0
  25. package/dist/shared/adapters/index.d.ts +34 -0
  26. package/dist/shared/adapters/index.js +298 -0
  27. package/dist/shared/adapters/index.js.map +18 -0
  28. package/dist/shared/adapters/logger.adapter.d.ts +44 -0
  29. package/dist/shared/adapters/offscreen.adapter.d.ts +20 -0
  30. package/dist/shared/adapters/runtime.adapter.d.ts +66 -0
  31. package/dist/shared/adapters/storage.adapter.d.ts +29 -0
  32. package/dist/shared/adapters/tabs.adapter.d.ts +39 -0
  33. package/dist/shared/adapters/window.adapter.d.ts +14 -0
  34. package/dist/shared/lib/context-helpers.d.ts +64 -0
  35. package/dist/shared/lib/context-helpers.js +1086 -0
  36. package/dist/shared/lib/context-helpers.js.map +24 -0
  37. package/dist/shared/lib/context-specific-helpers.d.ts +160 -0
  38. package/dist/shared/lib/errors.d.ts +67 -0
  39. package/dist/shared/lib/errors.js +94 -0
  40. package/dist/shared/lib/errors.js.map +10 -0
  41. package/dist/shared/lib/handler-execution-tracker.d.ts +24 -0
  42. package/dist/shared/lib/message-bus.d.ts +233 -0
  43. package/dist/shared/lib/message-bus.js +1033 -0
  44. package/dist/shared/lib/message-bus.js.map +23 -0
  45. package/dist/shared/lib/state.d.ts +102 -0
  46. package/dist/shared/lib/state.js +1265 -0
  47. package/dist/shared/lib/state.js.map +24 -0
  48. package/dist/shared/lib/test-helpers.d.ts +133 -0
  49. package/dist/shared/lib/test-helpers.js +136 -0
  50. package/dist/shared/lib/test-helpers.js.map +10 -0
  51. package/dist/shared/state/app-state.d.ts +8 -0
  52. package/dist/shared/state/app-state.js +1272 -0
  53. package/dist/shared/state/app-state.js.map +25 -0
  54. package/dist/shared/types/messages.d.ts +341 -0
  55. package/dist/shared/types/messages.js +25 -0
  56. package/dist/shared/types/messages.js.map +10 -0
  57. package/package.json +110 -0
@@ -0,0 +1,1309 @@
1
+ // src/shared/adapters/chrome/context-menus.chrome.ts
2
+ class ChromeContextMenusAdapter {
3
+ async create(createProperties) {
4
+ return new Promise((resolve, reject) => {
5
+ chrome.contextMenus.create(createProperties, () => {
6
+ if (chrome.runtime.lastError) {
7
+ reject(new Error(chrome.runtime.lastError.message));
8
+ } else {
9
+ resolve();
10
+ }
11
+ });
12
+ });
13
+ }
14
+ async update(id, updateProperties) {
15
+ await chrome.contextMenus.update(id, updateProperties);
16
+ }
17
+ async remove(id) {
18
+ await chrome.contextMenus.remove(id);
19
+ }
20
+ async removeAll() {
21
+ await chrome.contextMenus.removeAll();
22
+ }
23
+ onClicked(callback) {
24
+ chrome.contextMenus.onClicked.addListener(callback);
25
+ }
26
+ }
27
+
28
+ // src/shared/adapters/chrome/offscreen.chrome.ts
29
+ class ChromeOffscreenAdapter {
30
+ async createDocument(parameters) {
31
+ await chrome.offscreen.createDocument({
32
+ url: parameters.url,
33
+ reasons: parameters.reasons,
34
+ justification: parameters.justification
35
+ });
36
+ }
37
+ async closeDocument() {
38
+ await chrome.offscreen.closeDocument();
39
+ }
40
+ async hasDocument() {
41
+ const existingContexts = await chrome.runtime.getContexts({
42
+ contextTypes: ["OFFSCREEN_DOCUMENT"]
43
+ });
44
+ return existingContexts.length > 0;
45
+ }
46
+ }
47
+
48
+ // src/shared/adapters/chrome/runtime.chrome.ts
49
+ class ChromeRuntimeAdapter {
50
+ messageListeners = new Map;
51
+ static listenerCount = 0;
52
+ sendMessage(message) {
53
+ return chrome.runtime.sendMessage(message);
54
+ }
55
+ onMessage(callback) {
56
+ const wrappedCallback = (message, sender, sendResponse) => {
57
+ const mappedSender = {
58
+ ...sender.tab && {
59
+ tab: {
60
+ id: sender.tab.id ?? 0,
61
+ url: sender.tab.url ?? "",
62
+ title: sender.tab.title ?? ""
63
+ }
64
+ },
65
+ ...sender.frameId !== undefined && { frameId: sender.frameId },
66
+ ...sender.url && { url: sender.url }
67
+ };
68
+ return callback(message, mappedSender, sendResponse);
69
+ };
70
+ this.messageListeners.set(callback, wrappedCallback);
71
+ chrome.runtime.onMessage.addListener(wrappedCallback);
72
+ ChromeRuntimeAdapter.listenerCount++;
73
+ if (ChromeRuntimeAdapter.listenerCount > 1) {
74
+ console.warn(`⚠️ WARNING: ${ChromeRuntimeAdapter.listenerCount} chrome.runtime.onMessage listeners registered!
75
+
76
+ Multiple listeners will cause message handlers to execute multiple times.
77
+ This is usually caused by:
78
+ 1. Creating both MessageBus and MessageRouter with separate listeners
79
+ 2. Calling createBackground() multiple times
80
+ 3. Calling getMessageBus('background') after createBackground()
81
+
82
+ Fix: In background scripts, use createBackground() ONCE at startup.
83
+ Do not call getMessageBus('background') separately.`);
84
+ }
85
+ }
86
+ removeMessageListener(callback) {
87
+ const wrappedCallback = this.messageListeners.get(callback);
88
+ if (wrappedCallback) {
89
+ chrome.runtime.onMessage.removeListener(wrappedCallback);
90
+ this.messageListeners.delete(callback);
91
+ ChromeRuntimeAdapter.listenerCount = Math.max(0, ChromeRuntimeAdapter.listenerCount - 1);
92
+ }
93
+ }
94
+ connect(name) {
95
+ const port = chrome.runtime.connect({ name });
96
+ return new ChromePortAdapter(port);
97
+ }
98
+ onConnect(callback) {
99
+ chrome.runtime.onConnect.addListener((port) => {
100
+ callback(new ChromePortAdapter(port));
101
+ });
102
+ }
103
+ getURL(path) {
104
+ return chrome.runtime.getURL(path);
105
+ }
106
+ getId() {
107
+ return chrome.runtime.id;
108
+ }
109
+ openOptionsPage() {
110
+ chrome.runtime.openOptionsPage();
111
+ }
112
+ }
113
+
114
+ class ChromePortAdapter {
115
+ port;
116
+ listeners = {
117
+ message: new Set,
118
+ disconnect: new Set
119
+ };
120
+ constructor(port) {
121
+ this.port = port;
122
+ this.port.onMessage.addListener((message) => {
123
+ for (const callback of this.listeners.message) {
124
+ callback(message);
125
+ }
126
+ });
127
+ this.port.onDisconnect.addListener(() => {
128
+ for (const callback of this.listeners.disconnect) {
129
+ callback();
130
+ }
131
+ });
132
+ }
133
+ get name() {
134
+ return this.port.name;
135
+ }
136
+ postMessage(message) {
137
+ this.port.postMessage(message);
138
+ }
139
+ onMessage(callback) {
140
+ this.listeners.message.add(callback);
141
+ }
142
+ onDisconnect(callback) {
143
+ this.listeners.disconnect.add(callback);
144
+ }
145
+ disconnect() {
146
+ this.port.disconnect();
147
+ }
148
+ }
149
+
150
+ // src/shared/adapters/chrome/storage.chrome.ts
151
+ class ChromeStorageAdapter {
152
+ async get(keys) {
153
+ return await chrome.storage.local.get(keys);
154
+ }
155
+ async set(items) {
156
+ await chrome.storage.local.set(items);
157
+ }
158
+ async remove(keys) {
159
+ await chrome.storage.local.remove(keys);
160
+ }
161
+ async clear() {
162
+ await chrome.storage.local.clear();
163
+ }
164
+ onChanged(callback) {
165
+ chrome.storage.onChanged.addListener((changes, areaName) => {
166
+ const mappedChanges = {};
167
+ for (const [key, change] of Object.entries(changes)) {
168
+ mappedChanges[key] = {
169
+ oldValue: change.oldValue,
170
+ newValue: change.newValue
171
+ };
172
+ }
173
+ callback(mappedChanges, areaName);
174
+ });
175
+ }
176
+ }
177
+
178
+ // src/shared/adapters/chrome/tabs.chrome.ts
179
+ class ChromeTabsAdapter {
180
+ async query(queryInfo) {
181
+ return chrome.tabs.query(queryInfo);
182
+ }
183
+ async get(tabId) {
184
+ return chrome.tabs.get(tabId);
185
+ }
186
+ async sendMessage(tabId, message) {
187
+ return chrome.tabs.sendMessage(tabId, message);
188
+ }
189
+ async reload(tabId, reloadProperties) {
190
+ await chrome.tabs.reload(tabId, reloadProperties);
191
+ }
192
+ onRemoved(callback) {
193
+ chrome.tabs.onRemoved.addListener(callback);
194
+ }
195
+ onUpdated(callback) {
196
+ chrome.tabs.onUpdated.addListener(callback);
197
+ }
198
+ onActivated(callback) {
199
+ chrome.tabs.onActivated.addListener(callback);
200
+ }
201
+ async create(createProperties) {
202
+ return chrome.tabs.create(createProperties);
203
+ }
204
+ }
205
+
206
+ // src/shared/adapters/chrome/window.chrome.ts
207
+ class ChromeWindowAdapter {
208
+ postMessage(message, targetOrigin) {
209
+ window.postMessage(message, targetOrigin);
210
+ }
211
+ addEventListener(type, listener) {
212
+ window.addEventListener(type, listener);
213
+ }
214
+ removeEventListener(type, listener) {
215
+ window.removeEventListener(type, listener);
216
+ }
217
+ }
218
+
219
+ // src/shared/adapters/fetch.adapter.ts
220
+ class BrowserFetchAdapter {
221
+ fetch(input, init) {
222
+ return fetch(input, init);
223
+ }
224
+ }
225
+
226
+ // src/shared/adapters/logger.adapter.ts
227
+ class MessageLoggerAdapter {
228
+ runtime;
229
+ sourceContext;
230
+ options;
231
+ constructor(runtime, sourceContext, options) {
232
+ this.runtime = runtime;
233
+ this.sourceContext = sourceContext;
234
+ this.options = options;
235
+ }
236
+ debug(message, context) {
237
+ this.sendLog("debug", message, context);
238
+ }
239
+ info(message, context) {
240
+ this.sendLog("info", message, context);
241
+ }
242
+ warn(message, context) {
243
+ this.sendLog("warn", message, context);
244
+ }
245
+ error(message, error, context) {
246
+ this.sendLog("error", message, context, error);
247
+ }
248
+ log(level, message, context) {
249
+ this.sendLog(level, message, context);
250
+ }
251
+ sendLog(level, message, context, error) {
252
+ if (this.options?.consoleMirror) {
253
+ const consoleMethod = console[level] || console.log;
254
+ consoleMethod(`[${this.sourceContext}]`, message, context || "", error || "");
255
+ }
256
+ const logMessage = {
257
+ type: "LOG",
258
+ level,
259
+ message,
260
+ context,
261
+ error: error?.message,
262
+ stack: error?.stack,
263
+ source: this.sourceContext,
264
+ timestamp: Date.now()
265
+ };
266
+ this.runtime.sendMessage(logMessage).catch((sendError) => {
267
+ if (this.options?.fallbackToConsole !== false) {
268
+ console[level](`[${this.sourceContext}] ${message}`, context || "", error || "");
269
+ console.warn("Failed to send log message:", sendError);
270
+ }
271
+ });
272
+ }
273
+ }
274
+
275
+ // src/shared/adapters/index.ts
276
+ function createChromeAdapters(context, options) {
277
+ const runtime2 = new ChromeRuntimeAdapter;
278
+ return {
279
+ runtime: runtime2,
280
+ storage: new ChromeStorageAdapter,
281
+ tabs: new ChromeTabsAdapter,
282
+ window: new ChromeWindowAdapter,
283
+ offscreen: new ChromeOffscreenAdapter,
284
+ contextMenus: new ChromeContextMenusAdapter,
285
+ fetch: new BrowserFetchAdapter,
286
+ logger: new MessageLoggerAdapter(runtime2, context, {
287
+ ...options?.consoleMirror !== undefined && { consoleMirror: options.consoleMirror },
288
+ fallbackToConsole: true
289
+ })
290
+ };
291
+ }
292
+
293
+ // src/shared/types/messages.ts
294
+ var ALL_CONTEXTS = [
295
+ "background",
296
+ "content",
297
+ "page",
298
+ "devtools",
299
+ "popup",
300
+ "options",
301
+ "sidepanel",
302
+ "offscreen"
303
+ ];
304
+ var defaultSettings = {
305
+ theme: "auto",
306
+ autoSync: true,
307
+ debugMode: false,
308
+ notifications: true,
309
+ apiEndpoint: "https://api.example.com",
310
+ refreshInterval: 60000
311
+ };
312
+
313
+ // src/shared/lib/errors.ts
314
+ class ExtensionError extends Error {
315
+ code;
316
+ context;
317
+ constructor(message, code, context) {
318
+ super(message);
319
+ this.code = code;
320
+ this.context = context;
321
+ this.name = this.constructor.name;
322
+ Error.captureStackTrace?.(this, this.constructor);
323
+ }
324
+ }
325
+
326
+ class TimeoutError extends ExtensionError {
327
+ timeoutMs;
328
+ constructor(message, timeoutMs, context) {
329
+ super(message, "TIMEOUT_ERROR", { ...context, timeoutMs });
330
+ this.timeoutMs = timeoutMs;
331
+ }
332
+ }
333
+
334
+ class ConnectionError extends ExtensionError {
335
+ constructor(message, context) {
336
+ super(message, "CONNECTION_ERROR", context);
337
+ }
338
+ }
339
+
340
+ class MessageRouterError extends ExtensionError {
341
+ constructor(message, context) {
342
+ super(message, "MESSAGE_ROUTER_ERROR", context);
343
+ }
344
+ }
345
+
346
+ class HandlerError extends ExtensionError {
347
+ messageType;
348
+ constructor(message, messageType, context) {
349
+ super(message, "HANDLER_ERROR", { ...context, messageType });
350
+ this.messageType = messageType;
351
+ }
352
+ }
353
+
354
+ class APIError extends ExtensionError {
355
+ statusCode;
356
+ constructor(message, statusCode, context) {
357
+ super(message, "API_ERROR", { ...context, statusCode });
358
+ this.statusCode = statusCode;
359
+ }
360
+ }
361
+
362
+ class OffscreenError extends ExtensionError {
363
+ constructor(message, context) {
364
+ super(message, "OFFSCREEN_ERROR", context);
365
+ }
366
+ }
367
+
368
+ class ErrorHandler {
369
+ logger;
370
+ constructor(logger2) {
371
+ this.logger = logger2;
372
+ }
373
+ throw(error) {
374
+ this.logger.error(error.message, error, error.context);
375
+ throw error;
376
+ }
377
+ reject(error) {
378
+ this.logger.error(error.message, error, error.context);
379
+ return error;
380
+ }
381
+ wrap(error, message, code, context) {
382
+ const originalError = error instanceof Error ? error : new Error(String(error));
383
+ const wrappedError = new ExtensionError(`${message}: ${originalError.message}`, code, {
384
+ ...context,
385
+ originalError: originalError.message,
386
+ originalStack: originalError.stack
387
+ });
388
+ if (originalError.stack) {
389
+ wrappedError.stack = originalError.stack;
390
+ }
391
+ this.logger.error(wrappedError.message, wrappedError, wrappedError.context);
392
+ return wrappedError;
393
+ }
394
+ }
395
+
396
+ // src/shared/lib/context-specific-helpers.ts
397
+ function createContentScriptHelpers() {
398
+ return {
399
+ getPageInfo() {
400
+ return {
401
+ url: window.location.href,
402
+ title: document.title,
403
+ host: window.location.host,
404
+ pathname: window.location.pathname,
405
+ readyState: document.readyState
406
+ };
407
+ },
408
+ queryElements(selector) {
409
+ const elements = document.querySelectorAll(selector);
410
+ return Array.from(elements).map((el) => ({
411
+ tagName: el.tagName,
412
+ id: el.id,
413
+ className: el.className,
414
+ textContent: el.textContent?.slice(0, 100) || ""
415
+ }));
416
+ },
417
+ getPageMetadata() {
418
+ const metadata = {};
419
+ const metaTags = document.querySelectorAll("meta");
420
+ for (const tag of Array.from(metaTags)) {
421
+ const name = tag.getAttribute("name") || tag.getAttribute("property");
422
+ const content = tag.getAttribute("content");
423
+ if (name && content) {
424
+ metadata[name] = content;
425
+ }
426
+ }
427
+ return metadata;
428
+ },
429
+ injectCSS(css) {
430
+ const styleId = `ext-injected-${Date.now()}`;
431
+ const style = document.createElement("style");
432
+ style.id = styleId;
433
+ style.textContent = css;
434
+ document.head.appendChild(style);
435
+ },
436
+ removeCSS(styleId) {
437
+ const style = document.getElementById(styleId);
438
+ if (style) {
439
+ style.remove();
440
+ }
441
+ }
442
+ };
443
+ }
444
+ function createDevToolsHelpers() {
445
+ return {
446
+ get inspectedTabId() {
447
+ return chrome.devtools?.inspectedWindow?.tabId;
448
+ },
449
+ evalInPage(code) {
450
+ return new Promise((resolve, reject) => {
451
+ if (!chrome.devtools?.inspectedWindow) {
452
+ reject(new Error("DevTools inspectedWindow not available"));
453
+ return;
454
+ }
455
+ chrome.devtools.inspectedWindow.eval(code, (result, error) => {
456
+ if (error) {
457
+ reject(new Error(error.isException ? error.value : "Execution error"));
458
+ } else {
459
+ resolve(result);
460
+ }
461
+ });
462
+ });
463
+ },
464
+ getPageResource(url) {
465
+ return new Promise((resolve, reject) => {
466
+ if (!chrome.devtools?.inspectedWindow) {
467
+ reject(new Error("DevTools inspectedWindow not available"));
468
+ return;
469
+ }
470
+ chrome.devtools.inspectedWindow.getResources((resources) => {
471
+ const resource = resources.find((r) => r.url === url);
472
+ if (!resource) {
473
+ reject(new Error(`Resource not found: ${url}`));
474
+ return;
475
+ }
476
+ resource.getContent((content, encoding) => {
477
+ if (encoding === "base64") {
478
+ resolve(atob(content));
479
+ } else {
480
+ resolve(content);
481
+ }
482
+ });
483
+ });
484
+ });
485
+ },
486
+ reloadInspectedPage(options = {}) {
487
+ if (!chrome.devtools?.inspectedWindow) {
488
+ console.warn("DevTools inspectedWindow not available");
489
+ return;
490
+ }
491
+ chrome.devtools.inspectedWindow.reload(options);
492
+ }
493
+ };
494
+ }
495
+ function createPopupHelpers(adapters) {
496
+ return {
497
+ async getCurrentTab() {
498
+ const tabs2 = await adapters.tabs.query({ active: true, currentWindow: true });
499
+ return tabs2[0];
500
+ },
501
+ closePopup() {
502
+ window.close();
503
+ },
504
+ setDimensions(width, height) {
505
+ document.body.style.width = `${width}px`;
506
+ document.body.style.height = `${height}px`;
507
+ }
508
+ };
509
+ }
510
+ function createOptionsHelpers(adapters) {
511
+ return {
512
+ openInNewTab(path) {
513
+ adapters.tabs.create({ url: path });
514
+ },
515
+ showSaveConfirmation(message = "Settings saved!", duration = 3000) {
516
+ const notification = document.createElement("div");
517
+ notification.textContent = message;
518
+ notification.style.cssText = `
519
+ position: fixed;
520
+ top: 20px;
521
+ right: 20px;
522
+ background: #4caf50;
523
+ color: white;
524
+ padding: 12px 24px;
525
+ border-radius: 4px;
526
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
527
+ z-index: 10000;
528
+ animation: slideIn 0.3s ease;
529
+ `;
530
+ document.body.appendChild(notification);
531
+ setTimeout(() => {
532
+ notification.style.animation = "slideOut 0.3s ease";
533
+ setTimeout(() => notification.remove(), 300);
534
+ }, duration);
535
+ },
536
+ showError(message, duration = 5000) {
537
+ const notification = document.createElement("div");
538
+ notification.textContent = message;
539
+ notification.style.cssText = `
540
+ position: fixed;
541
+ top: 20px;
542
+ right: 20px;
543
+ background: #f44336;
544
+ color: white;
545
+ padding: 12px 24px;
546
+ border-radius: 4px;
547
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
548
+ z-index: 10000;
549
+ animation: slideIn 0.3s ease;
550
+ `;
551
+ document.body.appendChild(notification);
552
+ setTimeout(() => {
553
+ notification.style.animation = "slideOut 0.3s ease";
554
+ setTimeout(() => notification.remove(), 300);
555
+ }, duration);
556
+ }
557
+ };
558
+ }
559
+ function createSidePanelHelpers(adapters) {
560
+ return {
561
+ async getCurrentTab() {
562
+ const tabs2 = await adapters.tabs.query({ active: true, currentWindow: true });
563
+ return tabs2[0];
564
+ },
565
+ isVisible() {
566
+ return document.visibilityState === "visible";
567
+ },
568
+ setWidth(width) {
569
+ document.body.style.width = `${width}px`;
570
+ }
571
+ };
572
+ }
573
+ function createBackgroundHelpers(adapters) {
574
+ return {
575
+ async getAllTabs() {
576
+ return adapters.tabs.query({});
577
+ },
578
+ getExtensionViews(type) {
579
+ return chrome.extension.getViews(type ? { type } : undefined);
580
+ },
581
+ openOptionsPage() {
582
+ adapters.runtime.openOptionsPage();
583
+ },
584
+ setBadge(text, color = "#f44336") {
585
+ chrome.action.setBadgeText({ text });
586
+ chrome.action.setBadgeBackgroundColor({ color });
587
+ },
588
+ clearBadge() {
589
+ chrome.action.setBadgeText({ text: "" });
590
+ }
591
+ };
592
+ }
593
+
594
+ // src/shared/lib/handler-execution-tracker.ts
595
+ class HandlerExecutionTracker {
596
+ executions = new Map;
597
+ isDevelopment;
598
+ constructor() {
599
+ this.isDevelopment = typeof process !== "undefined" && (process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test");
600
+ }
601
+ track(messageId, handlerType) {
602
+ if (!this.isDevelopment)
603
+ return;
604
+ let handlerCounts = this.executions.get(messageId);
605
+ if (!handlerCounts) {
606
+ handlerCounts = new Map;
607
+ this.executions.set(messageId, handlerCounts);
608
+ }
609
+ const count = (handlerCounts.get(handlerType) || 0) + 1;
610
+ handlerCounts.set(handlerType, count);
611
+ if (count > 1) {
612
+ const error = new Error(`\uD83D\uDD34 DOUBLE EXECUTION DETECTED
613
+
614
+ Handler "${handlerType}" executed ${count} times for message ${messageId}.
615
+
616
+ This indicates multiple chrome.runtime.onMessage listeners are registered.
617
+ Common causes:
618
+ 1. Both MessageBus and MessageRouter registered listeners
619
+ 2. createBackground() called multiple times
620
+ 3. Handler registered in multiple places
621
+
622
+ Fix: Ensure only ONE listener is registered. In background scripts,
623
+ use createBackground() instead of getMessageBus().
624
+ `);
625
+ console.error(error);
626
+ console.error("Execution trace for message:", messageId);
627
+ console.error(Array.from(handlerCounts.entries()));
628
+ throw error;
629
+ }
630
+ setTimeout(() => {
631
+ this.executions.delete(messageId);
632
+ }, 5000);
633
+ }
634
+ reset() {
635
+ this.executions.clear();
636
+ }
637
+ getExecutionCount(messageId, handlerType) {
638
+ return this.executions.get(messageId)?.get(handlerType) || 0;
639
+ }
640
+ }
641
+ var globalExecutionTracker = new HandlerExecutionTracker;
642
+
643
+ // src/shared/lib/message-bus.ts
644
+ function isRoutedMessage(value) {
645
+ if (typeof value !== "object" || value === null)
646
+ return false;
647
+ if (!("id" in value) || !("source" in value) || !("targets" in value) || !("payload" in value)) {
648
+ return false;
649
+ }
650
+ return typeof value.id === "string" && typeof value.source === "string" && Array.isArray(value.targets) && typeof value.payload === "object" && value.payload !== null;
651
+ }
652
+ function isRoutedResponse(value) {
653
+ if (typeof value !== "object" || value === null)
654
+ return false;
655
+ if (!("id" in value) || !("success" in value)) {
656
+ return false;
657
+ }
658
+ return typeof value.id === "string" && typeof value.success === "boolean";
659
+ }
660
+
661
+ class MessageBus {
662
+ context;
663
+ adapters;
664
+ helpers;
665
+ pendingRequests = new Map;
666
+ handlers = new Map;
667
+ port = null;
668
+ errorHandler;
669
+ userErrorHandlers = [];
670
+ messageListener = null;
671
+ constructor(context, adapters, options) {
672
+ this.context = context;
673
+ this.adapters = adapters || createChromeAdapters(context);
674
+ this.errorHandler = new ErrorHandler(this.adapters.logger);
675
+ this.helpers = this.createContextHelpers();
676
+ if (!options?.skipListenerSetup) {
677
+ this.setupListeners();
678
+ }
679
+ }
680
+ async send(payload, options) {
681
+ const id = crypto.randomUUID();
682
+ let targets;
683
+ if (options?.target) {
684
+ if (Array.isArray(options.target)) {
685
+ targets = options.target;
686
+ } else {
687
+ targets = [options.target];
688
+ }
689
+ } else {
690
+ const inferredTarget = this.inferTarget(payload.type);
691
+ if (!inferredTarget) {
692
+ throw new Error(`Message type "${payload.type}" is not a framework message. Please provide explicit 'target' option.`);
693
+ }
694
+ targets = Array.isArray(inferredTarget) ? inferredTarget : [inferredTarget];
695
+ }
696
+ const message = {
697
+ id,
698
+ source: this.context,
699
+ targets,
700
+ ...options?.tabId !== undefined && { tabId: options.tabId },
701
+ timestamp: Date.now(),
702
+ payload
703
+ };
704
+ return new Promise((resolve, reject) => {
705
+ const timeoutMs = options?.timeout || 5000;
706
+ const timeout = setTimeout(() => {
707
+ this.pendingRequests.delete(id);
708
+ const error = new TimeoutError(`Message timeout: ${payload.type}`, timeoutMs, {
709
+ messageType: payload.type,
710
+ targets
711
+ });
712
+ this.notifyErrorHandlers(error);
713
+ reject(this.errorHandler.reject(error));
714
+ }, timeoutMs);
715
+ this.pendingRequests.set(id, {
716
+ resolve: (value) => {
717
+ clearTimeout(timeout);
718
+ resolve(value);
719
+ },
720
+ reject: (error) => {
721
+ clearTimeout(timeout);
722
+ reject(error);
723
+ },
724
+ timestamp: Date.now(),
725
+ timeout
726
+ });
727
+ this.sendMessage(message);
728
+ });
729
+ }
730
+ broadcast(payload) {
731
+ const message = {
732
+ id: crypto.randomUUID(),
733
+ source: this.context,
734
+ targets: ALL_CONTEXTS,
735
+ timestamp: Date.now(),
736
+ payload
737
+ };
738
+ this.sendMessage(message);
739
+ }
740
+ on(type, handler) {
741
+ const existing = this.handlers.get(type) || [];
742
+ existing.push(handler);
743
+ this.handlers.set(type, existing);
744
+ }
745
+ registerHandlers(handlers) {
746
+ for (const [type, handler] of Object.entries(handlers)) {
747
+ if (handler) {
748
+ const existing = this.handlers.get(type) || [];
749
+ existing.push(handler);
750
+ this.handlers.set(type, existing);
751
+ }
752
+ }
753
+ }
754
+ onError(handler) {
755
+ this.userErrorHandlers.push(handler);
756
+ }
757
+ async sendToBackground(payload, options) {
758
+ return this.send(payload, { ...options, target: "background" });
759
+ }
760
+ async sendToContentScript(tabId, payload, options) {
761
+ return this.send(payload, { ...options, target: "content", tabId });
762
+ }
763
+ async sendToAllTabs(payload, options) {
764
+ const tabs2 = await this.adapters.tabs.query({});
765
+ return Promise.all(tabs2.map((tab) => tab.id ? this.sendToContentScript(tab.id, payload, options) : Promise.resolve(undefined)));
766
+ }
767
+ async sendToPopup(payload, options) {
768
+ return this.send(payload, { ...options, target: "popup" });
769
+ }
770
+ async sendToOptions(payload, options) {
771
+ return this.send(payload, { ...options, target: "options" });
772
+ }
773
+ async sendToDevTools(payload, options) {
774
+ return this.send(payload, { ...options, target: "devtools" });
775
+ }
776
+ async sendToSidePanel(payload, options) {
777
+ return this.send(payload, { ...options, target: "sidepanel" });
778
+ }
779
+ connect(name) {
780
+ if (this.port) {
781
+ console.warn(`[${this.context}] Port already connected: ${this.port.name}`);
782
+ return;
783
+ }
784
+ this.port = this.adapters.runtime.connect(name);
785
+ this.port.onMessage((message) => {
786
+ if (isRoutedMessage(message) || isRoutedResponse(message)) {
787
+ this.handleMessage(message);
788
+ }
789
+ });
790
+ this.port.onDisconnect(() => {
791
+ this.adapters.logger.warn("Port disconnected", {
792
+ context: this.context,
793
+ portName: name
794
+ });
795
+ this.port = null;
796
+ for (const [id, pending] of this.pendingRequests.entries()) {
797
+ const error = new ConnectionError("Port disconnected", {
798
+ context: this.context,
799
+ portName: name,
800
+ requestId: id
801
+ });
802
+ this.notifyErrorHandlers(error);
803
+ pending.reject(this.errorHandler.reject(error));
804
+ clearTimeout(pending.timeout);
805
+ this.pendingRequests.delete(id);
806
+ }
807
+ });
808
+ }
809
+ disconnect() {
810
+ if (this.port) {
811
+ this.port.disconnect();
812
+ this.port = null;
813
+ }
814
+ }
815
+ destroy() {
816
+ this.disconnect();
817
+ this.handlers.clear();
818
+ for (const pending of this.pendingRequests.values()) {
819
+ clearTimeout(pending.timeout);
820
+ }
821
+ this.pendingRequests.clear();
822
+ if (this.messageListener) {
823
+ this.adapters.runtime.removeMessageListener(this.messageListener);
824
+ }
825
+ }
826
+ setupListeners() {
827
+ this.messageListener = (message, sender, sendResponse) => {
828
+ if (isRoutedMessage(message) || isRoutedResponse(message)) {
829
+ this.handleMessage(message, sender).then((response) => sendResponse(response)).catch((error) => {
830
+ sendResponse({ success: false, error: error.message });
831
+ });
832
+ }
833
+ return true;
834
+ };
835
+ this.adapters.runtime.onMessage(this.messageListener);
836
+ if (this.context === "content" || this.context === "page") {
837
+ this.adapters.window.addEventListener("message", (event) => {
838
+ if (event.source !== window)
839
+ return;
840
+ if (event.data?.__extensionMessage) {
841
+ this.handleMessage(event.data.message);
842
+ }
843
+ });
844
+ }
845
+ }
846
+ async handleMessage(message, _sender) {
847
+ if ("success" in message) {
848
+ const pending = this.pendingRequests.get(message.id);
849
+ if (pending) {
850
+ this.pendingRequests.delete(message.id);
851
+ clearTimeout(pending.timeout);
852
+ if (message.success) {
853
+ pending.resolve(message.data ?? undefined);
854
+ } else {
855
+ const error = new HandlerError(message.error || "Unknown error", "unknown", {
856
+ messageId: message.id
857
+ });
858
+ this.notifyErrorHandlers(error);
859
+ pending.reject(this.errorHandler.reject(error));
860
+ }
861
+ }
862
+ return;
863
+ }
864
+ if (!message.targets.includes(this.context)) {
865
+ if (this.context === "background") {
866
+ return;
867
+ }
868
+ return;
869
+ }
870
+ const handlers = this.handlers.get(message.payload.type);
871
+ if (!handlers || handlers.length === 0) {
872
+ if (message.targets.length === 1) {
873
+ console.warn(`[${this.context}] No handler for message type: ${message.payload.type}`);
874
+ }
875
+ return { success: false, error: "No handler" };
876
+ }
877
+ if (message.targets.length > 1) {
878
+ try {
879
+ globalExecutionTracker.track(message.id, message.payload.type);
880
+ await Promise.all(handlers.map((handler) => handler(message.payload, message)));
881
+ return { success: true, data: undefined, timestamp: Date.now() };
882
+ } catch (error) {
883
+ return {
884
+ success: false,
885
+ error: error instanceof Error ? error.message : "Unknown error",
886
+ timestamp: Date.now()
887
+ };
888
+ }
889
+ }
890
+ if (message.payload.type === "LOG") {
891
+ try {
892
+ globalExecutionTracker.track(message.id, message.payload.type);
893
+ await Promise.all(handlers.map((handler) => handler(message.payload, message)));
894
+ const response = {
895
+ id: message.id,
896
+ success: true,
897
+ timestamp: Date.now()
898
+ };
899
+ this.sendResponse(message, response);
900
+ return response;
901
+ } catch (error) {
902
+ const response = {
903
+ id: message.id,
904
+ success: false,
905
+ error: error instanceof Error ? error.message : "Unknown error",
906
+ timestamp: Date.now()
907
+ };
908
+ this.sendResponse(message, response);
909
+ return response;
910
+ }
911
+ }
912
+ try {
913
+ globalExecutionTracker.track(message.id, message.payload.type);
914
+ const handler = handlers[0];
915
+ if (!handler) {
916
+ throw new Error(`Handler not found for ${message.payload.type}`);
917
+ }
918
+ const data = await handler(message.payload, message);
919
+ const response = {
920
+ id: message.id,
921
+ success: true,
922
+ data,
923
+ timestamp: Date.now()
924
+ };
925
+ this.sendResponse(message, response);
926
+ return response;
927
+ } catch (error) {
928
+ const response = {
929
+ id: message.id,
930
+ success: false,
931
+ error: error instanceof Error ? error.message : "Unknown error",
932
+ timestamp: Date.now()
933
+ };
934
+ this.sendResponse(message, response);
935
+ return response;
936
+ }
937
+ }
938
+ sendMessage(message) {
939
+ if (this.context === "content" && message.targets.includes("page")) {
940
+ this.adapters.window.postMessage({ __extensionMessage: true, message }, "*");
941
+ } else if (this.context === "page") {
942
+ this.adapters.window.postMessage({ __extensionMessage: true, message }, "*");
943
+ } else if (this.port) {
944
+ this.port.postMessage(message);
945
+ } else {
946
+ this.adapters.runtime.sendMessage(message);
947
+ }
948
+ }
949
+ sendResponse(request, response) {
950
+ if (this.context === "content" && request.source === "page") {
951
+ this.adapters.window.postMessage({ __extensionMessage: true, message: response }, "*");
952
+ } else if (this.context === "page" && request.source === "content") {
953
+ this.adapters.window.postMessage({ __extensionMessage: true, message: response }, "*");
954
+ } else if (this.port && (this.context === "devtools" || this.context === "content")) {
955
+ this.port.postMessage(response);
956
+ } else {
957
+ this.adapters.runtime.sendMessage(response);
958
+ }
959
+ }
960
+ inferTarget(type) {
961
+ const handlers = {
962
+ DOM_QUERY: "content",
963
+ DOM_UPDATE: "content",
964
+ DOM_INSERT: "content",
965
+ DOM_REMOVE: "content",
966
+ PAGE_EVAL: "page",
967
+ PAGE_GET_VAR: "page",
968
+ PAGE_CALL_FN: "page",
969
+ PAGE_SET_VAR: "page",
970
+ API_REQUEST: "background",
971
+ API_BATCH: "background",
972
+ CLIPBOARD_WRITE: "offscreen",
973
+ CLIPBOARD_WRITE_HTML: "offscreen",
974
+ CLIPBOARD_WRITE_RICH: "offscreen",
975
+ CLIPBOARD_READ: "offscreen",
976
+ CONTEXT_MENU_CREATE: "background",
977
+ CONTEXT_MENU_REMOVE: "background",
978
+ STATE_SYNC: ALL_CONTEXTS,
979
+ TAB_QUERY: "background",
980
+ TAB_GET_CURRENT: "background",
981
+ TAB_RELOAD: "background",
982
+ LOG: "background",
983
+ LOGS_GET: "background",
984
+ LOGS_CLEAR: "background",
985
+ LOGS_EXPORT: "background"
986
+ };
987
+ function isHandlerKey(key) {
988
+ return key in handlers;
989
+ }
990
+ if (isHandlerKey(type)) {
991
+ return handlers[type];
992
+ }
993
+ return;
994
+ }
995
+ createContextHelpers() {
996
+ switch (this.context) {
997
+ case "content":
998
+ return createContentScriptHelpers();
999
+ case "devtools":
1000
+ return createDevToolsHelpers();
1001
+ case "popup":
1002
+ return createPopupHelpers(this.adapters);
1003
+ case "options":
1004
+ return createOptionsHelpers(this.adapters);
1005
+ case "sidepanel":
1006
+ return createSidePanelHelpers(this.adapters);
1007
+ case "background":
1008
+ return createBackgroundHelpers(this.adapters);
1009
+ default:
1010
+ return {};
1011
+ }
1012
+ }
1013
+ notifyErrorHandlers(error) {
1014
+ for (const handler of this.userErrorHandlers) {
1015
+ try {
1016
+ handler(error, this);
1017
+ } catch (handlerError) {
1018
+ console.error(`[${this.context}] Error in error handler:`, handlerError);
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ function getMessageBus(context, adapters, options) {
1024
+ return new MessageBus(context, adapters, options);
1025
+ }
1026
+
1027
+ // src/background/message-router.ts
1028
+ class MessageRouter {
1029
+ static instanceExists = false;
1030
+ bus;
1031
+ errorHandler;
1032
+ contentPorts = new Map;
1033
+ devtoolsPorts = new Map;
1034
+ popupPort = null;
1035
+ optionsPort = null;
1036
+ offscreenPort = null;
1037
+ sidePanelPort = null;
1038
+ constructor(bus) {
1039
+ if (MessageRouter.instanceExists) {
1040
+ throw new Error(`\uD83D\uDD34 MessageRouter already exists!
1041
+
1042
+ ` + `Only ONE MessageRouter can exist in the background context.
1043
+ ` + `Multiple MessageRouter instances will cause handlers to execute multiple times.
1044
+
1045
+ ` + `Fix: Ensure createBackground() is only called once at application startup.
1046
+ ` + 'Do not call getMessageBus("background") separately - use the bus returned by createBackground().');
1047
+ }
1048
+ MessageRouter.instanceExists = true;
1049
+ this.bus = bus || getMessageBus("background");
1050
+ this.errorHandler = new ErrorHandler(this.bus.adapters.logger);
1051
+ this.setupPortConnections();
1052
+ this.setupTabListeners();
1053
+ this.setupMessageHandlers();
1054
+ }
1055
+ setupPortConnections() {
1056
+ this.bus.adapters.runtime.onConnect((port) => {
1057
+ this.bus.adapters.logger.debug("Port connected", { port: port.name });
1058
+ const [context, tabIdStr] = port.name.split("-");
1059
+ switch (context) {
1060
+ case "content": {
1061
+ const contentTabId = Number.parseInt(tabIdStr || "0", 10);
1062
+ if (!Number.isNaN(contentTabId)) {
1063
+ this.contentPorts.set(contentTabId, port);
1064
+ port.onDisconnect(() => {
1065
+ this.bus.adapters.logger.debug("Content port disconnected", {
1066
+ tabId: contentTabId
1067
+ });
1068
+ this.contentPorts.delete(contentTabId);
1069
+ });
1070
+ }
1071
+ break;
1072
+ }
1073
+ case "devtools": {
1074
+ const devtoolsTabId = Number.parseInt(tabIdStr || "0", 10);
1075
+ if (!Number.isNaN(devtoolsTabId)) {
1076
+ this.devtoolsPorts.set(devtoolsTabId, port);
1077
+ port.onDisconnect(() => {
1078
+ this.bus.adapters.logger.debug("DevTools port disconnected", {
1079
+ tabId: devtoolsTabId
1080
+ });
1081
+ this.devtoolsPorts.delete(devtoolsTabId);
1082
+ });
1083
+ }
1084
+ break;
1085
+ }
1086
+ case "popup":
1087
+ this.popupPort = port;
1088
+ port.onDisconnect(() => {
1089
+ this.bus.adapters.logger.debug("Popup disconnected");
1090
+ this.popupPort = null;
1091
+ });
1092
+ break;
1093
+ case "options":
1094
+ this.optionsPort = port;
1095
+ port.onDisconnect(() => {
1096
+ this.bus.adapters.logger.debug("Options disconnected");
1097
+ this.optionsPort = null;
1098
+ });
1099
+ break;
1100
+ case "offscreen":
1101
+ this.offscreenPort = port;
1102
+ port.onDisconnect(() => {
1103
+ this.bus.adapters.logger.debug("Offscreen disconnected");
1104
+ this.offscreenPort = null;
1105
+ });
1106
+ break;
1107
+ case "sidepanel":
1108
+ this.sidePanelPort = port;
1109
+ port.onDisconnect(() => {
1110
+ this.bus.adapters.logger.debug("Side panel disconnected");
1111
+ this.sidePanelPort = null;
1112
+ });
1113
+ break;
1114
+ }
1115
+ port.onMessage((message) => {
1116
+ if (isRoutedResponse(message)) {
1117
+ this.routeResponse(message);
1118
+ } else if (isRoutedMessage(message)) {
1119
+ this.routeMessage(message);
1120
+ }
1121
+ });
1122
+ });
1123
+ }
1124
+ setupTabListeners() {
1125
+ this.bus.adapters.tabs.onRemoved((tabId) => {
1126
+ this.bus.adapters.logger.debug("Tab removed, cleaning up ports", {
1127
+ tabId
1128
+ });
1129
+ this.contentPorts.delete(tabId);
1130
+ this.devtoolsPorts.delete(tabId);
1131
+ });
1132
+ this.bus.adapters.tabs.onUpdated((tabId, changeInfo, tab) => {
1133
+ if (changeInfo.status === "complete") {
1134
+ this.bus.adapters.logger.debug("Tab loaded", { tabId, url: tab.url });
1135
+ }
1136
+ });
1137
+ }
1138
+ setupMessageHandlers() {
1139
+ this.bus.adapters.runtime.onMessage((message, _sender, sendResponse) => {
1140
+ if (isRoutedResponse(message)) {
1141
+ this.routeResponse(message);
1142
+ } else if (isRoutedMessage(message)) {
1143
+ this.routeMessage(message).then(sendResponse);
1144
+ }
1145
+ return true;
1146
+ });
1147
+ }
1148
+ async routeMessage(message) {
1149
+ this.bus.adapters.logger.debug("Routing message", {
1150
+ type: message.payload.type,
1151
+ source: message.source,
1152
+ targets: message.targets,
1153
+ tabId: message.tabId
1154
+ });
1155
+ const results = [];
1156
+ for (const target of message.targets) {
1157
+ const result = await this.routeToSingleTarget(message, target);
1158
+ results.push(result);
1159
+ }
1160
+ return message.targets.length === 1 ? results[0] : results;
1161
+ }
1162
+ async routeToSingleTarget(message, target) {
1163
+ switch (target) {
1164
+ case "background":
1165
+ return this.bus.handleMessage(message);
1166
+ case "content": {
1167
+ if (!message.tabId) {
1168
+ this.bus.adapters.logger.warn("Content target requires tabId", {
1169
+ messageType: message.payload.type
1170
+ });
1171
+ return { success: false, error: "tabId required for content target" };
1172
+ }
1173
+ const contentPort = this.contentPorts.get(message.tabId);
1174
+ if (contentPort) {
1175
+ contentPort.postMessage(message);
1176
+ return;
1177
+ }
1178
+ this.bus.adapters.logger.warn("No content script port for tab", {
1179
+ tabId: message.tabId,
1180
+ messageType: message.payload.type
1181
+ });
1182
+ return { success: false, error: "Content script not connected" };
1183
+ }
1184
+ case "devtools": {
1185
+ if (!message.tabId) {
1186
+ this.bus.adapters.logger.warn("DevTools target requires tabId", {
1187
+ messageType: message.payload.type
1188
+ });
1189
+ return {
1190
+ success: false,
1191
+ error: "tabId required for devtools target"
1192
+ };
1193
+ }
1194
+ const devtoolsPort = this.devtoolsPorts.get(message.tabId);
1195
+ if (devtoolsPort) {
1196
+ devtoolsPort.postMessage(message);
1197
+ return;
1198
+ }
1199
+ this.bus.adapters.logger.warn("No DevTools port for tab", {
1200
+ tabId: message.tabId,
1201
+ messageType: message.payload.type
1202
+ });
1203
+ return { success: false, error: "DevTools not connected" };
1204
+ }
1205
+ case "popup":
1206
+ if (this.popupPort) {
1207
+ this.popupPort.postMessage(message);
1208
+ return;
1209
+ }
1210
+ this.bus.adapters.logger.warn("Popup not connected", {
1211
+ messageType: message.payload.type
1212
+ });
1213
+ return { success: false, error: "Popup not connected" };
1214
+ case "options":
1215
+ if (this.optionsPort) {
1216
+ this.optionsPort.postMessage(message);
1217
+ return;
1218
+ }
1219
+ this.bus.adapters.logger.warn("Options not connected", {
1220
+ messageType: message.payload.type
1221
+ });
1222
+ return { success: false, error: "Options not connected" };
1223
+ case "offscreen":
1224
+ if (this.offscreenPort) {
1225
+ this.offscreenPort.postMessage(message);
1226
+ return;
1227
+ }
1228
+ this.bus.adapters.logger.warn("Offscreen not connected", {
1229
+ messageType: message.payload.type
1230
+ });
1231
+ return { success: false, error: "Offscreen not connected" };
1232
+ case "page": {
1233
+ if (!message.tabId) {
1234
+ this.bus.adapters.logger.warn("Page target requires tabId", {
1235
+ messageType: message.payload.type
1236
+ });
1237
+ return { success: false, error: "tabId required for page target" };
1238
+ }
1239
+ const contentPortForPage = this.contentPorts.get(message.tabId);
1240
+ if (contentPortForPage) {
1241
+ contentPortForPage.postMessage(message);
1242
+ return;
1243
+ }
1244
+ this.bus.adapters.logger.warn("No content script to forward to page", {
1245
+ tabId: message.tabId,
1246
+ messageType: message.payload.type
1247
+ });
1248
+ return { success: false, error: "Content script not connected" };
1249
+ }
1250
+ case "sidepanel":
1251
+ if (this.sidePanelPort) {
1252
+ this.sidePanelPort.postMessage(message);
1253
+ return;
1254
+ }
1255
+ this.bus.adapters.logger.warn("Side panel not connected", {
1256
+ messageType: message.payload.type
1257
+ });
1258
+ return { success: false, error: "Side panel not connected" };
1259
+ default:
1260
+ this.bus.adapters.logger.warn("Unknown target context", {
1261
+ target,
1262
+ messageType: message.payload.type
1263
+ });
1264
+ return { success: false, error: `Unknown target context: ${target}` };
1265
+ }
1266
+ }
1267
+ routeResponse(response) {
1268
+ this.bus.adapters.logger.debug("Routing response", {
1269
+ messageId: response.id
1270
+ });
1271
+ }
1272
+ async sendToTab(tabId, message) {
1273
+ const port = this.contentPorts.get(tabId);
1274
+ if (port) {
1275
+ port.postMessage(message);
1276
+ } else {
1277
+ this.errorHandler.throw(new MessageRouterError("No content script connected to tab", {
1278
+ tabId,
1279
+ messageType: message.payload.type
1280
+ }));
1281
+ }
1282
+ }
1283
+ broadcastToAll(message) {
1284
+ this.routeMessage(message);
1285
+ }
1286
+ isContentScriptConnected(tabId) {
1287
+ return this.contentPorts.has(tabId);
1288
+ }
1289
+ getConnectedTabs() {
1290
+ return Array.from(this.contentPorts.keys());
1291
+ }
1292
+ static resetInstance() {
1293
+ MessageRouter.instanceExists = false;
1294
+ }
1295
+ }
1296
+
1297
+ // src/background/index.ts
1298
+ function createBackground() {
1299
+ const bus = getMessageBus("background", undefined, { skipListenerSetup: true });
1300
+ new MessageRouter(bus);
1301
+ return bus;
1302
+ }
1303
+ export {
1304
+ getMessageBus,
1305
+ createBackground,
1306
+ MessageRouter
1307
+ };
1308
+
1309
+ //# debugId=E484BD2A05E1E50F64756E2164756E21