Table of Contents

  1. Learning Outcomes
  2. Learning Activities
  3. Standards
  4. History
  5. Our Opinion
  6. Reference List
  7. Resource List

Learning Outcomes

  • Learn how to make a basic web notification
  • Handling user input in web notifications
  • Cloud notification providers

Learning Activities

Basic Web Notifications

Modern browsers have begun including the Notifications API to allow developers an easy way to create basic web push notifications. In this section, we will walk through the steps on how to create and handle a notification.

Step 1: Set-up
To set-up for our tutorial, we are going to start with 2 seperate files:
  • index.html
  • notifications.js
Inside of index.html, we are going to put this starter code that just lays out some buttons:
<!DOCTYPE html>
<html>
    <head>
        <script src="notifications.js" defer></script>
    </head>
    <body>
        <div id="headerText"> Let's Make a Notification! </div>
        <input id="permissionButton" type="button" value="Click for permission">
        <input id="notificationButton" type="button" value="Click for a push notification">
    </body>
</html>
Opening this file in our browser we get a webpage that looks like this:


Inside of notifications.js, we want to grab a reference to these elements so that we can use them later
let header = document.getElementById("headerText");
let permButton = document.getElementById("permissionButton");
let notifButton = document.getElementById("notificationButton");

Step 2: Asking for permission
Before sending any notification, you need to ask the user if they will allow your page to send push notifications [1]. This is done using the requestPermission() function in a promise to wait for the response. We will add this promise in a listener for our permissionButton, and the result will be stored into Notifications.permission.
permButton.addEventListener('click', () => {
    let promise = Notification.requestPermission().then((result) => {
        console.log(result);
    });
});
Now, once we click on the button, a pop-up will appear asking for permission to send notifications. Depending on the user's response, the possible values stored can be 'granted', 'denied', or 'default'.

Here is an example of what that would look like.

Step 3: Creating our notification
Once we have asked the user for permission to send them notifications, we need to know how to be able to send them some. Our first step will be to create a Notification object that has two parameters, the Name and the Properties. We will start off leaving the properites field empty, but we will come back and address it later. Here is the code to create a notification:
const notif = new Notification('Our new notification', {});
This creates a new notification, but we currently have no way of triggering this notification to appear. Let's add a listener to this code to connect it to our button.
notifButton.addEventListener('click', () => {
    const notif = new Notification('Our new notification', {});
});
Unfortunately, notifications are not able to show from just a local file and requires a hosted website that uses HTTPS to ensure the notification is sent securely. Because of this, our index.html file will not be able to display a notification unless you are able to host it somewhere like github pages. However to see what it would look like, press the button below.


Depending on whether or not you gave us permission to send notifications, you may or may not have actually seen a notification there. But how did we do that? Before pushing the notification, you should include a check on the Notification.permission that we got from asking permission in Step 2. Here is how we did it:
notifButton.addEventListener('click', () => {
    if (Notification.permission === "granted") {
        const notif = new Notification('Our new notification', {});
    }
});

Step 4: Adding to our notification
Now that we know how to make a notification, lets learn how to add some new parts into our notification. We will be focusing on the 'body' and the 'icon' elements however there are many more options you can read up on
here [2]. The body element lets us set a more detailed message to be displayed within our push notification, and the icon element allows us to attatch an image that should be based on the notification or we can use for our logo. Here is how we implement it:
notifButton.addEventListener('click', () => {
    if (Notification.permission === "granted") {
        const notif = new Notification('Our new notification',
            {
                icon: "https://cdn-icons-png.flaticon.com/512/2431/2431996.png",
                body: "A more detailed message about our notification"
            });
    }
});
To see how our notification has changed, press the button below.

Step 5: Handling clicks
We are almost through the basic notifications, the last thing that we need to be able to do is to have our application respond to the user clicking on the notification. We can make this happen by adding an event listener to our pre-existing notification object, and having it listen for a click. Here is how to do that in code:
notifButton.addEventListener('click', () => {
    if (Notification.permission === "granted") {
        const notif = new Notification('Our new notification',
            {
                icon: "https://cdn-icons-png.flaticon.com/512/2431/2431996.png",
                body: "A more detailed message about our notification"
            });
        notif.addEventListener("click", () => {
            document.getElementById("headerText").innerText = "You clicked on the notification!";
        });
    }
});
By adding this event listener to our button, whenever the notification is clicked on we can make changes accordingly. In our example, this just changes the text on our screen to display that we clicked on the notification. Below is an interactive example that tracks the amount of times you have clicked directly on this notification.

You clicked on the notification 0 times!

Persistent Notifications

