offline mode

This commit is contained in:
cpu
2025-05-15 00:11:42 +02:00
parent 19fbae06fc
commit 6e11db9fb9

View File

@@ -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(
`<h1>Offline</h1><p>The application is currently offline and the requested page could not be loaded from the cache. Please check your connection.</p>`,
{ 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.
// 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);
});
})
);