fix(webui): harden PWA service worker caching and registration

- Exclude /webui/* endpoints from the service worker cache; the
  /webui/bootstrap endpoint issues fresh gateway credentials on every load
  and must never be cached or replayed offline.
- Restrict cache-first handling to hashed /assets/ files (served immutable).
  Un-hashed brand icons and the favicon stay network-first so future icon
  swaps reach installed clients.
- Prune stale hashed assets whenever the app shell refreshes, so old build
  assets cannot accumulate even when sw.js itself is unchanged.
- Serve the cached app shell for offline deep-link navigations.
- Ignore service worker registration failures; add unit tests for the
  service worker and the main-entry registration.
This commit is contained in:
moran
2026-08-11 20:59:06 +08:00
committed by chengyongru
parent 43ca12960b
commit 95287f7435
5 changed files with 451 additions and 16 deletions
@@ -0,0 +1,44 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const render = vi.fn();
const createRoot = vi.fn(() => ({ render }));
vi.mock("react-dom/client", () => ({
default: { createRoot },
}));
vi.mock("@/App", () => ({
default: () => null,
}));
describe("service worker registration in main entry", () => {
const register = vi.fn(() => Promise.resolve());
beforeEach(() => {
vi.resetModules();
createRoot.mockClear();
render.mockClear();
register.mockClear();
document.body.innerHTML = '<div id="root"></div>';
Object.defineProperty(navigator, "serviceWorker", {
value: { register },
configurable: true,
});
});
afterEach(() => {
document.body.innerHTML = "";
delete (navigator as Navigator & { serviceWorker?: unknown }).serviceWorker;
});
it("registers /sw.js with updateViaCache none only after window load", async () => {
await import("../main");
expect(register).not.toHaveBeenCalled();
window.dispatchEvent(new Event("load"));
expect(register).toHaveBeenCalledTimes(1);
expect(register).toHaveBeenCalledWith("/sw.js", { updateViaCache: "none" });
});
});