@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alex Jeffcott
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,322 @@
1
+ # @fairfox/web-ext
2
+
3
+ **Build Chrome extensions with reactive state and zero boilerplate.**
4
+
5
+ Stop fighting Chrome's extension APIs. Write extensions like modern web apps.
6
+
7
+ ```typescript
8
+ // Define state once
9
+ export const counter = $sharedState('counter', 0)
10
+
11
+ // Use everywhere - automatically syncs!
12
+ counter.value++ // Updates popup, options, background, everywhere
13
+ ```
14
+
15
+ ## Why?
16
+
17
+ Chrome extension development is painful:
18
+
19
+ - ❌ State scattered across contexts (popup, background, content scripts)
20
+ - ❌ Manual `chrome.storage` calls everywhere
21
+ - ❌ Complex message passing with `chrome.runtime.sendMessage`
22
+ - ❌ No reactivity - manually update UI when state changes
23
+ - ❌ Hard to test - mock 50+ Chrome APIs
24
+
25
+ This framework fixes all of that:
26
+
27
+ - ✅ **Reactive state** - UI updates automatically
28
+ - ✅ **Auto-syncing** - State syncs across all contexts instantly
29
+ - ✅ **Persistence** - State survives restarts (automatic)
30
+ - ✅ **Type-safe messaging** - Send messages between contexts easily
31
+ - ✅ **Built for testing** - DOM-based E2E tests with Playwright
32
+
33
+ ## Quick Start
34
+
35
+ ### Install
36
+
37
+ ```bash
38
+ # Using Bun (recommended)
39
+ bun add @fairfox/web-ext
40
+
41
+ # Using npm
42
+ npm install @fairfox/web-ext
43
+
44
+ # Using pnpm
45
+ pnpm add @fairfox/web-ext
46
+
47
+ # Using yarn
48
+ yarn add @fairfox/web-ext
49
+ ```
50
+
51
+ ### Create Extension
52
+
53
+ **1. Define shared state** (automatically syncs everywhere):
54
+
55
+ ```typescript
56
+ // src/shared/state.ts
57
+ import { $sharedState } from '@fairfox/web-ext/state'
58
+
59
+ export const counter = $sharedState('counter', 0)
60
+ export const settings = $sharedState('settings', { theme: 'dark' })
61
+ ```
62
+
63
+ **2. Use in popup UI** (reactive - updates automatically):
64
+
65
+ ```typescript
66
+ // src/popup/index.tsx
67
+ import { render } from 'preact'
68
+ import { counter } from '../shared/state'
69
+
70
+ function Popup() {
71
+ return (
72
+ <div>
73
+ <p>Count: {counter.value}</p>
74
+ <button onClick={() => counter.value++}>Increment</button>
75
+ </div>
76
+ )
77
+ }
78
+
79
+ render(<Popup />, document.getElementById('root')!)
80
+ ```
81
+
82
+ **3. Setup background** (handles routing):
83
+
84
+ ```typescript
85
+ // src/background/index.ts
86
+ import { createBackground } from '@fairfox/web-ext/background'
87
+
88
+ const bus = createBackground()
89
+ ```
90
+
91
+ > **⚠️ Important:** Always use `createBackground()` in background scripts, not `getMessageBus('background')`.
92
+ > The framework protects against misconfiguration with singleton enforcement and automatic double-execution detection.
93
+ > See [Background Setup Guide](./docs/BACKGROUND_SETUP.md) for details.
94
+
95
+ **4. Build and load**:
96
+
97
+ ```bash
98
+ bunx web-ext build
99
+ ```
100
+
101
+ Load `dist/` folder in Chrome → **Done!** 🎉
102
+
103
+ ## Features
104
+
105
+ ### 🔄 Automatic State Sync
106
+
107
+ ```typescript
108
+ // Change state anywhere
109
+ counter.value = 5
110
+
111
+ // Instantly appears EVERYWHERE:
112
+ // - Popup ✓
113
+ // - Options page ✓
114
+ // - Background ✓
115
+ // - All tabs ✓
116
+ ```
117
+
118
+ ### 💾 Automatic Persistence
119
+
120
+ ```typescript
121
+ // State automatically saves to chrome.storage
122
+ const theme = $sharedState('theme', 'dark')
123
+
124
+ // Survives:
125
+ // - Extension reload ✓
126
+ // - Browser restart ✓
127
+ // - Chrome crash ✓
128
+ ```
129
+
130
+ ### ⚡️ Three Types of State
131
+
132
+ ```typescript
133
+ // Syncs + persists (most common)
134
+ const settings = $sharedState('settings', { theme: 'dark' })
135
+
136
+ // Syncs, no persist (temporary shared state)
137
+ const activeTab = $syncedState('activeTab', null)
138
+
139
+ // Local only (like regular React state)
140
+ const loading = $state(false)
141
+ ```
142
+
143
+ ### 📡 Easy Message Passing
144
+
145
+ ```typescript
146
+ // Background
147
+ bus.on('GET_DATA', async (payload) => {
148
+ const data = await fetchData(payload.id)
149
+ return { success: true, data }
150
+ })
151
+
152
+ // Popup
153
+ const result = await bus.send({ type: 'GET_DATA', id: 123 })
154
+ console.log(result.data)
155
+ ```
156
+
157
+ ### 🧪 Built for Testing
158
+
159
+ ```typescript
160
+ // E2E tests with Playwright
161
+ test('counter increments', async ({ page }) => {
162
+ await page.click('[data-testid="increment"]')
163
+ const count = await page.locator('[data-testid="count"]').textContent()
164
+ expect(count).toBe('1')
165
+ })
166
+ ```
167
+
168
+ State automatically syncs during tests - no mocks needed!
169
+
170
+ ## Examples
171
+
172
+ - [**Minimal**](./examples/minimal/) - Dead simple counter (30 lines)
173
+ - [**Full Featured**](./tests/framework-validation/test-extension/) - Shows all features
174
+ - More coming soon...
175
+
176
+ ## Architecture
177
+
178
+ ```
179
+ ┌─────────────────────────────────────────┐
180
+ │ Your Extension │
181
+ ├─────────────────────────────────────────┤
182
+ │ Popup Options Content Script │
183
+ │ ↓ ↓ ↓ │
184
+ │ ┌─────────────────────────────────┐ │
185
+ │ │ Framework State Layer │ │
186
+ │ │ (Auto-sync, Lamport clocks) │ │
187
+ │ └─────────────────────────────────┘ │
188
+ │ ↓ │
189
+ │ ┌─────────────────────────────────┐ │
190
+ │ │ Message Router (Background) │ │
191
+ │ └─────────────────────────────────┘ │
192
+ │ ↓ │
193
+ │ ┌─────────────────────────────────┐ │
194
+ │ │ Chrome Extension APIs │ │
195
+ │ │ (storage, runtime, tabs) │ │
196
+ │ └─────────────────────────────────┘ │
197
+ └─────────────────────────────────────────┘
198
+ ```
199
+
200
+ ## API Reference
201
+
202
+ ### State Primitives
203
+
204
+ ```typescript
205
+ // Syncs across contexts + persists to storage
206
+ $sharedState<T>(key: string, initialValue: T): Signal<T>
207
+
208
+ // Syncs across contexts (no persistence)
209
+ $syncedState<T>(key: string, initialValue: T): Signal<T>
210
+
211
+ // Persists to storage (no sync)
212
+ $persistedState<T>(key: string, initialValue: T): Signal<T>
213
+
214
+ // Local only (like Preact signal)
215
+ $state<T>(initialValue: T): Signal<T>
216
+ ```
217
+
218
+ ### Message Bus
219
+
220
+ ```typescript
221
+ // Send message to background
222
+ await bus.send({ type: 'MY_MESSAGE', data: 'foo' })
223
+
224
+ // Broadcast to all contexts
225
+ bus.broadcast({ type: 'NOTIFICATION', text: 'Hello!' })
226
+
227
+ // Handle messages
228
+ bus.on('MY_MESSAGE', async (payload) => {
229
+ return { success: true }
230
+ })
231
+ ```
232
+
233
+ ### Adapters
234
+
235
+ All Chrome APIs available via `bus.adapters`:
236
+
237
+ ```typescript
238
+ // Storage
239
+ await bus.adapters.storage.set({ key: 'value' })
240
+ const data = await bus.adapters.storage.get('key')
241
+
242
+ // Tabs
243
+ const tabs = await bus.adapters.tabs.query({ active: true })
244
+
245
+ // Runtime
246
+ const id = bus.adapters.runtime.id
247
+ ```
248
+
249
+ ## How It Works
250
+
251
+ **State Synchronization:**
252
+ - Uses **Lamport clocks** for distributed consistency
253
+ - Broadcasts changes via `chrome.runtime` ports
254
+ - Conflict-free (CRDT-style convergence)
255
+
256
+ **Reactivity:**
257
+ - Built on [Preact Signals](https://preactjs.com/guide/v10/signals/)
258
+ - Automatic UI updates (no manual re-renders)
259
+ - Works with any framework (Preact, React, Vue, etc.)
260
+
261
+ **Message Routing:**
262
+ - Background acts as message hub
263
+ - Popup/Options/Content scripts connect via ports
264
+ - Type-safe request/response pattern
265
+
266
+ ## Testing
267
+
268
+ Run the full test suite:
269
+
270
+ ```bash
271
+ bun test # Unit tests
272
+ bun run test:framework # E2E tests (Playwright)
273
+ ```
274
+
275
+ All 16 E2E tests validate real Chrome extension behavior:
276
+ - ✅ State sync (popup ↔ options ↔ background)
277
+ - ✅ Persistence (survives reload)
278
+ - ✅ Reactivity (UI updates automatically)
279
+ - ✅ Message passing (request/response)
280
+ - ✅ Chrome APIs (storage, tabs, runtime)
281
+
282
+ ## Requirements
283
+
284
+ - **Bun** 1.0+ (for building)
285
+ - **Chrome** 88+ (Manifest V3)
286
+ - **TypeScript** 5.0+ (recommended)
287
+
288
+ ## Contributing
289
+
290
+ Contributions welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md)
291
+
292
+ ## Development
293
+
294
+ ### TypeScript Configuration
295
+
296
+ This project uses separate TypeScript configs for source code and tests:
297
+
298
+ - **`tsconfig.json`** - Main config for source code (`src/`)
299
+ - Strict mode enabled
300
+ - Excludes `tests/` directory
301
+
302
+ - **`tsconfig.test.json`** - Config for test files (`tests/`)
303
+ - Extends main config
304
+ - Relaxes some strict rules for testing
305
+ - Includes path mappings: `@/*` → `./src/*`
306
+
307
+ #### For Neovim/LSP Users
308
+
309
+ If your LSP shows errors about missing modules (like `@/shared/types/messages`), restart your LSP:
310
+ ```vim
311
+ :LspRestart
312
+ ```
313
+
314
+ Your LSP will automatically use the correct config based on which file you're editing.
315
+
316
+ ## License
317
+
318
+ MIT © 2024
319
+
320
+ ---
321
+
322
+ **[View Examples](./examples/)** · **[Read Docs](./docs/)** · **[Report Issue](https://github.com/fairfox/web-ext/issues)**