Last active
January 28, 2022 09:45
-
-
Save sampolahtinen/400306ae8481f0d1b4704a03a35697e5 to your computer and use it in GitHub Desktop.
React hook to initiate a service worker update checker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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