Push Notifications using Firebase Cloud Messaging

Kartik Nema
6 min readFeb 25, 2023

--

FCM

Working with push notifications is often complicated, so we’ll try to break it up into a few parts and then approach it.

Notifications

Notifications are supported by html (in particular html 5). To begin with we first need to ask the user if they want to allow notifications or not.

Begin with creating a simple project consisting of index.html and script.js, link script.js in index and run your project using any server you prefer. Here we’ll be using vscode’s live server.

In index.js ask the user for permission to display notification

if(window.Notification) {
if(Notification.permission === 'granted') {
send_notification();
} else if(Notification.permission !== 'denied') {
Notification.requestPermission(permission => {
if(permission === 'granted') {
send_notification();
}
})
}
}

The first line of the above snippet checks if the browser supports notifications or not. If it does we check if the user has already allowed notifications or not. If the user hasn’t provided his/her choice yet, we will prompt them to either allow or block the notifications.

function showNotification() {
let notificationOptions = {
body: 'Some Notification information',
icon: '<>'
}
let notif = new Notification('My New Notification', notificationOptions);

notif.onclick = () => {
console.log('Notification clicked');
}
}

Implementing Push Notifications using FCM

Showing notifications when the application is not in focus or not running at all is the bigger challenge. This is where service worker comes into play. Service worker runs in the background and hears for any messages from the server.

Adding service worker alone doesn’t make the project a PWA. Adding other components like manifest and offline support will make it a PWA.

FCM enables us to easily send push notifications to our apps, begin by creating a firebase project. Go to project settings -> Cloud Messaging Tab -> And generate a web push certificate. Copy the key pair for future use.

Add the service worker, begin by creating a file named ‘firebase-messaging-sw.js’ in the root of the project. Next go to index.js and register the service worker.

if (navigator.serviceWorker) {
// Register the SW
navigator.serviceWorker.register('/firebase-messaging-sw.js').then(function(registration){
}).catch(console.log);
}

Workflow

It’s important to discuss how notifications are actually handled. There is a big diffrence in the way notifications are handled when the app is in focus (i.e. perhaps you are using the website) as supposed to when it’s in the background.

The service worker is responsible for handling notifications when the app is in the background, this is becasue service worker doesn’t run on the website’s main thread it instead runs on a separate thread continously.

On the other hand if the notification is received when the site is in focus then the notification is received by the site itself, i.e. main thread or the static js file which you would have linked in the html file.

FCM works using a secret token which remains the same for a particular client. This token is needed to send the push notification.

Begin by including firebase into your project.

index.html

<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js"></script>

<script>
var firebaseConfig = {
apiKey: "AIzaSyBWiyH8DQXWWwwOgXSe1UMxD7I2YKFPsIU",
authDomain: "pwa-project-a0bd7.firebaseapp.com",
projectId: "pwa-project-a0bd7",
storageBucket: "pwa-project-a0bd7.appspot.com",
messagingSenderId: "379310423780",
appId: "1:379310423780:web:6bf4bf1908f64b508136f1",
measurementId: "G-KEHQSF1SD0"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
</script>

Move to index.js and write the code to get the FCM token, in our case we’ll just copy it and use it to test push notifcations. In any actual app of larger scale a more useful thing to do would be to store this token in some database so that it can be used to send push notifications to all clients.

index.js

messaging.getToken({ vapidKey: '<the token you copied earlier>' })
.then(currentToken => {
if (currentToken) {
console.log(currentToken);
return currentToken;
} else {
console.log('No registration token available. Request permission to generate one.');
}
}).catch(err => {
console.log('An error occurred while retrieving token. ', err);
});

Next implement the message handler in index.js which will be responsilbe for handling notifications when app is in focus, since index.js runs on main thread and is a part of the site’s core shell.

messaging.onMessage(payload => {
console.log('Message received. ', payload);
});

Since the app is already in focus, displaying the notification on top of it won’t lead to a great experience. Also note that index.js has direct access to messaging object as it can access the variables defined in the script tag of index.html

Now we’ll move to ‘firebase-messaging-sw.js’ to handle notifications when app is in background. Note we’ll need to initialize firebase again since service worker has no access to main thread elements.

firebase-messaging-sw.js


// Service Worker
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js');

const firebaseConfig = {
apiKey: "<>",
authDomain: "<>",
projectId: "<>",
storageBucket: "<>",
messagingSenderId: "<>",
appId: "<>",
measurementId: "<>"
};

// // Initialize Firebase
const app = firebase.initializeApp(firebaseConfig);

// Initialize Firebase Cloud Messaging and get a reference to the service
const messaging = firebase.messaging()

Finally handle the notifications for this case

messaging.onBackgroundMessage(payload => {
const notificationTitle = 'Background Message Title';
const notificationOptions = {
body: notificationTitle,
icon: '<>'
};

self.registration.showNotification(notificationTitle,
notificationOptions);
});

Save the changes and go to the application in chrome and open the console (available in chrome dev Tools), you’ll find the FCM token printed here. Copy it. Now open the firebase messaging service in Firebase. Click on ‘Create your first campaign’ -> Firebase Notification messages -> Provide a title and description for your notification and click on ‘Send test message’ -> paste the FCM token -> Test. If there are no errors you’ll see a notification on your screen (since the site is not in focus we’ll get a Notification, clicking on it will bring the app in focus. If the app was in focus at the time of receiving the notification, it would have been just logged in the console.

Full code

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<title>Progressive Web Apps</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>Page 1</h1>
<img src="/thumb.png">
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Donec sed odio dui. Donec sed odio dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js"></script>

<script>
var firebaseConfig = {
apiKey: "<>",
authDomain: "<>",
projectId: "<>",
storageBucket: "<>",
messagingSenderId: "<>",
appId: "<>",
measurementId: "<>"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
</script>
<script src="/index.js"></script>
</body>
</html>

index.js

// Progressive Enhancement (SW supported)
if (navigator.serviceWorker) {
// Register the SW
navigator.serviceWorker.register('/firebase-messaging-sw.js').then(function(registration){
}).catch(console.log);
}


// Notification support

function showNotification() {
let notificationOptions = {
body: 'Some Notification information',
icon: '/thumb.png'
}
let notif = new Notification('My New Notification', notificationOptions);

notif.onclick = () => {
console.log('Notification clicked');
}
}

if(window.Notification) {
if(Notification.permission === 'granted') {
} else if(Notification.permission !== 'denied') {
Notification.requestPermission(permission => {
if(permission === 'granted') {
}
})
}
}

messaging.getToken({ vapidKey: 'BDn5L5Z4sw9cXI-zHZBqexRDSyw2afRVM03ph9er3LNar-_tMiy5Q7xSaWnch6IrNDMwnxYC3fek1YW2qRHPhtA' }).then((currentToken) => {
if (currentToken) {
// Send the token to your server and update the UI if necessary
// ...
console.log(currentToken);
return currentToken;
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});

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

firebase-messaging-sw.js


// Service Worker
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js');

const firebaseConfig = {
apiKey: "<>",
authDomain: "<>",
projectId: "<>",
storageBucket: "<>",
messagingSenderId: "<>",
appId: "<>",
measurementId: "<>"
};

// // Initialize Firebase
const app = firebase.initializeApp(firebaseConfig);

// Initialize Firebase Cloud Messaging and get a reference to the service
const messaging = firebase.messaging()

messaging.onBackgroundMessage(payload => {
// Customize notification here
const notificationTitle = 'Background Message Title';
const notificationOptions = {
body: 'Background Message body.',
icon: '/thumb.png'
};

self.registration.showNotification(notificationTitle,
notificationOptions);
});

--

--