1// Service Worker for Boundary Graph 2// Enhanced caching for mobile performance 3 4const CACHE_NAME = 'boundary-graph-v1'; 5const API_CACHE_NAME = 'boundary-graph-api-v1'; 6 7// Assets to cache immediately 8const STATIC_ASSETS = [ 9 '/', 10 '/bg-logo.png', 11 '/icon.png', 12 '/icon-512.png', 13 '/manifest.json' 14]; 15 16// API endpoints to cache with different strategies 17const API_ENDPOINTS = [ 18 '/api/overview', 19 '/api/batsmen/top', 20 '/api/bowlers/top', 21 '/api/teams', 22 '/api/venues' 23]; 24 25// Install event - cache static assets 26self.addEventListener('install', event => { 27 event.waitUntil( 28 caches.open(CACHE_NAME) 29 .then(cache => { 30 console.log('ð¦ Caching static assets...'); 31 return cache.addAll(STATIC_ASSETS); 32 }) 33 .then(() => { 34 console.log('â Static assets cached successfully'); 35 return self.skipWaiting(); 36 }) 37 .catch(err => { 38 console.error('â Cache installation failed:', err); 39 }) 40 ); 41}); 42 43// Activate event - clean up old caches 44self.addEventListener('activate', event => { 45 event.waitUntil( 46 caches.keys() 47 .then(cacheNames => { 48 return Promise.all( 49 cacheNames.map(cacheName => { 50 if (cacheName !== CACHE_NAME && cacheName !== API_CACHE_NAME) { 51 console.log('ðï¸ Deleting old cache:', cacheName); 52 return caches.delete(cacheName); 53 } 54 }) 55 ); 56 }) 57 .then(() => { 58 console.log('â Cache cleanup completed'); 59 return self.clients.claim(); 60 }) 61 ); 62}); 63 64// Fetch event - implement caching strategies 65self.addEventListener('fetch', event => { 66 const { request } = event; 67 const url = new URL(request.url); 68 69 // Handle API requests with network-first strategy 70 if (url.pathname.startsWith('/api/')) { 71 event.respondWith( 72 networkFirstStrategy(request, API_CACHE_NAME) 73 ); 74 return; 75 } 76 77 // Handle static assets with cache-first strategy 78 if (request.method === 'GET' && 79 (request.destination === 'document' || 80 request.destination === 'script' || 81 request.destination === 'style' || 82 request.destination === 'image')) { 83 event.respondWith( 84 cacheFirstStrategy(request, CACHE_NAME) 85 ); 86 return; 87 } 88}); 89 90// Network-first strategy for API calls 91async function networkFirstStrategy(request, cacheName) { 92 try { 93 // Try network first 94 const networkResponse = await fetch(request); 95 96 if (networkResponse.ok) { 97 // Cache successful response for mobile users 98 const cache = await caches.open(cacheName); 99 cache.put(request, networkResponse.clone()); 100 101 console.log('ð¡ Network response cached:', request.url); 102 return networkResponse; 103 } 104 105 throw new Error('Network response not ok'); 106 } catch (error) { 107 // Fall back to cache 108 console.log('ð Network failed, trying cache for:', request.url); 109 const cachedResponse = await caches.match(request); 110 111 if (cachedResponse) { 112 console.log('ð Serving from cache:', request.url); 113 return cachedResponse; 114 } 115 116 // Return offline fallback for API calls 117 return new Response( 118 JSON.stringify({ 119 error: 'Offline', 120 message: 'Data temporarily unavailable. Please check your connection.' 121 }), 122 { 123 status: 503, 124 statusText: 'Service Unavailable', 125 headers: { 'Content-Type': 'application/json' } 126 } 127 ); 128 } 129} 130 131// Cache-first strategy for static assets 132async function cacheFirstStrategy(request, cacheName) { 133 try { 134 // Try cache first 135 const cachedResponse = await caches.match(request); 136 137 if (cachedResponse) { 138 console.log('â¡ Serving from cache:', request.url); 139 return cachedResponse; 140 } 141 142 // Fall back to network 143 console.log('ð¡ Cache miss, fetching from network:', request.url); 144 const networkResponse = await fetch(request); 145 146 if (networkResponse.ok) { 147 // Cache for future use 148 const cache = await caches.open(cacheName); 149 cache.put(request, networkResponse.clone()); 150 console.log('ð¦ Cached new resource:', request.url); 151 } 152 153 return networkResponse; 154 } catch (error) { 155 console.error('â Both cache and network failed for:', request.url); 156 157 // Return a basic offline page for navigation requests 158 if (request.mode === 'navigate') { 159 return new Response( 160 `<!DOCTYPE html> 161 <html> 162 <head> 163 <title>Boundary Graph - Offline</title> 164 <meta name="viewport" content="width=device-width, initial-scale=1"> 165 <style> 166 body { font-family: system-ui, sans-serif; text-align: center; padding: 2rem; } 167 .offline { color: #64748b; } 168 </style> 169 </head> 170 <body> 171 <h1>ð Boundary Graph</h1> 172 <p class="offline">You're offline. Please check your connection.</p> 173 </body> 174 </html>`, 175 { 176 headers: { 'Content-Type': 'text/html' } 177 } 178 ); 179 } 180 181 throw error; 182 } 183} 184
185// Background sync for mobile data efficiency 186self.addEventListener('sync', event => { 187 if (event.tag === 'background-sync-api') { 188 event.waitUntil( 189 syncApiData() 190 ); 191 } 192}); 193 194// Sync essential API data in background 195async function syncApiData() { 196 try { 197 console.log('ð Background syncing API data...'); 198 199 const cache = await caches.open(API_CACHE_NAME); 200 const baseUrl = 'https://boundary-graph.onrender.com'; 201 202 // Sync essential endpoints 203 const endpoints = [ 204 '/api/overview', 205 '/api/batsmen/top?limit=20', 206 '/api/bowlers/top?limit=20' 207 ]; 208 209 for (const endpoint of endpoints) { 210 try { 211 const response = await fetch(baseUrl + endpoint); 212 if (response.ok) { 213 await cache.put(endpoint, response); 214 console.log('â Synced:', endpoint); 215 } 216 } catch (err) { 217 console.log('â ï¸ Sync failed for:', endpoint, err.message); 218 } 219 } 220 221 console.log('â Background sync completed'); 222 } catch (error) { 223 console.error('â Background sync failed:', error); 224 } 225} 226 227// Message handling for cache management 228self.addEventListener('message', event => { 229 if (event.data && event.data.type === 'SKIP_WAITING') { 230 self.skipWaiting(); 231 } 232 233 if (event.data && event.data.type === 'CLEAR_CACHE') { 234 event.waitUntil( 235 caches.keys().then(cacheNames => { 236 return Promise.all( 237 cacheNames.map(cacheName => caches.delete(cacheName)) 238 ); 239 }) 240 ); 241 } 242});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.