Skip to content

Commit 1e37148

Browse files
authored
Merge pull request #486 from DEEIX-AI/pwa
refactor: remove legacy PWA service worker and update related assets
2 parents e0796b9 + 05c1e2c commit 1e37148

8 files changed

Lines changed: 50 additions & 195 deletions

File tree

Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ COPY scripts /src/scripts
3535
COPY frontend/package.json frontend/pnpm-lock.yaml ./
3636
COPY frontend/scripts ./scripts
3737
COPY frontend/public/pwa ./public/pwa
38-
COPY frontend/public/sw.js ./public/sw.js
3938

4039
RUN corepack enable
4140

frontend/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { AppearancePreferencesProvider } from "@/features/settings";
66
import { AppI18nProvider } from "@/i18n/app-i18n-provider";
77
import { DevtoolsBrandBanner } from "@/shared/components/devtools-brand-banner";
88
import { ThemeProvider } from "@/shared/components/theme-provider";
9-
import { PWAServiceWorkerRegister } from "@/shared/components/pwa-service-worker-register";
109
import { brandAssets, brandText } from "@/shared/lib/branding";
10+
import { LegacyPWAServiceWorkerMigration } from "@/shared/pwa/migrations/legacy-service-worker-migration";
1111
import { Toaster } from "@/components/ui/sonner";
1212

