Hemanth.HM

A Computer Polyglot, CLI + WEB ♥'r.

Succeed With Service Workers

| Comments

A brief introduction to service workers:

Web Applications are alive only if the network is reachable, if you are not connected to the network you ending you seeing an error page, this has been the major drawback of the web content delivery when compared of other technology stacks.

The service worker comes to the rescue at this scenario which provides a Web Worker context, which can be started by a runtime when navigations are about to occur and can be consulted when navigations occur to that location, network requests are dispatched to this worker and it can over-ride the default network stack behavior.

Conceptually, the worker is between the network and a document renderer, allowing the it to provide content for documents, even while offline!

With previous effort to provide offline support, using HTML5 Application Cache, we have also experienced that several attributes of the design contribute to unrecoverable errors.. As a result, the major design principle of the service worker is ‘error recoverability ’. Service workers are started and kept alive by their relationship to events, not documents. This behaviour is highly influenced by Shared Workers and Event Pages in the chromium extensions model.

Service workers gives us an overall benefit in having an excellent user experience as it’s gives support out of the box for offline support, instead of seeing an ‘You are offline message’, one could give a smoother offline-first experience. As a result of caching one can provide faster experience with lesser bandwidth consumption, combined with the power of ‘Add to homescreen’, ‘Push Notifications’ and more it would be easy to create truly progressive applications on par, and often better experience than native apps!

Service workers may be started by user agents without an attached document and may be killed by the user agent at nearly any time.

Service worker definition from the spec would be, “Service workers are generic, event-driven, time-limited script contexts that run at an origin.“

In simple terms we can consider it as a Shared Workers that can start, process events, and die without ever handling messages from documents and may be started and killed many times a second.

These special power of a service worker makes them a good candidate for a range of runtime services that may outlive the context of a particular document, such as handling push notifications, background data synchronization, responding to resource requests from other origins, or receiving centralized updates to expensive-to-calculate data. Mind of a Service Worker.

Here are some attributes of what a service worker thinks and acts like:

  • It executes in the registering service worker client's origin.
  • Has a state, which is one of parsed, installing, installed, activating, activated, and redundant.
  • It has a script URL.
  • It has an associated containing service worker registration, which contains itself.
  • Has an associated id (a UUID), which uniquely identifies itself during the lifetime of its containing service worker registration.
  • Lifecycle events being install and activate.
  • Functional events including fetch.
  • Has a script resource map which is a List of the Record {[[key]], [[value]]} where [[key]] is a URL and [[value]] is a script resource.
  • Has a skip waiting flag. Unless stated otherwise it is unset.
  • Has an imported scripts updated flag. It is also initially unset.

Interface definition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Exposed=(Window,Worker)]
interface ServiceWorker : EventTarget {
  readonly attribute USVString scriptURL;
  readonly attribute ServiceWorkerState state;
  readonly attribute DOMString id;
  void postMessage(any message, optional sequence<Transferable> transfer);
  // event
  attribute EventHandler onstatechange;
};
ServiceWorker implements AbstractWorker;
enum ServiceWorkerState {
  "installing",
  "installed",
  "activating",
  "activated",
  "redundant"
};

Up and running with service workers:

Before we get up and running with service workers, there are few important things to keep in mind. A service worker runs in a worker context, has no DOM access, is non-blocking, fully async and hence APIs such as synchronous XHR and localStorage can't be used inside a service worker. Service workers only run over HTTPS, for security reasons.

Service workers has no access to DOM, but can access:

  • The navigator object
  • The location object (read-only) setTimeout()/clearTimeout() and setInterval()/clearInterval()
  • The Application Cache
  • Importing external scripts using the importScripts() method
  • Other service workers.

Service workers need to be on HTTPS only for certain reasons like:

  • Better to protect end users from man-in-the-middle attacks
  • Do good by encouraging HTTPS adoption
  • Existing "playground" services (e.g. github.io) now work with HTTPS
  • HTTPS is coming across much more of the web quickly
  • Devtools can loosen the restriction for development (file://, localhost, etc.)

Here is how one would register for a ServiceWorker:

1
2
3
4
5
6
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/pwa/sw.js', {
      scope: '/pwa/'
    }).then(reg => console.log('Yes!', reg);)
   .catch(err => console.log('No :(', err);)
}

In this example, /pwa/sw.js is the location of the ServiceWorker script, and it controls pages whose URL begins with /pwa/. Note that the scope is optional, and defaults to /. .register returns a promise which will resolve to based on whether the serviceworker was registered or not. P.S: pwa is your progress web app ;)

It’s important to note that, the page where the service worker is registered, must have been served securely i.e on HTTPS without any certificate errors and script must be on the same origin as the page unless you are using importScripts.

Life cycle:

On .register the worker script goes through three stages:

  • Download
  • Install
  • Activate

You can use the events to interact with install and activate phases:

1
2
3
4
5
self.addEventListener('install', function(event) {
  event.waitUntil(
   doAllTheWork();
  );
});

event.waitUntil basically extends the installation process, once done the activate event is triggered.

1
2
3
self.addEventListener('activate', function(event) {
  // Have fun!
});

So finally we are all set the control the page?

Well, not yet.

The document we called .register from isn't being controlled yet as the ServiceWorker wasn’t there for the first load, a document will pick a ServiceWorker to be its controller when it navigates to the same. If you refresh the document, it'll be under the ServiceWorker's control and it will set at navigator.serviceWorker.controller. Friends with the Network

There is this event called fetch that will be fired when:

Navigations within the Service Worker's scope. All most all the Requests triggered by the page which have registered to that Service Worker.

P.S: Requests being: page itself, the JS, CSS, images, XHR, beacons et.al.

Expectations being: iframes & s ServiceWorkers. (Any other service worker) Requests triggered within a ServiceWorker. (No scope for inception)

Hearing for fetch events:

1
2
3
self.addEventListener('fetch', function(event) {
  console.log(event.request);
});

request object would have information about URL, method & headers.

But the real power is in hijacking the response and sending a different response!

1
2
3
self.addEventListener('fetch', function(event) {
  event.respondWith(new Response("Hello from a different world!"));
});

The Response object comes from the Fetch Spec, which also returns a promise, which is helpful if the response is from a remote URL.

Hope this was useful, there is much more to be added to this article, but this stayed in my draft for a very loooong time, had to publish it.

Thanks to Shwetank from Opera for quick reviews.

Comments

Copyright © 2021 - Hemanth.HM - Powered by Octopress