notify the user when a new service worker gets installed

This commit is contained in:
m5r 2022-06-06 00:34:50 +02:00
parent 0cb999d260
commit 1425a72d39
5 changed files with 65 additions and 3 deletions

View File

@ -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 (
<div className="absolute inset-0">
<button
onClick={() => {
setHasUpdate(false);
location.reload();
}}
title="An updated version of the app is available. Reload to get the latest version."
>
<IoDownloadOutline
className="-ml-1 mr-2 h-5 w-5"
aria-hidden="true"
aria-label="An updated version of the app is available. Reload to get the latest version."
/>
</button>
;
</div>
);
}

View File

@ -4,6 +4,7 @@ import { Outlet, useCatch, useMatches } from "@remix-run/react";
import serverConfig from "~/config/config.server"; import serverConfig from "~/config/config.server";
import { type SessionData, requireLoggedIn } from "~/utils/auth.server"; import { type SessionData, requireLoggedIn } from "~/utils/auth.server";
import Footer from "~/features/core/components/footer"; 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 useServiceWorkerRevalidate from "~/features/core/hooks/use-service-worker-revalidate";
import footerStyles from "~/features/core/components/footer.css"; import footerStyles from "~/features/core/components/footer.css";
import appStyles from "~/styles/app.css"; import appStyles from "~/styles/app.css";
@ -37,6 +38,7 @@ export default function __App() {
return ( return (
<div className="h-full w-full overflow-hidden fixed bg-gray-100"> <div className="h-full w-full overflow-hidden fixed bg-gray-100">
<div className="flex flex-col w-full h-full"> <div className="flex flex-col w-full h-full">
<ServiceWorkerUpdateNotifier />
<div className="flex flex-col flex-1 w-full overflow-y-auto"> <div className="flex flex-col flex-1 w-full overflow-y-auto">
<main className="flex flex-col flex-1 my-0 h-full"> <main className="flex flex-col flex-1 my-0 h-full">
<Outlet /> <Outlet />

View File

@ -10,5 +10,5 @@ export default async function handleActivate(event: ExtendableEvent) {
await self.registration.navigationPreload.enable(); await self.registration.navigationPreload.enable();
} }
await deleteCaches(); await deleteCaches(); // TODO: maybe wait for the user to reload before busting the cache
} }

View File

@ -27,7 +27,7 @@ export function cacheAsset(event: FetchEvent): Promise<Response> {
ignoreSearch: true, ignoreSearch: true,
}) })
.then((cachedResponse) => { .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 const fetchPromise = event.preloadResponse
.then((preloadedResponse?: Response) => preloadedResponse || fetch(event.request.clone())) .then((preloadedResponse?: Response) => preloadedResponse || fetch(event.request.clone()))
@ -253,7 +253,7 @@ export function cacheDocument(event: FetchEvent): Promise<Response> {
} }
export async function deleteCaches() { export async function deleteCaches() {
console.debug("Caches deleted");
const allCaches = await caches.keys(); const allCaches = await caches.keys();
await Promise.all(allCaches.map((cacheName) => caches.delete(cacheName))); await Promise.all(allCaches.map((cacheName) => caches.delete(cacheName)));
console.debug("Caches deleted");
} }

View File

@ -4,4 +4,5 @@
module.exports = { module.exports = {
serverBuildTarget: "node-cjs", serverBuildTarget: "node-cjs",
serverDependenciesToBundle: ["@headlessui/react"], serverDependenciesToBundle: ["@headlessui/react"],
devServerPort: 8002,
}; };