1313
import "./globals.css";
@@ -81,7 +81,7 @@ export default function RootLayout({
8181
<AppearancePreferencesProvider>
8282
{children}
8383
<AppVersionGuard />
84-
<PWAServiceWorkerRegister />
84+
<LegacyPWAServiceWorkerMigration />
8585
<Toaster />
8686
<DevtoolsBrandBanner />
8787
</AppearancePreferencesProvider>

frontend/public/sw.js

Lines changed: 6 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,19 @@
1-
const PWA_ASSET_VERSION = "0.3.2";
2-
const PWA_ASSET_CACHE_KEY = "632cb83037d9";
3-
const PWA_ASSET_MANIFEST = {
4-
"/pwa/icon.svg": "/pwa/generated/icon.fe1d64d9758c.svg",
5-
"/pwa/icon-192.png": "/pwa/generated/icon-192.d7a0049daaa7.png",
6-
"/pwa/icon-512.png": "/pwa/generated/icon-512.bd37e5f66cc5.png",
7-
"/pwa/icon-maskable-512.png": "/pwa/generated/icon-maskable-512.647d8497d850.png",
8-
"/pwa/apple-touch-icon.png": "/pwa/generated/apple-touch-icon.0c62df73d41b.png"
9-
};
10-
const STATIC_CACHE = `deeix-chat-static-${PWA_ASSET_VERSION}-${PWA_ASSET_CACHE_KEY}`;
11-
const PAGE_CACHE = `deeix-chat-pages-${PWA_ASSET_VERSION}`;
12-
const STATIC_CACHE_MAX_ENTRIES = 160;
13-
const PAGE_CACHE_MAX_ENTRIES = 24;
14-
15-
const APP_SHELL_URLS = [
16-
"/",
17-
"/chat",
18-
"/logo.svg",
19-
"/logo-color.svg",
20-
pwaAsset("/pwa/icon.svg"),
21-
pwaAsset("/pwa/icon-192.png"),
22-
pwaAsset("/pwa/icon-512.png"),
23-
];
24-
25-
const BACKEND_PATH_PREFIXES = [
26-
"/api/",
27-
"/swagger",
28-
"/healthz",
29-
"/readyz",
30-
];
31-
32-
function pwaAsset(path) {
33-
return PWA_ASSET_MANIFEST[path] ?? path;
34-
}
1+
// Temporary tombstone for the retired PWA service worker. Keep this URL stable during migration.
2+
const LEGACY_CACHE_PREFIX = "deeix-chat-";
353

364
self.addEventListener("install", (event) => {
37-
event.waitUntil(
38-
caches.open(STATIC_CACHE)
39-
.then((cache) => cache.addAll(APP_SHELL_URLS))
40-
.catch(() => undefined)
41-
.then(() => self.skipWaiting()),
42-
);
5+
event.waitUntil(self.skipWaiting());
436
});
447

458
self.addEventListener("activate", (event) => {
469
event.waitUntil(
4710
caches.keys()
4811
.then((keys) => Promise.all(
4912
keys
50-
.filter((key) => key !== STATIC_CACHE && key !== PAGE_CACHE)
13+
.filter((key) => key.startsWith(LEGACY_CACHE_PREFIX))
5114
.map((key) => caches.delete(key)),
5215
))
53-
.then(() => self.clients.claim()),
16+
.then(() => self.clients.claim())
17+
.then(() => self.registration.unregister()),
5418
);
5519
});
56-
57-
self.addEventListener("fetch", (event) => {
58-
const request = event.request;
59-
if (request.method !== "GET") {
60-
return;
61-
}
62-
63-
const url = new URL(request.url);
64-
if (url.origin !== self.location.origin || shouldBypassCache(url)) {
65-
return;
66-
}
67-
68-
if (request.mode === "navigate") {
69-
event.respondWith(networkFirst(request, PAGE_CACHE, PAGE_CACHE_MAX_ENTRIES));
70-
return;
71-
}
72-
73-
if (isStaticAsset(url)) {
74-
event.respondWith(staleWhileRevalidate(request, STATIC_CACHE, STATIC_CACHE_MAX_ENTRIES));
75-
}
76-
});
77-
78-
function shouldBypassCache(url) {
79-
if (BACKEND_PATH_PREFIXES.some((prefix) => url.pathname === prefix || url.pathname.startsWith(prefix))) {
80-
return true;
81-
}
82-
if (url.pathname.includes("/content") || url.pathname.includes("/download")) {
83-
return true;
84-
}
85-
return false;
86-
}
87-
88-
function isStaticAsset(url) {
89-
return url.pathname.startsWith("/_next/static/") ||
90-
url.pathname.startsWith("/pwa/") ||
91-
url.pathname.startsWith("/vendor/") ||
92-
/\.(?:css|js|mjs|png|jpg|jpeg|gif|webp|svg|ico|woff2?|ttf|otf|wasm)$/i.test(url.pathname);
93-
}
94-
95-
async function networkFirst(request, cacheName, maxEntries) {
96-
const cache = await caches.open(cacheName);
97-
try {
98-
const response = await fetch(request);
99-
if (isCacheable(response)) {
100-
await cache.put(request, response.clone());
101-
await trimCache(cacheName, maxEntries);
102-
}
103-
return response;
104-
} catch {
105-
const cached = await cache.match(request);
106-
if (cached) {
107-
return cached;
108-
}
109-
return cache.match("/") || Response.error();
110-
}
111-
}
112-
113-
async function staleWhileRevalidate(request, cacheName, maxEntries) {
114-
const cache = await caches.open(cacheName);
115-
const cached = await cache.match(request);
116-
const fresh = fetch(request)
117-
.then(async (response) => {
118-
if (isCacheable(response)) {
119-
await cache.put(request, response.clone());
120-
await trimCache(cacheName, maxEntries);
121-
}
122-
return response;
123-
})
124-
.catch(() => undefined);
125-
126-
return cached || fresh || Response.error();
127-
}
128-
129-
function isCacheable(response) {
130-
return response && response.ok && response.type === "basic";
131-
}
132-
133-
async function trimCache(cacheName, maxEntries) {
134-
const cache = await caches.open(cacheName);
135-
const keys = await cache.keys();
136-
if (keys.length <= maxEntries) {
137-
return;
138-
}
139-
await Promise.all(keys.slice(0, keys.length - maxEntries).map((key) => cache.delete(key)));
140-
}

frontend/scripts/sync-pwa-assets.mjs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,28 +38,12 @@ for (const asset of assets) {
3838
pwaAssetManifest[asset.path] = targetPath;
3939
}
4040

41-
const pwaAssetCacheKey = contentHash(Buffer.from(JSON.stringify(pwaAssetManifest)));
42-
4341
mkdirSync(dirname(manifestFile), { recursive: true });
4442
writeFileSync(
4543
manifestFile,
4644
[
4745
"// This file is generated by scripts/sync-pwa-assets.mjs.",
48-
`export const pwaAssetCacheKey = ${JSON.stringify(pwaAssetCacheKey)};`,
4946
`export const pwaAssetManifest = ${JSON.stringify(pwaAssetManifest, null, 2)} as const;`,
5047
"",
5148
].join("\n"),
5249
);
53-
54-
const serviceWorkerFile = join(publicDir, "sw.js");
55-
const serviceWorker = readFileSync(serviceWorkerFile, "utf8")
56-
.replace(
57-
/const PWA_ASSET_CACHE_KEY = "[^"]+";/u,
58-
`const PWA_ASSET_CACHE_KEY = ${JSON.stringify(pwaAssetCacheKey)};`,
59-
)
60-
.replace(
61-
/const PWA_ASSET_MANIFEST = \{[\s\S]*?\};/u,
62-
`const PWA_ASSET_MANIFEST = ${JSON.stringify(pwaAssetManifest, null, 2)};`,
63-
);
64-
65-
writeFileSync(serviceWorkerFile, serviceWorker);

frontend/shared/components/pwa-service-worker-register.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

frontend/shared/generated/pwa-assets.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// This file is generated by scripts/sync-pwa-assets.mjs.
2-
export const pwaAssetCacheKey = "632cb83037d9";
32
export const pwaAssetManifest = {
43
"/pwa/icon.svg": "/pwa/generated/icon.fe1d64d9758c.svg",
54
"/pwa/icon-192.png": "/pwa/generated/icon-192.d7a0049daaa7.png",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
5+
const LEGACY_CACHE_PREFIX = "deeix-chat-";
6+
7+
export function LegacyPWAServiceWorkerMigration() {
8+
React.useEffect(() => {
9+
if (process.env.NODE_ENV !== "production" || !("serviceWorker" in navigator)) {
10+
return;
11+
}
12+
13+
const migrate = async () => {
14+
const cacheNames = "caches" in window ? await window.caches.keys() : [];
15+
const legacyCacheNames = cacheNames.filter((name) =>
16+
name.startsWith(LEGACY_CACHE_PREFIX),
17+
);
18+
const registration = await navigator.serviceWorker.getRegistration("/");
19+
const worker = registration?.active ?? registration?.waiting ?? registration?.installing;
20+
const isLegacyRegistration = worker
21+
? new URL(worker.scriptURL).pathname === "/sw.js"
22+
: false;
23+
24+
await Promise.all(legacyCacheNames.map((name) => window.caches.delete(name)));
25+
26+
if (!isLegacyRegistration) {
27+
return;
28+
}
29+
30+
await navigator.serviceWorker.register("/sw.js", {
31+
scope: "/",
32+
updateViaCache: "none",
33+
});
34+
};
35+
36+
void migrate().catch(() => {
37+
// Migration is best-effort and must not block the application.
38+
});
39+
}, []);
40+
41+
return null;
42+
}

scripts/sync-version.mjs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,9 @@ function replaceOrThrow(content, pattern, replacement, label) {
4040

4141
function syncFrontend() {
4242
const packageFile = join(repoRoot, "frontend", "package.json");
43-
const serviceWorkerFile = join(repoRoot, "frontend", "public", "sw.js");
4443
const packageJson = JSON.parse(readFileSync(packageFile, "utf8"));
4544
packageJson.version = version;
4645
writeIfChanged(packageFile, `${JSON.stringify(packageJson, null, 2)}\n`);
47-
48-
writeIfChanged(
49-
serviceWorkerFile,
50-
replaceOrThrow(
51-
readFileSync(serviceWorkerFile, "utf8"),
52-
/const PWA_ASSET_VERSION = "[^"]+";/u,
53-
`const PWA_ASSET_VERSION = ${JSON.stringify(version)};`,
54-
"frontend service worker PWA asset version",
55-
),
56-
);
5746
}
5847

5948
function syncBackend() {

0 commit comments

Comments
 (0)