Creating Our Service Worker

Building the system behind the Guardian Mobile Innovation Lab’s web notifications for news.

Connor Jennings
The Guardian Mobile Innovation Lab
5 min readJul 1, 2016

--

Photography by Petr Svarc / Alamy

Setting up a service worker

Implementing web push notifications requires a service worker. Service workers can be thought of as independent Javascript processes that run in a separate thread and have limited access to browser API’s (service workers do not have access to the DOM, geolocation API’s, etc).

Important Note: At the time of our implementation, web push notifications were (and still are) a moving target for Firefox. We performed checks on our registration page to ensure that users who wished to participate were using a browser that supported service workers and web push.

This table is the support for web push notifications across platforms and browsers at the time of this writing.

This is the code for our service worker. It can be divided into two sections:

Analytics

Google Analytics powers the tracking and analytics component of our service worker.

We had to work around a few of the nuances of service workers when implementing analytics. Because service workers are inherently asynchronous, they can only utilize APIs that have asynchronous functionality. That means no access to the synchronous XHR API (fallback to the asynchronous Fetch API). Another important caveat: Service workers cannot access the Cookie API (among other browser APIs like DOM and geolocation).

The out-of-the-box Google Analytics tracking code relies on browser features like the Cookies API and synchronous XHR. Fortunately, Google Analytics does have an external API (Google Analytics Measurement Protocol). The API has a quickstart on how to implement your own tracking code to be used in your service worker (but feel free to use ours).

We can break our core service worker code into three sections:

Installing the service worker

Installing a service worker is encapsulated in the following steps:

  1. Check that the user’s device and browser support the service workers
  2. Attempt to install the service worker on the user’s device
  3. Even if the user’s browser has the implemented the service worker API, it may not fully support service workers and web push notifications. Do a few manual checks to make sure the user is using a browser and device we want to support
  4. The service worker should be successfully installed (and we can now subscribe the user to a push notification topic)

Service worker/client communication

Note: In this section, “client” refers to the main Javascript thread of a user’s browser

Communication with a service worker is facilitated by message passing.

Message passing in service workers leverages the Channel Messaging API, and the Message Channel interface is implemented with callbacks. To deal with this discrepancy we implemented code found in GoogleChrome Sample Github repo to wrap the message passing code in a Promise. We use Promises to coordinate the asynchronous aspects of our Javascript code.

The functionality of our service worker can be divided in two parts:

Displaying web push notifications

To receive push notifications our service worker subscribes to PushEvents. Both Mozilla and Google provide examples of the data field of these Push Events, but feel free to consult the section on PushMessageData in the W3C Notifications API Spec).

Showing the notification

First we subscribe our service worker to the push event. Any push notification received by our service worker is then deserialized into json.

Getting our service worker to show a notification is just a matter of having the ServiceWorkerRegistration call the showNotification method with the correct parameters.

Reacting to user action

A requirement of our Web Notification experiment was binding functionality to notification click and close events. To handle this requirement we built a contract into our payload that allowed for mapping events to actions built into the core service worker code. This way we could push notifications to a service worker that would trigger things like opening a browser or automatically subscribe/unsubscribe a user to a topic. It’s up to you to implement a similar contract however you see fit.

Here’s a Gist of one example of our notification payload contract.

Command Mappings

Our contract is structured so that command names map to service worker functionality. In our contract commands like openURL require a parameter which is reflected in our payload.

The contract also supports Notification Actions. The notification text, icon and command are encapsulated by options while the action buttons (given they’re optional) are contained within the actionCommands field.

Responding to click events involves binding to the notificationclick or notificationclose event and then parsing the event object.

Important: How do you differentiate between a click on a notification and a click on a notification action? The action property of the event object! In the documentation for showNotification, the options parameter has a property called actions which can be populated with an array of objects. The value of the action property on this object is what will be passed to the event object on click.

In our code, to trigger commands on a notification click, we determine if the action property of the notificationclick event is populated.

We leverage the action property of the event object to store multiple commands to be executed when an action button is clicked.

Important: At the time of this writing, only Chrome has implemented and shipped the notificationclose event. The event has been implemented in Firefox but has yet to be shipped at the time of this writing (stable FF release version at the time of this writing: 47.0.1).

Updating a service worker

The ServiceWorkerRegistration interface provides a method for updating a service worker. In our code we update user’s Service Workers by sending a notification with an update command.

Note At the time of this writing, Google Chrome requires a Service Worker to visibly display a notification if it receives a push message (justification for the requirement can be found here). In our updating code we don’t execute showNotification so the user will be shown the default Google Chrome notification.

Service worker quirks

Chromium implements native push notifications by leveraging the Android Notification API with the help of JNI. The coverage of the Notification API in Chromium is extensive and functionality like updating notifications is exposed to the Web Notification API. Despite this, we did encounter a few quirks when using web push notifications.

Updating a notification

We noticed a quirk when we wanted to update a notification in place (example: updating a notification in place with primaries results). This is accomplished by sending a push notification with the same tag parameter (mentioned in the showNotification documentation on MDN). When sending an update, if there were multiple notifications on the lockscreen the notification that was updated would temporarily move to the top of the screen and then back to it’s original position without input from the user.

Opening a web page

One of the requirements of our notifications was letting a user click a notification to open a web page. This is a common use case and is documented on MDN. Notice that in the documentation the url parameter “Generally…must be a URL from the same origin as the calling script.” If a user has already clicked on a notification that opened a web page and then clicked on the same notification again, a second web page will open (instead of just focusing on the already opened page).

The Guardian Mobile Innovation Lab operates with the generous support of the John S. and James L. Knight Foundation.

--

--