Last week's article was Part 2 (of 3) in my series on how I built a Progressive Web Application for this blog.

The application

Get the Application | View Source

In this final part, I will show how I implemented web push notifications. For users that subscribe, a notification is sent to their device once a week when a new article is released on the blog.

Web Applications can use the Push API, in combination with the Service Worker, to receive push notifications messages pushed to them from a server, whether or not the web app is in the foreground, or even currently loaded, on a user agent. It works in the following way -

  • The user subscribes to receive push notifications via the Service Worker's Push Manager
  • We receive and endpoint with the user's subscription ID, which we save securely in a database
  • We pass the user's subscription ID when we post messages from the server to our messaging service (in this case, Firebase Cloud Messaging)

So there are two parts to this. First, the client side, where we let the user subscribe to push notifications and display the notifications sent to them. Second, the server side, where we store the subscription IDs securely, and post the messages to the messaging service. This second part was done with the help of Timi Ajiboye, who created a simple Rails application for this purpose. If you're interested in seeing how he did that, you can read his article here. In this article I will cover the first part, the client side.

Setting up Firebase Cloud Messaging

The first thing we need to do is setup a new project with Firebase and get our Firebase Cloud Messaging Sender ID and Server Key. These can be found under Settings > Cloud Messaging in the Firebase project.

Sender ID and Server Key in Firebase Cloud Messaging

The Sender ID goes in our application's manifest.json file, and is used to identify our client.

{
  "gcm_sender_id": "FIREBASE_CLOUD_MESSAGING_SENDER_ID_HERE",
  // more manifest.json stuff...
}

We use the Server Key when posting to Firebase Cloud Messaging via our server (see the tutorial for the rails application).

Subscribing to Push Notifications

To use the Push API, we need to have a registered and active Service Worker, as the Push Manager service is only available within the Service Worker registration.

navigator.serviceWorker.register('./service-worker.js')  
    .then(function(reg) { 
        console.log(reg)
    });

Push Manager in Service Worker Registration

To subscribe a user to push notifications, all we need to do is call the subscribe() method of the push manager. This will generate a popup in the browser, asking the user if they will allow push notifications from the domain. We pass the subscribe() method an object with userVisibleOnly: true to indicate that the push subscription will only be used for messages whose effect is made visible to the user.

function subscribe(serviceWorkerReg) {  
    serviceWorkerReg.pushManager.subscribe({userVisibleOnly: true})
}

navigator.serviceWorker.register('./service-worker.js')  
    .then(function(reg) { subscribe(reg) });

Push Notification Popup in Browser

Although I've set it to automatically ask the user to subscribe once the Service Worker is registered in this example, this isn't the most user friendly method. Only prompt the user to subscribe to push notifications when it makes sense to do so.

Once the user has subscribed, we need to pass their subscription ID to our server so that it can be saved securely in the database. We do this using the API endpoint provided by the rails application.

function addSubscriptionIDToDB(subscription) {  
    const uid = subscription.endpoint.split('gcm/send/')[1];
    const options = {
        method: 'POST',
        headers: new Headers({'Content-Type': 'application/json'}),
        body: JSON.stringify({ uid: uid })
    };
    fetch(apiUrl, options);
}

function subscribe(serviceWorkerReg) {  
    serviceWorkerReg.pushManager.subscribe({userVisibleOnly: true})
        .then((subscription) => addSubscriptionIDToDB(subscription))
}

Now the database has all the information it needs to send the notifications.

Unsubscribing from Push Notifications

We also need to give the user the ability to unsubscribe from receiving push notifications. Like when we subscribed, we need to do two things - unsubscribe from the Push Manager and delete the subscription ID from the database.

To unsubscribe from the push manager, we need to get the user's current subscription. We can do this using the getSubscription() method. Once we have the user's current subscription, we can unsubscribe them with the unsubscribe() method.

function unsubscribe(serviceWorkerReg) {  
    serviceWorkerReg.pushManager.getSubscription()
        .then((subscription) => { subscription.unsubscribe() });
}

Next, we need to remove the user's subscription ID from our database. Again, we use the API endpoint provided by the rails application.

function deleteSubscriptionIDFromDB(subscription) {  
    const uid = subscription.endpoint.split('gcm/send/')[1];
    const options = {
        method: 'DELETE',
        headers: new Headers({'Content-Type': 'application/json'})
    };
    fetch(apiUrl+uid, options);
}

function unsubscribe(serviceWorkerReg) {  
    serviceWorkerReg.pushManager.getSubscription()
        .then((subscription) => {
            subscription.unsubscribe().then(() => deleteSubscriptionIDFromDB(subscription))
        })
}

Sending Notifications on a Weekly Schedule

Finally, we want to trigger the sending of notifications. The rails application has an endpoint which we can POST to, to send a notification.

const options = {  
    method: 'POST',
    headers: new Headers({'Content-Type': 'application/json'})
};
fetch(apiUrl, options);  

I could run this script manually everytime I wanted to send a notification to all users, but that won't be very efficient. So, I decided to setup an IFTTT recipe to make this POST request every week at 10am BST (to coincide with the newsletter going out).

IFTT Recipe - If every day of the week at 12:45 PM on Tue, then make a web request

Support for Push API

Can I Use push-api? Data on support for the push-api feature across the major browsers from caniuse.com.