Aleem Isiaka Hacking through HTML/CSS, making useful stuffs.

Using Firebase Cloud Messaging as a pub/sub service

8 min read 2460

Firebase Cloud Messaging (FCM) is primarily known for simplifying the process of sending a notification to client devices. In this post, we are going learn how to use Firebase Cloud Messaging as a push notification service and a pub/sub service in a React application.

What is a pub/sub system?

A publish/subscribe system consists of two parties: the publisher responsible for sending out a message to the system, and a subscriber, who actively listens to that notification from the system and can decide to further act on the message.

A use case for a pub/sub system is stateless communication from a server. When verifying card payments, it is almost impossible for a server to let the client know it has verified the payment and granted the service requested by the user. We can easily do this using a pub/sub system.

With a pub/sub system, the browser listens to a particular topic while the server sends a message to the topic. Immediately the browser receives the message, and it can continue with the rest of the client-side processing.

Setting up a pub/sub service with FCM in React

In this tutorial, we will learn the following:

  • How to set up FCM on Firebase Console and create a new project
  • How to set up a React App to include the Firebase SDK
  • Essential concepts in Firebase Cloud Messaging
  • How to listen to a message on a particular topic
  • How to publish a message to the topic using an HTTP request to the FCM API

Let’s get started!

Creating an FCM project

For this part, a Google account is required.

Start by heading over to https://console.firebase.google.com/ and log in with your Google account. Click on the big white Create a project button.

Screenshot of Firebase welcome screen

Enter the name of the project, accept the terms, then click on Continue. Select the account to which you want the project to be attached.

We made a custom demo for .
No really. Click here to check it out.

Gif of project creation process in Firebase

Creating a Firebase application

On the Firebase console, click on the code icon (</>) in the white circle, enter the name of the app, select setting up Firebase Hosting, then click on Register app. This will take some time to provision the app before it will prompt you for the next step.

At the Add Firebase SDK and Install Firebase CLI steps, scan through the instructions, then click Continue to console to finalize the setup.

Gif of adding Firebase to a React app process

Getting the credentials

Let’s get the Firebase API key that gives browsers the power to authenticate requests to the Firebase API and the Firebase JSON file.

From the dashboard, click on your new app’s name, then click on the gear icon to access the settings.

Gif detailing how to get API keys from Firebase

Next, scroll down to the bottom of the tab; under the SDK setup and configuration section, click on the Config button to unveil the web push configuration. Make sure to copy that and save it somewhere safe.

Gif of SDK setup configuration in Firebase

A server key is required to perform authorized actions through the Google Firebase APIs. To get this, go to the Cloud Messaging tab under Project Settings and scroll down to Project credentials. Copy and save the server key somewhere safe.

Screenshot of highlighted server key in Firebase project settings

Setting up a React App

In this section, we will create a React app and with it, set up Firebase.

Enter the following in your terminal:

$ npx create-react-app pub-sub && cd pub-sub && code .

The command above will create a new React application into a pub-sub folder of the current directory. Then, change the current directory to the React application’s directory, and open the project in Visual Studio Code for editing.

Also, from the terminal in the pub-sub project directory, you can run npm start to open the development folder.

Installing the Firebase SDK into the React application

In your terminal, run npm i firebase --save from the project root folder to install Firebase.

Create a new folder at path-to-project/src/utils and add a new file, firebaseConfig.json, to the folder. This file should have all the JSON values from the Firebase web push settings page.

The content of the file should look like this:

{
  apiKey: "***",
  authDomain: "logrocket-pub-sub.firebaseapp.com",
  projectId: "logrocket-pub-sub",
  storageBucket: "logrocket-pub-sub.appspot.com",
  messagingSenderId: "***",
  appId: "1:***:web:***",
  measurementId: "G-G7Q3DJ5GCN"
}

Creating a Firebase helper

Inside of the /src/utils folder, create a file called firebase.js with the below content:

import firebase from "firebase/app";
// eslint-disable-next-line
import _messaging from "firebase/messaging";
import firebaseConfig from "./firebaseConfig";

const app = firebase.initializeApp(firebaseConfig);
export const fMessaging = app.messaging();

The first line imports the Firebase app. Firebase messaging is imported to add and initialize the Firebase messaging SDK on the Firebase app. The fourth line imports the Firebase config file that you created above.

Line six initializes the Firebase app using the firebaseConfig JSON details. And the last line initializes cloud messaging on the Firebase application that was initialized on the line above it.

Adding firebase-messaging-sw.js

To complete the Firebase integration, you have to add a firebase-messaging-sw.js file at a publicly accessible path of your app, in this case, in the path-to-project/public.

The content of the file should be the following:

// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here. Other Firebase libraries
// are not available in the service worker.
// eslint-disable-next-line
importScripts("https://www.gstatic.com/firebasejs/8.6.7/firebase-app.js");
// eslint-disable-next-line
importScripts("https://www.gstatic.com/firebasejs/8.6.7/firebase-messaging.js");

// Initialize the Firebase app in the service worker by passing in
// your app's Firebase config object.
// https://firebase.google.com/docs/web/setup#config-object
// eslint-disable-next-line
firebase.initializeApp({
  apiKey: "AIzaSyCu7r3TlqiiI_3HTJft_G-SSC8_*******",
  authDomain: "logrocket-pub-sub.firebaseapp.com",
  projectId: "logrocket-pub-sub",
  storageBucket: "logrocket-pub-sub.appspot.com",
  messagingSenderId: "*************",
  appId: "1:709132711133:web:***********************",
  measurementId: "G-*********",
});

// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
// eslint-disable-next-line
const messaging = firebase.messaging();

messaging.onBackgroundMessage((message) => {
  return self.showNotification(
    message.notification.title,
    message.notification
  );
});

The first few lines should be familiar; the Firebase app and messaging scripts are imported into the service worker context. Next, initialize the Firebase application before initializing Firebase messaging.

The onBackgroundMessage method on the Firebase messaging SDK captures any messages delivered to a client application (browser, in this case) while the browser, webpage, or app is not active.

Here, the notification badge is triggered to keep the user informed about the new information that was received in the background.

Firebase Cloud Messaging concepts

To fully have a hand in the integration, you should understand these essential Firebase Cloud Messaging concepts.

Foreground messages

These are messages that are received by the client when the browser is active (e.g., the user is on the page/browser tab). This is available through the .onMessage((message) => message) method on the Firebase messaging SDK, and cannot be called in a service worker context.

Background messages

These messages are delivered to a client browser while inactive. This is available through the .onBackgroundMessage((message) => message) method on the Firebase messaging SDK and can be called only in a service worker context.

Subscribers to a topic

Subscribers are a targeted group where messages are sent. Mobile apps can subscribe to receive messages, while browsers cannot subscribe to any issue using the browser SDK. We will learn how to subscribe to a topic from a browser later in this article.

Message/notification data

By default, all messages received by a client should be an object that looks like the following:

{
  "notification": {
      "title": "This is the title",
    "body": "This is the body",
    "priority": "high|normal"
  },
  data: {
    anExtra: "Something",
    size: "has a size limit to avoid failure"
  }
}

The notification object must have a minimum of title and body to be successfully sent, while the data can be an arbitrary object, and according to FCM docs, should not be more than 4000 bytes.

The notification object is used to display native notification based on the client device, and we don’t want that in our case. Later, we will see learn to prevent the notification from popping up when there is a new message from FCM.

Subscribing to a known topic name

A pub/sub system mostly deals with topics. A topic is a group of a users or clients that can get a particular set of messages.

The Firebase web JavaScript SDK does not support topic subscription, but it is achievable through an HTTP request to https://iid.googleapis.com/iid/v1/' + accessToken + '/rel/topics/' + topic.

The accessToken is the current access token of the client that needs to be subscribed. The topic is a string holding the name of the topic.

To implement topic subscription, you need the accessToken as specified above. In your React application, open the Firebase utility helper and add the code below:

export const subscribeToTopic = (topicName, handler = () => {}) =>
  fMessaging.getToken().then((currentToken) => {
    if (currentToken) {
      const FIREBASE_API_KEY = `AAAA*******:********************************************************************************************************************************************`;
      // Subscribe to the topic
      const topicURL = `https://iid.googleapis.com/iid/v1/${currentToken}/rel/topics/`;
      return fetch({
        url: topicURL,
        method: "POST",
        headers: {
          Authorization: `key=${FIREBASE_API_KEY}`,
        },
      })
        .then((response) => {
          fMessaging.onMessage(
            (payload) => {
              handler(payload);
            },
            (error) => {
              console.log(error);
            }
          );
        })
        .catch(() => {
          console.error(`Can't subscribe to ${topicName} topic`);
        });
    }
  });

Here, the getToken function on the messaging SDK returns the current token of a client; sometimes, it fails if the user has not given the required permission for push notifications.

Next, an HTTP request is made for a topic subscription; once this is successful, messaging().onMessage is used to listen to messages for the client.

To implement subscribeToTopic in your React application, replace the App.js file in the app to contain the content below:

import React, { useEffect } from "react";
import "./App.css";
import { subscribeToTopic } from "./utils/firebase";

function App() {
  function topicOnMessageHandler(message) {
    console.log(message);
  }

  useEffect(() => {
    subscribeToTopic("LOGROCKET_PUB_SUB_TOPICS", topicOnMessageHandler).then();
  }, []);

  return <div className="App">Firebase Pub / Sub System</div>;
}

