diff --git a/src/sw.js b/src/sw.js index 0cc7aac..f744b7b 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,21 +1,18 @@ -// This global constant __APP_CACHE_VERSION__ will be replaced by Vite -// during the build process due to the `define` config in vite.config.js. const CACHE_VERSION = typeof __APP_CACHE_VERSION__ !== 'undefined' ? __APP_CACHE_VERSION__ - : 'nexus-timer-cache-fallback-dev-vManual'; // Fallback for dev or if define fails + : 'nexus-timer-cache-fallback-dev-vManual'; const APP_SHELL_URLS = [ - // Note: '/' (index.html) is handled by NetworkFirst strategy, no need to precache explicitly here. - '/manifest.json', // Will be served from public, copied to dist root - '/favicon.ico', // Will be served from public, copied to dist root - // Icons from public/icons, will be copied to dist/icons + // Precache the root (index.html) explicitly for better offline fallback + '/', + '/manifest.json', + '/favicon.ico', '/icons/icon-192x192.png', '/icons/icon-512x512.png', '/icons/maskable-icon-192x192.png', '/icons/maskable-icon-512x512.png', '/icons/shortcut-setup-96x96.png', '/icons/shortcut-info-96x96.png', - // Any other critical static assets from the public folder that should be part of the app shell ]; self.addEventListener('install', event => { @@ -57,47 +54,68 @@ self.addEventListener('fetch', event => { const url = new URL(request.url); if (request.method !== 'GET' || !url.protocol.startsWith('http')) { - // console.log(`[SW ${CACHE_VERSION}] Ignoring non-GET or non-http(s) request: ${request.url}`); return; } - // Strategy 1: Network First for HTML (navigations or direct / request) - if (request.mode === 'navigate' || (request.destination === 'document' || url.pathname === '/')) { - // console.log(`[SW ${CACHE_VERSION}] NetworkFirst for: ${request.url}`); + // Strategy 1: Network First, then Cache for Navigation/HTML requests + if (request.mode === 'navigate' || request.destination === 'document' || url.pathname === '/') { + // console.log(`[SW ${CACHE_VERSION}] NetworkFirst for navigation/document: ${request.url}`); event.respondWith( fetch(request) - .then(response => { - if (response.ok) { - const responseClone = response.clone(); - caches.open(CACHE_VERSION).then(cache => cache.put(request, responseClone)); + .then(networkResponse => { + // If successful, cache the response and return it + if (networkResponse.ok) { + const responseToCache = networkResponse.clone(); + caches.open(CACHE_VERSION).then(cache => { + // For navigations, it's often best to cache the specific URL requested + // as well as potentially updating the '/' cache if this is the root. + cache.put(request, responseToCache); + if (url.pathname === '/') { // Also update root cache if it's the index + const rootResponseClone = networkResponse.clone(); // Need another clone + cache.put('/', rootResponseClone); + } + }); } - return response; + return networkResponse; }) .catch(async () => { - // console.warn(`[SW ${CACHE_VERSION}] Network fetch failed for ${request.url}, trying cache.`); + // Network failed. Try to serve from cache. + // console.warn(`[SW ${CACHE_VERSION}] Network fetch failed for ${request.url}. Attempting cache.`); + + // 1. Try matching the specific request first (e.g. /info, /game) const cachedResponse = await caches.match(request); - if (cachedResponse) return cachedResponse; - // Fallback to root /index.html from cache if specific page not found offline - const rootCache = await caches.match('/'); - if (rootCache) return rootCache; - // console.error(`[SW ${CACHE_VERSION}] Network and cache miss for navigation: ${request.url}`); - return new Response('Network error: You are offline and this page is not cached.', { - status: 404, - statusText: 'Not Found', - headers: { 'Content-Type': 'text/html' } // Important for SPA offline fallback - }); + if (cachedResponse) { + // console.log(`[SW ${CACHE_VERSION}] Serving from cache (specific request): ${request.url}`); + return cachedResponse; + } + + // 2. If specific request not found, try serving the app shell ('/') + // This is crucial for SPAs to work offline. + const appShellResponse = await caches.match('/'); + if (appShellResponse) { + // console.log(`[SW ${CACHE_VERSION}] Serving app shell ('/') from cache for: ${request.url}`); + return appShellResponse; + } + + // 3. If even the app shell is not in cache (shouldn't happen if install was successful) + console.error(`[SW ${CACHE_VERSION}] CRITICAL: Network and cache miss for navigation AND app shell ('/') for: ${request.url}`); + // Return a very basic offline message, but ideally this state is avoided. + return new Response( + `

Offline

The application is currently offline and the requested page could not be loaded from the cache. Please check your connection.

`, + { headers: { 'Content-Type': 'text/html' } } + ); }) ); return; } - // Strategy 2: Stale-While-Revalidate for assets (CSS, JS, images, fonts) + // Strategy 2: Stale-While-Revalidate for assets (CSS, JS, images, fonts, workers) if (request.destination === 'style' || request.destination === 'script' || request.destination === 'worker' || request.destination === 'image' || request.destination === 'font') { - // console.log(`[SW ${CACHE_VERSION}] StaleWhileRevalidate for: ${request.url}`); + // console.log(`[SW ${CACHE_VERSION}] StaleWhileRevalidate for asset: ${request.url}`); event.respondWith( caches.open(CACHE_VERSION).then(cache => { return cache.match(request).then(cachedResponse => { @@ -105,21 +123,19 @@ self.addEventListener('fetch', event => { if (networkResponse.ok) { const responseToCache = networkResponse.clone(); cache.put(request, responseToCache); - } else { - // console.warn(`[SW ${CACHE_VERSION}] StaleWhileRevalidate: Network fetch for ${request.url} failed with status ${networkResponse.status}`); } return networkResponse; }).catch(err => { - // console.warn(`[SW ${CACHE_VERSION}] StaleWhileRevalidate: Network fetch error for ${request.url}:`, err); - // If fetch fails, and we already served from cache, that's okay. - // If cache also missed (i.e., cachedResponse was null), then this error will propagate. - throw err; + // If fetch fails, and we served from cache, it's fine. + // If cache also missed, this error will propagate. + // console.warn(`[SW ${CACHE_VERSION}] SWR: Network fetch error for ${request.url}`, err); + throw err; }); return cachedResponse || fetchPromise; - }).catch(err => { - // This catch block handles errors from cache.match() or if fetchPromise was returned and rejected - // console.error(`[SW ${CACHE_VERSION}] StaleWhileRevalidate: Error for ${request.url}. Trying network fallback.`, err); - return fetch(request); // Final fallback to network if cache interactions fail + }).catch(() => { + // Fallback to network if cache.match fails + // console.warn(`[SW ${CACHE_VERSION}] SWR: Cache match error for ${request.url}, trying network directly.`); + return fetch(request); }); }) );