Incoming Call Notification from Service Worker using Firebase Push Notifications

Incoming Call Notification from Service Worker using Firebase Push Notifications

In modern web applications, Without push notifications, you can’t engage with your customers efficiently. For example, to share new updates/posts, new messages & even incoming call notification. Push notifications are notifications that will be shown by your native OS when the web app is not in an active state.

In this tutorial, I’ll guide you on how to show custom UI components for FCM background push notifications in react web application (You can use the same strategy for other frontend frameworks like Angular, Vue, and Svelte as well). We are going to create incoming call push notifications UI as a custom component for the FCM background notification.

Table of Contents

  1. Firebase FCM Background Messages
  2. Firebase Service Worker for FCM Background Messages
  3. Design Incoming Call UI Component
  4. Send Data from Service Worker to Browser Window
  5. Test Incoming Call Notification using Firebase

Firebase FCM Background Messages

Firebase provides a quick to set up and scalable cloud messaging system across different platforms. In this tutorial, we are going to use Firebase cloud messaging to show push notifications with custom UI.

If you are new to firebase push notification integration, go through this Step by Step Guide on How to Implement Firebase in your React Web Application before moving on to the next sections.

In short, FCM has two types of notifications,

  1. Foreground Messages: Foreground Message triggers when the app is in the currently active tab of your browser. It’s handled inside the onMessage method of firebase inside our web application.
  2. Background Messages: Background Messages trigger when the app is not in an active state and the tab/browser is in closed/minimized mode. It’s handled by onBackgroundMessage method of firebase inside service worker out of our web application.

In most applications showing native os push notification UI is fine. But let’s say we have to integrate incoming call push notifications in our application. In that case, we have to show custom components with call answer & reject buttons in it.

Since background messages are handled by service workers, we can’t show custom components/UI inside it using javascript. Let’s learn how to implement the incoming call UI notification step by step.

Firebase Service Worker for FCM Background Messages

The service workers are used to handle background push notifications in web applications. To integrate one into your application, create firebase-messaging-sw.js in your public/root folder and paste the following code.

importScripts("https://www.gstatic.com/firebasejs/9.8.3/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/9.8.3/firebase-messaging-compat.js");


const firebaseConfig = {
    apiKey: "YOUR_API_KEY",
    authDomain: "YOUR_AUTH_DOMAIN",
    databaseURL: "YOUR_DATABASE_URL",
    projectId: "YOUR_PROJECT_ID",
    storageBucket: "YOUR_STORAGE_BUCKET",
    messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
    appId: "YOUR_APP_ID",
    measurementId: "YOUR_MEASUREMENT_ID"
};

firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
    console.log({ payload });
    const title = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
    }
    return self.registration.showNotification(title, notificationOptions);
});

The onBackgroundMessage method will be triggered when the user receives any push notification. The self.registrations.showNotification method will show the native OS UI for background push notifications. If you want to understand what’s happening line by line, go through this detailed explanation of Firebase Service Worker in React.

Since service workers are running in a different thread, we can’t write code in the service worker to manipulate DOM or add new elements to the UI. To solve this problem, we need a way to pass data from the service worker to the window. Before that let’s create a custom incoming call UI component in React.

Design Incoming Call UI Component

Install react-toastify using the following command

npm install --save react-toastify

After installation, add the following imports to your main/app component

import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

To show a custom UI for react toast, Create a file in your react application IncomingCall.js and add the following code to it

import React from "react";
import './IncomingCallStyles.css';
import callAcceptIcon from  "./images/call-accept-icon.svg";
import callRejectIcon from "./images/call-decline-icon.svg";


const IncomingCall = ({
    picture,
    name,
    onAnswer,
    onReject,
}) => {
  return (
    <div className="call-notification-bar">
      <div className="caller-img-container">
        {<img src={picture} alt="profile picture" className="caller-img" alt="incoming call user"/>}
      </div>
      <div className="caller-name">
        <div className="name">{name}</div>
        <div className="call-tag text-small">Incoming Video Call</div>
      </div>
      <div className="call-actions">
        {/* <p>Accept Call Button</p>
    <p>Reject Call Button</p> */}
        <div
          className="call-accept__wrapper"
          onClick={() => onAnswer()}
          title="Accept"
        >
          <img
            src={callAcceptIcon}
            alt="call-accept-icon"
            className="call-accept__image"
          />
        </div>
        <div
          className="call-accept__wrapper"
          onClick={() => onReject()}
          title="Reject"
        >
          <img
            src={callRejectIcon}
            alt="call-decline-icon"
            className="call-decline__icon"
          />
        </div>
      </div>
    </div>
  );
};

export default IncomingCall;

