fix(pwa): keep the new worker's precache when loading a new version

The reload cleared every HealthLog cache after asking the worker to
update, but the update resolves as soon as the new worker starts
installing. Its fresh precache of logos and favicon was deleted with
the old ones, so the installed app lacked them offline. Leave the
target release's caches in place.
This commit is contained in:
Marc-André Bombeck committed 2026-10-08 22:57:47 +02:00
1 parent 58592e4822
commit ad620bcf80
2 files changed
+51 -4

No files matched your search

@@ -15,7 +15,7 @@
import { describe, expect, it } from "vitest";
import { resolveVersionPollDecision } from "../version-poller";
import { cachesToEvict, resolveVersionPollDecision } from "../version-poller";
describe("resolveVersionPollDecision", () => {
it("is up-to-date when live matches the shell", () => {
@@ -144,3 +144,31 @@ describe("evictAndReload", () => {
]);
});
});
describe("cachesToEvict", () => {
const keys = [
"healthlog-static-v1.41.2",
"healthlog-pages-v1.41.2",
"healthlog-data-v1.41.2",
"healthlog-static-v1.42.0",
"another-app-cache",
];
it("keeps the target release's caches the new worker just filled", () => {
expect(cachesToEvict(keys, "1.42.0")).toEqual([
"healthlog-static-v1.41.2",
"healthlog-pages-v1.41.2",
"healthlog-data-v1.41.2",
]);
});
it("reads the target with or without its v", () => {
expect(cachesToEvict(keys, "v1.42.0")).not.toContain(
"healthlog-static-v1.42.0",
);
});
it("never touches another app's caches", () => {
expect(cachesToEvict(keys, "1.43.0")).not.toContain("another-app-cache");
});
});
+22 -3
View File
@@ -101,6 +101,27 @@ async function fetchLiveVersion(signal: AbortSignal): Promise<string | null> {
// to break the loop within one document lifetime.
let inMemoryReloadGuard: string | null = null;
/**
* The worker caches the "new version" reload clears: every HealthLog cache
* except the target release's own. `registration.update()` resolves as soon
* as the new worker starts installing, so its freshly precached logos and
* favicon (`healthlog-static-v<target>`) may already exist by then; deleting
* them left the installed app without them offline until they were fetched
* again. Cache names carry the version with a `v`, the version endpoint may
* answer without one.
*/
export function cachesToEvict(
keys: readonly string[],
targetVersion: string,
): string[] {
const target = targetVersion.replace(/^v/, "");
return keys.filter((key) => {
if (!HEALTHLOG_CACHE_NAME_RE.test(key)) return false;
const version = key.replace(HEALTHLOG_CACHE_NAME_RE, "").replace(/^v/, "");
return version !== target;
});
}
export async function evictAndReload(targetVersion: string): Promise<void> {
inMemoryReloadGuard = targetVersion;
try {
@@ -123,9 +144,7 @@ export async function evictAndReload(targetVersion: string): Promise<void> {
try {
const keys = await caches.keys();
await Promise.all(
keys
.filter((k) => HEALTHLOG_CACHE_NAME_RE.test(k))
.map((k) => caches.delete(k)),
cachesToEvict(keys, targetVersion).map((k) => caches.delete(k)),
);
} catch {
/* best effort */