diff --git a/app/features/core/components/service-worker-update-notifier.tsx b/app/features/core/components/service-worker-update-notifier.tsx new file mode 100644 index 0000000..b873807 --- /dev/null +++ b/app/features/core/components/service-worker-update-notifier.tsx @@ -0,0 +1,59 @@ +import { useEffect, useState } from "react"; +import { IoDownloadOutline } from "react-icons/io5"; + +export default function ServiceWorkerUpdateNotifier() { + const [hasUpdate, setHasUpdate] = useState(false); + useEffect(() => { + (async () => { + const registration = await navigator.serviceWorker.getRegistration(); + if (!registration) { + return; + } + + registration.addEventListener( + "updatefound", + () => { + console.debug("Service Worker update detected"); + const installingWorker = registration.installing; + if (!installingWorker) { + return; + } + + installingWorker.addEventListener("statechange", () => { + if (installingWorker.state !== "activated") { + // should maybe retry if state === "redundant" + console.debug(`Service worker state changed to ${installingWorker.state}`); + return; + } + + setHasUpdate(true); + }); + }, + { once: true }, + ); + })(); + }, []); + + if (!hasUpdate) { + return null; + } + + return ( +
+ + ; +
+ ); +} diff --git a/app/routes/__app.tsx b/app/routes/__app.tsx index f2ac361..6163304 100644 --- a/app/routes/__app.tsx +++ b/app/routes/__app.tsx @@ -4,6 +4,7 @@ import { Outlet, useCatch, useMatches } from "@remix-run/react"; import serverConfig from "~/config/config.server"; import { type SessionData, requireLoggedIn } from "~/utils/auth.server"; import Footer from "~/features/core/components/footer"; +import ServiceWorkerUpdateNotifier from "~/features/core/components/service-worker-update-notifier"; import useServiceWorkerRevalidate from "~/features/core/hooks/use-service-worker-revalidate"; import footerStyles from "~/features/core/components/footer.css"; import appStyles from "~/styles/app.css"; @@ -37,6 +38,7 @@ export default function __App() { return (
+
diff --git a/app/service-worker/activate.ts b/app/service-worker/activate.ts index 6869b38..b228ac0 100644 --- a/app/service-worker/activate.ts +++ b/app/service-worker/activate.ts @@ -10,5 +10,5 @@ export default async function handleActivate(event: ExtendableEvent) { await self.registration.navigationPreload.enable(); } - await deleteCaches(); + await deleteCaches(); // TODO: maybe wait for the user to reload before busting the cache } diff --git a/app/service-worker/cache-utils.ts b/app/service-worker/cache-utils.ts index c92daf6..dc978eb 100644 --- a/app/service-worker/cache-utils.ts +++ b/app/service-worker/cache-utils.ts @@ -27,7 +27,7 @@ export function cacheAsset(event: FetchEvent): Promise { ignoreSearch: true, }) .then((cachedResponse) => { - console.debug(`Serving asset from ${cachedResponse ? "cache" : " network"}`, url.pathname); + console.debug(`Serving asset from ${cachedResponse ? "cache" : "network"}`, url.pathname); const fetchPromise = event.preloadResponse .then((preloadedResponse?: Response) => preloadedResponse || fetch(event.request.clone())) @@ -253,7 +253,7 @@ export function cacheDocument(event: FetchEvent): Promise { } export async function deleteCaches() { - console.debug("Caches deleted"); const allCaches = await caches.keys(); await Promise.all(allCaches.map((cacheName) => caches.delete(cacheName))); + console.debug("Caches deleted"); } diff --git a/remix.config.js b/remix.config.js index 6fd37cf..e4378e7 100644 --- a/remix.config.js +++ b/remix.config.js @@ -4,4 +4,5 @@ module.exports = { serverBuildTarget: "node-cjs", serverDependenciesToBundle: ["@headlessui/react"], + devServerPort: 8002, };