const CACHE_NAME = 'dgray-v50'; const STATIC_ASSETS = [ '/', '/index.html', '/favicon.png', '/manifest.json', // CSS '/css/components.css', '/css/animate.custom.css', '/css/vendor/cropper.min.css', // Core JS '/js/app.js', '/js/router.js', '/js/i18n.js', '/js/utils/helpers.js', // Services '/js/services/directus.js', '/js/services/auth.js', '/js/services/listings.js', '/js/services/categories.js', '/js/services/locations.js', '/js/services/conversations.js', '/js/services/crypto.js', '/js/services/currency.js', '/js/services/pow-captcha.js', '/js/services/btcpay.js', '/js/services/favorites.js', '/js/services/notifications.js', // Components '/js/components/app-shell.js', '/js/components/app-header.js', '/js/components/app-footer.js', '/js/components/auth-modal.js', '/js/components/chat-widget.js', '/js/components/listing-card.js', '/js/components/skeleton-card.js', '/js/components/search-box.js', '/js/components/error-boundary.js', '/js/components/image-cropper.js', '/js/components/location-picker.js', '/js/components/location-map.js', '/js/components/pow-captcha.js', // Pages (critical only — lazy-loaded pages are runtime-cached via stale-while-revalidate) '/js/components/pages/page-home.js', '/js/components/pages/page-not-found.js', // Vendor '/js/vendor/cropper.min.js', '/js/vendor/nacl-fast.min.js', '/js/vendor/nacl-util.min.js', // Locales '/locales/de.json', '/locales/en.json', '/locales/fr.json', '/locales/it.json', '/locales/es.json', '/locales/pt.json', '/locales/ru.json', // Fonts '/assets/fonts/Inter-Regular.woff2', '/assets/fonts/Inter-Medium.woff2', '/assets/fonts/Inter-SemiBold.woff2', '/assets/fonts/Inter-Bold.woff2', '/assets/fonts/SpaceGrotesk-Medium.woff2', '/assets/fonts/SpaceGrotesk-Bold.woff2' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(STATIC_ASSETS)) ); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys() .then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ); }) .then(() => self.clients.claim()) ); }); self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); if (request.method !== 'GET') return; // API calls: Network First (external API domain) if (url.hostname === 'api.dgray.io') { event.respondWith(networkFirst(request)); return; } // Legacy: API calls via path if (url.pathname.includes('/api/')) { event.respondWith(networkFirst(request)); return; } // HTML and JS: Stale-While-Revalidate (show cached, update in background) if (url.pathname.endsWith('.html') || url.pathname.endsWith('.js') || url.pathname === '/') { event.respondWith(staleWhileRevalidate(request)); return; } // Everything else: Cache First event.respondWith(cacheFirst(request)); }); async function cacheFirst(request) { const cached = await caches.match(request); if (cached) return cached; try { const response = await fetch(request); if (response.ok) { const cache = await caches.open(CACHE_NAME); cache.put(request, response.clone()); } return response; } catch { return new Response('Offline', { status: 503 }); } } async function networkFirst(request) { try { const response = await fetch(request); if (response.ok) { const cache = await caches.open(CACHE_NAME); cache.put(request, response.clone()); } return response; } catch { const cached = await caches.match(request); if (cached) return cached; return new Response(JSON.stringify({ error: 'Offline' }), { status: 503, headers: { 'Content-Type': 'application/json' } }); } } async function staleWhileRevalidate(request) { const cache = await caches.open(CACHE_NAME); const cached = await cache.match(request); const fetchPromise = fetch(request).then((response) => { if (response.ok) { cache.put(request, response.clone()); } return response; }).catch(() => cached || new Response('Offline', { status: 503 })); return cached || fetchPromise; }