/** * sw.js — YoTrip Service Worker * ───────────────────────────────────────────────────────────────────────────── * Strategy: Network-First with Cache-API fallback. * * Caches: * 1. App Shell (static assets): cached at install time * 2. OpenStreetMap tiles: cached dynamically on first fetch, served from * cache when offline — so previously visited map areas remain navigable * * Cache names are versioned so old caches get evicted on SW update. */ const SHELL_CACHE = 'yotrip-shell-v1'; const MAP_CACHE = 'yotrip-map-tiles-v1'; // App shell files to pre-cache at install const SHELL_URLS = [ '/', '/index.html', '/manifest.json', '/favicon.ico', ]; // URL patterns for map tile providers const MAP_TILE_ORIGINS = [ 'tile.openstreetmap.org', 'a.tile.openstreetmap.org', 'b.tile.openstreetmap.org', 'c.tile.openstreetmap.org', 'tiles.stadiamaps.com', 'server.arcgisonline.com', ]; // ─── Install ────────────────────────────────────────────────────────────── self.addEventListener('install', (event) => { console.log('[SW] Installing yotrip service worker…'); event.waitUntil( caches.open(SHELL_CACHE).then((cache) => { return cache.addAll(SHELL_URLS).catch((err) => { // Non-fatal: some shell files may not exist in dev mode console.warn('[SW] Shell pre-cache partial failure (non-fatal):', err); }); }) ); // Activate immediately without waiting for old tabs to close self.skipWaiting(); }); // ─── Activate ───────────────────────────────────────────────────────────── self.addEventListener('activate', (event) => { const CURRENT_CACHES = [SHELL_CACHE, MAP_CACHE]; event.waitUntil( caches.keys().then((cacheNames) => Promise.all( cacheNames .filter((name) => !CURRENT_CACHES.includes(name)) .map((name) => { console.log('[SW] Evicting stale cache:', name); return caches.delete(name); }) ) ) ); // Take control of all open clients immediately self.clients.claim(); }); // ─── Fetch ──────────────────────────────────────────────────────────────── self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); // 1. Map tile requests — Network-First, fall back to cache const isMapTile = MAP_TILE_ORIGINS.some((origin) => url.hostname.includes(origin)); if (isMapTile) { event.respondWith( fetch(event.request) .then((response) => { if (response && response.status === 200) { const clone = response.clone(); caches.open(MAP_CACHE).then((cache) => { cache.put(event.request, clone); }); } return response; }) .catch(() => caches.match(event.request)) ); return; } // 2. API calls — Network-Only (never cache API responses) if (url.pathname.startsWith('/api/')) { return; // Let the browser handle normally } // 3. App shell navigation — Cache-First for HTML, then network if (event.request.mode === 'navigate') { event.respondWith( caches.match('/index.html').then((cached) => { return cached || fetch(event.request); }) ); return; } // 4. Static assets (JS/CSS/images) — Cache-First event.respondWith( caches.match(event.request).then((cached) => { return cached || fetch(event.request); }) ); });