feat: PWA update strategy with hashed assets and cache control
Move frontend sources to frontend/, build content-hashed assets at deploy, network-first SW for shell/JS, immutable cache for /assets/, version.json update banner, and Cache-Control middleware. Docs in docs/PWA-STRATEGY.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
let bannerVisible = false;
|
||||
|
||||
function showUpdateBanner() {
|
||||
if (bannerVisible) return;
|
||||
const banner = document.getElementById("updateBanner");
|
||||
if (!banner) return;
|
||||
banner.hidden = false;
|
||||
bannerVisible = true;
|
||||
}
|
||||
|
||||
async function applyUpdate() {
|
||||
const reg = await navigator.serviceWorker?.getRegistration();
|
||||
if (reg?.waiting) {
|
||||
reg.waiting.postMessage({ type: "SKIP_WAITING" });
|
||||
} else {
|
||||
await reg?.update();
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
export function wireAppUpdates() {
|
||||
if (!("serviceWorker" in navigator)) return;
|
||||
|
||||
let refreshing = false;
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
if (refreshing) return;
|
||||
refreshing = true;
|
||||
location.reload();
|
||||
});
|
||||
|
||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||
if (event.data?.type === "UPDATE_AVAILABLE") showUpdateBanner();
|
||||
});
|
||||
|
||||
const button = document.getElementById("updateBannerBtn");
|
||||
button?.addEventListener("click", () => applyUpdate());
|
||||
|
||||
const checkVersion = async () => {
|
||||
try {
|
||||
const reg = await navigator.serviceWorker.getRegistration();
|
||||
await reg?.update();
|
||||
|
||||
const built = document.querySelector('meta[name="app-build"]')?.content;
|
||||
if (!built) return;
|
||||
|
||||
const res = await fetch("/version.json", { cache: "no-store" });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (data.buildHash && data.buildHash !== built) {
|
||||
showUpdateBanner();
|
||||
}
|
||||
} catch {
|
||||
/* ignore — offline or transient */
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("focus", checkVersion);
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "visible") checkVersion();
|
||||
});
|
||||
checkVersion();
|
||||
}
|
||||
Reference in New Issue
Block a user