@fairfox/polly 0.1.5 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairfox/polly",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Multi-execution-context framework with reactive state and cross-context messaging for Chrome extensions, PWAs, and worker-based applications",
@@ -44,7 +44,7 @@
44
44
  "types": "./dist/shared/types/messages.d.ts"
45
45
  }
46
46
  },
47
- "files": ["dist", "README.md", "LICENSE"],
47
+ "files": ["dist", "templates", "README.md", "LICENSE"],
48
48
  "scripts": {
49
49
  "dev": "bun run build.ts --watch",
50
50
  "build": "bun run build.ts",
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.log
@@ -0,0 +1,144 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A Progressive Web App (PWA) built with Polly framework, demonstrating multi-context architecture with:
4
+
5
+ - **Main Context** - UI application
6
+ - **Service Worker** - Background tasks, caching, push notifications
7
+ - **Shared Worker** - Shared state across tabs/windows
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ ┌─────────────────┐
13
+ │ Main Context │ ← User Interface
14
+ │ (main.ts) │
15
+ └────────┬────────┘
16
+
17
+ ┌────┴────┐
18
+ │ │
19
+ ▼ ▼
20
+ ┌─────────┐ ┌──────────────┐
21
+ │ Service │ │ Shared │
22
+ │ Worker │ │ Worker │
23
+ │ │ │ │
24
+ │ Caching │ │ Shared State │
25
+ │ Offline │ │ Multi-tab │
26
+ └─────────┘ └──────────────┘
27
+ ```
28
+
29
+ ## Getting Started
30
+
31
+ 1. **Install dependencies:**
32
+ ```bash
33
+ bun install
34
+ ```
35
+
36
+ 2. **Start development server:**
37
+ ```bash
38
+ bun run serve
39
+ ```
40
+
41
+ 3. **Open in browser:**
42
+ ```
43
+ http://localhost:3000
44
+ ```
45
+
46
+ 4. **Try the features:**
47
+ - Click "Ping Service Worker" - tests communication with SW
48
+ - Click "Ping Shared Worker" - tests communication with shared worker
49
+ - Click "Broadcast" - sends message to all workers
50
+ - Open multiple tabs to see shared worker coordination
51
+
52
+ ## Development
53
+
54
+ - `bun run serve` - Start development server with hot reload
55
+ - `bun run build` - Build for production
56
+ - `bun run typecheck` - Type check TypeScript
57
+ - `bun run visualize` - Generate architecture diagrams
58
+
59
+ ## Project Structure
60
+
61
+ ```
62
+ {{PROJECT_NAME}}/
63
+ ├── src/
64
+ │ ├── main.ts # Main application context
65
+ │ ├── service-worker.ts # Service Worker (caching, offline)
66
+ │ └── shared-worker.ts # Shared Worker (cross-tab state)
67
+ ├── public/
68
+ │ └── manifest.json # PWA manifest
69
+ ├── index.html # Entry point
70
+ ├── server.ts # Dev server
71
+ ├── build.ts # Build script
72
+ ├── package.json
73
+ └── tsconfig.json
74
+ ```
75
+
76
+ ## Features Demonstrated
77
+
78
+ ### Service Worker
79
+ - Asset caching for offline support
80
+ - Background fetch and sync
81
+ - Push notifications (requires HTTPS)
82
+ - Message passing with main context
83
+
84
+ ### Shared Worker
85
+ - State shared across multiple tabs
86
+ - Broadcast messages to all connected tabs
87
+ - Connection tracking
88
+ - Periodic heartbeat
89
+
90
+ ### Main Context
91
+ - Service Worker registration
92
+ - Shared Worker connection
93
+ - Bidirectional communication
94
+ - UI updates from worker messages
95
+
96
+ ## Visualization
97
+
98
+ Generate architecture diagrams:
99
+
100
+ ```bash
101
+ bun run visualize
102
+ ```
103
+
104
+ This will create:
105
+ - System context diagram
106
+ - Container diagram showing all 3 contexts
107
+ - Message flow diagrams
108
+
109
+ View diagrams:
110
+ ```bash
111
+ bun run visualize --serve
112
+ ```
113
+
114
+ ## Testing
115
+
116
+ Open multiple browser tabs to test:
117
+ 1. Shared worker coordination across tabs
118
+ 2. Service worker message broadcasting
119
+ 3. State synchronization
120
+
121
+ ## Production Build
122
+
123
+ ```bash
124
+ bun run build
125
+ ```
126
+
127
+ Output in `dist/` directory. Serve with any static file server or deploy to:
128
+ - Netlify
129
+ - Vercel
130
+ - GitHub Pages
131
+ - Cloudflare Pages
132
+
133
+ ## Notes
134
+
135
+ - Service Worker requires HTTPS in production (or localhost for dev)
136
+ - Shared Worker support varies by browser (Chrome, Firefox, Edge)
137
+ - Check browser DevTools → Application tab to debug workers
138
+
139
+ ## Learn More
140
+
141
+ - [Polly Documentation](https://github.com/fairfox/polly)
142
+ - [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
143
+ - [Shared Worker API](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker)
144
+ - [PWA Documentation](https://web.dev/progressive-web-apps/)
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Build script for PWA
3
+ * Uses Bun's built-in bundler
4
+ */
5
+
6
+ import { rmSync, cpSync, mkdirSync } from 'node:fs'
7
+ import { join } from 'node:path'
8
+
9
+ const distDir = './dist'
10
+
11
+ // Clean dist directory
12
+ console.log('🧹 Cleaning dist directory...')
13
+ rmSync(distDir, { recursive: true, force: true })
14
+ mkdirSync(distDir, { recursive: true })
15
+
16
+ // Copy static files
17
+ console.log('📋 Copying static files...')
18
+ cpSync('./public', distDir, { recursive: true, force: true })
19
+ cpSync('./index.html', join(distDir, 'index.html'))
20
+
21
+ // Build main application
22
+ console.log('📦 Building main application...')
23
+ await Bun.build({
24
+ entrypoints: ['./src/main.ts'],
25
+ outdir: join(distDir, 'src'),
26
+ format: 'esm',
27
+ minify: true,
28
+ sourcemap: 'external',
29
+ target: 'browser',
30
+ })
31
+
32
+ // Build service worker
33
+ console.log('⚙️ Building service worker...')
34
+ await Bun.build({
35
+ entrypoints: ['./src/service-worker.ts'],
36
+ outdir: join(distDir, 'src'),
37
+ format: 'esm',
38
+ minify: true,
39
+ sourcemap: 'external',
40
+ target: 'browser',
41
+ })
42
+
43
+ // Build shared worker
44
+ console.log('👥 Building shared worker...')
45
+ await Bun.build({
46
+ entrypoints: ['./src/shared-worker.ts'],
47
+ outdir: join(distDir, 'src'),
48
+ format: 'esm',
49
+ minify: true,
50
+ sourcemap: 'external',
51
+ target: 'browser',
52
+ })
53
+
54
+ console.log('✅ Build complete!')
55
+ console.log(`\n📦 Output: ${distDir}/`)
56
+ console.log('\n💡 To serve: bun run serve')
@@ -0,0 +1,127 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="description" content="{{PROJECT_NAME}} - A PWA with Polly" />
7
+ <link rel="manifest" href="/manifest.json" />
8
+ <title>{{PROJECT_NAME}}</title>
9
+ <style>
10
+ * {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ body {
17
+ font-family: system-ui, -apple-system, sans-serif;
18
+ padding: 20px;
19
+ max-width: 800px;
20
+ margin: 0 auto;
21
+ }
22
+
23
+ h1 {
24
+ margin-bottom: 20px;
25
+ color: #333;
26
+ }
27
+
28
+ .status {
29
+ margin-bottom: 20px;
30
+ padding: 15px;
31
+ background: #f5f5f5;
32
+ border-radius: 8px;
33
+ }
34
+
35
+ .status-item {
36
+ display: flex;
37
+ justify-content: space-between;
38
+ margin-bottom: 8px;
39
+ }
40
+
41
+ .status-label {
42
+ font-weight: 600;
43
+ }
44
+
45
+ .status-value {
46
+ color: #666;
47
+ }
48
+
49
+ .actions {
50
+ display: flex;
51
+ gap: 10px;
52
+ margin-bottom: 20px;
53
+ }
54
+
55
+ button {
56
+ padding: 10px 20px;
57
+ background: #007bff;
58
+ color: white;
59
+ border: none;
60
+ border-radius: 5px;
61
+ cursor: pointer;
62
+ font-size: 14px;
63
+ }
64
+
65
+ button:hover {
66
+ background: #0056b3;
67
+ }
68
+
69
+ button:disabled {
70
+ background: #ccc;
71
+ cursor: not-allowed;
72
+ }
73
+
74
+ #messages {
75
+ border: 1px solid #ddd;
76
+ border-radius: 8px;
77
+ padding: 15px;
78
+ min-height: 200px;
79
+ background: white;
80
+ }
81
+
82
+ .message {
83
+ padding: 8px;
84
+ margin-bottom: 8px;
85
+ background: #f9f9f9;
86
+ border-left: 3px solid #007bff;
87
+ border-radius: 4px;
88
+ font-size: 14px;
89
+ }
90
+
91
+ .message-time {
92
+ color: #666;
93
+ font-size: 12px;
94
+ margin-right: 8px;
95
+ }
96
+ </style>
97
+ </head>
98
+ <body>
99
+ <h1>{{PROJECT_NAME}}</h1>
100
+
101
+ <div class="status">
102
+ <div class="status-item">
103
+ <span class="status-label">Service Worker:</span>
104
+ <span id="sw-status" class="status-value">Checking...</span>
105
+ </div>
106
+ <div class="status-item">
107
+ <span class="status-label">Shared Worker:</span>
108
+ <span id="shared-worker-status" class="status-value">Checking...</span>
109
+ </div>
110
+ <div class="status-item">
111
+ <span class="status-label">Messages Received:</span>
112
+ <span id="message-count" class="status-value">0</span>
113
+ </div>
114
+ </div>
115
+
116
+ <div class="actions">
117
+ <button id="ping-sw">Ping Service Worker</button>
118
+ <button id="ping-shared">Ping Shared Worker</button>
119
+ <button id="broadcast">Broadcast Message</button>
120
+ </div>
121
+
122
+ <h2>Messages</h2>
123
+ <div id="messages"></div>
124
+
125
+ <script type="module" src="/src/main.ts"></script>
126
+ </body>
127
+ </html>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bun run --hot src/main.ts",
7
+ "build": "bun build.ts",
8
+ "serve": "bun --hot server.ts",
9
+ "typecheck": "bun tsc --noEmit",
10
+ "visualize": "bun ../../visualize/src/cli.ts"
11
+ },
12
+ "dependencies": {
13
+ "@fairfox/polly": "*"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.0.0",
17
+ "@types/bun": "latest"
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "short_name": "{{PROJECT_NAME}}",
4
+ "description": "A PWA built with Polly framework",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#ffffff",
8
+ "theme_color": "#000000",
9
+ "icons": [
10
+ {
11
+ "src": "/icon-192.png",
12
+ "sizes": "192x192",
13
+ "type": "image/png"
14
+ },
15
+ {
16
+ "src": "/icon-512.png",
17
+ "sizes": "512x512",
18
+ "type": "image/png"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Development server for PWA
3
+ * Serves the application with hot reload support
4
+ */
5
+
6
+ const port = 3000
7
+
8
+ const server = Bun.serve({
9
+ port,
10
+ async fetch(req) {
11
+ const url = new URL(req.url)
12
+ let filePath = url.pathname
13
+
14
+ // Serve root
15
+ if (filePath === '/') {
16
+ filePath = '/index.html'
17
+ }
18
+
19
+ // Try to serve from public directory first
20
+ const publicFile = Bun.file(`./public${filePath}`)
21
+ if (await publicFile.exists()) {
22
+ return new Response(publicFile)
23
+ }
24
+
25
+ // Try to serve from root
26
+ const rootFile = Bun.file(`.${filePath}`)
27
+ if (await rootFile.exists()) {
28
+ return new Response(rootFile)
29
+ }
30
+
31
+ // Handle TypeScript files - transpile on the fly
32
+ if (filePath.endsWith('.ts')) {
33
+ const tsFile = Bun.file(`.${filePath}`)
34
+ if (await tsFile.exists()) {
35
+ const transpiled = await Bun.build({
36
+ entrypoints: [`.${filePath}`],
37
+ format: 'esm',
38
+ target: 'browser',
39
+ })
40
+
41
+ if (transpiled.outputs.length > 0) {
42
+ return new Response(transpiled.outputs[0], {
43
+ headers: {
44
+ 'Content-Type': 'application/javascript',
45
+ },
46
+ })
47
+ }
48
+ }
49
+ }
50
+
51
+ // 404
52
+ return new Response('Not Found', { status: 404 })
53
+ },
54
+ })
55
+
56
+ console.log(`🚀 Server running at http://localhost:${port}`)
57
+ console.log(`\n📱 PWA: {{PROJECT_NAME}}`)
58
+ console.log(`\n💡 Open in browser and check the console for worker messages`)
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Main Application Context
3
+ * Coordinates between Service Worker and Shared Worker
4
+ */
5
+
6
+ // Register Service Worker
7
+ if ('serviceWorker' in navigator) {
8
+ window.addEventListener('load', async () => {
9
+ try {
10
+ const registration = await navigator.serviceWorker.register('/src/service-worker.ts', {
11
+ type: 'module'
12
+ })
13
+ console.log('Service Worker registered:', registration.scope)
14
+ updateStatus('sw-status', '✓ Active')
15
+ } catch (error) {
16
+ console.error('Service Worker registration failed:', error)
17
+ updateStatus('sw-status', '✗ Failed')
18
+ }
19
+ })
20
+
21
+ // Listen for messages from Service Worker
22
+ navigator.serviceWorker.addEventListener('message', (event) => {
23
+ console.log('Message from Service Worker:', event.data)
24
+ addMessage(`[SW] ${JSON.stringify(event.data)}`)
25
+ })
26
+ }
27
+
28
+ // Initialize Shared Worker
29
+ let sharedWorker: SharedWorker | null = null
30
+ let messageCount = 0
31
+
32
+ if (typeof SharedWorker !== 'undefined') {
33
+ sharedWorker = new SharedWorker(
34
+ new URL('./shared-worker.ts', import.meta.url),
35
+ { type: 'module', name: 'polly-shared-worker' }
36
+ )
37
+
38
+ sharedWorker.port.start()
39
+
40
+ // Listen for messages from Shared Worker
41
+ sharedWorker.port.addEventListener('message', (event) => {
42
+ console.log('Message from Shared Worker:', event.data)
43
+ addMessage(`[Shared] ${JSON.stringify(event.data)}`)
44
+ messageCount++
45
+ updateMessageCount()
46
+ })
47
+
48
+ updateStatus('shared-worker-status', '✓ Connected')
49
+ } else {
50
+ updateStatus('shared-worker-status', '✗ Not Supported')
51
+ }
52
+
53
+ // UI Event Handlers
54
+ document.getElementById('ping-sw')?.addEventListener('click', async () => {
55
+ if (navigator.serviceWorker.controller) {
56
+ navigator.serviceWorker.controller.postMessage({
57
+ type: 'PING',
58
+ timestamp: Date.now()
59
+ })
60
+ addMessage('[Main] Sent PING to Service Worker')
61
+ } else {
62
+ addMessage('[Main] Service Worker not active')
63
+ }
64
+ })
65
+
66
+ document.getElementById('ping-shared')?.addEventListener('click', () => {
67
+ if (sharedWorker) {
68
+ sharedWorker.port.postMessage({
69
+ type: 'PING',
70
+ timestamp: Date.now()
71
+ })
72
+ addMessage('[Main] Sent PING to Shared Worker')
73
+ } else {
74
+ addMessage('[Main] Shared Worker not available')
75
+ }
76
+ })
77
+
78
+ document.getElementById('broadcast')?.addEventListener('click', () => {
79
+ const message = {
80
+ type: 'BROADCAST',
81
+ data: 'Hello from main context',
82
+ timestamp: Date.now()
83
+ }
84
+
85
+ // Send to both workers
86
+ if (navigator.serviceWorker.controller) {
87
+ navigator.serviceWorker.controller.postMessage(message)
88
+ }
89
+
90
+ if (sharedWorker) {
91
+ sharedWorker.port.postMessage(message)
92
+ }
93
+
94
+ addMessage('[Main] Broadcast sent to all workers')
95
+ })
96
+
97
+ // UI Helper Functions
98
+ function updateStatus(elementId: string, status: string) {
99
+ const element = document.getElementById(elementId)
100
+ if (element) {
101
+ element.textContent = status
102
+ }
103
+ }
104
+
105
+ function updateMessageCount() {
106
+ const element = document.getElementById('message-count')
107
+ if (element) {
108
+ element.textContent = messageCount.toString()
109
+ }
110
+ }
111
+
112
+ function addMessage(text: string) {
113
+ const messagesDiv = document.getElementById('messages')
114
+ if (!messagesDiv) return
115
+
116
+ const messageDiv = document.createElement('div')
117
+ messageDiv.className = 'message'
118
+
119
+ const time = new Date().toLocaleTimeString()
120
+ messageDiv.innerHTML = `<span class="message-time">${time}</span>${text}`
121
+
122
+ messagesDiv.insertBefore(messageDiv, messagesDiv.firstChild)
123
+
124
+ // Keep only last 20 messages
125
+ while (messagesDiv.children.length > 20) {
126
+ messagesDiv.removeChild(messagesDiv.lastChild!)
127
+ }
128
+ }
129
+
130
+ // Initial message
131
+ addMessage('[Main] Application initialized')
132
+
133
+ console.log('{{PROJECT_NAME}} initialized')