How to implement Enhanced Push Notification in Angular using JS SDK

Guide Overview

This guide provides a step-by-step approach to implementing Enhanced Push Notifications using the Angular UI Kit. While we’ll focus on Angular, the same method can be applied across any CometChat UI Kit.

Steps to integrate the Enhanced Push Notifications

1. Create a firebase project

Follow the steps outlined in our documentation to setup a firebase project and add the credentials to the CometChat Dashboard.
Here is the link to our documentation - Integration | Push notifications (Enhanced) | Push Notifications | Notifications | CometChat Docs

2. Create a file firebase-messaging-sw.js in src folder

Now add the code snippet given below in the firebase-messaging-sw.js

// /* eslint-disable no-restricted-globals */
// /* eslint-disable no-undef */
// // required to setup background notification handler when browser is not in focus or in background and
// // In order to receive the onMessage event,  app must define the Firebase messaging service worker
// // self.importScripts("localforage.js");

importScripts(
  "https://www.gstatic.com/firebasejs/9.15.0/firebase-app-compat.js"
);
importScripts(
  "https://www.gstatic.com/firebasejs/9.15.0/firebase-messaging-compat.js"
);
var TAG = "[Firebase-sw.js]";

self.addEventListener("notificationclick", async function (event) {
  console.log(TAG, "notificationclick", event, event.clientId);
  if (event?.notification?.data) {
    let data = event.notification.data;
    event.waitUntil(
      self.clients
        .matchAll({ type: "window", includeUncontrolled: true })
        .then((clientList) => {
          if (clientList.length > 0) {
            clientList[0].postMessage({
              message: data,
            });
            return (
              clientList[0]
                .focus()
                .catch((error) => {
                  console.log(error);
                  return self.clients.openWindow(clientList[0].url); // Adjust this URL as necessary for your application
                })
            );
          } else {
            // Open a new client (tab) if there are no existing clients
            self.clients.openWindow("/");
            setTimeout(() => {
              self.clients
                .matchAll({ type: "window", includeUncontrolled: true })
                .then((clientList) => {
                  if (clientList.length > 0) {
                    clientList[0].postMessage({
                      message: {...data,fromBackground: true},
                    });
                  }
                  return;
                });
            }, 1500);
          }
        })
    );
  }

  event.notification.close();
});
// "Default" Firebase configuration (prevents errors)
const defaultConfig = {
  apiKey: true,
  projectId: true,
  messagingSenderId: true,
  appId: true,
};

// Initialize Firebase app
firebase.initializeApp(self.firebaseConfig || defaultConfig);
let messaging;
try {
  messaging = firebase.messaging();
  // Customize background notification handling here
  messaging.onBackgroundMessage((payload) => {
    console.log("Background Message:", payload);
    const notificationTitle = payload.data.title;
    if (
      payload.data.type === "call" &&
      (payload.data.callAction === "unanswered" ||
        payload.data.callAction === "busy" ||
        payload.data.callAction === "ongoing")
    ) {
      return;
    }
    let body = payload.data.body;
    if (payload.data.type === "call") {
      switch (payload.data.callAction) {
        case "cancelled":
          body = `Call cancelled`;
          break;
        case "initiated":
          body = `Incoming ${payload.data.callType} call`;
          break;
        default:
          break;
      }
    }
    const notificationOptions = {
      title: payload.data.title,
      icon: payload.data.senderAvatar,
      data: payload.data,
      tag: payload.data.tag,
      body: body,
    };
    self.registration.showNotification(notificationTitle, notificationOptions)
  });
} catch (err) {
  console.error("Failed to initialize Firebase Messaging", err);
}

2. Create a service for firebase messaging-

Add add the following code snippet in the firebase.service.ts

import { Injectable } from '@angular/core';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
import { initializeApp } from 'firebase/app';
import { Router } from '@angular/router';

const firebaseConfig = {
  apiKey: "API_KEY",
  authDomain: "AUTH_DOMAIN",
  projectId: "PROJECT_ID",
  storageBucket: "STORAGE_BUCKET",
  messagingSenderId: "SENDERID",
  appId: "APP_ID_FIREBASE",
  measurementId: "MEASUREMENT_ID"
};

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  private messaging = getMessaging(initializeApp(firebaseConfig));

  constructor(private router: Router) {}

  async getFirebaseMessagingToken(serviceWorkerRegistration: any): Promise<string | undefined> {
    try {
      const currentToken = await getToken(this.messaging, {
        vapidKey: "VAPID_KEY",
      });
      return currentToken || undefined;
    } catch (error) {
      console.error("Error retrieving token:", error);
      return undefined;
    }
  }
}