Step 1: Creating a Service Worker
To be able to create notificiations with actions (buttons), you must first create a service worker for your website. You first need to create a service worker file within your directory. We named ours "sw.js" and populated it with three events [3]. Oninstall triggers when the browser first sees that a service worker is registered and where you will create a cache. Onactivate triggers when the service worker is activated in the browser and Onfetch allows for you to send non-network responses.
self.oninstall = function() {
    //creates a new cache with the name parameter
    caches.open('webNotif').then(function(cache) {
        //add array of strings of relative paths to files w/in application
        cache.addAll(['/','index.html'])
        .then(function() {
            console.log('cached!');
        }).catch((err) => {
            console.log("Error: " + err);
        })
    })
}

self.onactivate = function() {
    console.log("SW activated");
}

self.onfetch = function(event) {
    event.respondWith(
        caches.match(event.request)
        .then(function(response){
            if(response){
                return response;
            } else {
                return fetch(event.request);
            }
        })
    )
}

                    
Note: Service workers only work on HTTPS websites or localhost.

You now need to register your service worker onto your web page with this script:

<script>
    if(navigator.serviceWorker) {
        navigator.serviceWorker.register('sw.js');
    }
</script>
                    

Step 2: Creating a Persistent Notification
The creation of a persistent notification is very similar to the creation of a normal notification. You can first add an event listener onto a new button and use the service worker API to create the notification. When creating a persistent notification, you can add these attributes [4] to the notification. Inside of notification.js:
let persistentButton = document.getElementByID("persistentButton");

persistentButton.addEventListener('click', () => {
    if(Notification.permission === 'granted') {
        navigator.serviceWorker.ready.then((registration) => {
            registration.showNotification("Persistent notification up",
                {body:"The buttons will change the color of the div",
                actions:[{action:"yellow", title:"yellow", icon:"https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Solid_blue.svg/512px-Solid_blue.svg.png?20150316143734"},
                          {action:'red', title:"red", icon:"https://emojis.wiki/emoji-pics/mozilla/red-circle-mozilla.png"}]});
        });
    }
});
                    
To find the max number of actions possible for your browser, you can use Notification.maxActions.

Step 3: Use the MessageChannel API to communicate
Due to service workers not having access to the document, you need to find a way to communicate between the service worker and any script you are running. This can be done using the MessageChannel API to communicate between scripts using messages. You first need to set up your message channel in your notifications.js and send the port to the Service Worker [5]:
const messageChannel = new MessageChannel();
navigator.serviceWorker.controller.postMessage({
    type: 'INIT_PORT'
}, [messageChannel.port2]);
                    
We then need to save the port into the Service Worker by adding the following in sw.js:
let port;

//create an event listener to listen for messages from notification.js
self.addEventListener("message", event => {
    if(event.data && event.data.type === 'INIT_PORT') {
        port = event.ports[0];
    }
})
                    
With the saved port, we can now send messages from sw.js to notification.js with the "onnotificationclick" event that is only available to service workers.
self.addEventListener("notificationclick",(event) => {

    //event.action gets the name of the action that you created with showNotification();
    if(event.action === 'yellow') {
        getVersionPort.postMessage({type:"yellowType"});

    } else if (event.action === 'red') {
        getVersionPort.postMessage({type:"redType"});
    }
})
                    
In your notification.js file, you can now create an event listener for receiving messages from sw.js and handle the event accordingly.
messageChannel.port1.onmessage = (event) => {
    //the inputDiv is an element in the DOM
    if(event.data.type === "yellowType") {
        document.getElementById("inputDiv").style.backgroundColor = "yellow";
    } else if (event.data.type === "redType") {
        document.getElementById("inputDiv").style.backgroundColor = "red";
    }
}                        
                    
The result of all the previous code can be seen below:

This color will change based on your input

Cloud Notifications

For remote notifications through the cloud, we can use Google Firebase Cloud Messaging[10].

Step 1: Create a Firebase Project
This step is based on steps in the firebase docs [14] with modifications and additions

First, sign up or log in and click "Go to Console"

Next, create a new project.

Enter a name for the project. For this tutorial, we're going to call it "notifications-tutorial".

When prompted about Google Analytics, select no.

Click "Create Project", and wait for Firebase to create your project for you. This may take several minutes.

Step 2: Register a Webapp
This step is based on steps in the firebase docs [14] with modifications and additions

After creating the Firebase project, you should be on the project's dashboard. Click the Webapp icon to register a webapp with your firebase project.

Enter a nickname for the webapp. For this tutorial we'll use "notification-tutorial-webapp". You can also at this point choose to setup firebase hosting for your website, but for this tutorial we're going to assume you are hosting your website elsewhere.
Click "Register app".

