// Service Worker for offline support const CACHE_NAME = 'juntete-v1' const RUNTIME_CACHE = 'juntete-runtime-v1' const STATIC_CACHE = 'juntete-static-v1' // Install event - cache static assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => { // Cache static assets that are available at install time return cache.addAll([ '/', '/dashboard', '/join', ]).catch((err) => { console.log('Some static assets failed to cache:', err) }) }) ) self.skipWaiting() }) // Activate event - clean up old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((cacheName) => { return cacheName !== CACHE_NAME && cacheName !== RUNTIME_CACHE && cacheName !== STATIC_CACHE }) .map((cacheName) => caches.delete(cacheName)) ) }) ) self.clients.claim() }) // Fetch event - serve from cache, fallback to network self.addEventListener('fetch', (event) => { const { request } = event const url = new URL(request.url) // Skip non-GET requests if (request.method !== 'GET') { return } // Skip API requests (handled by offline queue) if (url.pathname.startsWith('/api/')) { return } // Skip external requests if (url.origin !== location.origin) { return } // Handle navigation requests (page loads) if (request.mode === 'navigate') { event.respondWith( fetch(request) .then((response) => { // Cache successful navigation responses (including dynamic routes like /trip/[id]) if (response && response.status === 200) { const responseToCache = response.clone() caches.open(RUNTIME_CACHE).then((cache) => { // Cache the exact request URL cache.put(request, responseToCache) // Also cache with a normalized URL (remove query params for better matching) const urlWithoutQuery = new URL(request.url) urlWithoutQuery.search = '' cache.put(urlWithoutQuery.toString(), responseToCache.clone()) }) } return response }) .catch(() => { // If fetch fails, try to get from cache return caches.match(request).then((cachedResponse) => { if (cachedResponse) { return cachedResponse } // Try matching without query params const urlWithoutQuery = new URL(request.url) urlWithoutQuery.search = '' return caches.match(urlWithoutQuery.toString()).then((cachedResponseNoQuery) => { if (cachedResponseNoQuery) { return cachedResponseNoQuery } // If no cached version, try to return index page return caches.match('/').then((indexResponse) => { if (indexResponse) { return indexResponse } // Last resort: return a basic offline page return new Response('You are offline. Please check your connection.', { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'text/plain' } }) }) }) }) }) ) return } // Handle other requests (assets, scripts, etc.) event.respondWith( caches.match(request).then((cachedResponse) => { if (cachedResponse) { return cachedResponse } return fetch(request) .then((response) => { // Don't cache non-successful responses if (!response || response.status !== 200 || response.type !== 'basic') { return response } // Clone the response const responseToCache = response.clone() caches.open(RUNTIME_CACHE).then((cache) => { cache.put(request, responseToCache) }) return response }) .catch(() => { // If fetch fails, return cached version if available return caches.match(request) }) }) ) })