offline mode
This commit is contained in:
96
src/sw.js
96
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'
|
const CACHE_VERSION = typeof __APP_CACHE_VERSION__ !== 'undefined'
|
||||||
? __APP_CACHE_VERSION__
|
? __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 = [
|
const APP_SHELL_URLS = [
|
||||||
// Note: '/' (index.html) is handled by NetworkFirst strategy, no need to precache explicitly here.
|
// Precache the root (index.html) explicitly for better offline fallback
|
||||||
'/manifest.json', // Will be served from public, copied to dist root
|
'/',
|
||||||
'/favicon.ico', // Will be served from public, copied to dist root
|
'/manifest.json',
|
||||||
// Icons from public/icons, will be copied to dist/icons
|
'/favicon.ico',
|
||||||
'/icons/icon-192x192.png',
|
'/icons/icon-192x192.png',
|
||||||
'/icons/icon-512x512.png',
|
'/icons/icon-512x512.png',
|
||||||
'/icons/maskable-icon-192x192.png',
|
'/icons/maskable-icon-192x192.png',
|
||||||
'/icons/maskable-icon-512x512.png',
|
'/icons/maskable-icon-512x512.png',
|
||||||
'/icons/shortcut-setup-96x96.png',
|
'/icons/shortcut-setup-96x96.png',
|
||||||
'/icons/shortcut-info-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 => {
|
self.addEventListener('install', event => {
|
||||||
@@ -57,47 +54,68 @@ self.addEventListener('fetch', event => {
|
|||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
if (request.method !== 'GET' || !url.protocol.startsWith('http')) {
|
if (request.method !== 'GET' || !url.protocol.startsWith('http')) {
|
||||||
// console.log(`[SW ${CACHE_VERSION}] Ignoring non-GET or non-http(s) request: ${request.url}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 1: Network First for HTML (navigations or direct / request)
|
// Strategy 1: Network First, then Cache for Navigation/HTML requests
|
||||||
if (request.mode === 'navigate' || (request.destination === 'document' || url.pathname === '/')) {
|
if (request.mode === 'navigate' || request.destination === 'document' || url.pathname === '/') {
|
||||||
// console.log(`[SW ${CACHE_VERSION}] NetworkFirst for: ${request.url}`);
|
// console.log(`[SW ${CACHE_VERSION}] NetworkFirst for navigation/document: ${request.url}`);
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(request)
|
fetch(request)
|
||||||
.then(response => {
|
.then(networkResponse => {
|
||||||
if (response.ok) {
|
// If successful, cache the response and return it
|
||||||
const responseClone = response.clone();
|
if (networkResponse.ok) {
|
||||||
caches.open(CACHE_VERSION).then(cache => cache.put(request, responseClone));
|
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 () => {
|
.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);
|
const cachedResponse = await caches.match(request);
|
||||||
if (cachedResponse) return cachedResponse;
|
if (cachedResponse) {
|
||||||
// Fallback to root /index.html from cache if specific page not found offline
|
// console.log(`[SW ${CACHE_VERSION}] Serving from cache (specific request): ${request.url}`);
|
||||||
const rootCache = await caches.match('/');
|
return cachedResponse;
|
||||||
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.', {
|
// 2. If specific request not found, try serving the app shell ('/')
|
||||||
status: 404,
|
// This is crucial for SPAs to work offline.
|
||||||
statusText: 'Not Found',
|
const appShellResponse = await caches.match('/');
|
||||||
headers: { 'Content-Type': 'text/html' } // Important for SPA offline fallback
|
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;
|
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' ||
|
if (request.destination === 'style' ||
|
||||||
request.destination === 'script' ||
|
request.destination === 'script' ||
|
||||||
request.destination === 'worker' ||
|
request.destination === 'worker' ||
|
||||||
request.destination === 'image' ||
|
request.destination === 'image' ||
|
||||||
request.destination === 'font') {
|
request.destination === 'font') {
|
||||||
// console.log(`[SW ${CACHE_VERSION}] StaleWhileRevalidate for: ${request.url}`);
|
// console.log(`[SW ${CACHE_VERSION}] StaleWhileRevalidate for asset: ${request.url}`);
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.open(CACHE_VERSION).then(cache => {
|
caches.open(CACHE_VERSION).then(cache => {
|
||||||
return cache.match(request).then(cachedResponse => {
|
return cache.match(request).then(cachedResponse => {
|
||||||
@@ -105,21 +123,19 @@ self.addEventListener('fetch', event => {
|
|||||||
if (networkResponse.ok) {
|
if (networkResponse.ok) {
|
||||||
const responseToCache = networkResponse.clone();
|
const responseToCache = networkResponse.clone();
|
||||||
cache.put(request, responseToCache);
|
cache.put(request, responseToCache);
|
||||||
} else {
|
|
||||||
// console.warn(`[SW ${CACHE_VERSION}] StaleWhileRevalidate: Network fetch for ${request.url} failed with status ${networkResponse.status}`);
|
|
||||||
}
|
}
|
||||||
return networkResponse;
|
return networkResponse;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
// console.warn(`[SW ${CACHE_VERSION}] StaleWhileRevalidate: Network fetch error for ${request.url}:`, err);
|
// If fetch fails, and we served from cache, it's fine.
|
||||||
// If fetch fails, and we already served from cache, that's okay.
|
// If cache also missed, this error will propagate.
|
||||||
// If cache also missed (i.e., cachedResponse was null), then this error will propagate.
|
// console.warn(`[SW ${CACHE_VERSION}] SWR: Network fetch error for ${request.url}`, err);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
return cachedResponse || fetchPromise;
|
return cachedResponse || fetchPromise;
|
||||||
}).catch(err => {
|
}).catch(() => {
|
||||||
// This catch block handles errors from cache.match() or if fetchPromise was returned and rejected
|
// Fallback to network if cache.match fails
|
||||||
// console.error(`[SW ${CACHE_VERSION}] StaleWhileRevalidate: Error for ${request.url}. Trying network fallback.`, err);
|
// console.warn(`[SW ${CACHE_VERSION}] SWR: Cache match error for ${request.url}, trying network directly.`);
|
||||||
return fetch(request); // Final fallback to network if cache interactions fail
|
return fetch(request);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user