3. In your main file add the permissions for push notification and register the service worker.

CometChatUIKit.init(uiKitSettings)!.then(() => {
  console.log("UIKIT initialized successfully")
  askPermission();

})
if ("serviceWorker" in navigator) {
  console.log("serviceWorker");
  navigator.serviceWorker
    .register("/firebase-messaging-sw.js")

    .then(function (registration) {
      console.log("registration", registration);
      console.log("Registration succlgcessful, scope is:", registration.scope);
    })
    .catch((error) => console.log("Registration error", error));
}

async function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });
    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== "granted") {
      console.log("Permission not granted");
    }
  });
}

4. Change to be made in the Angular.json file

  • To use the styling of the UIKit please make sure you have added this object.
{
    "glob": "**/*",
    "input":  "./node_modules/@cometchat/chat-uikit-angular/assets/",
    "output": "assets/"
}
  • Add the Service worker file in assets so that it can be accessed by the App.
"assets": [
       "src/favicon.ico",
       "src/firebase-messaging-sw.js",
       "src/assets",
        {
          "glob": "**/*",
          "input":  "./node_modules/@cometchat/chat-uikit-angular/assets/",
          "output": "assets/"
       }
 ],

5. To register and unregister the FCM tokens with the CometChat.

import { FirebaseService } from ‘./firebase.service’;
import {CometChatNotifications} from ‘@cometchat/chat-sdk-javascript’;

loginWithAuthToken(authToken: string) {
    CometChatUIKit.login({ authToken: authToken }).then(async(user) => {
      console.log("User loggedin", user)
      this.loggedInUser = user as any
      await this.initializeFirebase();
    })
  }
 logout() {
    CometChatNotifications.unregisterPushToken().then(()=>{
      console.log("Token unregistered")
    })
    CometChatUIKit.logout().then((user) => {
      console.log("User logged-out successfully...");
      window.location.reload()
    })
  }
  async initializeFirebase() {
    try {
      const permission = await Notification.requestPermission();
      if (permission === 'granted') {
        const token = await this.firebaseService.getFirebaseMessagingToken(null);
        if (token) {
          CometChatNotifications.registerPushToken(
            token,
            CometChatNotifications.PushPlatforms.FCM_WEB,
            'UNIQUE_PROVIDER_ID'
          )
            .then((payload) => {
              console.log('Token registration successful');
            })
            .catch((err) => {
              console.log('Token registration failed:', err);
            });
        } else {
          console.log("No FCM token found.");
        }
      } else {
        console.log("Notification permission denied.");
      }
    } catch (error) {
      console.error("Firebase initialization error:", error);
    }
  }

6. To handle the incoming call screen via Push Notification

  • Add the following code snippet in the ngOninit() to detect the Notification tap event.
  ngOnInit() {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.addEventListener('message', this.handleMessage.bind(this));
    }
  }
  async handleMessage(event:any) {
    console.log(event.data, "Event was");
    if (event.data && (parseInt(event.data.message.sentAt)+ 30000 >= Date.now()) && 
    event.data.message.callAction==="initiated") {
      const data = event.data.message;
      let callNotifi=new CometChat.Call(data.receiver, data.receiverType, data.callType);
      let caller=new CometChat.User(data.sender, data.senderName);
      let receiver= await CometChat.getGroup(data.receiver)
      caller.setAvatar(data.senderAvatar);
      callNotifi.setSender(caller);
      callNotifi.setReceiver(receiver as any)
      callNotifi.setCallInitiator(caller as any)
      callNotifi.setCallReceiver(receiver)
      callNotifi.setSessionId(data.sessionId)
      this.callObject=callNotifi
      this.incomingCallVisible = true
    }
    this.cdr.detectChanges();      
  }
  • When the component unmounts remove the listener.
  ngOnDestroy() {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.removeEventListener('message', this.handleMessage);
    }
    this.cdr.detach()
  }

Output:

  • Outgoing Call Screen

  • Push Notification for Incoming Call

  • Display the incoming call screen when the browser tab is reopened from a closed or inactive state.