const CACHE_NAME = "apicore-shell-v3"; self.addEventListener("install", () => { self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches .keys() .then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))), ) .then(() => self.clients.claim()), ); }); self.addEventListener("fetch", (event) => { const { request } = event; if (request.method !== "GET") { return; } const url = new URL(request.url); // /api/runtime-config tells the app which backend origin to use - it // must always be fetched live. A stale cached value would point the app // at the wrong (or no longer running) backend, which is worse than no // cache at all. if (url.pathname === "/api/runtime-config") { return; } // Same-origin static assets/pages, or the real backend API. The backend // runs on its own origin (API_URL, injected at runtime via // /api/runtime-config) rather than this app's own origin, so it can't be // matched by origin the way static assets are - matched by path instead, // since every backend call goes through /api/v1/. Without this, colonies, // apiaries, tasks etc. were never cached at all: a beekeeper opening the // app with no signal at an apiary saw nothing, not even data they had // already loaded minutes earlier on the same device. const isCacheable = url.origin === self.location.origin || url.pathname.includes("/api/v1/"); if (!isCacheable) { return; } // Page navigations must never be answered with a synthetic Response.error() - // Chrome/Edge render that as a native "This page couldn't load" interstitial // instead of the app. Let the browser handle navigations directly; only // static assets/API data go through the offline-cache fallback below. if (request.mode === "navigate") { return; } event.respondWith( fetch(request) .then((response) => { // Never cache an error response (e.g. a 401 hit right as an access // token expired, before refresh could kick in) - it would otherwise // get served back later as if it were still the current state. if (!response.ok) { return response; } const responseClone = response.clone(); return caches .open(CACHE_NAME) .then((cache) => cache.put(request, responseClone)) .then(() => response); }) .catch(() => caches.match(request).then((cached) => cached || Response.error())), ); });