Web Push Notifications – Part 2 / technical

For this post, I assume you’ve read my post from yesterday about a small ordering prototype that we’ve created using web push technology. We’ve learnt lot’s about recent technologies such as browser notifications, serviceworkers and push-messaging, which we’re sharing in this post.

For web push to work, a few things have to come together:

  • a service worker needs to be registered to get access to the PushManager and the push event needs to be reacted upon in a visible way (e.g. with a notification)
  • the user must have granted the notification permission
  • a push subscription needs to be created and shared with the system that will later send out the push notification

All these things happen on the client side, it’s all javascript in your browser. Luckily, these things will work security-wise if you are developing via localhost. But for later deployment, you need to run on HTTPS – otherwise service workers are disabled.

Service Workers

Service workers need to be registered once and then automatically become active when the pages under it’s scope are loaded. That’s right, a service worker can be active for a number of pages (Clients) and this is controlled by the placement of the service worker and it’s scope. In our case, we place the service worker in the root directory, but then limit the scope to the event subpage of our web application. And of course we check first if our browser is able to support our features:

The register Method of the serviceWorker container returns a promise, which gives you access to a service worker registration. This is what you want later on, as it allows you to create the Push Subscription we need for our push notifications. But there is a big gotcha that we really needed some time to figure out:

  • the first time you register a service worker, it will need some time to become active. For example it needs to be registered correctly or you even do some caching (like for offline web apps) which will take some time. So you cannot directly access the serviceWorker! Instead, you need to wait a bit – the bad way is using a setTimeout (been there, done that), the good way is to use the ready property as seen above. It returns a promise that fulfills once the worker is ready.

You can see that we post a message to the worker. It would technically not be necessary, but we’ve added a simple “tell us your version” call to it. Inside of the eventworker.js script, you find this method:

So once the worker is ready, it quickly responds with the version. We have to manually increase the version when we make changes. We find that a good tool for debugging, because very often you deal with issues around old worker files during development. Of course, if using Chrome, you should also turn on automatic service worker reloads in the Application part of the dev console. It makes sure each page refresh also refreshes the worker.

At this point our worker will not deal with push messages it receives, but we’ll add that later.

Notifications

Push messages themselves can be received without permissions – all you need to have is a valid service worker, a push subscription (will be covered soon) and an event listener for the push event in you worker. The problem ist that silent pushes (push messages that do not result in visible elements like notifications) are not allowed by Chrome and likely will never be. It could be harmful to carry out tasks in the background without the user even noticing. So at some point, you need to ask the user to grant the notifications. This better happens at a point, where the user has understood that these permissions are a vital part of your service – otherwise chances are high they get blocked. Once blocked, it is really hard to unblock the notifications. Hardly any user knows how. In our case, we ask the user before he finishes the order. If we have figured out that push will work for his browser, we ask him just before the order is sent off:

The button to trigger the following will not be shown if push will technically not work on the users browser. But if we know it will work, the following will happen:

We call startWebPush(), which is where we ask for the notification permission, then access the serviceWorker (that got registered before or already is active now) and get a fresh push subscripton. The push subscription is a small json object that includes all info like the endpoint to push messages to. Let’s step through that process:

First, we call askPermission() which itself is a Promise, so we can chain these calls. Notification.requestPermission() will popup a permission dialog for the user to answer. If OKed, we will resolve, reject otherwise. As explained, it’s a good idea the user knows why he should press OK!

Next, we ask for the service worker registration that we’ve previously created. Either it became active uponn page load, or we’ve registered that worker at page load (and again it will be active then). Using the serviceRegistration, we access the PushManager to get a PushSubscription. For this, we have created some mysterious VAPID keys before. These are public/private keys which are used by the push systems to verify our messages. You can create these keys here online or use a command line node.js tool to create them. Be sure to only share the public key in the public JS/HTML of your page of course! In our case, we’re puttin the public key in a data-field of the body tag of our HTML page. The key needs to be converted to a UInt8Array – which we found tricky, too, but here is the solution (the magic decodeKey method):

At this point we have the valuable pushSubscription object, which we’re sending to our server via the executeOrder method in our code (no magic here, just AJAX). The push sub looks roughly like this:

The server side

This is where we had little issues, mostly because there are excellent node.js libraries to help you. All we need to do is require the web-push node.js lib and there you go:

As we request a new push sub for each order, we save these internally under a scope (based on a random id and the name of the event) – pushSubscriptions[scope] will be the exact pushSubscription we’ve sent before to the server. You can see that sendNotification simply sends out the push message – all the heavy lifting happens in this method. The web push lib just needed to be initialized with both VAPID keys – private and public – which happened at server startup:

Uh, almost forgot the client push implementation

Finally, we’re back on the client side. Our serviceWorker needs to implement the push event listener and show a notification. That part is simple:

As a tipp: JSON.stringify your push messages, then decode in the event listener. You can send all text data, we prefer JSON. The only important thing here is the pushEvent.waitUntil() call – it makes sure the browser waits for the promise (notification) to appear and the user to react. Otherwise, nothing happens.

Yay – I’ve just used 45mins wating at the airport in a productive way. Hope you liked it – next I will cover more service worker insights, for example we later added offline support and web install banners to our app. More later… Please let me know in the comments if you have questions –  or send me a tweet.

 

 

Tell us what you think:

2 thoughts on “Web Push Notifications – Part 2 / technical

    • Sven Haiges says:

      Yep, it is. On Android, you open the site settings link underneath a notification and that’s where you can then change the permissions that thsi site has. I just tried it out.

Leave a Reply

Your email address will not be published. Required fields are marked *