Select "Use a <script> tag" instead of "Use npm". This allows us to make a simple standalone webapp that uses the Firebase API rather than depending on Node JS packages. You will be shown an html script element that imports the Firebase API and initializes the app with your authentication keys. Your firebaseConfig will be unique to your webapp, so make sure you hold on to it and use it when appropriate. Copy this text to your clipboard.

Paste the script element into your html file from earlier. Note that we have added a "defer" attribute in order to ensure the script loads at the correct time. We're also going to add a new label at the bottom to display some text later.

                    
<!DOCTYPE html>
<html>
<head>
    <script src="_sample_notifications.js" defer></script>
</head>
<body>
    <script type="module" defer>
        // Import the functions you need from the SDKs you need
        import { initializeApp } from "https://www.gstatic.com/firebasejs/9.14.0/firebase-app.js";
        // TODO: Add SDKs for Firebase products that you want to use
        // https://firebase.google.com/docs/web/setup#available-libraries

        // Your web app's Firebase configuration
        const firebaseConfig = {
            //YOUR FIREBASE CONFIG HERE
        };

        // Initialize Firebase
        const app = initializeApp(firebaseConfig);
    </script>
    <div id="headerText"> Let's Make a Notification! </div>
    <input id="permissionButton" type="button" value="Click for permission">
    <input id="notificationButton" type="button" value="Click for a push notification"> <br>
    <label id="tokenLabel"></label>
</body>
</html>
                    
                

Next, we import the Firebase messaging API and get a messaging object that allows us to register our client with Firebase to be able to receive messages.

                    
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.14.0/firebase-app.js";
import { getMessaging, getToken, onMessage} from "https://www.gstatic.com/firebasejs/9.14.0/firebase-messaging.js";
...
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
                    
                
Step 3: Configure Notification Certificate
This step is based on steps in the firebase docs [11] with modifications and additions

Going back to our firebase dashboard, now we need to set up a certificate for Web Push Notifications. Click the cog wheel, click "Project Settings", go to the "Cloud messaging Tab", scroll down and click "Generate key pair".



A key will then be displayed to you. This key is unique to you so you must copy it and paste it anywhere [WEB PUSH HERE] is in the example code.

Step 4: Set Up Notification Reception
This step is based on steps in the firebase docs [13] with modifications and additions

Back in our html, we need to add code to register the client with firebase using the key we just generated. We also need to print the token that we get back so that we can use it to reference our client when we actually send a notification. This code itself also generates a request for notification permissions, so we want to make sure it triggers from the button press instead of automatically.

                    
function register_client() {
    //generate token
    getToken(messaging, {vapidKey: '[WEB PUSH HERE]'}).then((currentToken) => {
                if (currentToken) {
                    console.log(currentToken)
                    document.getElementById("tokenLabel").textContent = String(currentToken)
                }
    }).catch((err) => {
        console.log('An error occurred while retrieving token. ', err);
    });
}
document.getElementById ("permissionButton").addEventListener("click", register_client, false);
                    
                

We also need to set up an event handler that actually presents the notification to the user upon receipt from firebase.

                    
//handler for received message
onMessage(messaging, (payload) => {
    console.log('Message received. ', payload);

    //extract notification payload title and message body
    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
    };
    const notif = new Notification(notificationTitle, notificationOptions);
});
                    
                

Next we need to create a service worker in a file called firebase-messaging-sw.js in the root directory of our website for firebase to be able to register with our client. The simplest way to do this involves using the older v8 Firebase API which works better with being in a separate file. We must be careful to use the same firebaseConfig that we used earlier in our HTML. This service worker needs to have a dummy event handler for the message receipt, but it does not actually need to do anything. The handler in our main html will handle the notification.

                    
// Scripts for firebase and firebase messaging
//This needs to be version **8**.2.0
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");

// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
            //YOUR FIREBASE CONFIG HERE
};

firebase.initializeApp(firebaseConfig);

// Initialize firebase messaging
const messaging = firebase.messaging();

messaging.onBackgroundMessage(function(payload) {
    //do nothing
});
                    
                
Additionally, the full html code you should have at this point is
                    
<!DOCTYPE html>
<html>
<head>
    <script src="_sample_notifications.js" defer></script>
