Skip to content

Instantly share code, notes, and snippets.

@SalahHamza
Last active August 12, 2018 08:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SalahHamza/6489cd13e1a35823108cc03b30112529 to your computer and use it in GitHub Desktop.
Save SalahHamza/6489cd13e1a35823108cc03b30112529 to your computer and use it in GitHub Desktop.
Service Workers Notes

Intro To Service Workers

Service Worker is a programmable network proxy, allowing you to control how network requests from your page are handled. Service Workers require HTTPs on the page (The only exception is when working locally).

Note: If there is information that you need to persist and reuse across restarts, service workers do have access to the IndexDB API.

Registering a Service Worker

How to register a service worker

// Check if the SW is provided by the Browser
if(navigator.serviceWorker){
   navigator.serviceWorker.register(scriptUrl, options)
   .then((registration) => {
      // SW was registered
   })
   .catch((err) => {
      // SW registration failed
   });   
}
  • Parameters:
    • scriptURL: The URL of the SW script.
    • options: An object containing the SW options. Currently available options are:
      • scope: a URL representing the SW’s registration scope; that is, what range of URLs the SW can control. It is relative to the base URL of the application. By default, the scope value for a service worker registration is set to the directory where the service worker script is located.
  • Returns:
    • Promise that resolves with the ServiceWorkerRegistration Object.
sw script Location Default Scope Pages Controlled
/foo/sw.js /foo/ /foo/ or deeper but not shallower like /foo
/foo/bar/sw.js /foo/bar/ /foo/bar/ or deeper
/sw.js / / or deeper

When to register a service worker

Spinning up a new Service Worker thread to download and cache resources in the background can work against your goal of providing the shortest time-to-interactive experience the first time a user visits your site.

The solution is to control start of the service worker by choosing when to call navigator.serviceWorker.register(). A simple rule of thumb would be to delay registration until after the load event fires on window, like so:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js');
    // ...
  });
}

All said and done, the right time to register a Service Worker depends on how the app works. Read more about improving service worker boiler plate.

Hijacking Requests

After the sw is installed, the service worker will begin to receive fetch events, but only after the user navigates to a different page or refreshes (not always for the latter).

self.addEventListener('fetch', (event) => {
   event.respondWith(
      // accepts promise that resolves with a response object.
   );
});

This will hijack all requests. To hijack particular requests we can use event.request.url:

self.addEventListener('fetch', (event) => {
   if(event.request.url === '/foo'){
      event.respondWith(
         // return custom response here
      );
   } else {
      event.respondWith(
         // return default response here
      );
   }
});

We can also hijack 404 responses by checking the status and returning another fetch() request:

self.addEventListener('fetch', (event) => {
   event.respondWith(
      fetch(event.request)
      .then((response) => {
         if(response.status === 404){
            return fetch('/path/to/custom/file');
         }
         return response;
      })
      .catch((err) => {
         // Handle Error
      })
   );
});

Caching data:

We can also cache data to respond with, caching this data can be done when the service worker is being installed; we can check that with the install event:

self.addEventListener('install', (event) => {
   // Perform install steps
});

Install steps:

  1. Open cache: To open a cache we need to call the open() on the Cache API;
caches.open(CACHE_NAME).then((openedCache) => {
   // add items to cache
}).catch((err) => {
   // handle error
});

Returns a Promise that resolves to the requested cache object. 2. Cache files: To add cache items we can either use the put() or addAll() methods on the openedCache object.

openedCache.put(req, res).then(() => {
   // request/response pair has been added to the cache
}).catch(() => {
   // cache wasn't successful
});

Takes the request to add to the cache, and the response to match that request. And returns a Promise that resolves with void.

openedCache.addAll(urlsToCache).then(() => {
   // URLs/requests have been added to the cache
}).catch(() => {
   // cache wasn't successful
});
  1. Confirm whether all the required assets are cached: The confirmation can be done by using the event.waitUntil() method.
self.addEventListener('install', (event) => {
   event.waitUntil(
      caches.open(CACHE_NAME).then((openedCache) => {
         return openedCache.addAll(urlsToCache);
      })
   );
});

The install events in service workers use waitUntil() to hold the service worker in the installing phase until tasks complete. If the promise passed to waitUntil() rejects, the install is considered a failure, and the installing service worker is discarded. This is primarily used to ensure that a service worker is not considered installed until all of the core caches it depends on are successfully populated.

Tracking Service Worker Registration

When registering a service worker we have access to the serviceWorkerRegistration object.

navigator.serviceWorker.register(PATH)
.then((registration) => {
   // successful registration, track registration
}).catch((err) => {
   // registration failed, Do something about it
});

The registration object has a bunch of methods and properties:

  • reg.unregister() unregisters the service worker and returns a promise. The service worker will finish any ongoing operations before it is unregistered.
  • reg.update() checks the server for an updated version of the service worker, if the new service worker script is not byte-to-byte identical with the current, it updates the service worker.
  • reg.installing returns a SW whose state is installing, initially set to null.
  • reg.waiting returns a SW whose state is waiting, initially set to null.
  • reg.active returns a SW whose state is either active or activating, initially set to null.

If the SW has been changed since the last time it was registered the registration object fires an updatefound event:

registration.addEventListener('updatefound', () => {
   // when fired it means that there is a new SW
   // being installed. 
   // and can be accessed with reg.installing 
});

Every installing service worker has a state that can be accessed with reg.installing.state:

  • installing hasn’t yet completed installation.
  • installed installed but hasn’t yet been activated.
  • activating active event fired but not yet completed.
  • active the worker is ready to receive fetch events.
  • redundant the service worker has been updated or failed to install.

The installing service worker fires an event whenever the its state (i.e reg.installing.state) changes:

registration.installing.addEventListener('statechange', () => {
   // Do something
});

navigator.serviceWorker.controller refers to the service worker currently controlling the page. Returns a service worker if its state is activated or null if there is no active worker.

The skipWaiting() method allows the service worker to progress from the registration's waiting position to active even while service worker clients are using the registration.

self.skipWaiting();

The postMessage() method of the Worker interface sends a message to the worker's inner scope (i.e. inside the service worker script sw.js).

worker.postMessage(message);

The worker’s inner scope can listen for messages with the message event:

self.addEventListener('message', (event) => {
   // the message object can be accessed with event.data
});

When the service worker controlling the page changes a controllerchange event is fired

navigator.serviceWorker.addEventListener('controllerchange', () => {
   // a new service worker in charge
});

Notes:

  • Synchronous requests are not allowed from within a service worker — only asynchronous requests, like those initiated via the fetch() method, can be used.
  • In the above code self refers to the ServiceWorkerGlobalScope.

Useful Links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment