Service worker: importScripts never updates scripts

Pushpad is a service for web push notifications. In order to add push notifications to their websites, our customers need to install the service worker that we provide on their websites.

Initially we just asked customers to copy and paste the whole content of the service worker. However this meant that for every update we made to the script, our customers had to take action.

Then we discovered importScripts and asked all our customers to do one last update to their service worker by replacing all its content with this simple line:

importScripts('https://pushpad.xyz/service-worker.js');

Finally something that could deliver modularization to the service workers!

Then we make some changes to the imported script confident that all the end users who subscribed to the push notifications will receive the update automatically. However that doesn’t happen. Yep… the service worker is byte to byte identical so the browsers don’t update it: they don’t notice that the imported script has actually changed. Even the update which happens every 24 hours and serviceWorkerRegistration.update() are completely useless. All the HTTP headers returned (for the imported script or the service worker) are also ignored.

This means that scripts imported with importScripts inside the service worker will never ever get updates. The only workaround is to add some dummy content to the main code of the service worker to force the update on all clients:

// new dummy code
importScripts('https://pushpad.xyz/service-worker.js');

Since the current behavior is unusual and confusing we hope that the future specs of the standard will fix this.

The easiest solution for the developers would be to have browsers check for updates also in the imported scripts every time they check for updates for the service worker.

One option is to extend byte-to-byte comparison to the whole imported scripts (importScripts) graph. If one node has change, update.

The cons is that every check for updates triggers multiple HTTP requests (not a big deal if proper HTTP cache headers are used).

Another solution would be to have a method like serviceWorkerRegistration.update({force: true} or serviceWorkerRegistration.update({updateScripts: true}. This would allow a service like Pushpad to deliver updates triggering a push signal that on reception would invoke that method. The cons of this is that it looks like an hack, it is much more complex than the previous solution and can be potentially abused forcing updates also when it’s not needed.

UPDATE 2016 / 08: after some discussion on Github, the proposal described in this blog post has been taken into consideration and will become part of the standard in the next months. So in the future the byte to byte check will be extended to all imported scripts. This means that developers won’t have to worry anymore because the imported scripts will be updated automatically in the same way as the main script.

6 thoughts on “Service worker: importScripts never updates scripts”

  1. A less ugly workaround is to add the hash of the imported script to its name, this way when the script changes, the hash changes and so the service worker changes.

    Reply
    • Yes, you can also add a version number at the end of the script name, however someone still need to manually update the service worker each time. That’s the point.

      Obviously if you use a server side language to generate the service worker, then you can also generate the hash automatically but that’s a lot of complication (e.g. when the service worker is requested, PHP checks all the imported scripts for changes, caches the results and adds the computed hash to the returned service worker…). It looks clear that it’s more appropriate that the browser manages that.

      Reply
      • It’s not really that complicated, you don’t need to check for changes, you just need to hash the contents of the scripts (you would need to check for changes only if you used a version number, but my idea was to use a hash).
        In the dummy content solution, don’t you still need to know when something has changed in the imported scripts? When do you add the dummy content otherwise?

        Reply
  2. You can also force a full dependency check by changing the name of the service worker itself. i.e. instead of navigator.serviceWorker.register(‘/sw.js’) do something like navigator.serviceWorker.register(`/sw.js?hash=kjTJn6fMjgCE`). (Note that this can be dynamically generated too; see https://stackoverflow.com/a/43471531/ for more info.)

    Reply
    • Thanks for the useful suggestion. However the service worker should also be updated when the user receives a push message, not only when the user visits your website. Your approach doesn’t work in the first case.

      Reply

Leave a Comment