Web Push Notifications

Welcome to the future of the web — where push messages can help you achieve better engagement for your site or web app.

It goes without saying that push notifications expand your reach to your users in a timely, power-efficient and dependable way. Users are re-engaged with customized and relevant content that will have them coming back for more.

Inspired by the parallels between basic astrological ideas and push notification architecture, come join us on this journey to better understand how to set up your site – both the front-end and the back-end – to send push messages to a user without needing the browser or app to be opened. You can fork this tutorial itself on GitHub and even set it up locally so that you can play with it.

To see it in action, click the button below, accept the permission prompt (if you haven’t already), and then immediately close this tab (or browser). You should get a notification within 5 seconds after clicking the button below. Don’t worry, when you click the notification, it’ll take you right back here, so you can continue learning about push messages.

Just so you know, after clicking the button above, you’ll be getting push message updates daily. If you prefer to stop getting these push messages, click below to unsubcribe.

The different pieces

At a high-level, there is a server-side and client-side component that makes push messages possible. The client side consists of the browser and the app’s web page while the server side involves the app’s server and the notification service’s server. Each of these different pieces play a role in subscribing for the push message, sending push message updates, showing a notification and ultimately opening a page when that notification is clicked.

Server Side

If the signs of the zodiac were sending messages to those on a terrestrial plane, they would be the server side (and up in the cloud no less) of push notifications.

Client Side

The client side of push notifications, the browser or user agent and the app page, is represented by the terrestrial plane (earth) where the updates from the zodiac are routed through and displayed.

Before we explore building out a push notification in depth, let's break down the interaction between front-end and back-end.

The app first attempts to subscribe the user to push messages. The browser will ask the user for permission to show notifications.

Once the user has given consent, the browser will communicate with the notification service’s server to establish a push message channel that can be used to send push messages.

The app’s server can store the necessary information related to the push subscription so that it can send push messages even after the page is gone. It does this by sending a message to the notification service’s server to push a message to the relevant client’s device.

When such a message arrives at the client’s device, the browser allows the app to prescribe a notification to show.

Once the notification is clicked, the browser allows the app to prescribe a page to open, focus, or navigate.

Now that you have an overview of how Push Notifications work, let's dive into building the back-end and front-end pieces to put it all together.

Step 1 Set up your server

To start, you’ll first need to make sure your web server is setup to send pushes. We’ll be using a node.js server and take advantage of the open-source web-push library so that we don’t have to worry about the encryption details involved with sending a push.

We’ll first need to call npm install express (web server) and npm install web-push (web push library) from a terminal or command prompt so that we can use them in our app.

We’ll need to specify the VAPID keys that will allow identifications between our app’s server and the notification server (e.g. Firebase Cloud Messaging (FCM), Mozilla Cloud Services (MCS), and Windows Push Notification Service (WNS) depending on which browser is being used). You only need to set up the VAPID keys once which can be generated easily:

var webpush = require('web-push');
console.log(webpush.generateVAPIDKeys());

Once they’re generated, you can use them for as long as you need to by setting the vapidKeys variable to what you just got when calling generateVAPIDKeys():

var vapidPublicKey = 'BL6As_YCGHPf3ZeDbklyVxgvJVb4Tr5qjZFS-J7XzkT5zQNghd9iUBUsqSlVO5znwTsZZrEOx8JFRDJc1JmkymA';
var vapidPrivateKey = 'GnMVDgbtZrqs7tgKEkJaV5aZF8cVjoq7Ncz_TEVI_lo';
        
webpush.setVapidDetails(
    'mailto:myaccount@outlook.com',
    vapidPublicKey,
    vapidPrivateKey
);

We’ll need to set up a few endpoints on our server so that we can provide the public key to our site. The public key will be used when subscribing for push messages so that the notifications server (e.g. WNS or FCM) can know that we are who we say we are when we send subsequent push messages.

var express = require('express');
var app = express();

app.get('/api/key', function(req, res) {
    res.send({
        key: vapidPublicKey
    });
});

We’ll also need an endpoint that will allow us to store the subscription details for a user so that we’ll be able to push messages to them from our app’s server.

app.post('/api/save-subscription, function(req, res) {
    // save req.body.subscription to a database

    res.send('Success');
});

In our example, we are going to be sending astrology trivia push updates to each user that is subscribed daily at 7am Pacific Time. This will be done in a scheduled task that runs on the server. Therefore, it’s important that we store the subscription information on the server so that we can send this update whenever we need to.

You can send pushes whenever you like, such as when something changes in your database, or after something changes on your site or app. For instance, if your site allows users to message each other and someone messages a user, you will want to send a push to that user so that they know they just got a message. Be mindful that the goal is to produce a notification that will have a good chance of being clicked by the user, otherwise it might defeat the purpose. For instance, it might make more sense to not send any push messages to a user that is already on your page since it might annoy them to get notifications when they’re already there. You’ll need to coordinate with the server to ensure that users are only getting notifications when they should be getting them.