</head>
<body>
    <script type="module" defer>
        // Import the functions you need from the SDKs you need
        import { initializeApp } from "https://www.gstatic.com/firebasejs/9.14.0/firebase-app.js";
        import { getMessaging, getToken, onMessage} from "https://www.gstatic.com/firebasejs/9.14.0/firebase-messaging.js";
        // TODO: Add SDKs for Firebase products that you want to use
        // https://firebase.google.com/docs/web/setup#available-libraries

        // Your web app's Firebase configuration
        const firebaseConfig = {
            //YOUR FIREBASE CONFIG HERE
        };

        // Initialize Firebase
        const app = initializeApp(firebaseConfig);
        const messaging = getMessaging(app);

        function register_client() {
            //generate token
            getToken(messaging, {vapidKey: '[WEB PUSH HERE]'}).then((currentToken) => {
                if (currentToken) {
                    console.log(currentToken)
                    document.getElementById("tokenLabel").textContent = String(currentToken)
                }
            }).catch((err) => {
                console.log('An error occurred while retrieving token. ', err);
            });
        }
        document.getElementById ("permissionButton").addEventListener("click", register_client, false);

        //handler for received message
        onMessage(messaging, (payload) => {
            console.log('Message received. ', payload);

            //extract notification payload title and message body
            const notificationTitle = payload.notification.title;
            const notificationOptions = {
                body: payload.notification.body,
            };
            const notif = new Notification(notificationTitle, notificationOptions);
        });
    </script>
    <div id="headerText"> Let's Make a Notification! </div>
    <input id="permissionButton" type="button" value="Click for permission">
    <input id="notificationButton" type="button" value="Click for a push notification"> <br>
    <label id="tokenLabel"></label>
</body>
</html>
                    
                
Step 5: Deploy your website

For Firebase cloud notifications to work, the website must be served to the browser using HTTPS rather than HTTP. This means you will either need to deploy the code to a secured web host or have a local self signed server, which is outside of the scope of this tutorial.

Step 6: Send a Notification
This step is based on steps in the firebase docs [12] with modifications and additions

Once your website is deployed, visit it with a supported browser (Chrome works best) and click the "Click for permission" button. If all went well you will see a label appear beneath the button with the Firebase token identifying your device. Copy this token and hold on to it.

Return to your firebase project dashboard. Enter the messaging pane and click "Create your first campaign".

Select "Firebase Notification messages" in the pop-up box.

In the notification page, enter in a title and message body for your notification. Then, click "Send test message".

In the pop up, paste the client token you saved a few steps earlier and click the "+".

Click "Test". If all goes well, you should receive a notification on your client with your specified notification title and body text!


Demo

If you want to see a working version of this, one is deployed at https://notifications-tutorial-18861.web.app/

Standards

The main standard for push notifications is requesting permission from the user to allow your website to send notifications. MDN outlines that "Before an app can send a notification, the user must grant the application the right to do so" [1]. Furthermore, they discuss how push notifications had been abused in the past and that one of the new countermeasures against that is to only request permission after the user gestures in some way that they may want these notifications, for example, clicking a button that says 'Keep Me Notified'. Once permission has been asked for, every notification should check to ensure that the current permission status is 'granted'.

History

Web push notifications are a relatively new innovation to the web development scene, making their history not span a very long way back in time. The first predecessor to notifications through the web is email communication in which websites and companies can send mass emails to all of their users giving an update on whatever it is they desire. In their article What are Push Notificiations - The Ultimate Guide, Gravitec states that "In 2001, RIM created the first email alert system" [6]. This was the first appearance of any type of push notification system, and started out as a functionality for the email app that was only on blackberry phones. Todd Grennan highlights that web push stems off of the idea of mobile push, which originated back in 2009 with the rise of smart phones, however historically web notifications have had a much harder time of being widely used and accepted. This reluctance of integration mainly comes from users not trusting web notifications as much as mobile push notifications, causing a large percent of people to decline websites the permissions necessary [7]. The first addition of web push notifications to browsers came in 2014 as Google added this functionality into Google Chrome. Slowly, thoroughout the next 4 years or so browsers such as Opera, Firefox, Safari, and Edge begin to support this functionality [8].

Our Opinion

As technology becomes more and more integrated into everyones daily life, more and more users will begin getting comfortable with the conceptof web push and help it to grow into a mainstream feature on many websites. We think that this is overall a positive for both web developers and web users. On the side of the web devs, push notifications help to retain visitors and keep people coming back to your site, as well as providing a distinct advantage to marketing by allowing you direct access to the customers [9]. These benefits can lead to more revenue on your site and help to push your site more into the mainstream attention. As for the web users, more websites utilizing push notifications can help to build you a more customized experience on the website, "based on both [your] own preferences as well as historical click behaviour" [9]. With this, users will see more of the content that they would like to see and have it all delivered directly to them through notifications.

Reference List

Resource List