In the above component, we are designing the incoming call component UI with props picture, name, onAnswer and onReject. Import the following CSS styles by creating IncomingCallStyles.css file.

  .call-notification-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: #fff;
    border-radius: 10px;
  }
  
  .caller-name {
    margin: 15px;
    font-size: 14px;
    color: #3e4543;
    width: 125px
  }
  
  .call-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  
  .call-accept__wrapper {
    cursor: pointer;
    border-radius: 50%;
    box-shadow: 1px 1px 2px #dedede;
    transition: .2s;
    width: 40px;
    height: 40px;
  }
  
  .call-accept__wrapper:hover {
    transform: translateY(-2px);
    box-shadow: 1px 1px 2px rgb(0 0 0 / 15%) !important;
    /* background: #e5e5e5; */
    border-radius: 50%;
    
  }
  
  .call-decline__wrapper {
    background: red;
    margin: 10px;
    padding: 10px;
    border-radius: 50%;
    cursor: pointer;
    width: 40px;
    height: 40px;
    box-shadow: 1px 1px 2px #dedede;
    transition: .2s;
  }
  
  .call-decline__wrapper:hover {
    transform: translateY(-2px);
    box-shadow: 1px 1px 2px rgb(0 0 0 / 15%) !important;
  }
  
  .call-accept__image, .call-decline__icon {
    height: 40px;
    width: 40px;
  }
  
  .call-decline__icon {
    margin-left: 8px;
  }
  
  .caller-name .name {
    font-size: 14px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    font-weight: bold;
  }
  
  .call-tag {
    font-size: 10px;
  }
  
  .caller-toast {
    background-color: #fff;
  }
  
  .caller-img-container {
    width: 40px;
    height: 40px;
    flex: 0 0 40px;
  }
  
  .caller-img {
    width: 40px;
    height: 40px;
    border-radius: 50%;
  }
  
  .msg-name {
    width: 200px;
  }
  
  .msg-text {
    font-size: 12px;
  }

To show call accept and call reject icons in the incoming call notification component, Create an images folder inside the src folder. Download and paste the following two icons into it.

call accept icon
Call Answer
call reject icon
Call Reject

The next step is to pass data from the service worker to the window object. Then show our incoming call component as toast.

Send data from Service Worker to Window and Show Incoming Call UI

As we know that the service worker and window are in two separate threads, we need a way to send data from the service worker to the window for rendering a custom UI on each notification. We can use the client.postMessage(message) method to pass data from a service worker to a client window, worker, or even shared worker. It accepts message parameters where we can send any data to the listeners.

There are two parts involved in rendering Custom UI for background push notifications:

  1. Pass the data from the service worker to the window which can be done by using Client.postMessage(message) method. Using this we can pass the entire notification payload to the window object for showing our incoming call UI component.
  2. Listen for the data passed from postMessage method and render the UI using the “message” event listener of the service worker object.

Send Data from Service Worker to Window

In your firebase-messaging-sw.js file, add the following code inside the onBackgroundMessage method’s callback function.

messaging.onBackgroundMessage((payload) => {
  const notificationTitle = payload.data.title;

 // pass data to all the clients (window objects) that are available
  self.clients.matchAll({includeUncontrolled: true}).then((clients) => {
    console.log(clients); 
    //you can see your main window client in this list.
    clients.forEach((client) => {
        // pass notification payload data to client.
        client.postMessage(payload);
    })
  })


  return self.registration.showNotification(notificationTitle, payload);
})

The self.clients.matchAll() method returns a promise with a list of client objects available using the client object we can pass the notification payload data by invoking client.postMessage(payload) method.

The first part of passing the data is done. let’s move on to the next part which is listening for the data.

Listen for data from service workers inside component

Inside your component, add the following code in your useEffect method to listen for the message event in serviceWorker object. The message event gets triggered whenever the postMessage sends data.

useEffect(() => {
 if (navigator.serviceWorker) {
    navigator.serviceWorker.addEventListener('message', (event) => {
       const payload = event.data;
       toast(<IncomingCall picture={payload.data.picture} name={payload.data.name}/>, {
        autoClose: false,
      })
    });
  }
}, [])

In the above code, we are checking if navigator.serviceWorker is available and supported by the browser and then we are listening to the “message” event. The event listener function gets the event object, and the passed data from the postMessage method will be present inside the data key of the event object, i.e, event.data.

Inside the listener function, we are showing our Incoming Call Component as toast by passing the props picture and name from the notification data.

Test Our Integration using FCM Data Messages

To test the above integration, you should pass data messages from FCM Cloud messaging console. Navigate to “Cloud Messaging” -> “Send Your First Message” fill up the title and body and then on additional options section add the following keys with your own values.

Navigate to the notifications section and click on the “Send Test Message” button. Add FCM tokens in the popup and click on the Test button to send a notification. if you don’t know what is FCM token and how to generate one, please check out this article on How to generate an FCM token and vapid key.

Once done you can see the notification in both your native OS UI and also the incoming call component in the background like the below picture

Incoming Call Notification Toast React
Incoming Call Notification Toast React

Conclusion

In this post, we have covered how to show custom components in FCM background notifications using client.postMessage(message) and message event listener of the service worker. I hope this solves your problem of showing custom UI/incoming call notifications with FCM background push notifications. If you need any help with the integration, feel free to comment here.