added systemd service howto traefik nginix set_real_ip_from improved readme visuals fixed on mobile labels removed updated readme fixed visuals overlay for the hotkey disable screen lock clean up git precommit hooks clean up clean up update check for update feature added build-time information fixed date clean up added hook script fix fix fix hooks fixed webhook setup players stay in run all timers mode mqtt mqtt allways connected mqtt messages work capturing mqtt in edit player mqtt in Setup updated readme state of the mqtt Global Pass turn offline mode docs: update documentation to reflect current codebase and MQTT features - Update README.md with global MQTT commands - Enhance architecture.md with comprehensive data model and MQTT state - Update development.md with project structure and workflow - Remove redundant script listings - Fix formatting and organization rebase
166 lines
6.4 KiB
JavaScript
166 lines
6.4 KiB
JavaScript
const CACHE_VERSION = typeof __APP_CACHE_VERSION__ !== 'undefined'
|
|
? __APP_CACHE_VERSION__
|
|
: 'nexus-timer-cache-fallback-dev-vManual';
|
|
|
|
const APP_SHELL_URLS = [
|
|
// 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',
|
|
];
|
|
|
|
self.addEventListener('install', event => {
|
|
console.log(`[SW ${CACHE_VERSION}] Install`);
|
|
event.waitUntil(
|
|
caches.open(CACHE_VERSION)
|
|
.then(cache => {
|
|
console.log(`[SW ${CACHE_VERSION}] Caching app shell essentials`);
|
|
return cache.addAll(APP_SHELL_URLS);
|
|
})
|
|
.then(() => {
|
|
console.log(`[SW ${CACHE_VERSION}] Skip waiting on install.`);
|
|
return self.skipWaiting();
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', event => {
|
|
console.log(`[SW ${CACHE_VERSION}] Activate`);
|
|
event.waitUntil(
|
|
caches.keys().then(cacheNames => {
|
|
return Promise.all(
|
|
cacheNames.map(cacheName => {
|
|
if (cacheName !== CACHE_VERSION) {
|
|
console.log(`[SW ${CACHE_VERSION}] Deleting old cache: ${cacheName}`);
|
|
return caches.delete(cacheName);
|
|
}
|
|
})
|
|
);
|
|
}).then(() => {
|
|
console.log(`[SW ${CACHE_VERSION}] Clients claimed.`);
|
|
return self.clients.claim();
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', event => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
if (request.method !== 'GET' || !url.protocol.startsWith('http')) {
|
|
return;
|
|
}
|
|
|
|
// 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(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 networkResponse;
|
|
})
|
|
.catch(async () => {
|
|
// 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) {
|
|
// 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, workers)
|
|
if (request.destination === 'style' ||
|
|
request.destination === 'script' ||
|
|
request.destination === 'worker' ||
|
|
request.destination === 'image' ||
|
|
request.destination === 'font') {
|
|
// console.log(`[SW ${CACHE_VERSION}] StaleWhileRevalidate for asset: ${request.url}`);
|
|
event.respondWith(
|
|
caches.open(CACHE_VERSION).then(cache => {
|
|
return cache.match(request).then(cachedResponse => {
|
|
const fetchPromise = fetch(request).then(networkResponse => {
|
|
if (networkResponse.ok) {
|
|
const responseToCache = networkResponse.clone();
|
|
cache.put(request, responseToCache);
|
|
}
|
|
return networkResponse;
|
|
}).catch(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(() => {
|
|
// 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);
|
|
});
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Strategy 3: Cache First for other types of requests (e.g., manifest.json if not in APP_SHELL_URLS)
|
|
// console.log(`[SW ${CACHE_VERSION}] CacheFirst for: ${request.url}`);
|
|
event.respondWith(
|
|
caches.match(request)
|
|
.then(response => {
|
|
return response || fetch(request).then(networkResponse => {
|
|
if(networkResponse.ok) {
|
|
const responseClone = networkResponse.clone();
|
|
caches.open(CACHE_VERSION).then(cache => cache.put(request, responseClone));
|
|
}
|
|
return networkResponse;
|
|
});
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener('message', event => {
|
|
if (event.data && event.data.action === 'skipWaiting') {
|
|
console.log(`[SW ${CACHE_VERSION}] Received skipWaiting message, activating new SW.`);
|
|
self.skipWaiting();
|
|
}
|
|
}); |