1// Service Worker for FedaCash PWA 2const CACHE_NAME = 'fedacash-v1'; 3const RUNTIME_CACHE = 'fedacash-runtime-v1'; 4 5// Assets to cache on install 6const PRECACHE_ASSETS = [ 7 '/', 8 '/favicon.ico', 9 '/logo-light.png', 10 '/logo-dark.png', 11 '/favicon/android-chrome-192x192.png', 12 '/favicon/android-chrome-512x512.png', 13 '/images/empty-state.png', 14]; 15 16// Install event - cache critical assets 17self.addEventListener('install', (event) => { 18 console.log('[Service Worker] Installing...'); 19 event.waitUntil( 20 caches.open(CACHE_NAME).then((cache) => { 21 console.log('[Service Worker] Precaching assets'); 22 return cache.addAll(PRECACHE_ASSETS); 23 }).then(() => { 24 console.log('[Service Worker] Skip waiting'); 25 return self.skipWaiting(); 26 }) 27 ); 28}); 29 30// Activate event - clean up old caches 31self.addEventListener('activate', (event) => { 32 console.log('[Service Worker] Activating...'); 33 event.waitUntil( 34 caches.keys().then((cacheNames) => { 35 return Promise.all( 36 cacheNames.map((cacheName) => { 37 if (cacheName !== CACHE_NAME && cacheName !== RUNTIME_CACHE) { 38 console.log('[Service Worker] Deleting old cache:', cacheName); 39 return caches.delete(cacheName); 40 } 41 }) 42 ); 43 }).then(() => { 44 console.log('[Service Worker] Claiming clients'); 45 return self.clients.claim(); 46 }) 47 ); 48}); 49 50// Fetch event - network first, fallback to cache 51self.addEventListener('fetch', (event) => { 52 const { request } = event; 53 const url = new URL(request.url); 54 55 // Skip cross-origin requests 56 if (url.origin !== location.origin) { 57 return; 58 } 59 60 // Skip POST, PUT, DELETE requests 61 if (request.method !== 'GET') { 62 return; 63 } 64 65 // API requests and Remix loader/action data requests - network only (don't cache). 66 // Remix's single-fetch mode requests data via "*.data" URLs on every client-side 67 // navigation; caching these caused stale data (e.g. previous user's name) to 68 // flash on login until a hard refresh. 69 if ( 70 url.pathname.startsWith('/api/') || 71 url.pathname.endsWith('.data') || 72 url.searchParams.has('_data') 73 ) { 74 event.respondWith(fetch(request)); 75 return; 76 } 77 78 // For navigation requests (HTML pages) - network first 79 if (request.mode === 'navigate') { 80 event.respondWith( 81 fetch(request) 82 .then((response) => { 83 // Cache successful responses 84 if (response.status === 200) { 85 const responseClone = response.clone(); 86 caches.open(RUNTIME_CACHE).then((cache) => { 87 cache.put(request, responseClone); 88 }); 89 } 90 return response; 91 }) 92 .catch(() => { 93 // Fallback to cache if network fails 94 return caches.match(request).then((cached) => { 95 return cached || caches.match('/'); 96 }); 97 }) 98 ); 99 return; 100 } 101 102 // For other requests - cache first, fallback to network 103 event.respondWith( 104 caches.match(request).then((cached) => { 105 if (cached) { 106 // Return cached version and update cache in background 107 fetch(request).then((response) => { 108 if (response.status === 200) { 109 caches.open(RUNTIME_CACHE).then((cache) => { 110 cache.put(request, response.clone()); 111 }); 112 } 113 }); 114 return cached; 115 } 116 117 // Not in cache, fetch from network 118 return fetch(request).then((response) => { 119 if (response.status === 200) { 120 const responseClone = response.clone(); 121 caches.open(RUNTIME_CACHE).then((cache) => { 122 cache.put(request, responseClone); 123 }); 124 } 125 return response; 126 }); 127 }) 128 ); 129}); 130 131// Handle messages from clients 132self.addEventListener('message', (event) => { 133 if (event.data && event.data.type === 'SKIP_WAITING') { 134 self.skipWaiting(); 135 } 136 137 if (event.data && event.data.type === 'CLEAR_CACHE') { 138 event.waitUntil( 139 caches.keys().then((cacheNames) => { 140 return Promise.all( 141 cacheNames.map((cacheName) => caches.delete(cacheName)) 142 ); 143 }) 144 ); 145 } 146}); 147
148// Periodic background sync (if supported) 149self.addEventListener('sync', (event) => { 150 if (event.tag === 'sync-data') { 151 event.waitUntil(syncData()); 152 } 153}); 154 155async function syncData() { 156 // Implement background sync logic here 157 console.log('[Service Worker] Background sync'); 158} 159 160// Push notifications (if needed in future) 161self.addEventListener('push', (event) => { 162 const options = { 163 body: event.data ? event.data.text() : 'New notification', 164 icon: '/favicon/android-chrome-192x192.png', 165 badge: '/favicon/android-chrome-192x192.png', 166 vibrate: [200, 100, 200], 167 tag: 'fedacash-notification', 168 requireInteraction: false, 169 }; 170 171 event.waitUntil( 172 self.registration.showNotification('FedaCash', options) 173 ); 174}); 175 176self.addEventListener('notificationclick', (event) => { 177 event.notification.close(); 178 event.waitUntil( 179 clients.openWindow('/') 180 ); 181});
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.