


























@@ -2,26 +2,116 @@
22import fs from "node:fs";
33import path from "node:path";
44import { fileURLToPath } from "node:url";
5-import { describe, expect, it } from "vitest";
5+import vm from "node:vm";
6+import { describe, expect, it, vi } from "vitest";
6778const here = path.dirname(fileURLToPath(import.meta.url));
9+const serviceWorkerPath = path.join(here, "../../public/sw.js");
810911describe("Control UI service worker cache versioning", () => {
1012it("registers the service worker with a build id and bounds prior build caches", () => {
1113const mainSource = fs.readFileSync(path.join(here, "../main.ts"), "utf8");
12-const serviceWorkerSource = fs.readFileSync(path.join(here, "../../public/sw.js"), "utf8");
14+const serviceWorkerSource = fs.readFileSync(serviceWorkerPath, "utf8");
1315const viteConfigSource = fs.readFileSync(path.join(here, "../../vite.config.ts"), "utf8");
14161517expect(mainSource).toContain('swUrl.searchParams.set("v"');
1618expect(mainSource).toContain('updateViaCache: "none"');
19+expect(mainSource).toContain('navigator.serviceWorker.addEventListener("message"');
20+expect(mainSource).toContain("event.data.version !== currentControlUiBuildId");
1721expect(serviceWorkerSource).toContain(
1822'const EMBEDDED_CACHE_VERSION = "__OPENCLAW_CONTROL_UI_BUILD_ID__"',
1923);
2024expect(serviceWorkerSource).toContain("URL_CACHE_VERSION");
2125expect(serviceWorkerSource).toContain("CONTROL_CACHE_LIMIT = 3");
2226expect(serviceWorkerSource).toContain("slice(-priorCacheLimit)");
2327expect(serviceWorkerSource).toContain("caches.delete");
28+expect(serviceWorkerSource).toContain("includeUncontrolled: true");
29+expect(serviceWorkerSource).not.toContain(
30+'postMessage({ type: "sw-updated", version: CACHE_VERSION },',
31+);
2432expect(viteConfigSource).toContain("source.replace(placeholder, JSON.stringify(buildId))");
2533expect(serviceWorkerSource).not.toContain('const CACHE_NAME = "openclaw-control-v1"');
2634});
35+36+it("broadcasts updated versions to uncontrolled window clients during activation", async () => {
37+const serviceWorkerSource = fs.readFileSync(serviceWorkerPath, "utf8");
38+const windowClient = { postMessage: vi.fn() };
39+const matchedClients = createDeferred<Array<typeof windowClient>>();
40+const listeners = new Map<string, Array<(event: ActivateEventStub) => void>>();
41+const cacheDelete = vi.fn(async () => true);
42+const clients = {
43+claim: vi.fn(async () => undefined),
44+matchAll: vi.fn(() => matchedClients.promise),
45+};
46+const caches = {
47+delete: cacheDelete,
48+keys: vi.fn(async () => [
49+"openclaw-control-oldest",
50+"openclaw-control-older",
51+"openclaw-control-previous",
52+"openclaw-control-new-build",
53+"other-cache",
54+]),
55+open: vi.fn(),
56+};
57+const serviceWorkerGlobal = {
58+addEventListener(type: string, listener: (event: ActivateEventStub) => void) {
59+listeners.set(type, [...(listeners.get(type) ?? []), listener]);
60+},
61+ clients,
62+location: { href: "https://control.example/sw.js?v=new-build" },
63+registration: { showNotification: vi.fn() },
64+skipWaiting: vi.fn(),
65+};
66+const context = vm.createContext({
67+URL,
68+ caches,
69+fetch: vi.fn(),
70+self: serviceWorkerGlobal,
71+});
72+73+new vm.Script(serviceWorkerSource, { filename: "ui/public/sw.js" }).runInContext(context);
74+75+const activateHandler = listeners.get("activate")?.[0];
76+expect(activateHandler).toBeDefined();
77+let activationPromise: Promise<unknown> | undefined;
78+activateHandler?.({
79+waitUntil(promise: Promise<unknown>) {
80+activationPromise = promise;
81+},
82+});
83+84+let activationSettled = false;
85+void activationPromise?.then(() => {
86+activationSettled = true;
87+});
88+await Promise.resolve();
89+90+expect(activationSettled).toBe(false);
91+expect(windowClient.postMessage).not.toHaveBeenCalled();
92+93+matchedClients.resolve([windowClient]);
94+await activationPromise;
95+96+expect(clients.matchAll).toHaveBeenCalledWith({ type: "window", includeUncontrolled: true });
97+expect(clients.claim).toHaveBeenCalled();
98+expect(cacheDelete).toHaveBeenCalledWith("openclaw-control-oldest");
99+expect(windowClient.postMessage).toHaveBeenCalledWith({
100+type: "sw-updated",
101+version: "new-build",
102+});
103+expect(windowClient.postMessage.mock.calls[0]).toHaveLength(1);
104+});
27105});
106+107+type ActivateEventStub = {
108+waitUntil(promise: Promise<unknown>): void;
109+};
110+111+function createDeferred<T>() {
112+let resolve!: (value: T) => void;
113+const promise = new Promise<T>((resolvePromise) => {
114+resolve = resolvePromise;
115+});
116+return { promise, resolve };
117+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。