diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-08-06 21:52:21 +0200 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-08-06 21:52:21 +0200 |
| commit | ebfb7c1c8bc0a9ec50bde72eb9a0859c6e5dcee5 (patch) | |
| tree | 35353c15726d7d036907df731b00d390c1d1f538 /src/frontend/public/pwa-worker.js | |
| parent | 5cc27f852b02446659e0ab85305916c9f5e5a5f0 (diff) | |
Fix this fucking pile of steaming garbage
Diffstat (limited to 'src/frontend/public/pwa-worker.js')
| -rw-r--r-- | src/frontend/public/pwa-worker.js | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/src/frontend/public/pwa-worker.js b/src/frontend/public/pwa-worker.js new file mode 100644 index 0000000..f47ede0 --- /dev/null +++ b/src/frontend/public/pwa-worker.js @@ -0,0 +1,145 @@ +const CACHE_VERSION = "2025-0806a"; +const API_CACHE_NAME = `api-cache-${CACHE_VERSION}`; +const STATIC_CACHE_NAME = `static-cache-${CACHE_VERSION}`; + +const API_URL_PATTERN = /\/api\/(GetStopList|GetStopEstimates|GetStopTimetable)/; +const API_MAX_AGE = 24 * 60 * 60 * 1000; // 24h +const ESTIMATES_MIN_AGE = 15 * 1000; +const ESTIMATES_MAX_AGE = 30 * 1000; + +self.addEventListener("install", (event) => { + console.log("SW: Installing new version"); + event.waitUntil( + caches.open(STATIC_CACHE_NAME).then((cache) => + cache.addAll([ + "/favicon.ico", + "/logo-256.png", + "/logo-512.jpg", + "/stops.json" + ]) + ).then(() => self.skipWaiting()) + ); +}); + +self.addEventListener("activate", (event) => { + console.log("SW: Activating new version"); + event.waitUntil( + (async () => { + const cacheNames = await caches.keys(); + await Promise.all( + cacheNames.map((name) => { + if (name !== API_CACHE_NAME && name !== STATIC_CACHE_NAME) { + console.log("SW: Deleting old cache:", name); + return caches.delete(name); + } + }) + ); + await self.clients.claim(); + const clients = await self.clients.matchAll(); + clients.forEach((client) => + client.postMessage({ type: "SW_UPDATED" }) + ); + })() + ); +}); + +self.addEventListener("fetch", (event) => { + const request = event.request; + const url = new URL(request.url); + + // Ignore requests with unsupported schemes + if (!url.protocol.startsWith('http')) { + return; + } + + // API + if (request.method === "GET" && API_URL_PATTERN.test(url.pathname)) { + event.respondWith(handleApiRequest(request)); + return; + } + + // Navegación -> network first + if (request.mode === "navigate") { + event.respondWith(handleNavigationRequest(request)); + return; + } + + // Estáticos -> cache first + if (request.method === "GET") { + event.respondWith(handleStaticRequest(request)); + } +}); + +async function handleApiRequest(request) { + const url = new URL(request.url); + const isEstimates = url.pathname.includes("GetStopEstimates"); + const maxAge = isEstimates + ? ESTIMATES_MIN_AGE + Math.random() * (ESTIMATES_MAX_AGE - ESTIMATES_MIN_AGE) + : API_MAX_AGE; + + const cache = await caches.open(API_CACHE_NAME); + const cachedResponse = await cache.match(request); + + if (cachedResponse) { + const dateHeader = cachedResponse.headers.get("date"); + const age = dateHeader ? Date.now() - new Date(dateHeader).getTime() : 0; + if (age && age < maxAge) { + console.debug("SW: Cache HIT", request.url); + return cachedResponse; + } + cache.delete(request); + } + + try { + const netResponse = await fetch(request); + if (netResponse.ok) cache.put(request, netResponse.clone()); + return netResponse; + } catch (error) { + if (cachedResponse) return cachedResponse; + throw error; + } +} + +async function handleStaticRequest(request) { + const cache = await caches.open(STATIC_CACHE_NAME); + const cachedResponse = await cache.match(request); + if (cachedResponse) return cachedResponse; + + try { + const netResponse = await fetch(request); + if (netResponse.ok) cache.put(request, netResponse.clone()); + return netResponse; + } catch (err) { + return new Response("Offline asset not available", { + status: 503, + headers: { "Content-Type": "text/plain" } + }); + } +} + +async function handleNavigationRequest(request) { + try { + const netResponse = await fetch(request); + return netResponse; + } catch (err) { + // Si no hay red, intenta fallback con caché estática + const cache = await caches.open(STATIC_CACHE_NAME); + const offline = await cache.match("/stops.json"); + return ( + offline || + new Response("App offline", { + status: 503, + headers: { "Content-Type": "text/plain" } + }) + ); + } +} + +self.addEventListener("message", (event) => { + if (event.data?.type === "SKIP_WAITING") self.skipWaiting(); + if (event.data?.type === "CLEAR_CACHE") { + event.waitUntil( + caches.keys().then((names) => Promise.all(names.map((n) => caches.delete(n)))) + ); + } +}); |
