mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-15 16:49:24 +03:00
fix(webui): keep credentials out of service worker caches
This commit is contained in:
+31
-23
@@ -1,6 +1,23 @@
|
||||
const CACHE_NAME = "nanobot-static-v1";
|
||||
const CACHE_PREFIX = "nanobot-static-";
|
||||
const CACHE_NAME = `${CACHE_PREFIX}v2`;
|
||||
const ASSET_MANIFEST_PATH = "/asset-manifest.json";
|
||||
const PRECACHE = ["/", "/manifest.json", ASSET_MANIFEST_PATH];
|
||||
const NETWORK_FIRST_STATIC_PATHS = new Set([
|
||||
"/",
|
||||
"/manifest.json",
|
||||
ASSET_MANIFEST_PATH,
|
||||
"/brand/nanobot_apple_touch.png",
|
||||
"/brand/nanobot_favicon_32.png",
|
||||
"/brand/nanobot_icon_192.png",
|
||||
"/brand/nanobot_icon_512.png",
|
||||
"/brand/nanobot_icon_maskable.png",
|
||||
"/brand/nanobot_mark.svg",
|
||||
]);
|
||||
|
||||
function responseMayBeCached(response) {
|
||||
const cacheControl = response.headers?.get("Cache-Control") ?? "";
|
||||
return response.ok && !/(?:^|,)\s*(?:private|no-cache|no-store)\b/i.test(cacheControl);
|
||||
}
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
@@ -91,7 +108,7 @@ self.addEventListener("activate", (event) => {
|
||||
.then((keys) =>
|
||||
Promise.all(
|
||||
keys
|
||||
.filter((k) => k !== CACHE_NAME)
|
||||
.filter((k) => k.startsWith(CACHE_PREFIX) && k !== CACHE_NAME)
|
||||
.map((k) => caches.delete(k))
|
||||
)
|
||||
)
|
||||
@@ -107,28 +124,13 @@ self.addEventListener("fetch", (event) => {
|
||||
// (never reconstructed), so their credentials mode is preserved and gateway
|
||||
// auth cookies flow through on every path we touch. WebSocket upgrades are
|
||||
// never dispatched to a service worker's fetch handler, so the WS endpoint
|
||||
// cannot be cached; the /__nanobot exclusion below still protects its HTTP
|
||||
// polling/socket bootstrap endpoints.
|
||||
// cannot be cached. Unknown HTTP endpoints are passed through below.
|
||||
if (request.method !== "GET") return;
|
||||
if (new URL(request.url).origin !== self.location.origin) return;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Never cache API, auth, WebSocket, HMR, or WebUI endpoint paths. In
|
||||
// particular /webui/bootstrap issues fresh gateway credentials on every page
|
||||
// load and must never be cached or replayed offline. The /auth prefix covers
|
||||
// the default token endpoint; custom token_issue_path values should be kept
|
||||
// under one of these prefixes.
|
||||
if (
|
||||
path.startsWith("/api") ||
|
||||
path.startsWith("/auth") ||
|
||||
path.startsWith("/__nanobot") ||
|
||||
path.startsWith("/webui")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Static assets: cache-first. Only files under /assets/ carry content hashes
|
||||
// (the gateway serves them immutable); brand icons, the favicon and other
|
||||
// un-hashed files can change between releases and stay on the network-first
|
||||
@@ -138,7 +140,7 @@ self.addEventListener("fetch", (event) => {
|
||||
caches.match(request).then((cached) => {
|
||||
if (cached) return cached;
|
||||
return fetch(request).then((response) => {
|
||||
if (response.ok) {
|
||||
if (responseMayBeCached(response)) {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_NAME).then((c) => c.put(request, clone));
|
||||
}
|
||||
@@ -149,22 +151,28 @@ self.addEventListener("fetch", (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything else: network-first (index.html, manifest, brand assets, etc.)
|
||||
// Cache only explicit public files and browser navigations. Dynamic routes
|
||||
// are deliberately passed through because token_issue_path and extension
|
||||
// endpoints are configurable and cannot be safely identified by prefixes.
|
||||
const isNavigation = request.mode === "navigate";
|
||||
if (!isNavigation && !NETWORK_FIRST_STATIC_PATHS.has(path)) return;
|
||||
|
||||
// App shell and public files: network-first with an offline fallback.
|
||||
const networkResponse = fetch(request);
|
||||
event.waitUntil(
|
||||
networkResponse
|
||||
.then(async (response) => {
|
||||
if (!response.ok) return;
|
||||
if (!responseMayBeCached(response)) return;
|
||||
// Clone before the first await. The original response is also handed
|
||||
// to respondWith(), which may lock its body as soon as this callback
|
||||
// yields to the event loop.
|
||||
const cachedResponse = response.clone();
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
await cache.put(request, cachedResponse);
|
||||
await cache.put(isNavigation ? "/" : request, cachedResponse);
|
||||
// Refresh the complete build graph before pruning. A deployment can
|
||||
// change index.html without changing sw.js, so this cannot rely only
|
||||
// on the manifest cached when the worker was installed.
|
||||
if (path === "/") {
|
||||
if (isNavigation || path === "/") {
|
||||
if (await refreshAssetManifest(cache)) await pruneStaleEntries();
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user