/* Food Market — service worker (read-only owner PWA). * * Стратегии: * - Навигация (mode=navigate): network-first с offline-fallback на /offline.html. * - Статика (CSS/JS/SVG): stale-while-revalidate из cache. * - GET /api/... : network-first; при ошибке/offline отдаём кэш если есть * (read-only стратегия: записи не кешируем, мутации не оффлайн-обходимы). * - POST/PUT/DELETE на /api/... : всегда сеть. При отсутствии — toast в UI * (через api interceptor). * * Версия кэша инкрементируется при изменении SW — старые удаляются на activate. */ const CACHE_VERSION = 'fm-v1'; const STATIC_CACHE = `${CACHE_VERSION}-static`; const API_CACHE = `${CACHE_VERSION}-api`; const OFFLINE_URL = '/offline.html'; self.addEventListener('install', (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => // Прекеш offline-page чтобы fallback работал даже после reboot'a. cache.addAll(['/offline.html', '/favicon.svg', '/logo.svg', '/manifest.webmanifest']) ).then(() => self.skipWaiting()) ); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all(keys .filter((k) => !k.startsWith(CACHE_VERSION)) .map((k) => caches.delete(k))) ).then(() => self.clients.claim()) ); }); self.addEventListener('fetch', (event) => { const req = event.request; if (req.method !== 'GET') return; // не вмешиваемся в мутации const url = new URL(req.url); // Навигация по приложению — SPA shell. if (req.mode === 'navigate') { event.respondWith( fetch(req).catch(() => caches.match(OFFLINE_URL)) ); return; } // API GET — network-first + cache-fallback. Кешируем только успешные ответы. if (url.origin === self.location.origin && url.pathname.startsWith('/api/')) { event.respondWith( fetch(req) .then((resp) => { if (resp.ok) { const copy = resp.clone(); caches.open(API_CACHE).then((c) => c.put(req, copy)); } return resp; }) .catch(() => caches.match(req)) ); return; } // Статика — stale-while-revalidate. if (req.destination === 'script' || req.destination === 'style' || req.destination === 'image' || req.destination === 'font' || url.pathname.endsWith('.svg') || url.pathname.endsWith('.css') || url.pathname.endsWith('.js')) { event.respondWith( caches.open(STATIC_CACHE).then((cache) => cache.match(req).then((cached) => { const network = fetch(req).then((resp) => { if (resp.ok) cache.put(req, resp.clone()); return resp; }); return cached || network; }) ) ); } });