"use client"; import { useEffect, useState } from "react"; /** * The app icon's badge number counts push notifications still sitting in * the notification shelf (see updateAppBadge in public/sw.js). Opening the * app is the "I've seen it" signal, so clear both together — otherwise the * next push would count notifications the person already dealt with. */ function clearAppBadgeAndNotifications() { const badging = navigator as Navigator & { clearAppBadge?: () => Promise }; badging.clearAppBadge?.().catch(() => {}); navigator.serviceWorker?.ready .then((registration) => registration.getNotifications()) .then((notifications) => notifications.forEach((notification) => notification.close())) .catch(() => {}); } /** * Registers the service worker once, app-wide, and surfaces the * update-available handshake public/sw.js sets up: a new SW version * installs and sits `waiting` rather than taking over immediately, so an * open tab isn't silently switched to a new app version mid-session. This * component detects that waiting worker and lets the user opt into the * refresh instead. */ export function ServiceWorkerManager() { const [waitingWorker, setWaitingWorker] = useState(null); const [dismissed, setDismissed] = useState(false); useEffect(() => { if (!("serviceWorker" in navigator)) return; const onVisibilityChange = () => { if (document.visibilityState === "visible") clearAppBadgeAndNotifications(); }; onVisibilityChange(); document.addEventListener("visibilitychange", onVisibilityChange); navigator.serviceWorker .register("/sw.js") .then((registration) => { // An update was already downloaded (e.g. by another tab) before this page loaded. if (registration.waiting && navigator.serviceWorker.controller) { setWaitingWorker(registration.waiting); } registration.addEventListener("updatefound", () => { const installing = registration.installing; if (!installing) return; installing.addEventListener("statechange", () => { // Only a real update — an installing worker with no existing // controller is just this tab's first-ever SW install, not an update. if (installing.state === "installed" && navigator.serviceWorker.controller) { setWaitingWorker(installing); } }); }); }) .catch(() => { // Best-effort — offline/install/update support degrades gracefully without it. }); let reloaded = false; const onControllerChange = () => { if (reloaded) return; reloaded = true; window.location.reload(); }; navigator.serviceWorker.addEventListener("controllerchange", onControllerChange); return () => { document.removeEventListener("visibilitychange", onVisibilityChange); navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange); }; }, []); if (!waitingWorker || dismissed) return null; return (
A new version is available.
); }