notify the user when a new service worker gets installed
This commit is contained in:
parent
0cb999d260
commit
1425a72d39
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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 />
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
serverBuildTarget: "node-cjs",
|
serverBuildTarget: "node-cjs",
|
||||||
serverDependenciesToBundle: ["@headlessui/react"],
|
serverDependenciesToBundle: ["@headlessui/react"],
|
||||||
|
devServerPort: 8002,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user