@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,298 @@
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
+ export {
293
+ createChromeAdapters,
294
+ MessageLoggerAdapter,
295
+ BrowserFetchAdapter
296
+ };
297
+
298
+ //# debugId=88CAE80DB6C0B30564756E2164756E21
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/shared/adapters/chrome/context-menus.chrome.ts", "../src/shared/adapters/chrome/offscreen.chrome.ts", "../src/shared/adapters/chrome/runtime.chrome.ts", "../src/shared/adapters/chrome/storage.chrome.ts", "../src/shared/adapters/chrome/tabs.chrome.ts", "../src/shared/adapters/chrome/window.chrome.ts", "../src/shared/adapters/fetch.adapter.ts", "../src/shared/adapters/logger.adapter.ts", "../src/shared/adapters/index.ts"],
4
+ "sourcesContent": [
5
+ "// Chrome context menus adapter implementation\n\nimport type { ContextMenusAdapter } from \"../context-menus.adapter\";\n\nexport class ChromeContextMenusAdapter implements ContextMenusAdapter {\n async create(createProperties: chrome.contextMenus.CreateProperties): Promise<void> {\n return new Promise((resolve, reject) => {\n chrome.contextMenus.create(createProperties, () => {\n if (chrome.runtime.lastError) {\n reject(new Error(chrome.runtime.lastError.message));\n } else {\n resolve();\n }\n });\n });\n }\n\n async update(id: string, updateProperties: chrome.contextMenus.UpdateProperties): Promise<void> {\n await chrome.contextMenus.update(id, updateProperties);\n }\n\n async remove(id: string): Promise<void> {\n await chrome.contextMenus.remove(id);\n }\n\n async removeAll(): Promise<void> {\n await chrome.contextMenus.removeAll();\n }\n\n onClicked(\n callback: (info: chrome.contextMenus.OnClickData, tab?: chrome.tabs.Tab) => void\n ): void {\n chrome.contextMenus.onClicked.addListener(callback);\n }\n}\n",
6
+ "// Chrome offscreen adapter implementation\n\nimport type { CreateOffscreenDocumentParameters, OffscreenAdapter } from \"../offscreen.adapter\";\n\nexport class ChromeOffscreenAdapter implements OffscreenAdapter {\n async createDocument(parameters: CreateOffscreenDocumentParameters): Promise<void> {\n await chrome.offscreen.createDocument({\n url: parameters.url,\n reasons: parameters.reasons as chrome.offscreen.Reason[],\n justification: parameters.justification,\n });\n }\n\n async closeDocument(): Promise<void> {\n await chrome.offscreen.closeDocument();\n }\n\n async hasDocument(): Promise<boolean> {\n // Chrome doesn't provide a direct API, so we query for offscreen contexts\n const existingContexts = await chrome.runtime.getContexts({\n contextTypes: [\"OFFSCREEN_DOCUMENT\" as chrome.runtime.ContextType],\n });\n return existingContexts.length > 0;\n }\n}\n",
7
+ "// Chrome runtime adapter implementation\n\nimport type { MessageSender, PortAdapter, RuntimeAdapter } from \"../runtime.adapter\";\n\ntype MessageListener = (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n) => undefined | boolean;\n\ntype ChromeMessageListener = (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n sendResponse: (response?: unknown) => void\n) => undefined | boolean;\n\nexport class ChromeRuntimeAdapter implements RuntimeAdapter {\n private messageListeners = new Map<MessageListener, ChromeMessageListener>();\n private static listenerCount = 0;\n\n sendMessage<T>(message: T): Promise<unknown> {\n return chrome.runtime.sendMessage(message);\n }\n\n onMessage(\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ): void {\n const wrappedCallback = (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n sendResponse: (response?: unknown) => void\n ) => {\n const mappedSender: MessageSender = {\n ...(sender.tab && {\n tab: {\n id: sender.tab.id ?? 0,\n url: sender.tab.url ?? \"\",\n title: sender.tab.title ?? \"\",\n },\n }),\n ...(sender.frameId !== undefined && { frameId: sender.frameId }),\n ...(sender.url && { url: sender.url }),\n };\n return callback(message, mappedSender, sendResponse);\n };\n\n this.messageListeners.set(callback, wrappedCallback);\n // Chrome's listener signature uses void | boolean, ours uses undefined | boolean\n // These are compatible - undefined is assignable to void for return types\n chrome.runtime.onMessage.addListener(\n wrappedCallback as (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n sendResponse: (response?: unknown) => void\n ) => undefined | boolean\n );\n\n // Track listener count and warn if multiple listeners registered\n ChromeRuntimeAdapter.listenerCount++;\n\n if (ChromeRuntimeAdapter.listenerCount > 1) {\n console.warn(\n `⚠️ WARNING: ${ChromeRuntimeAdapter.listenerCount} chrome.runtime.onMessage listeners registered!\\n\\nMultiple listeners will cause message handlers to execute multiple times.\\nThis is usually caused by:\\n 1. Creating both MessageBus and MessageRouter with separate listeners\\n 2. Calling createBackground() multiple times\\n 3. Calling getMessageBus('background') after createBackground()\\n\\nFix: In background scripts, use createBackground() ONCE at startup.\\nDo not call getMessageBus('background') separately.`\n );\n }\n }\n\n removeMessageListener(\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ): void {\n const wrappedCallback = this.messageListeners.get(callback);\n if (wrappedCallback) {\n // Type-safe cast: wrappedCallback is stored with compatible signature\n chrome.runtime.onMessage.removeListener(\n wrappedCallback as (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n sendResponse: (response?: unknown) => void\n ) => undefined | boolean\n );\n this.messageListeners.delete(callback);\n\n // Decrement listener count\n ChromeRuntimeAdapter.listenerCount = Math.max(0, ChromeRuntimeAdapter.listenerCount - 1);\n }\n }\n\n connect(name: string): PortAdapter {\n const port = chrome.runtime.connect({ name });\n return new ChromePortAdapter(port);\n }\n\n onConnect(callback: (port: PortAdapter) => void): void {\n chrome.runtime.onConnect.addListener((port) => {\n callback(new ChromePortAdapter(port));\n });\n }\n\n getURL(path: string): string {\n return chrome.runtime.getURL(path);\n }\n\n getId(): string {\n return chrome.runtime.id;\n }\n\n openOptionsPage(): void {\n chrome.runtime.openOptionsPage();\n }\n}\n\nclass ChromePortAdapter implements PortAdapter {\n private listeners = {\n message: new Set<(message: unknown) => void>(),\n disconnect: new Set<() => void>(),\n };\n\n constructor(private port: chrome.runtime.Port) {\n // Set up Chrome port listeners\n this.port.onMessage.addListener((message) => {\n for (const callback of this.listeners.message) {\n callback(message);\n }\n });\n\n this.port.onDisconnect.addListener(() => {\n for (const callback of this.listeners.disconnect) {\n callback();\n }\n });\n }\n\n get name(): string {\n return this.port.name;\n }\n\n postMessage(message: unknown): void {\n this.port.postMessage(message);\n }\n\n onMessage(callback: (message: unknown) => void): void {\n this.listeners.message.add(callback);\n }\n\n onDisconnect(callback: () => void): void {\n this.listeners.disconnect.add(callback);\n }\n\n disconnect(): void {\n this.port.disconnect();\n }\n}\n",
8
+ "// Chrome storage adapter implementation\n\nimport type { StorageAdapter, StorageChanges } from \"../storage.adapter\";\n\nexport class ChromeStorageAdapter implements StorageAdapter {\n async get<T = Record<string, unknown>>(keys: string | string[] | null): Promise<T> {\n return (await chrome.storage.local.get(keys)) as T;\n }\n\n async set(items: Record<string, unknown>): Promise<void> {\n await chrome.storage.local.set(items);\n }\n\n async remove(keys: string | string[]): Promise<void> {\n await chrome.storage.local.remove(keys);\n }\n\n async clear(): Promise<void> {\n await chrome.storage.local.clear();\n }\n\n onChanged(callback: (changes: StorageChanges, areaName: string) => void): void {\n chrome.storage.onChanged.addListener((changes, areaName) => {\n const mappedChanges: StorageChanges = {};\n for (const [key, change] of Object.entries(changes)) {\n mappedChanges[key] = {\n oldValue: change.oldValue,\n newValue: change.newValue,\n };\n }\n callback(mappedChanges, areaName);\n });\n }\n}\n",
9
+ "// Chrome tabs adapter implementation\n\nimport type { TabsAdapter } from \"../tabs.adapter\";\n\nexport class ChromeTabsAdapter implements TabsAdapter {\n async query(queryInfo: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> {\n return chrome.tabs.query(queryInfo);\n }\n\n async get(tabId: number): Promise<chrome.tabs.Tab> {\n return chrome.tabs.get(tabId);\n }\n\n async sendMessage(tabId: number, message: unknown): Promise<unknown> {\n return chrome.tabs.sendMessage(tabId, message);\n }\n\n async reload(tabId: number, reloadProperties?: { bypassCache?: boolean }): Promise<void> {\n await chrome.tabs.reload(tabId, reloadProperties);\n }\n\n onRemoved(callback: (tabId: number, removeInfo: chrome.tabs.TabRemoveInfo) => void): void {\n chrome.tabs.onRemoved.addListener(callback);\n }\n\n onUpdated(\n callback: (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => void\n ): void {\n chrome.tabs.onUpdated.addListener(callback);\n }\n\n onActivated(callback: (activeInfo: { tabId: number; windowId: number }) => void): void {\n chrome.tabs.onActivated.addListener(callback);\n }\n\n async create(createProperties: chrome.tabs.CreateProperties): Promise<chrome.tabs.Tab> {\n return chrome.tabs.create(createProperties);\n }\n}\n",
10
+ "// Chrome window adapter implementation\n\nimport type { WindowAdapter } from \"../window.adapter\";\n\nexport class ChromeWindowAdapter implements WindowAdapter {\n postMessage(message: unknown, targetOrigin: string): void {\n window.postMessage(message, targetOrigin);\n }\n\n addEventListener(type: \"message\", listener: (event: MessageEvent) => void): void {\n window.addEventListener(type, listener as EventListener);\n }\n\n removeEventListener(type: \"message\", listener: (event: MessageEvent) => void): void {\n window.removeEventListener(type, listener as EventListener);\n }\n}\n",
11
+ "// Fetch adapter interface (wraps fetch API)\n\nexport interface FetchAdapter {\n fetch(input: string | URL, init?: RequestInit): Promise<Response>;\n}\n\nexport class BrowserFetchAdapter implements FetchAdapter {\n fetch(input: string | URL, init?: RequestInit): Promise<Response> {\n return fetch(input, init);\n }\n}\n",
12
+ "import type { Context, LogLevel } from \"../types/messages\";\n// Logger adapter interface (message-based centralized logging)\nimport type { RuntimeAdapter } from \"./runtime.adapter\";\n\nexport interface LoggerAdapter {\n /**\n * Debug-level logging (verbose, development info)\n */\n debug(message: string, context?: Record<string, unknown>): void;\n\n /**\n * Info-level logging (general information)\n */\n info(message: string, context?: Record<string, unknown>): void;\n\n /**\n * Warning-level logging (non-critical issues)\n */\n warn(message: string, context?: Record<string, unknown>): void;\n\n /**\n * Error-level logging (errors and exceptions)\n */\n error(message: string, error?: Error, context?: Record<string, unknown>): void;\n\n /**\n * Log with explicit level\n */\n log(level: LogLevel, message: string, context?: Record<string, unknown>): void;\n}\n\nexport interface MessageLoggerOptions {\n consoleMirror?: boolean; // Also log to console (for development)\n fallbackToConsole?: boolean; // Log to console if message send fails (default: true)\n}\n\n/**\n * Message-based logger that sends LOG messages to background LogStore\n * Uses RuntimeAdapter directly to avoid circular dependency with MessageBus\n */\nexport class MessageLoggerAdapter implements LoggerAdapter {\n constructor(\n private runtime: RuntimeAdapter,\n private sourceContext: Context,\n private options?: MessageLoggerOptions\n ) {}\n\n debug(message: string, context?: Record<string, unknown>): void {\n this.sendLog(\"debug\", message, context);\n }\n\n info(message: string, context?: Record<string, unknown>): void {\n this.sendLog(\"info\", message, context);\n }\n\n warn(message: string, context?: Record<string, unknown>): void {\n this.sendLog(\"warn\", message, context);\n }\n\n error(message: string, error?: Error, context?: Record<string, unknown>): void {\n this.sendLog(\"error\", message, context, error);\n }\n\n log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n this.sendLog(level, message, context);\n }\n\n private sendLog(\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n error?: Error\n ): void {\n // Optional console mirror for development\n if (this.options?.consoleMirror) {\n const consoleMethod = console[level] || console.log;\n consoleMethod(`[${this.sourceContext}]`, message, context || \"\", error || \"\");\n }\n\n // Send LOG message to background (fire-and-forget)\n const logMessage = {\n type: \"LOG\" as const,\n level,\n message,\n context,\n error: error?.message,\n stack: error?.stack,\n source: this.sourceContext,\n timestamp: Date.now(),\n };\n\n // Use runtime.sendMessage for fire-and-forget messaging\n this.runtime.sendMessage(logMessage).catch((sendError) => {\n // Fallback to console if messaging fails\n if (this.options?.fallbackToConsole !== false) {\n console[level](`[${this.sourceContext}] ${message}`, context || \"\", error || \"\");\n console.warn(\"Failed to send log message:\", sendError);\n }\n });\n }\n}\n",
13
+ "// Adapter factory and exports\n\nimport { ChromeContextMenusAdapter } from \"./chrome/context-menus.chrome\";\nimport { ChromeOffscreenAdapter } from \"./chrome/offscreen.chrome\";\nimport { ChromeRuntimeAdapter } from \"./chrome/runtime.chrome\";\nimport { ChromeStorageAdapter } from \"./chrome/storage.chrome\";\nimport { ChromeTabsAdapter } from \"./chrome/tabs.chrome\";\nimport { ChromeWindowAdapter } from \"./chrome/window.chrome\";\nimport { BrowserFetchAdapter } from \"./fetch.adapter\";\nimport { MessageLoggerAdapter } from \"./logger.adapter\";\n\nimport type { Context } from \"../types/messages\";\nimport type { ContextMenusAdapter } from \"./context-menus.adapter\";\nimport type { FetchAdapter } from \"./fetch.adapter\";\nimport type { LoggerAdapter } from \"./logger.adapter\";\nimport type { OffscreenAdapter } from \"./offscreen.adapter\";\nimport type { RuntimeAdapter } from \"./runtime.adapter\";\nimport type { StorageAdapter } from \"./storage.adapter\";\nimport type { TabsAdapter } from \"./tabs.adapter\";\nimport type { WindowAdapter } from \"./window.adapter\";\n\nexport interface ExtensionAdapters {\n runtime: RuntimeAdapter;\n storage: StorageAdapter;\n tabs: TabsAdapter;\n window: WindowAdapter;\n offscreen: OffscreenAdapter;\n contextMenus: ContextMenusAdapter;\n fetch: FetchAdapter;\n logger: LoggerAdapter;\n}\n\nexport interface CreateChromeAdaptersOptions {\n consoleMirror?: boolean; // Mirror logs to console for development\n}\n\n/**\n * Create Chrome-specific adapters with context\n */\nexport function createChromeAdapters(\n context: Context,\n options?: CreateChromeAdaptersOptions\n): ExtensionAdapters {\n const runtime = new ChromeRuntimeAdapter();\n\n return {\n runtime,\n storage: new ChromeStorageAdapter(),\n tabs: new ChromeTabsAdapter(),\n window: new ChromeWindowAdapter(),\n offscreen: new ChromeOffscreenAdapter(),\n contextMenus: new ChromeContextMenusAdapter(),\n fetch: new BrowserFetchAdapter(),\n logger: new MessageLoggerAdapter(runtime, context, {\n ...(options?.consoleMirror !== undefined && { consoleMirror: options.consoleMirror }),\n fallbackToConsole: true,\n }),\n };\n}\n\n// Re-export types\nexport * from \"./runtime.adapter\";\nexport * from \"./storage.adapter\";\nexport * from \"./tabs.adapter\";\nexport * from \"./window.adapter\";\nexport * from \"./offscreen.adapter\";\nexport * from \"./context-menus.adapter\";\nexport * from \"./fetch.adapter\";\nexport * from \"./logger.adapter\";\n"
14
+ ],
15
+ "mappings": "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",
16
+ "debugId": "88CAE80DB6C0B30564756E2164756E21",
17
+ "names": []
18
+ }
@@ -0,0 +1,44 @@
1
+ import type { Context, LogLevel } from "../types/messages";
2
+ import type { RuntimeAdapter } from "./runtime.adapter";
3
+ export interface LoggerAdapter {
4
+ /**
5
+ * Debug-level logging (verbose, development info)
6
+ */
7
+ debug(message: string, context?: Record<string, unknown>): void;
8
+ /**
9
+ * Info-level logging (general information)
10
+ */
11
+ info(message: string, context?: Record<string, unknown>): void;
12
+ /**
13
+ * Warning-level logging (non-critical issues)
14
+ */
15
+ warn(message: string, context?: Record<string, unknown>): void;
16
+ /**
17
+ * Error-level logging (errors and exceptions)
18
+ */
19
+ error(message: string, error?: Error, context?: Record<string, unknown>): void;
20
+ /**
21
+ * Log with explicit level
22
+ */
23
+ log(level: LogLevel, message: string, context?: Record<string, unknown>): void;
24
+ }
25
+ export interface MessageLoggerOptions {
26
+ consoleMirror?: boolean;
27
+ fallbackToConsole?: boolean;
28
+ }
29
+ /**
30
+ * Message-based logger that sends LOG messages to background LogStore
31
+ * Uses RuntimeAdapter directly to avoid circular dependency with MessageBus
32
+ */
33
+ export declare class MessageLoggerAdapter implements LoggerAdapter {
34
+ private runtime;
35
+ private sourceContext;
36
+ private options?;
37
+ constructor(runtime: RuntimeAdapter, sourceContext: Context, options?: MessageLoggerOptions | undefined);
38
+ debug(message: string, context?: Record<string, unknown>): void;
39
+ info(message: string, context?: Record<string, unknown>): void;
40
+ warn(message: string, context?: Record<string, unknown>): void;
41
+ error(message: string, error?: Error, context?: Record<string, unknown>): void;
42
+ log(level: LogLevel, message: string, context?: Record<string, unknown>): void;
43
+ private sendLog;
44
+ }
@@ -0,0 +1,20 @@
1
+ export interface OffscreenAdapter {
2
+ /**
3
+ * Create offscreen document
4
+ */
5
+ createDocument(parameters: CreateOffscreenDocumentParameters): Promise<void>;
6
+ /**
7
+ * Close offscreen document
8
+ */
9
+ closeDocument(): Promise<void>;
10
+ /**
11
+ * Check if offscreen document exists
12
+ */
13
+ hasDocument(): Promise<boolean>;
14
+ }
15
+ export interface CreateOffscreenDocumentParameters {
16
+ url: string;
17
+ reasons: OffscreenReason[];
18
+ justification: string;
19
+ }
20
+ export type OffscreenReason = "AUDIO_PLAYBACK" | "BLOBS" | "CLIPBOARD" | "DOM_PARSER" | "DOM_SCRAPING" | "IFRAME_SCRIPTING" | "LOCAL_STORAGE" | "MATCH_MEDIA" | "TESTING" | "USER_MEDIA" | "WEB_RTC" | "WORKERS";
@@ -0,0 +1,66 @@
1
+ export interface RuntimeAdapter {
2
+ /**
3
+ * Send a one-off message to another context
4
+ */
5
+ sendMessage<T>(message: T): Promise<unknown>;
6
+ /**
7
+ * Listen for one-off messages
8
+ * Return true from callback to indicate async response
9
+ */
10
+ onMessage(callback: (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => undefined | boolean): void;
11
+ /**
12
+ * Remove a previously registered message listener
13
+ */
14
+ removeMessageListener(callback: (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => undefined | boolean): void;
15
+ /**
16
+ * Create a long-lived connection
17
+ */
18
+ connect(name: string): PortAdapter;
19
+ /**
20
+ * Listen for incoming connections
21
+ */
22
+ onConnect(callback: (port: PortAdapter) => void): void;
23
+ /**
24
+ * Get URL for extension resource
25
+ */
26
+ getURL(path: string): string;
27
+ /**
28
+ * Get extension ID
29
+ */
30
+ getId(): string;
31
+ /**
32
+ * Open the extension's options page
33
+ */
34
+ openOptionsPage(): void;
35
+ }
36
+ export interface PortAdapter {
37
+ /**
38
+ * Port name
39
+ */
40
+ readonly name: string;
41
+ /**
42
+ * Send message through port
43
+ */
44
+ postMessage(message: unknown): void;
45
+ /**
46
+ * Listen for messages on port
47
+ */
48
+ onMessage(callback: (message: unknown) => void): void;
49
+ /**
50
+ * Listen for disconnection
51
+ */
52
+ onDisconnect(callback: () => void): void;
53
+ /**
54
+ * Disconnect port
55
+ */
56
+ disconnect(): void;
57
+ }
58
+ export interface MessageSender {
59
+ tab?: {
60
+ id: number;
61
+ url: string;
62
+ title: string;
63
+ };
64
+ frameId?: number;
65
+ url?: string;
66
+ }
@@ -0,0 +1,29 @@
1
+ export interface StorageAdapter {
2
+ /**
3
+ * Get items from storage
4
+ * @param keys - String, array of strings, object with defaults, or null for all items
5
+ */
6
+ get<T = Record<string, unknown>>(keys?: string | string[] | Record<string, unknown> | null): Promise<T>;
7
+ /**
8
+ * Set items in storage
9
+ */
10
+ set(items: Record<string, unknown>): Promise<void>;
11
+ /**
12
+ * Remove items from storage
13
+ */
14
+ remove(keys: string | string[]): Promise<void>;
15
+ /**
16
+ * Clear all storage
17
+ */
18
+ clear(): Promise<void>;
19
+ /**
20
+ * Listen for storage changes
21
+ */
22
+ onChanged(callback: (changes: StorageChanges, areaName: string) => void): void;
23
+ }
24
+ export interface StorageChanges {
25
+ [key: string]: {
26
+ oldValue?: unknown;
27
+ newValue?: unknown;
28
+ };
29
+ }
@@ -0,0 +1,39 @@
1
+ export interface TabsAdapter {
2
+ /**
3
+ * Query tabs
4
+ */
5
+ query(queryInfo: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]>;
6
+ /**
7
+ * Get tab by ID
8
+ */
9
+ get(tabId: number): Promise<chrome.tabs.Tab>;
10
+ /**
11
+ * Send message to specific tab
12
+ */
13
+ sendMessage(tabId: number, message: unknown): Promise<unknown>;
14
+ /**
15
+ * Reload tab
16
+ */
17
+ reload(tabId: number, reloadProperties?: {
18
+ bypassCache?: boolean;
19
+ }): Promise<void>;
20
+ /**
21
+ * Listen for tab removal
22
+ */
23
+ onRemoved(callback: (tabId: number, removeInfo: chrome.tabs.TabRemoveInfo) => void): void;
24
+ /**
25
+ * Listen for tab updates
26
+ */
27
+ onUpdated(callback: (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => void): void;
28
+ /**
29
+ * Listen for tab activation
30
+ */
31
+ onActivated(callback: (activeInfo: {
32
+ tabId: number;
33
+ windowId: number;
34
+ }) => void): void;
35
+ /**
36
+ * Create a new tab
37
+ */
38
+ create(createProperties: chrome.tabs.CreateProperties): Promise<chrome.tabs.Tab>;
39
+ }
@@ -0,0 +1,14 @@
1
+ export interface WindowAdapter {
2
+ /**
3
+ * Post message to window
4
+ */
5
+ postMessage(message: unknown, targetOrigin: string): void;
6
+ /**
7
+ * Listen for messages
8
+ */
9
+ addEventListener(type: "message", listener: (event: MessageEvent) => void): void;
10
+ /**
11
+ * Remove message listener
12
+ */
13
+ removeEventListener(type: "message", listener: (event: MessageEvent) => void): void;
14
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Context Helpers - DX Improvements for Extension Context Initialization
3
+ *
4
+ * Provides utilities to reduce boilerplate when initializing extension contexts.
5
+ */
6
+ import type { BaseMessage, Context, ExtensionMessage } from "../types/messages";
7
+ import { type MessageBus } from "./message-bus";
8
+ export interface ContextConfig<TMessage extends BaseMessage = ExtensionMessage> {
9
+ /**
10
+ * Called when the context is initialized.
11
+ * Use this to register handlers, setup UI, etc.
12
+ */
13
+ onInit?: (bus: MessageBus<TMessage>) => Promise<void> | void;
14
+ /**
15
+ * Called when an error occurs during initialization or runtime.
16
+ */
17
+ onError?: (error: Error, bus: MessageBus<TMessage>) => void;
18
+ /**
19
+ * Whether to wait for DOM to be ready before initializing.
20
+ * Only applies to contexts with a window (popup, options, devtools, sidepanel).
21
+ * @default true
22
+ */
23
+ waitForDOM?: boolean;
24
+ /**
25
+ * Custom logger prefix.
26
+ * @default `[${context}]`
27
+ */
28
+ logPrefix?: string;
29
+ }
30
+ /**
31
+ * Create and initialize an extension context with reduced boilerplate.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // src/popup/index.ts
36
+ * createContext<MyMessages>('popup', {
37
+ * async onInit(bus) {
38
+ * registerHandlers(bus)
39
+ * setupUI()
40
+ * },
41
+ * onError(err) {
42
+ * console.error('Popup failed:', err)
43
+ * }
44
+ * })
45
+ * ```
46
+ */
47
+ export declare function createContext<TMessage extends BaseMessage = ExtensionMessage>(context: Context, config?: ContextConfig<TMessage>): MessageBus<TMessage>;
48
+ /**
49
+ * Helper to run code only in specific contexts.
50
+ * Useful for shared modules that need context-specific behavior.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // shared/features/analytics.ts
55
+ * const bus = createContext('popup', { ... })
56
+ *
57
+ * if (bus.context === 'popup' || bus.context === 'options') {
58
+ * setupUI()
59
+ * }
60
+ * ```
61
+ *
62
+ * @deprecated Use bus.context directly instead. This function cannot reliably detect context.
63
+ */
64
+ export declare function runInContext(context: Context, contexts: Context | Context[], fn: (bus: MessageBus) => void | Promise<void>): void;