From cdb7ded29da537f543f02642048ac8376bab4c39 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sat, 6 Jun 2026 11:41:20 +0530 Subject: [PATCH] fix: stale whie revalidate service worker cache --- public/sw.js | 41 +++++++++++++++---- src/App.jsx | 4 ++ src/components/UpdatePrompt.jsx | 70 +++++++++++++++++++++++++++++++++ src/main.jsx | 4 +- 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 src/components/UpdatePrompt.jsx diff --git a/public/sw.js b/public/sw.js index ee4e211..4c918f5 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'brew-journal-v1'; +const CACHE_NAME = 'brew-journal-v2'; // Static assets to cache immediately const PRECACHE_ASSETS = [ @@ -20,7 +20,7 @@ self.addEventListener('install', (e) => { self.skipWaiting(); }); -// Activate Event +// Activate Event — clean old caches and notify clients of update self.addEventListener('activate', (e) => { e.waitUntil( caches.keys().then((keys) => { @@ -31,9 +31,18 @@ self.addEventListener('activate', (e) => { } }) ); + }).then(() => { + // Take control of all open clients immediately + return self.clients.claim(); + }).then(() => { + // Notify all clients that a new version is now active + return self.clients.matchAll({ type: 'window' }).then((clients) => { + clients.forEach((client) => { + client.postMessage({ type: 'SW_UPDATED' }); + }); + }); }) ); - self.clients.claim(); }); // Fetch Event @@ -46,7 +55,23 @@ self.addEventListener('fetch', (e) => { return; } - // Stale-while-revalidate for static assets + // Network-first for HTML documents so users always get fresh markup + if (e.request.mode === 'navigate') { + e.respondWith( + fetch(e.request).then((networkResponse) => { + const responseToCache = networkResponse.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(e.request, responseToCache); + }); + return networkResponse; + }).catch(() => { + return caches.match(e.request); + }) + ); + return; + } + + // Stale-while-revalidate for other static assets e.respondWith( caches.match(e.request).then((cachedResponse) => { if (cachedResponse) { @@ -54,11 +79,11 @@ self.addEventListener('fetch', (e) => { fetch(e.request).then((networkResponse) => { if (networkResponse.status === 200) { caches.open(CACHE_NAME).then((cache) => { - cache.put(e.request, networkResponse); + cache.put(e.request, networkResponse.clone()); }); } }).catch(() => {/* Ignore network errors offline */}); - + return cachedResponse; } @@ -67,12 +92,12 @@ self.addEventListener('fetch', (e) => { if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') { return networkResponse; } - + const responseToCache = networkResponse.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(e.request, responseToCache); }); - + return networkResponse; }); }) diff --git a/src/App.jsx b/src/App.jsx index 03cc4bf..dcefd79 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,6 +14,7 @@ import BeanDetail from "./components/BeanDetail"; import BrewCard from "./components/BrewCard"; import BottomNav from "./components/BottomNav"; import IosPromptModal from "./components/IosPromptModal"; +import UpdatePrompt from "./components/UpdatePrompt"; // Import constants @@ -343,6 +344,9 @@ export default function CoffeeLogbook() { {/* iOS/iPadOS PWA Install Prompt */} + + {/* PWA Update Prompt */} + ); } diff --git a/src/components/UpdatePrompt.jsx b/src/components/UpdatePrompt.jsx new file mode 100644 index 0000000..d28f33c --- /dev/null +++ b/src/components/UpdatePrompt.jsx @@ -0,0 +1,70 @@ +import React, { useState, useEffect } from "react"; +import { RefreshCw } from "lucide-react"; + +export default function UpdatePrompt() { + const [show, setShow] = useState(false); + const [active, setActive] = useState(false); + + useEffect(() => { + if (!("serviceWorker" in navigator)) return; + + const handleMessage = (event) => { + if (event.data?.type === "SW_UPDATED") { + setShow(true); + requestAnimationFrame(() => setActive(true)); + } + }; + + navigator.serviceWorker.addEventListener("message", handleMessage); + return () => navigator.serviceWorker.removeEventListener("message", handleMessage); + }, []); + + if (!show) return null; + + const handleUpdate = () => { + setActive(false); + setTimeout(() => { + window.location.reload(); + }, 250); + }; + + const handleDismiss = () => { + setActive(false); + setTimeout(() => setShow(false), 200); + }; + + return ( +
+
+ {/* Icon */} +
+ +
+ + {/* Text */} +
+
Update available
+
A new version of Brew is ready.
+
+ + {/* Actions */} +
+ + +
+
+
+ ); +} diff --git a/src/main.jsx b/src/main.jsx index eb0bcc1..60c3750 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -15,7 +15,9 @@ createRoot(document.getElementById('root')).render( if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') - .then(reg => console.log('Service Worker registered', reg)) + .then(reg => { + console.log('Service Worker registered', reg); + }) .catch(err => console.error('Service Worker registration failed', err)); }); }