Anthony

Push Notifications Using Node.js & Service Worker

Push Notifications Using Node.js & Service Worker

Before we dive into building a push notification system, it’s advisable that you have some basic knowledge of node.js and a little bit of service works. For service workers, there are quite difficult to grasp if you’re just getting started with them, there are basically the core of progress web apps (PWA), here is a friendly introduction to service workers on the Medium website.

We’re going to build this notification system using a nodejs module called web-push. For those who had used push notification before using services like Pusher or PubNub, this web-push module allows us to thesame thing without using those third party services.

What is a push notification?

A Push notifications is like SMS text messages and mobile alerts, but they only reach users who visited the website. Most browsers has support for push notifications. If a user visit your site on a browser that doesn’t support push notifications it does no harm, the user just wouldn’t see the notification and that’s it.

Let’s get started!

Prerequisite

You need to have node.js installed on your machine.

Setup

    npm init -y

Installing the dependencies

Next we want to install the dependencies, we need web-push, express & body-parser

   npm install web-push express body-parser

package.json

In your package.json file, change the script from this

   "scripts":{
      "test": "echo \"Error: no test specified\" && exit 1"
   }
   //=====TO THIS====//
    "scripts":{
         "start": "node index.js"
     }

Noticed that I used index.js as my entry file, but you can use what ever name you like as your entry JS file.

Requiring the modules

Create a js file in the root directory name it (the same name you used in the package.json file). Now go into the js file you just created and require all the modules we installed.

  const express = require("express");
  const webpush = require("web-push");
  const bodyParser = require("body-parser");
  //Path module
  const path = require("path");

initiallizing express

   const app = express();

We want to create a set of vapid keys, but first you’ll need to specify the location in your terminal. Make sure you’re in the root directory of the project you’re working. run this command

    ./node_modules/.bin/web-push generate-vapid-keys

After that, you should see two keys generated in the terminal (Public key: …….) & (Private key: …….). DONT CLOSE THE TERMINAL YET.

In your js file create two variables call one publicVapidKey and the other privateVapidKey. In your terminal, copy the generated keys from the Public & Private key and paste them in their specific variables. Like this

    const publicVapidKey = "BBZ9QzXuTWpafrqGx4Qxo3Q6Bu4RLIoxnv__-U11AvFumVsfBq6q3KTwqTjHIHUJSr1-vrSYA5xL5MIZfjmCwd4";
    const privateVapidKey = "YBoYT788kv-cV2vrV9I48Kii955WWnIhRYpubZQZGpc";

Setting the body-parser & the static path

At the bottom of where we initialized express() place this code.

    //set static path
    app.use(express.static(path.join(__dirname, "client")));
    //use body-parser
    app.use(bodyParser.json());

setVapidDetails

   webpush.setVapidDetails("mailto:test@gmail.com", publicVapidKey, privateVapidKey);

What the vapidkeys does is basicaly identifying who’s sending the push notification.

Subscribe Route

This is resposnsible for sending the notification to the service workers

      app.post("/subscribe", (req, res) => {
       //get pushSubscription object
       const subscription = req.body;
       res.status(201).json({});
       //creat paylaod
       const paylaod = JSON.stringify({ title: "Notification From Node.js App" });
   //pass object into sendNotification
   webpush.sendNotification(subscription, paylaod).catch(err => console.error(err));
   });

Creating the server

  const port = 3000;
  app.listen(port, () => console.log(`Server started on port ${port}`));

Setup 2

In your root directory create a folder, name it “client” remember when we set the express.static we used client as the folder name. so make sure you name the folder the same name you used in the path module.

In that client folder create a client.js, worker.js and index.html file.

The index.html page is the page you want the push notification to show on. For sake of this tutorial we’re going to put a h1 tag inside the index.html page.

client.js

We actually want that Public key inside the client.js, so copy the publicVapidKey variable you created in the previous js file and paste it in the client.js file. Like this

client.js

   const publicVapidKey = "BBZ9QzXuTWpafrqGx4Qxo3Q6Bu4RLIoxnv__-U11AvFumVsfBq6q3KTwqTjHIHUJSr1-vrSYA5xL5MIZfjmCwd4";

