Files
brk/website/service-worker.js
2026-01-14 16:38:53 +01:00

118 lines
2.6 KiB
JavaScript

const CACHE = "v1";
const ROOT = "/";
const API = "/api";
const BYPASS = new Set([
"/changelog",
"/crate",
"/discord",
"/github",
"/install",
"/nostr",
"/service",
"/status",
]);
// Match hashed filenames: name.abc12345.js/mjs/css
const HASHED_RE = /\.[0-9a-f]{8}\.(js|mjs|css)$/;
/** @type {ServiceWorkerGlobalScope} */
const sw = /** @type {any} */ (self);
const offline = () =>
new Response("Offline", {
status: 503,
headers: { "Content-Type": "text/plain" },
});
sw.addEventListener("install", (e) => {
e.waitUntil(
caches
.open(CACHE)
.then((c) => c.addAll([ROOT]))
.then(() => sw.skipWaiting()),
);
});
sw.addEventListener("activate", (e) => {
e.waitUntil(
Promise.all([
sw.clients.claim(),
caches
.keys()
.then((keys) =>
Promise.all(
keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)),
),
),
]),
);
});
sw.addEventListener("fetch", (event) => {
const req = event.request;
const url = new URL(req.url);
// Only handle same-origin GET requests
if (req.method !== "GET" || url.origin !== location.origin) return;
const path = url.pathname;
// Bypass API and redirects
if (path.startsWith(API) || BYPASS.has(path)) return;
// Navigation: network-first for shell
if (req.mode === "navigate") {
event.respondWith(
fetch(ROOT)
.then((res) => {
if (res.ok) caches.open(CACHE).then((c) => c.put(ROOT, res.clone()));
return res;
})
.catch(() => caches.match(ROOT).then((c) => c || offline())),
);
return;
}
// Hashed assets: cache-first (immutable)
if (HASHED_RE.test(path)) {
event.respondWith(
caches
.match(req)
.then(
(cached) =>
cached ||
fetch(req).then((res) => {
if (res.ok)
caches.open(CACHE).then((c) => c.put(req, res.clone()));
return res;
}),
)
.catch(() => offline()),
);
return;
}
// Other: network-first with cache fallback
// SPA routes (no extension) fall back to ROOT, static assets get 503
const isStatic = path.includes(".") && !path.endsWith(".html");
event.respondWith(
fetch(req)
.then((res) => {
if (res.ok) caches.open(CACHE).then((c) => c.put(req, res.clone()));
return res;
})
.catch(() =>
caches
.match(req)
.then(
(cached) =>
cached ||
(isStatic
? offline()
: caches.match(ROOT).then((c) => c || offline())),
),
),
);
});