117 lines
3.9 KiB
JavaScript
117 lines
3.9 KiB
JavaScript
/**
|
|
* 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);
|
|
})
|
|
);
|
|
});
|