Sign in
Log inSign up
Javascript Notifications and Push API

Javascript Notifications and Push API

Liron Navon's photo
Liron Navon
·Jan 26, 2019

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.

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.

  // Let's check if the browser supports notifications
  if (!("Notification" in window)) {
    alert("This browser does not support desktop notification");
  }

  function notify() {
    new Notification("Hello world!");
  }

  // Let's check whether notification permissions have already been granted
  else if (Notification.permission === "granted") {
    notify();
  }

  // Otherwise, we need to ask the user for permission
  else if (Notification.permission !== "denied") {
    Notification.requestPermission().then(function(permission) {
      // If the user accepts, let's create a notification
      if (permission === "granted") {
        notify();
      }
    });
  }

  // the user doesn't want a notification 🤷‍♀️

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>

# we will get the following result for port 3000 #
Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.2.8
Region                        United States (us)
Web Interface                 
Forwarding                    
 -> localhost:3000
Forwarding                    
 -> 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.<br> -server.js: this is our server, it will serve the files from the public directory and manage the notifications API.<br> -Public/<br> — index.html<br> — indexed-db.js: a script to update the UI with data from the indexedDB.<br> — register-push.js: a script to register our service worker<br> — 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: <br> publicKey: will be sent to the client to create a push context by the browser.<br> privateKey: will be used when we send the notification the browser push service, the service is different in chrome, or firefox, or opera, etc…<br> 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() {

if(fs.existsSync(localConfigFile)) {
    return JSON.parse(fs.readFileSync(localConfigFile, ‘utf8’));
  }

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.<br> 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.<br> 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 🙆‍♀️).

navigator.serviceWorker.register(“service-worker.js”);
const registration = await navigator.serviceWorker.ready;
let subscription = await registration.pushManager.getSubscription();

if (!subscription) {

  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.

{
  “subscription”:{
    “endpoint”:”
...",
    "expirationTime":null,
    "keys":{
      "p256dh":"...",
      "auth":"..."
    }
  },
  "payload":{
    "title":"My title”,
    ”body”:”My body”,
    ”icon”:”/my-icon.png”,
    ”silent”:true
  },
  ”delay”:”0",
  ”ttl”:”2419200"
}

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.<br> 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) {

return event.waitUntil(
      openIndexDB(self.indexedDB)
      .then((db) => addPush(db, `${title} — ${body}`))
      .then(() => console.log(‘inserted to indexedDB’))
    )
  }

event.waitUntil(
    self.registration.showNotification(title, {
    body,
    icon,

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.<br> 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!

Liron Navon

A fullstack software engineer

An engineering blog for lazy people