fix(library): keep the shell the deploy replaced out of a failed navigation, and say when another tab holds the library open

This commit is contained in:
2026-10-08 09:23:23 +07:00
parent eac206890a
commit d2549cfde1
5 changed files with 62 additions and 7 deletions
+29 -7
View File
@@ -37,9 +37,15 @@ const cachedFetch = (request) =>
return response;
});
// Offline, the studio the app is for; the landing page for a build without /app.
const offlineShell = async () =>
(await caches.match('/app')) ?? (await caches.match('/')) ?? Response.error();
// Offline, the route that was asked for if this browser has ever been on it, then
// the studio the app is for, then the landing page for a build without /app. The
// route first because that is the copy a visit keeps up to date: the other two may
// be shells of a build the deploy has replaced.
const offlineShell = async (request) =>
(await caches.match(new Request(new URL(request.url).pathname))) ??
(await caches.match('/app')) ??
(await caches.match('/')) ??
Response.error();
self.addEventListener('install', (event) => {
// Best effort, one route at a time: a deploy caught mid-flight must not leave the
@@ -73,12 +79,28 @@ self.addEventListener('fetch', (event) => {
// `no-store` because a plain fetch is not the network: it may be answered by the
// browser's own cache, which is the other place a stale shell hides.
if (request.mode === 'navigate') {
event.respondWith(fetch(request, { cache: 'no-store' }).catch(offlineShell));
event.respondWith(
fetch(request, { cache: 'no-store' })
.then((response) => {
// The shell kept for offline is the one the server handed over last, not
// the one this worker installed with: a navigation that fails — a deploy
// recreating the container is the ordinary way one does — otherwise
// answers with the build of the first visit, `activate` never runs to
// replace it while this worker's bytes stay the same, and the browser
// runs an old build until a reload happens to land after the server is
// back. Stored under the route the navigation asked for, which is one of
// the three SHELL keys — a navigation request is not a key a cache takes.
if (response.ok) {
const copy = response.clone();
const key = new Request(new URL(request.url).pathname);
caches.open(VERSION).then((cache) => cache.put(key, copy)).catch(() => {});
}
return response;
})
.catch(() => offlineShell(request))
);
return;
}
// ponytail: a navigation is never written back, the precached entry is what
// answers offline. Add a put() here once a route must survive on its own.
if (STATIC.test(url.pathname)) {
event.respondWith(caches.match(request).then((hit) => hit ?? cachedFetch(request)));
return;