Skip to content

Instantly share code, notes, and snippets.

@sampolahtinen
Last active January 28, 2022 09:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sampolahtinen/400306ae8481f0d1b4704a03a35697e5 to your computer and use it in GitHub Desktop.
Save sampolahtinen/400306ae8481f0d1b4704a03a35697e5 to your computer and use it in GitHub Desktop.
React hook to initiate a service worker update checker
import { useEffect, useRef } from 'react';
const SERVICE_WORKER_UPDATE_CHECK_INTERVAL = 5 * 60 * 1000; // every 5 minutes
/**
* A hook to enable a periodic update checker for new service workers.
*
* For example, when new build is deployed, a new service worker is generated.
* However, when user navigates to the web page old Service Worker is still in control,
* which will return an old build from its cache.
* With help of this hook, we can detect a new service worker and prompt user to reload the page,
* once the new service worker is ready to take over
*
* @param promptUser Any function that should handle posting a message to service worker indicating it can claim the control.
* @example
* function (registration) {
* if (registration.waiting && confirm('New app version available! Reload?')) {
* registration.waiting.postMessage('SKIP_WAITING')
* }
* }
*
* // in service worker:
* self.addEventListener('message', event => {
* if (event.data === 'SKIP_WAITING') {
* self.skipWaiting();
* }
* });
*/
export const useServiceWorkerUpdateChecker = (
promptUser: (registration: ServiceWorkerRegistration) => void
) => {
const serviceWorkerUpdateCheckInterval = useRef<NodeJS.Timeout>();
const isRefreshing = useRef(false);
const initServiceWorkerUpdateChecker = async () => {
if ('serviceWorker' in navigator) {
const registration =
await navigator.serviceWorker.getRegistration();
if (registration) {
/**
* Force trigger service worker update check
* https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#manual_updates
*/
serviceWorkerUpdateCheckInterval.current = setInterval(() => {
registration.update();
}, SERVICE_WORKER_UPDATE_CHECK_INTERVAL);
/**
* If a new service worker is installed and waiting to claim control,
* prompt user!
*/
if (registration.waiting) {
promptUser(registration);
}
// detect Service Worker update available and wait for it to become installed
registration.addEventListener('updatefound', () => {
if (registration.installing) {
/**
* wait until the new Service worker is actually installed (ready to take over aka claim)
* Installation might fail, thus we must wait for statechange
*/
registration.installing.addEventListener(
'statechange',
() => {
if (registration.waiting) {
// if there's an existing controller (previous Service Worker), show the prompt
if (navigator.serviceWorker.controller) {
promptUser(registration);
} else {
// otherwise it's the first install, nothing to do
console.log(
'Service Worker initialized for the first time'
);
}
}
}
);
}
});
/**
* When new service worker takes control, 'controllerchange' event is emitted,
* this is the moment we can reload the page.
*/
navigator.serviceWorker.addEventListener(
'controllerchange',
() => {
if (!isRefreshing.current) {
window.location.reload();
isRefreshing.current = true;
}
}
);
}
}
};
useEffect(() => {
initServiceWorkerUpdateChecker();
return () => {
if (serviceWorkerUpdateCheckInterval.current) {
clearTimeout(serviceWorkerUpdateCheckInterval.current);
}
};
}, []);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment