@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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/cli/polly.ts +564 -0
- package/dist/background/api-client.d.ts +7 -0
- package/dist/background/context-menu.d.ts +7 -0
- package/dist/background/index.d.ts +31 -0
- package/dist/background/index.js +1309 -0
- package/dist/background/index.js.map +25 -0
- package/dist/background/log-store.d.ts +22 -0
- package/dist/background/message-router.d.ts +30 -0
- package/dist/background/message-router.js +1300 -0
- package/dist/background/message-router.js.map +24 -0
- package/dist/background/offscreen-manager.d.ts +10 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +1471 -0
- package/dist/index.js.map +27 -0
- package/dist/shared/adapters/chrome/context-menus.chrome.d.ts +8 -0
- package/dist/shared/adapters/chrome/offscreen.chrome.d.ts +6 -0
- package/dist/shared/adapters/chrome/runtime.chrome.d.ts +13 -0
- package/dist/shared/adapters/chrome/storage.chrome.d.ts +8 -0
- package/dist/shared/adapters/chrome/tabs.chrome.d.ts +16 -0
- package/dist/shared/adapters/chrome/window.chrome.d.ts +6 -0
- package/dist/shared/adapters/context-menus.adapter.d.ts +22 -0
- package/dist/shared/adapters/fetch.adapter.d.ts +6 -0
- package/dist/shared/adapters/index.d.ts +34 -0
- package/dist/shared/adapters/index.js +298 -0
- package/dist/shared/adapters/index.js.map +18 -0
- package/dist/shared/adapters/logger.adapter.d.ts +44 -0
- package/dist/shared/adapters/offscreen.adapter.d.ts +20 -0
- package/dist/shared/adapters/runtime.adapter.d.ts +66 -0
- package/dist/shared/adapters/storage.adapter.d.ts +29 -0
- package/dist/shared/adapters/tabs.adapter.d.ts +39 -0
- package/dist/shared/adapters/window.adapter.d.ts +14 -0
- package/dist/shared/lib/context-helpers.d.ts +64 -0
- package/dist/shared/lib/context-helpers.js +1086 -0
- package/dist/shared/lib/context-helpers.js.map +24 -0
- package/dist/shared/lib/context-specific-helpers.d.ts +160 -0
- package/dist/shared/lib/errors.d.ts +67 -0
- package/dist/shared/lib/errors.js +94 -0
- package/dist/shared/lib/errors.js.map +10 -0
- package/dist/shared/lib/handler-execution-tracker.d.ts +24 -0
- package/dist/shared/lib/message-bus.d.ts +233 -0
- package/dist/shared/lib/message-bus.js +1033 -0
- package/dist/shared/lib/message-bus.js.map +23 -0
- package/dist/shared/lib/state.d.ts +102 -0
- package/dist/shared/lib/state.js +1265 -0
- package/dist/shared/lib/state.js.map +24 -0
- package/dist/shared/lib/test-helpers.d.ts +133 -0
- package/dist/shared/lib/test-helpers.js +136 -0
- package/dist/shared/lib/test-helpers.js.map +10 -0
- package/dist/shared/state/app-state.d.ts +8 -0
- package/dist/shared/state/app-state.js +1272 -0
- package/dist/shared/state/app-state.js.map +25 -0
- package/dist/shared/types/messages.d.ts +341 -0
- package/dist/shared/types/messages.js +25 -0
- package/dist/shared/types/messages.js.map +10 -0
- 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)**
|