// TETRIG 서비스 워커 — 오프라인 캐시 (앱처럼 설치/실행) const CACHE = 'tetrig-v2'; const ASSETS = [ './', './manifest.webmanifest', './icon-192.png', './icon-512.png', './apple-touch-icon.png', ]; // Cloudflare Pages 는 /index.html 을 / 로 308 리다이렉트한다. // 리다이렉트된 Response 를 그대로 캐시하면 navigation 요청에 응답할 수 없어 // (spec 상 금지) 두 번째 실행부터 ERR_FAILED 가 난다. 새 Response 로 복사해 저장한다. async function cacheClean(cache, url) { const res = await fetch(url, { cache: 'reload' }); if (!res.ok) return; const body = await res.blob(); await cache.put(url, new Response(body, { status: 200, statusText: 'OK', headers: res.headers })); } self.addEventListener('install', (e) => { e.waitUntil( caches.open(CACHE).then((c) => Promise.all(ASSETS.map((u) => cacheClean(c, u)))) ); self.skipWaiting(); }); self.addEventListener('activate', (e) => { e.waitUntil( caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))) ) ); self.clients.claim(); }); self.addEventListener('fetch', (e) => { if (e.request.method !== 'GET') return; // 페이지 진입: 네트워크 우선, 실패하면 캐시된 시작 문서로 오프라인 동작 if (e.request.mode === 'navigate') { e.respondWith( fetch(e.request).catch(async () => (await caches.match('./')) || Response.error()) ); return; } // 나머지 정적 에셋: 캐시 우선 e.respondWith(caches.match(e.request).then((r) => r || fetch(e.request))); });