Javascript Notifications and Push API
By: Liron Navon
Tags: JavaScript, Web Api Development, Web Development, Notifications

As someone who started his career as a mobile developer, one of the first and most important things I had to do, was handling push notifications — on the web, however, push notifications are much less common and the documentation can be confusing, so in this article I’m going to explain how to send a push notification on the web using the push API.

An image of notifications on a mobile phone

What are push notifications anyway? and what can we do with them?

Let's start by separating the name into the two words that compose it:

Notification — In this context, refers to native system messages that pop up on the screen outside the browser.

Push — The notifications can be created by pushing data to the browser, similar to what we can achieve using WebSockets, but those notifications can be pushed by the browser’s background processes, without the website or even the browser being open at the time. Well, we can do more things with push notifications, there is something that is being referred to as silent notifications/data messages, it mostly works in mobile — WhatsApp, for instance, uses them to collect your messages, those messages are saved locally to the device, that way all the messages are accessible with no web connection, and they don’t even have to be kept by WhatsApp servers, so it’s a huge plus for privacy, it’s less useful on the web, but we will see how to send data to the local storage without showing a notification.

So, what can we do with push notifications? why should we care?

Regular browser interactions depend on the user interacting with our service, but push notifications are our service interacting with the user, without him needing to be in our web app at the time — a good example is Facebook, I get notifications by facebook whenever I get comments on my posts and similar events, but for each web app there are different uses for notifications, if any.

Our first notification

Creating a notification is pretty simple, we just need permission, and to create a notification using window.Notification() constructor, but that’s not very useful since the webpage must be open, and in that case, we can just create a regular notification with HTML5.

Push notifications!

First of all here is the git repository, you can simply clone it and run:

npm install

npm start

We are going to use inexedDB to store information from push notifications on silent mode, you can learn more about indexedDB here though there are a lot of comments and it should be simple to understand, you are also expected to know express, though we will use very simple features from it.

We will also work with service workers, so we need to have HTTPS on our localhost (otherwise the browser will not allow us to access them), the simplest and quickest way to achieve this is to use ngrok it’s a free open source solution to create a proxy from their servers to our local host, we can use it like this:

npm install -g ngrok

ngrok http <PORT_NUMBER>

Session Status online

Session Expires 7 hours, 59 minutes

Version 2.2.8

Region United States (us)

Web Interface

Forwarding

Forwarding # we will get the following result for port 3000 #Session Status onlineSession Expires 7 hours, 59 minutesVersion 2.2.8Region United States (us)Web Interface http://127.0.0.1:4040 Forwarding http://ef169615.ngrok.io -> localhost:3000Forwarding https://ef169615.ngrok.io -> localhost:3000

What we want is the second "Forwarding", the one with the https, this will redirect from this URL "http://ef169615.ngrok.io" to localhost on port 3000.

I will only show here snippets from the code, so you should follow with the Github repository. Let’s start by looking at the project structure:

-package.json: we will only use express, body-parser, and web-push.

-server.js: this is our server, it will serve the files from the public directory and manage the notifications API.

-Public/

— index.html

— indexed-db.js: a script to update the UI with data from the indexedDB.

— register-push.js: a script to register our service worker

— service-worker.js: the service worker will be the one listening to the push notifications.

— lazy-engineering-logo.png: just a default icon for the notifications 😅.

server.js:

The first function we will look at is the one where we generate the keys, those keys will be used like so:

publicKey: will be sent to the client to create a push context by the browser.

privateKey: will be used when we send the notification the browser push service, the service is different in chrome, or firefox, or opera, etc…

We should store the keys to keep identifying the client, so if none exist, we generate new keys using webPush and store them in a config file, just make sure you don’t lose those or the push servers will lose the clients.

function getVAPIDKeys() {

// check if we saved the config already

if(fs.existsSync(localConfigFile)) {

return JSON.parse(fs.readFileSync(localConfigFile, ‘utf8’));

} // we will create new config by generating new keys

const generatedKeys = webPush.generateVAPIDKeys();

fs.writeFileSync(localConfigFile, JSON.stringify(generatedKeys));

console.log(`new VAPID keys were generated and saved to ${localConfigFile}`);

return generatedKeys;

}

We use initiate webPush with the keys, serve the public key through ‘/api/vapid-public-key’ so the client will have access to it.

And for the ‘/api/send-notification’ route we simply need to call:

webPush.sendNotification(subscription, JSON.stringify(payload), options)

We can send whatever we want in the payload, but it should be a string, so if we want to send to the client different icons, title, body text and options, we should stringify the request and parse it on the client.

And that’s mostly it for the server, the rest is simple express code.

register-push.js

We need to create a service-worker, register it through the browser’s push manager. When we subscribe we have to use “userVisibleOnly” as true, otherwise, chrome will throw an error (but we don’t really have to show a notification 🙆‍♀️).

// we register the service worker script

navigator.serviceWorker.register(“service-worker.js”);

const registration = await navigator.serviceWorker.ready;

let subscription = await registration.pushManager.getSubscription(); // we will create a new subscripion if one doesn’t exist, or it has expired

if (!subscription) {

// we need the public key from the server

const response = await fetch(`/api/vapid-public-key`);

const vapidPublicKey = await response.text();

subscription = await registration.pushManager.subscribe({

userVisibleOnly: true,

applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)

});

}

The next part is sending the notification, it’s a simple fetch request, the request would look something like this for chrome, but it’s different for each browser, the payload can be whatever we want, with restrictions about the size, the delay is an option to send to our server for experimentation, and the ttl will be sent to the push server (in this case fcm.googleapis.com) to tell them how long we want them to hold onto the message in seconds, the default is 4 weeks, and that’s this magic number, in seconds.

After the service-worker will be registered, we need to save the “subscription” parameter in a database, then we can make push requests whenever we need.

service-worker.js

That’s the actually interesting part in our little app, it will parse the payload, and according to the ”silent” parameter we are sending, we will decide if to show a notification or simply store the notification locally, since we have no window in service worker, we cannot store it in local storage, that’s why we are going to store it in indexedDB.

The “event.waitUntil” will tell the browser to give us time until our promise is resolved, so the process will not exit before we are done.

self.addEventListener(“push”, event => {

console.log(“We got a push notification”);

const payload = event.data ? event.data.text() : ‘{}’;

const {

title = “no title”,

body = “no body”,

icon = “/lazy-engineering-logo.png”,

silent = false

} = JSON.parse(payload); if (silent) {

// will wait until the promise is resolved

return event.waitUntil(

openIndexDB(self.indexedDB)

.then((db) => addPush(db, `${title} — ${body}`))

.then(() => console.log(‘inserted to indexedDB’))

)

} // will wait until the promise is resolved

event.waitUntil(

self.registration.showNotification(title, {

body,

icon,

// we can also add vibrations for mobile devices

vibrate: [200, 100, 200, 100, 200, 100, 200]

})

);

});

About vibration, it is an array of numbers, that define milliseconds, it is read like this: for [100, 200, 300], we vibrate 100ms, rest 200ms, and vibrate 300ms again.

There are much more options for the notification constructor, such as actions, and requires interaction, there are a lot of ways to make interaction with notifications!

The last file we are not going to go through here, is indexed-db.js, but it is simple, it just creates an interval, reads from the indexDB, and display it.

Summary

The web notification standards are still evolving, and so does the push standards, we can use the notifications API for a lot of nice interactions on the web, and for better support of offline first web apps!