From 8aba034344447fd4de49e9e328a3f433520e7fc0 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 30 Jan 2026 13:00:19 +0100 Subject: [PATCH] Respect cache-control in guest API cache --- resources/js/guest/guest-sw.ts | 4 ++++ .../guest/lib/__tests__/cachePolicy.test.ts | 24 +++++++++++++++++++ resources/js/guest/lib/cachePolicy.ts | 20 ++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 resources/js/guest/lib/__tests__/cachePolicy.test.ts create mode 100644 resources/js/guest/lib/cachePolicy.ts diff --git a/resources/js/guest/guest-sw.ts b/resources/js/guest/guest-sw.ts index 6894d98..2a27cb4 100644 --- a/resources/js/guest/guest-sw.ts +++ b/resources/js/guest/guest-sw.ts @@ -7,6 +7,7 @@ import { ExpirationPlugin } from 'workbox-expiration'; import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'; +import { shouldCacheResponse } from './lib/cachePolicy'; declare const self: ServiceWorkerGlobalScope & { __WB_MANIFEST: Array; @@ -63,6 +64,9 @@ registerRoute( cacheName: 'guest-api', networkTimeoutSeconds: 6, plugins: [ + { + cacheWillUpdate: async ({ response }) => (shouldCacheResponse(response) ? response : null), + }, new CacheableResponsePlugin({ statuses: [0, 200] }), new ExpirationPlugin({ maxEntries: 80, maxAgeSeconds: 60 * 60 * 24 }), ], diff --git a/resources/js/guest/lib/__tests__/cachePolicy.test.ts b/resources/js/guest/lib/__tests__/cachePolicy.test.ts new file mode 100644 index 0000000..f75bae4 --- /dev/null +++ b/resources/js/guest/lib/__tests__/cachePolicy.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import { shouldCacheResponse } from '../cachePolicy'; + +describe('shouldCacheResponse', () => { + it('returns false when Cache-Control is no-store', () => { + const response = new Response('ok', { headers: { 'Cache-Control': 'no-store' } }); + expect(shouldCacheResponse(response)).toBe(false); + }); + + it('returns false when Cache-Control is private', () => { + const response = new Response('ok', { headers: { 'Cache-Control': 'private, max-age=0' } }); + expect(shouldCacheResponse(response)).toBe(false); + }); + + it('returns false when Pragma is no-cache', () => { + const response = new Response('ok', { headers: { Pragma: 'no-cache' } }); + expect(shouldCacheResponse(response)).toBe(false); + }); + + it('returns true for cacheable responses', () => { + const response = new Response('ok', { headers: { 'Cache-Control': 'public, max-age=60' } }); + expect(shouldCacheResponse(response)).toBe(true); + }); +}); diff --git a/resources/js/guest/lib/cachePolicy.ts b/resources/js/guest/lib/cachePolicy.ts new file mode 100644 index 0000000..ed24bb2 --- /dev/null +++ b/resources/js/guest/lib/cachePolicy.ts @@ -0,0 +1,20 @@ +export function shouldCacheResponse(response: Response | null): boolean { + if (!response) { + return false; + } + + const cacheControl = response.headers.get('Cache-Control') ?? ''; + const pragma = response.headers.get('Pragma') ?? ''; + const normalizedCacheControl = cacheControl.toLowerCase(); + const normalizedPragma = pragma.toLowerCase(); + + if (normalizedCacheControl.includes('no-store') || normalizedCacheControl.includes('private')) { + return false; + } + + if (normalizedPragma.includes('no-cache')) { + return false; + } + + return true; +}