javascriptprogressive-web-appsservice-workerservice-worker-events

Show service worker update notification even after page reload


I'm using this well-known pattern for showing a notification when a service worker update is ready to install (this code goes into the web page, it's NOT the service worker code, of course):

// Register service worker.
let newWorker;
if ('serviceWorker' in navigator) {
    function showUpdateNotification () {
        document.getElementById('updatenotification').style['visibility'] = 'visible';
    };

    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js').then(registration => {
            console.log('Service worker registered at scope "' + registration.scope + '".');

            // The commented code below is needed to show the notification after a page reload.
            //
            // if (registration.waiting) {
            //     console.log('Service working in skipwaiting state.');
            //     showUpdateNotification();
            // }
            registration.onupdatefound = () => {
                console.log('Service worker update found.');
                console.log('Installing service worker is', registration.installing);
                newWorker = registration.installing;

                newWorker.onstatechange = function () {
                    console.log('Service worker state changed to', newWorker.state);
                    if (newWorker.state == 'installed' && navigator.serviceWorker.controller) {
                        console.log('New service worker is ready to install on refresh.');
                        showUpdateNotification();
                    }
                };
            };
            console.log('Updating service worker.');
            registration.update();
        }).catch(error => console.log('Service worker not registered (' + error +').'))
    })
}

Of course, that code works, in the sense that it shows a notification on the web page if a new version of the service worker is ready to be installed.

The problem is that if the page is reloaded at that point, the notification is no longer shown, because if the new service worker is installed and waiting to activate, the updatefound event is no longer fired.

So, the notification only appears ONCE, when the new service worker is installed and waiting to be activated and start controlling the page, but once the page reloads, the notification is gone.

I've solved that by using the commented code:

// The commented code below is needed to show the notification after a page reload.
//
// if (registration.waiting) {
//     console.log('Service working in skipwaiting state.');
//     showUpdateNotification();
// }

This code, upon registration, checks if there's some service worker in waiting state and shows, again, the notification.

My question is: is this correct? Can I use that "trick" safely or am I calling trouble?

I'm new to service workers so I'm not sure if I can do this kind of things.

Thanks A LOT in advance.


Solution

  • Well, sorry for the self-reply, but more or less I got what I needed, while at the same time handling all the cases (at least, all the cases I need for my project).

    I think the code below more or less covers the handling of the entire life-cycle of a service worker, and can be used as a non-very sophisticated boilerplate code for that.

    I designed this code using information from a myriad of sources, including StackOverflow, blogs, code from other PWAs, etc. Unfortunately I didn't wrote down each and every source of information, so I'm very sorry for that and I wanted to make clear that I wrote the code below but I didn't invent it, I used information and wisdom from others.

    Thanks a lot!

    if ('serviceWorker' in navigator) {
        window.addEventListener('load', () => {
    
            let refreshing = false;
            navigator.serviceWorker.addEventListener('controllerchange', () => {
                console.log('New service worker in charge.');
                if (refreshing) return;
                refreshing = true;
                window.location.reload();
            });
    
            navigator.serviceWorker.register('/sw.js').then(registration => {
                console.log('Service worker registered.');
    
                // No controller for this page, nothing to do for now.
                if (!navigator.serviceWorker.controller) {
                    console.log('No service worker controlling this page.');
                }    
    
                // A new service worker has been fetched, watch for state changes.
                //
                // This event is fired EVERY TIME a service worker is fetched and
                // succesfully parsed and goes into 'installing' state. This
                // happens, too, the very first time the page is visited, the very
                // first time a service worker is fetched for this page, when the
                // page doesn't have a controller, but in that case there's no new
                // version available and the notification must not appear.
                //
                // So, if the page doesn't have a controller, no notification shown.
                registration.addEventListener('updatefound', function () {
                    console.log('New service worker in installing state.');
    
                    registration.installing.onstatechange = function () {
                        console.log('Service worker state changed to', registration.state);
                        if (registration.state == 'installed') {
                            if (!navigator.serviceWorker.controller) {
                                console.log('First install for this service worker.');
                            } else {
                                console.log('New service worker is ready to install on refresh.');
                            }
                        }
                    };
                });
    
                // If a service worker is in 'waiting' state, then maybe the user
                // dismissed the notification when the service worker was in the
                // 'installing' state or maybe the 'updatefound' event was fired
                // before it could be listened, or something like that. Anyway, in
                // that case the notification has to be shown again.
                //
                if (registration.waiting) {
                    console.log('Service working in skipwaiting state.');
                }
    
                // Well, really this should go into a setInterval() call, but I'm
                // including it here to be exhaustive.
                console.log('Updating service worker.');
                registration.update();
            }).catch(error => console.log('Service worker not registered (' + error +').'))
        })
    } else {
        console.log('Service workers not supported.');
    }