diff --git a/app/static/sw.js b/app/static/sw.js index 4c110ee..4b4e58d 100644 --- a/app/static/sw.js +++ b/app/static/sw.js @@ -1,9 +1,9 @@ // NextSnap Service Worker // Provides offline-first caching for the app shell -const CACHE_VERSION = 'nextsnap-v18'; -const APP_SHELL_CACHE = 'nextsnap-shell-v14'; -const RUNTIME_CACHE = 'nextsnap-runtime-v14'; +const CACHE_VERSION = 'nextsnap-v19'; +const APP_SHELL_CACHE = 'nextsnap-shell-v15'; +const RUNTIME_CACHE = 'nextsnap-runtime-v15'; // Assets to cache on install const APP_SHELL_ASSETS = [ @@ -62,91 +62,72 @@ self.addEventListener('activate', (event) => { ); }); -// Fetch event - cache-first for static assets, network-first for API +// Fetch event - route requests to appropriate caching strategy self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); - // Skip non-GET requests + // Skip non-GET requests (uploads, form posts, etc.) if (request.method !== 'GET') { return; } - // API requests - network-first with offline fallback - if (url.pathname.startsWith('/api/')) { + // Static assets (/static/) - cache-first (versioned via SW cache bump) + if (url.pathname.startsWith('/static/')) { event.respondWith( - fetch(request) - .then((response) => { - // Clone response for cache - const responseClone = response.clone(); - - // Only cache successful responses (not errors or auth failures) - if (response.status === 200) { - caches.open(RUNTIME_CACHE).then((cache) => { - // Don't cache file uploads or large responses - if (!url.pathname.includes('/upload') && - !url.pathname.includes('/thumbnail')) { - cache.put(request, responseClone); - } - }); + caches.match(request) + .then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; } - - return response; - }) - .catch(() => { - // Network failed - try cache, then offline response - return caches.match(request) - .then((cachedResponse) => { - if (cachedResponse) { - return cachedResponse; + return fetch(request) + .then((response) => { + if (!response || response.status !== 200) { + return response; } - - // Return offline fallback for API - return new Response( - JSON.stringify({ - error: 'offline', - message: 'You are offline. This feature requires connectivity.' - }), - { - status: 503, - statusText: 'Service Unavailable', - headers: { 'Content-Type': 'application/json' } - } - ); - }); + const responseClone = response.clone(); + caches.open(APP_SHELL_CACHE).then((cache) => { + cache.put(request, responseClone); + }); + return response; + }) + .catch(() => new Response('Offline', { status: 503 })); }) ); return; } - // Static assets - cache-first strategy + // Everything else (pages, API) - network-first with cache fallback event.respondWith( - caches.match(request) - .then((cachedResponse) => { - if (cachedResponse) { - return cachedResponse; - } - - // Not in cache - fetch from network and cache it - return fetch(request) - .then((response) => { - // Don't cache non-successful responses - if (!response || response.status !== 200 || response.type === 'error') { - return response; - } - - // Clone response for cache - const responseClone = response.clone(); - - caches.open(RUNTIME_CACHE).then((cache) => { + fetch(request) + .then((response) => { + // Cache successful GET responses for offline fallback + if (response.status === 200) { + const responseClone = response.clone(); + caches.open(RUNTIME_CACHE).then((cache) => { + // Don't cache file uploads or large binary responses + if (!url.pathname.includes('/upload') && + !url.pathname.includes('/thumbnail')) { cache.put(request, responseClone); - }); - - return response; - }) - .catch(() => { - // Network failed and not in cache - // For HTML pages, could return offline page here + } + }); + } + return response; + }) + .catch(() => { + // Network failed - try cache + return caches.match(request) + .then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + // No cache - return offline response + if (url.pathname.startsWith('/api/')) { + return new Response( + JSON.stringify({ error: 'offline', message: 'You are offline.' }), + { status: 503, headers: { 'Content-Type': 'application/json' } } + ); + } return new Response('Offline', { status: 503 }); }); })