fix: stale whie revalidate service worker cache
This commit is contained in:
41
public/sw.js
41
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;
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
70
src/components/UpdatePrompt.jsx
Normal file
70
src/components/UpdatePrompt.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user