From 55bf338a765df0d3691c406077941af96dd8c27e Mon Sep 17 00:00:00 2001 From: dfabulich Date: Wed, 13 Dec 2017 16:25:06 -0800 Subject: [PATCH] Simplify the refresh banner example. Use a prolyfill of `navigator.serviceWorker.waiting` and `ServiceWorker.skipWaiting()` to simply implementation. Fixes #5504 --- .../tools/workbox/guides/advanced-recipes.md | 111 ++++++------------ 1 file changed, 39 insertions(+), 72 deletions(-) diff --git a/src/content/en/tools/workbox/guides/advanced-recipes.md b/src/content/en/tools/workbox/guides/advanced-recipes.md index b3cedf66eece..ea3050c089f9 100644 --- a/src/content/en/tools/workbox/guides/advanced-recipes.md +++ b/src/content/en/tools/workbox/guides/advanced-recipes.md @@ -15,11 +15,34 @@ description: Advanced recipes to use with Workbox. A common UX pattern for progressive web apps is to show a banner when a service worker has updated and waiting to install. -To do this you'll need to add some code to your page and to your service worker. +To do this you'll need to add some polyfill code to your page and to your service worker. **Add to your page** ```javascript +// polyfill navigator.serviceWorker.waiting +if (!('waiting' in navigator.serviceWorker)) { + navigator.serviceWorker.waiting = new Promise(function(resolve) { + navigator.serviceWorker.ready.then(function(reg) { + function awaitStateChange() { + reg.installing.addEventListener('statechange', function() { + if (this.state === 'installed') resolve(reg); + }); + } + if (reg.waiting) resolve(reg); + if (reg.installing) awaitStateChange(); + reg.addEventListener('updatefound', awaitStateChange); + }) + }); +} + +// polyfill ServiceWorker.skipWaiting() +if (!('skipWaiting' in ServiceWorker.prototype)) { + ServiceWorker.prototype.skipWaiting = function() { + this.postMessage('skipWaiting'); + } +} + function showRefreshUI(registration) { // TODO: Display a toast or refresh UI. @@ -40,79 +63,27 @@ function showRefreshUI(registration) { button.disabled = true; - registration.waiting.postMessage('force-activate'); + registration.waiting.skipWaiting(); }); document.body.appendChild(button); }; -function onNewServiceWorker(registration, callback) { - if (registration.waiting) { - // SW is waiting to activate. Can occur if multiple clients open and - // one of the clients is refreshed. - return callback(); - } - - function listenInstalledStateChange() { - registration.installing.addEventListener('statechange', function() { - if (event.target.state === 'installed') { - // A new service worker is available, inform the user - callback(); - } - }); - }; - - if (registration.installing) { - return listenInstalledStateChange(); - } - - // We are currently controlled so a new SW may be found... - // Add a listener in case a new SW is found, - registration.addEventListener('updatefound', listenInstalledStateChange); -} - -window.addEventListener('load', function() { - // When the user asks to refresh the UI, we'll need to reload the window - navigator.serviceWorker.addEventListener('message', function(event) { - if (!event.data) { - return; - } - - switch (event.data) { - case 'reload-window': - window.location.reload(); - break; - default: - // NOOP - break; - } - }); - - navigator.serviceWorker.register('/sw.js') - .then(function (registration) { - // Track updates to the Service Worker. - if (!navigator.serviceWorker.controller) { - // The window client isn't currently controlled so it's a new service - // worker that will activate immediately - return; - } - - onNewServiceWorker(registration, function() { - showRefreshUI(registration); - }); - }); +navigator.serviceWorker.addEventListener('controllerchange', function() { + window.location.reload(); }); + +navigator.serviceWorker.waiting.then(showRefreshUI); ``` -This code handles the verious possible lifecycles of the service worker +The polyfill handles the verious possible lifecycles of the service worker and detects when a new service worker has become installed and is waiting to activate. -When a waiting service worker is found we set up a message listener on -`navigator.serviceWorker` so we know when to reload the window. When the -user clicks on the UI to refresh the page, we post a message to the new -service worker telling it to force an activation. After this a message is -posted to all windows telling them to reload. +When a waiting service worker is found we we tell the service worker to skip +waiting (posting a message if necessary). After this, the `controllerchange` +event will fire. The page should reload when the controller changes to +ensure that all tabs are running the same version of the code. Note: This is one possible approach to refreshing the page on a new service worker. For a more thorough answer as well as an explanation of alternative @@ -122,6 +93,9 @@ discuess a range of options. **Add to your service worker** +This code supports the polyfill while we wait for `ServiceWorker.skipWaiting` +to be available in all browsers. + ```javascript self.addEventListener('message', (event) => { if (!event.data){ @@ -129,12 +103,8 @@ self.addEventListener('message', (event) => { } switch (event.data) { - case 'force-activate': + case 'skipWaiting': self.skipWaiting(); - self.clients.claim(); - self.clients.matchAll().then((clients) => { - clients.forEach((client) => client.postMessage('reload-window')); - }); break; default: // NOOP @@ -143,10 +113,7 @@ self.addEventListener('message', (event) => { }); ``` -This will receive a the 'force-activate' message and call `skipWaiting()` and -`clients.claim()` so that the service worker activates immediately and controls -all of the currently open windows. We then message each window with a -'reload-window' message so each tab is refreshed with the latest content. +This will receive a 'skipWaiting' message and call `skipWaiting()`. ## "Warm" the runtime cache