fix: stale whie revalidate service worker cache

This commit is contained in:
2026-06-06 11:41:20 +05:30
parent afe10604c8
commit cdb7ded29d
4 changed files with 110 additions and 9 deletions

View File

@@ -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;
});
})

View File

@@ -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 */}
<IosPromptModal />
{/* PWA Update Prompt */}
<UpdatePrompt />
</div>
);
}

View File

@@ -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 (
<div
className={`fixed inset-x-0 bottom-28 z-[150] flex justify-center px-4 transition-all duration-300 ease-out ${active ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`}
>
<div className="bg-[#2C1810] text-[#FAF6F1] rounded-2xl shadow-[0_8px_32px_rgba(44,24,16,0.35)] flex items-center gap-3 px-4 py-3.5 max-w-[420px] w-full">
{/* Icon */}
<div className="w-9 h-9 rounded-xl bg-white/10 flex items-center justify-center flex-shrink-0">
<RefreshCw size={16} strokeWidth={2.5} className="text-white" />
</div>
{/* Text */}
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold text-white leading-tight">Update available</div>
<div className="text-xs text-white/60 mt-0.5">A new version of Brew is ready.</div>
</div>
{/* Actions */}
<div className="flex items-center gap-2 flex-shrink-0">
<button
onClick={handleDismiss}
className="text-xs text-white/50 hover:text-white/80 transition-colors cursor-pointer px-1 py-1"
>
Later
</button>
<button
onClick={handleUpdate}
className="text-xs font-semibold bg-white text-[#2C1810] px-3 py-1.5 rounded-lg hover:bg-white/90 transition-colors cursor-pointer"
>
Update
</button>
</div>
</div>
</div>
);
}

View File

@@ -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));
});
}