Check for service worker

We are going to check to see if we are able to use service worker in the current browser. Note that we’re now working in the client.js file.

   //check for service worker
   if("serviceWorker" in navigator){
       send().catch(err => console.error(err));
   }

Registering

We are going to register the service worker, the push notification and send the push notification. - Outside the if() statement

 async function send(){
    console.log("Registering service worker.");
    const register = await navigator.serviceWorker.register("/worker.js", {
        scope: "/"
    });
    console.log("Service worker registered");
    
    //Register Push
    console.log("Registering Push..");
    const subscription = await register.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
    });
    console.log("Push Registered..");
    
    //sending push
    await fetch("/subscribe", {
        method: 'POST',
        body: JSON.stringify(subscription),
        headers: {
            "content-type": "application/json"
        }
    });
    console.log("Push sent..");
}

Converting the URL safe base54 string to a Unit8Array

Outside of the async send() function

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

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

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

We are done with our client.js, now we procceed to the worker.js

Handling the push event

    self.addEventListener("push", e => {
        const data = e.data.json();
      self.registration.showNotification(data.title, {
          body: "Getting Started With Node.js: A beginners Guide - Anthonylan.com",
          icon: "https://cdn2.iconfinder.com/data/icons/nodejs-1/512/nodejs-512.png"
    });
    }); 

Now you can view your app by running npm start

Recap

To make it less confusing here are the codes for the 3 JavaScript files we created. take a look to see if you missed something.

The index.js file, whatever you named it

      const express = require("express");
      const webpush = require("web-push");
      const bodyParser = require("body-parser");
      const path = require("path");

      const app = express();

      //set static path
      app.use(express.static(path.join(__dirname, "client")));

      //use body-parser
      app.use(bodyParser.json());

        const publicVapidKey = "BBZ9QzXuTWpafrqGx4Qxo3Q6Bu4RLIoxnv__-U11AvFumVsfBq6q3KTwqTjHIHUJSr1-vrSYA5xL5MIZfjmCwd4";
        const privateVapidKey = "YBoYT788kv-cV2vrV9I48Kii955WWnIhRYpubZQZGpc";

      webpush.setVapidDetails("mailto:test@gmail.com", publicVapidKey, privateVapidKey);

      //subscribe route

      app.post("/subscribe", (req, res) => {
          //get pushSubscription object
          const subscription = req.body;

          res.status(201).json({});

          //creat paylaod
          const paylaod = JSON.stringify({ title: "Notification From Node.js App" });

      //pass object into sendNotification
      webpush.sendNotification(subscription, paylaod).catch(err => console.error(err));

      });

      const port = 3000;

      app.listen(port, () => console.log(`Server started on port ${port}`));

The client.js file

     const publicVapidKey = "BBZ9QzXuTWpafrqGx4Qxo3Q6Bu4RLIoxnv__-U11AvFumVsfBq6q3KTwqTjHIHUJSr1-vrSYA5xL5MIZfjmCwd4";

    //check for service worker

    if("serviceWorker" in navigator){
        send().catch(err => console.error(err));

    }

    async function send(){
        console.log("Registering service worker.");
        const register = await navigator.serviceWorker.register("/worker.js", {
            scope: "/"
        });

        console.log("Service worker registered");

        //Register Push
        console.log("Registering Push..");
        const subscription = await register.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
        });

        console.log("Push Registered..");

        //sending push
        await fetch("/subscribe", {
            method: 'POST',
            body: JSON.stringify(subscription),
            headers: {
                "content-type": "application/json"
            }
        });

        console.log("Push sent..");

    }

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

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

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

The worker.js file

  self.addEventListener("push", e => {
      const data = e.data.json();
    self.registration.showNotification(data.title, {
        body: "Getting Started With Node.js: A beginners Guide - Anthonylan.com",
        icon: "https://cdn2.iconfinder.com/data/icons/nodejs-1/512/nodejs-512.png"
  });
  });

comments powered by Disqus