/** * Weather Tile Cache ServiceWorker * * MapTiler Weather SDK 타일(Image 요소로 로드)을 Cache API로 캐싱. * 같은 tileset_id + z/x/y 좌표 → 동일 URL → cache-first 전략. * * 캐시 최대 2000장, 초과 시 가장 오래된 항목부터 제거. */ const CACHE_NAME = 'weather-tiles-v1'; const MAX_ENTRIES = 2000; /** api.maptiler.com/tiles/ 패턴만 캐싱 */ const TILE_RE = /api\.maptiler\.com\/tiles\//; self.addEventListener('install', () => { self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((k) => k.startsWith('weather-tiles-') && k !== CACHE_NAME) .map((k) => caches.delete(k)), ), ), ); self.clients.claim(); }); self.addEventListener('fetch', (event) => { const url = event.request.url; if (!TILE_RE.test(url)) return; // 타일 외 요청은 패스스루 event.respondWith( caches.open(CACHE_NAME).then(async (cache) => { // 1. 캐시 히트 → 즉시 반환 const cached = await cache.match(event.request); if (cached) return cached; // 2. 네트워크 fetch const response = await fetch(event.request); if (response.ok) { // 비동기 캐시 저장 (응답 지연 없음) cache.put(event.request, response.clone()).then(() => trimCache(cache)); } return response; }), ); }); /** 캐시 항목이 MAX_ENTRIES 초과 시 오래된 것부터 삭제 */ async function trimCache(cache) { const keys = await cache.keys(); if (keys.length <= MAX_ENTRIES) return; const excess = keys.length - MAX_ENTRIES; for (let i = 0; i < excess; i++) { await cache.delete(keys[i]); } }