fix: keep the old build's offline files from coming back after a deploy #523

This commit is contained in:
Linus Rath
2026-11-01 14:49:14 +01:00
parent 562a3270e3
commit 9ce7927627
2 changed files with 41 additions and 5 deletions
+30
View File
@@ -1,6 +1,8 @@
import { describe, expect, it } from 'vitest';
import { FakeCaches, loadWorker } from './sw-harness';
const settle = () => new Promise((r) => setTimeout(r, 0));
// The service worker names its caches after the build it came with (#523)
// and deletes a build's cache once no open tab runs that build.
describe('service worker caches per build', () => {
@@ -45,4 +47,32 @@ describe('service worker caches per build', () => {
await worker.dispatch('message', { data: { type: 'skip-waiting' } });
expect(worker.skipped()).toBe(before + 1);
});
it("the old build's worker doesn't bring back its cache after the new build deleted it", async () => {
const caches = new FakeCaches();
const state = await caches.open('bulwark-sw-state-v1');
await state.put('/__bulwark/offline-shell', new Response(JSON.stringify({ on: true })));
let online = true;
// The server runs build b now and says so on every answer.
const fetchFromB = async () => {
if (!online) throw new TypeError('Failed to fetch');
const res = new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json', 'X-Bulwark-Build': 'b' } });
Object.defineProperty(res, 'type', { value: 'basic' });
return res;
};
const old = loadWorker({ buildId: 'a', caches, clients: [], fetch: fetchFromB });
await settle();
// Build b's worker took over and deleted build a's files, while the
// reloaded tab's first requests still went through build a's worker.
expect(await caches.has('bulwark-shell-a')).toBe(false);
await old.request({ url: 'https://mail.example.com/api/config' });
await settle();
expect(await caches.has('bulwark-shell-a')).toBe(false);
expect(await caches.has('bulwark-shell-b')).toBe(true);
online = false;
await old.request({ url: 'https://mail.example.com/mail', mode: 'navigate' }).catch(() => null);
await settle();
expect(await caches.has('bulwark-shell-a')).toBe(false);
});
});
+11 -5
View File
@@ -139,10 +139,14 @@ async function cachedDocument(url) {
const path = appPagePath(url) || "/";
const section = "/" + (path.split("/").filter(Boolean)[0] || "");
const candidates = [documentKey(url), self.location.origin + BASE_PATH + (section === "/" ? "" : section), self.location.origin + BASE_PATH];
const own = await caches.open(SHELL_CACHE);
for (const key of candidates) {
const hit = await own.match(key);
if (hit) return hit;
// Only looked into when it exists: opening it would create it again
// after the next build's worker deleted it.
if (await caches.has(SHELL_CACHE)) {
const own = await caches.open(SHELL_CACHE);
for (const key of candidates) {
const hit = await own.match(key);
if (hit) return hit;
}
}
for (const key of candidates) {
const hit = await caches.match(key);
@@ -195,7 +199,9 @@ async function respondWithConfig(request) {
const response = await fetch(request);
if (response.ok) {
const copy = response.clone();
caches.open(SHELL_CACHE).then((cache) => cache.put(key, copy)).catch(() => {});
// Like pages and chunks, under the build that answered: right after a
// deploy this worker still fetches for the new build's tab.
caches.open(shellCacheFor(response)).then((cache) => cache.put(key, copy)).catch(() => {});
}
return response;
} catch (error) {