yyx990803 / register-service-worker

A script to simplify service worker registration with hooks for common events.
MIT License
638 stars 58 forks source link

add new hook for when sw is activated #27

Closed adam-t-b closed 5 years ago

adam-t-b commented 5 years ago

I'm currently using the pwa plugin in 'InjectManifest' mode since that's the only way I could find to add an event listener for messages on the service worker. So far, this has been useful when we want to skip waiting on user input. For example, I prompt a user when there's an updated service worker. Something like this:

New version available 'Refresh' or 'Dismiss'

However, I'm trying to figure out how to reload the page after the user clicks refresh. I can't do the following because the service worker has no context of the window object. its out of scope

self.addEventListener('message', (e) => {
  if (e.data.action === 'skipWaiting') {
    self.skipWaiting()
    window.location.reload()
  }
})

My thought was to add a new hook or to re-use the 'ready' hook in register-service-worker which would be triggered whenever the installing worker state === 'activated'

For example:

if (installingWorker.state === 'installed') {
  if (navigator.serviceWorker.controller) {
    // At this point, the old content will have been purged and
    // the fresh content will have been added to the cache.
    // It's the perfect time to display a "New content is
    // available; please refresh." message in your web app.
    emit('updated', registration)
  } else {
    // At this point, everything has been precached.
    // It's the perfect time to display a
    // "Content is cached for offline use." message.
    emit('cached', registration)
  }
} else if (installingWorker.state === 'activated') {
  emit('activated', registration)
}

This would allow the ability to reload the page safely from the registerServiceWorker.js file after a user has clicked 'Refresh'

Am I overthinking this? Perhaps there's a better way to handle this?

jamessouth commented 5 years ago

In this project I separate self.skipWaiting from window.location.reload. So in service-worker.js I have a message listener:

self.addEventListener('message', (e) => {
  if (!e.data) return;
  if (e.data === 'skipWaiting') self.skipWaiting();
});

and in registerServiceWorker.js, in the updatefound section, I have:

  let refreshing;
  navigator.serviceWorker.addEventListener('controllerchange', () => {
    if (refreshing) return;
    window.location.reload();
    refreshing = true;
  });

and in the updated section:

const UpdatedEvent = new CustomEvent('swUpdated', { detail: registration });
document.dispatchEvent(UpdatedEvent);

So, when a new SW is detected, I create a new event with the SW rego as payload and send it to the document. In my app, I am listening for this event in a Main component, and when it comes, a function runs to change an alert boolean to true and set the rego prop on the Main component's data to the SW rego payload. The boolean is used to render an Alert component and the rego is sent as a prop. The Alert component thus appears on-screen and when you close it, a function runs that calls waiting.postMessage on the rego object, with the message of skipWaiting. The SW is listening for that to call self.skipWaiting. When self.skipWaiting is called, the new SW activates. This triggers the controllerchange event that we are listening for in registerServiceWorker, and that is where we call reload.

adam-t-b commented 5 years ago

@jamessouth This is a better solution. I didn't think about putting the event listener for controllerchange directly in the registerServiceWorker.js. I added it to mine, and it negates the need for another hook. Great suggestion! This issue can probably be closed.

jamessouth commented 5 years ago

😊😁👍