fix(gateway-ui): refresh service worker and static cache headers

This commit is contained in:
William Valentin
2026-02-18 12:22:01 -08:00
parent 68ce0f77c4
commit 805bbaead9
4 changed files with 52 additions and 11 deletions
+11
View File
@@ -295,6 +295,7 @@ describe('GatewayServer integration', () => {
const res = await fetch(`http://127.0.0.1:${TEST_PORT}/`);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('text/html');
expect(res.headers.get('cache-control')).toBe('no-cache');
const body = await res.text();
expect(body).toContain('Flynn');
});
@@ -308,6 +309,16 @@ describe('GatewayServer integration', () => {
expect(res.headers.get('content-type')).toBe('text/css');
});
it('serves sw.js with no-store cache policy', async () => {
if (!LISTEN_ALLOWED) {
return;
}
const res = await fetch(`http://127.0.0.1:${TEST_PORT}/sw.js`);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe('application/javascript');
expect(res.headers.get('cache-control')).toBe('no-store');
});
it('returns 404 for unknown HTTP path', async () => {
if (!LISTEN_ALLOWED) {
return;
+7 -1
View File
@@ -71,7 +71,13 @@ export async function serveStatic(
// Try to read and serve the file
try {
const content = await readFile(filePath);
res.writeHead(200, { 'Content-Type': contentType });
const headers: Record<string, string> = { 'Content-Type': contentType };
if (pathname === '/sw.js') {
headers['Cache-Control'] = 'no-store';
} else if (pathname === '/index.html') {
headers['Cache-Control'] = 'no-cache';
}
res.writeHead(200, headers);
res.end(content);
return true;
} catch {
+4 -1
View File
@@ -60,7 +60,10 @@ export async function registerPwaServiceWorker() {
if (!('serviceWorker' in navigator)) {
return null;
}
return navigator.serviceWorker.register(withToken('/sw.js'));
const registration = await navigator.serviceWorker.register(withToken('/sw.js'));
// Ask the browser to check for a newer worker on app load.
void registration.update().catch(() => undefined);
return registration;
}
export async function getPushStatus() {
+30 -9
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = 'flynn-webchat-v1';
const CACHE_NAME = 'flynn-webchat-v2';
const token = new URL(self.location.href).searchParams.get('token');
const withToken = (path) => {
if (!token) {
@@ -34,17 +34,38 @@ self.addEventListener('fetch', (event) => {
return;
}
event.respondWith((async () => {
const cached = await caches.match(event.request, { ignoreSearch: true });
if (cached) {
return cached;
}
const requestUrl = new URL(event.request.url);
const isNavigation = event.request.mode === 'navigate';
const isStaticAsset = requestUrl.origin === self.location.origin
&& (requestUrl.pathname.startsWith('/lib/')
|| requestUrl.pathname.startsWith('/pages/')
|| requestUrl.pathname === '/app.js'
|| requestUrl.pathname === '/style.css'
|| requestUrl.pathname === '/index.html'
|| requestUrl.pathname === '/manifest.webmanifest');
event.respondWith((async () => {
try {
return await fetch(event.request);
const networkResponse = await fetch(event.request);
if (isNavigation || isStaticAsset) {
const cache = await caches.open(CACHE_NAME);
await cache.put(event.request, networkResponse.clone());
}
return networkResponse;
} catch {
const fallback = await caches.match('/index.html', { ignoreSearch: true });
return fallback || new Response('Offline', { status: 503 });
const cached = await caches.match(event.request, { ignoreSearch: true });
if (cached) {
return cached;
}
if (isNavigation) {
const fallback = await caches.match('/index.html', { ignoreSearch: true });
if (fallback) {
return fallback;
}
}
return new Response('Offline', { status: 503 });
}
})());
});