const CACHE_NAME = 'time-tracker-app-v2'; const urlsToCache = [ '/', '/index.html', '/manifest.json', '/favicon.ico', '/logo192.png', '/logo512.png' ]; // Install event - cache essential static assets self.addEventListener('install', (event) => { // Skip waiting to activate immediately self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { console.log('Opened cache'); // Use addAll for guaranteed files. If these fail, SW won't install. // We omit JS/CSS because CRA uses hashed filenames in production. return cache.addAll(urlsToCache); }) ); }); // Fetch event - Stale-while-revalidate strategy for most requests self.addEventListener('fetch', (event) => { // Only handle GET requests if (event.request.method !== 'GET') return; // Skip cross-origin requests if (!event.request.url.startsWith(self.location.origin)) return; event.respondWith( caches.match(event.request) .then((cachedResponse) => { const fetchPromise = fetch(event.request).then((networkResponse) => { // Dynamically cache successful GET requests if (networkResponse && networkResponse.status === 200) { const responseToCache = networkResponse.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }); } return networkResponse; }).catch(() => { // If network fails and we have no cache, return the cached index.html for navigation requests (SPA fallback) if (event.request.mode === 'navigate') { return caches.match('/'); } }); return cachedResponse || fetchPromise; }) ); }); // Activate event - clean up old caches self.addEventListener('activate', (event) => { // Take control of all clients immediately event.waitUntil(self.clients.claim()); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME) { console.log('Deleting old cache:', cacheName); return caches.delete(cacheName); } }) ); }) ); });