export default App;

First, the function topicOnMessageHandler is defined to handle any messages coming to the topic and process them; it is only logged to the console.

The subscribeToTopic function is called in a useEffect hook, and it receives the name of the topic as LOGROCKET_PUB_SUB_TOPICS and the topicOnMessageHandler as the handler.

Whenever there is a message sent to the LOGROCKET_PUB_SUB_TOPICS topic, your React app will receive it and log it to the console.

Handling background messages

The service worker file firebase-messaging-sw.js has implemented the onBackgroundMessage method of the Firebase messaging SDK. In that function, the message is logged to the console, which is suitable for this use case.

Publishing a message to a React app

In a pub/sub system, there should be a publisher of messages; the React app we just built has been the subscriber.

To test this implementation, go to the Firebase console, expand the Engage sidebar menu, then click on Cloud Messaging to access the cloud messaging dashboard. Then click on the Send your first message button.

Screenshot of FCM engage tab

In the Compose notification tool, enter the title of the notification and the body, then click Next. On the Target section, select a topic and enter the topic you used when subscribing. You can schedule the message for a later, or send it immediately. Click Review to finalize the process.

Gif of FCM compose notification tool

Once the notification is dispatched, you should see a notification badge like so:

Screenshot of test notification

Along with a console log for the received message:

Screenshot of console log for test notification

Sending messages outside the console

Aside from the dashboard, you can send messages using HTTP requests to https://fcm.googleapis.com/fcm/send with a body containing the notification object and an authorization header: key=FIREBASE_API_KEY.

The body of the request should look like this:

{
    "data": {"Holla": "True"},
    "to": "/topics/LOGROCKET_PUB_SUB_TOPICS",
    "notification": {
        "title": "This is from Postman",
        "body": "hello there"
    }
}

And an authorization header described as Authorization: "key=API_KEY":

Gif of authorization header being added

How is this useful? With this HTTP approach, it is possible for a remote operation on a server to send a notification to a particular topic that certain clients have subscribed to.

Just as it exists in a pub/sub system, the client browser is already serving as a subscriber; the remote server can act as publisher of the notification.

Preventing the notification badge

FCM is known for notifications. If it should serve as a pub/sub service, the notification is usually unnecessary.

Our approach in this article to publishing messages will always cause the popup notification badge. You can prevent that by omitting the notification object from the payload you are sending when publishing a new message, like so:

{
    "data": {"Holla": "True"},
    "to": "/topics/LOGROCKET_PUB_SUB_TOPICS"
}

This way, the messages are delivered, the notification badge won’t pop out, and the message handlers can handle the message effectively.

Sending messages from the service worker to the main browser thread

When a background message is received, the onBackgroundMessage is called in a service worker context.

You can send a message from the service worker thread to the main browser thread with self.postMessage({}), then receive the message on the main thread with window.addEventListener("onmessage", message => console.log(message)).

The above solution would work, but is not maintainable in this case where messages can arrive in two places: through the onMessage and the onBackgroundMessage.

The more manageable and maintainable way would be to push both messages to an event system that can be subscribed to, which would handle the messages regardless of where the message is coming from.

The BroadcastChannel API can be useful in this case, as this post suggests.

Inside of the onBackgroundMessage function, instead of consoling, you can post the message to a channel:

messaging.onBackgroundMessage((message) => {
  // if the sent data does not contain notification,
  // no notification would be shown to the user
  const fcmChannel = new BroadcastChannel("fcm-channel");
  fcmChannel.postMessage(message);
});

Also, inside of the handler for the subscribeToTopic, replace the console log with the following:

const fcmChannel = new BroadcastChannel("fcm-channel");
fcmChannel.postMessage(message);

To consume this message, anywhere inside of test React app, create another useEffect hook inside of the App.js file and implement the onmessage event of the BroadcastChannel API like below:

useEffect(() => {
    const fcmChannel = new BroadCastChannel("fcm-channel");
  fcmChannel.onmessage = (message) => console.log(message);
}, [])

With this change, the onmessage handler handles all the messages coming from FCM, which is logging them to the console.

Conclusion

As an effective push notification service, FCM can also serve as a Pub/Sub system and still leverage the existing available infrastructure.

This post has also shared how to use the Google APIs to make working with FCM easier instead of relying on the SDK and making some edge-case usage possible.

Using BroadcastChannel as an event is useful in synchronizing the data across the different FCM message delivery modes.

With the instructions in this post, you can do server-client communication seamlessly without interrupting the user with a notification badge.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Aleem Isiaka Hacking through HTML/CSS, making useful stuffs.

Leave a Reply