Using the web push library, we can send a push message from the server.

webPush.sendNotification(savedSubscriptionData, payload)
    .then(function (response) {
        console.log('sent');
    });
    

Now that we have the server all set up, let’s move on to making the actual page that will subscribe for the push!

Step 2 Set up your web page

When the page loads, the first thing you’ll want to do is get the public key from the application server so that you can set up the push subscription.

if (navigator.serviceWorker) {
    fetch('./api/key')
        .then(function(res) {
            res.json().then(function(data) {
                registerPush(data.key);
            });
        });
}

Now, with the public key in hand, we’ll need to install the service worker and also create a push subscription.

function registerPush(appPubkey) {
    navigator.serviceWorker.register('service-worker.js');
    navigator.serviceWorker.ready.then(function(registration) {
        return registration.pushManager.getSubscription()
            .then(function(subscription) {
                if (subscription) {
                    return subscription;
                }

                return registration.pushManager.subscribe({
                    userVisibleOnly: true,
                    applicationServerKey: urlBase64ToUint8Array(appPubkey)
                });
            }) 
            .then(function(subscription) {
                return fetch('./api/save-subscription', {
                    method: 'post',
                    headers: { 'Content-type': 'application/json' },
                    body: JSON.stringify({ subscription: subscription })
                });
            });
    });
}

function urlBase64ToUint8Array(base64String) {
    var padding = '='.repeat((4 - base64String.length % 4) % 4);
    var base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

    var rawData = window.atob(base64);
    var outputArray = new Uint8Array(rawData.length);

    for (var i = 0; i < rawData.length; ++i)  {
        outputArray[i] = rawData.charCodeAt(i);
    }

    return outputArray;
}

We’ll need to make sure that we have an active service worker before we attempt to subscribe for push. We can do this by using navigator.serviceWorker.ready which will resolve when a service worker is active.

At this point, before a new push subscription is created, the browser will check whether the user granted permission to receive notifications. If not, the user will be prompted by the browser for permission.

To create a push subscription, you’ll need to set the userVisibleOnly option to “true” – meaning a notification must be shown as a result of a push – and provide a valid applicationServerKey. If there is already a push subscription, there is no need to subscribe again. That said, we’ll want to first check if there’s an existing push subscription and use that first.

At any point when a push is received by the client, a corresponding service worker is run to handle the push event. As part of this push handling, a notification must be shown so that the user understands that something is happening in the background. In the service worker (sw.js), we will handle the push event and show a notification:

self.addEventListener('push', function(event) {
    event.waitUntil(
        registration.showNotification('WEATHER ADVISORY', {
            body: event.data ? event.data.text() : 'no payload',
            icon: 'icon.png'
        })
    );
});

After a notification is shown, there is still the matter of dealing with when it’s been clicked. As such, we need to have another event listener in the service worker that would handle this case. In the same service worker, we will handle the notificationclick event to close the notification and then open a new window for our site:

self.addEventListener('notificationclick', function(event) {
    event.notification.close();
    event.waitUntil(clients.openWindow('weather/advisory'));
});

You’re also able to sort through the already open windows and focus one of those, or perhaps even navigate an existing window.

Step 3 Make sure it all works

It’s now time to give it a try! The button below will send a message to the application server to send along a push request to the push service’s server so that your machine can get a push notification.

And again, after clicking the button above, you’ll be getting push message updates daily. If you prefer to stop getting these push messages, click below to unsubcribe.

As a reminder, you can check out a fully working version of this demo on the GitHub repo.

Try it out!

Great, so now we have a fully working push site. It’s time for you to get your hands dirty and see what you can come up with. Make sure you upgrade to the latest version of Windows which comes with Edge 17 that supports service workers and push. We’d love to hear from you if you have any questions or find any bugs. Please let us know!

Appendix

The W3C Push API and Notification API go hand-in-hand to enable push notifications in modern browsers. The Push API is used to set up a push subscription and is invoked when a message is pushed to the corresponding service worker. The service worker then is responsible for showing a notification to the user using the Notification API and reacting to user interaction with the notification.

A standardized method of message delivery is also important for the W3C Push API to work consistently across all major browsers where application servers will need to use multiple push services. For instance, Google Chrome and Mozilla Firefox use Firebase Cloud Messaging (FCM) and Mozilla Cloud Services (MCS), respectively while Microsoft Edge relies on the Windows Push Notification Service (WNS) to deliver push messages. To reach reasonable interoperability with other browsers’ messaging services, WNS has now deployed support for the Web Push protocols being finalized within IETF, as well as the Message Encryption spec and the Voluntary Application Server Identification (VAPID) spec for web push. Web developers can now use the Web Push APIs and service workers to provide an interoperable push service on the web.