Web Push Notifications With React and Firebase ( With Safari Error Handling )

·
Share on facebook
Share on twitter
Share on linkedin
Share on whatsapp
Share on email

Web push notifications are messages that appear on a user’s computer, tablet, or mobile device when they have an active web browser. Websites utilize web push to stay connected with visitors even after they have left their site, with the goal of enhancing user engagement, increasing conversions, and improving the digital experience. Web push is more convenient for users to enable and less likely to be ignored than conventional communication methods such as email.

In this blog, You will learn how you can setup a Firebase Project and integrate that with your react project to start receiving web notifications.

So lets start building the project.

React Project Setup

Lets create a new react app . Run the following command in your terminal to create a react app

				
					npx create-react-app react-firebase-web-notifications --template typescript
cd react-firebase-web-notifications
npm run start
				
			

Create a page to receive notifications

After creating the project with the initial codebase, we have the src/App.tsx file for the main page. Lets modify this page according to our need Firebase Project Setup

				
					import React, { useState } from "react";
import "./App.css";

function App() {
  const logo = require("./assets/sun.gif");
  const [open, setOpen] = useState(false);

  return (
    <div>
      <div className="App">
        <header>
          <h1 className="App-title">
            Web Push Notifications with react and firebase
          </h1>
        </header>
        <img className="App-logo" src={logo} alt="logo" />
        <div>
          <button
            className="Button"
            onClick={() => {
              setOpen(true);
              // To hide notification after 6 Seconds
              setTimeout(() => setOpen(false), 6000);
            }}
          >
            Show Web Push Notification
          </button>
        </div>
        {open && (
          <div
            className="notification"
            onClick={() => {
              setOpen(false);
            }}
          >
            <div className="push-notification-title">
              <h1>New Message</h1>
              <button
                className="close-button"
                onClick={() => {
                  setOpen(false);
                }}
              >
                X
              </button>
            </div>
            <div>
              <h1 className="push-notification-text">Hello Welcome, Today you will learn how to use firebase-notifications</h1>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;
				
			

after you are done writing this code,Run the project and you will get to see this page.

Home Page

Firebase Project Setup

  1. If you don’t already have an app at Firebase yet, you should create one.
  2. After successfully creating an account, you will be redirected to Firebase Console where you can create a project by clicking the Create a project button and follow the on screen instructions.
Create Project
  • 3. After you have a Firebase project, you can register your web app with that project.

If you have created a project before, you will have a list of project cards. In this case, you need to click Add project to create a new one.

Google analytics selection

Once you click Add Project, You will directed to this page , where you have to provide name for your project, I will be naming it firebase-web-notifications. You can provide any name you want and click on continue.

Firebase Console

In this page you can either opt-in for the Google analytics or Opt-out.

Google analytics selection

Hurray, Congrats There you go, Now you have successfully created your Firebase project.

Project Page

Lets integrate firebase to our react app. Click on the Web icon </> to add firebase to the project. Upon clicking you will be prompted to this page.
Provide any nickname you want, i will be providing firebase-web-notifications, then click on register app

Add firebase to web app

Here you will be provided with the firebase configs which are essential to integrate your web-app with react project, Copy the configs object and keep it in a file for future use.

Firebase Configs

 

Integration Of Firebase Messaging With React App

 

Lets install firebase in your react app for receiving notifications. Run the below given command in the terminal of the root of your project

npm install dotenv

Step 1 . Securing Firebase Configs

Now in order to use the firebase configs you got in a secure way, we make use of dotenv package. You can install it using the following the command

npm install dotenv

after successfully installing the package. create a file named .env in the src folder of your project.In that file add the configs you got in the below mentioned manner.

				
					REACT_APP_FB_API_KEY='API key'
REACT_APP_FB_AUTH_DOMAIN='Auth domain'
REACT_APP_FB_PROJECT_ID='Project id'
REACT_APP_FB_STORAGE_BUCKET='Storage bucket'
REACT_APP_FB_MESSAGIN_SENDER_ID='Messaging id'
REACT_APP_FB_APP_ID='App id'
REACT_APP_FB_VAPID_KEY='Vapid key'
				
			

if you notice the above lines, you will see a field named REACT_APP_FB_VAPID_KEY , you can get this key from Project setting

Project overview > Project settings > Cloud Messaging > Web Configuration > Generate key pair. Upon clicking Generate key pair. You will get a key, Then add that key to your REACT_APP_FB_VAPID_KEY field in .env file.

VAPID Key

Step 2 . Creating a service-worker

Lets create a file called FirebaseConfig.tsx to initialize firebaseapp by using the firebase configs that we stored in the .env file.

Here, we will generate a service worker that operates in the browser’s background without requiring user intervention, which we will use to handle push notifications. Currently, we do not possess a service worker. So lets create one.

				
					import { initializeApp } from "firebase/app";
import { getToken, getMessaging, isSupported } from "firebase/messaging";

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FB_API_KEY,
  authDomain: process.env.REACT_APP_FB_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FB_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FB_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FB_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FB_APP_ID,
};

export const firebaseApp = initializeApp(firebaseConfig);

export const messaging = getMessaging(firebaseApp);

// getOrRegisterServiceWorker function is used to try and get the service worker if it exists, otherwise it will register a new one.
export const getOrRegisterServiceWorker = () => {
  if (
    "serviceWorker" in navigator &&
    typeof window.navigator.serviceWorker !== "undefined"
  ) {
    return window.navigator.serviceWorker
      .getRegistration("/firebase-push-notification-scope")
      .then((serviceWorker) => {
        if (serviceWorker) return serviceWorker;
        return window.navigator.serviceWorker.register(
          "/firebase-messaging-sw.js",
          {
            scope: "/firebase-push-notification-scope",
          }
        );
      });
  }
  throw new Error("The browser doesn`t support service worker.");
};
 
// getFirebaseToken function generates the FCM token 
export const getFirebaseToken = async () => {
  try {
    const messagingResolve = await messaging;
    if (messagingResolve) {
      return getOrRegisterServiceWorker().then((serviceWorkerRegistration) => {
        return Promise.resolve(
          getToken(messagingResolve, {
            vapidKey: process.env.REACT_APP_FB_VAPID_KEY,
            serviceWorkerRegistration,
          })
        );
      });
    }
  } catch (error) {
    console.log("An error occurred while retrieving token. ", error);
  }
};
				
			

Now your React app is capable of receiving notifications, But it wont visible to the user , Hence in the next step lets modify our App.tsx to display these notifications.

Step 3 . Show notifications on web in foreground

Add the following code to your existing App.tsx file in-order to display the notifications.

				
					/* eslint-disable jsx-a11y/anchor-is-valid */
import { MessagePayload, onMessage } from "firebase/messaging";
import React, { useEffect, useState } from "react";
import "./App.css";
import { getFirebaseToken, messaging } from "./FirebaseConfig";

interface NotificationPayloadProps {
  data?: MessagePayload | undefined;
  open: boolean;
}

function App() {
  const logo = require("./assets/sun.gif");

  const [open, setOpen] = useState(false);

  // To store notification data from firebase
  const [notificationPayload, setNotificationPayload] = useState<
    (NotificationPayloadProps | undefined)[]
  >([]);

  // This is self invoking function that listen of the notification
  const onMessageListener = (async () => {
    const messagingResolve = await messaging;
    if (messagingResolve) {
      onMessage(messagingResolve, (payload: MessagePayload) => {
        setNotificationPayload([{ data: payload, open: true }]);
        setTimeout(() => setNotificationPayload([{ open: false }]), 6000);
      });
    }
  })();

  const handleGetFirebaseToken = () => {
    getFirebaseToken().then((firebaseToken: string | undefined) => {
      if (firebaseToken) {
        console.log(firebaseToken);
      }
    });
  };

  // Need this handle FCM token generation when a user manually blocks or allows notification
  useEffect(() => {
    if (
      window.Notification?.permission === "granted"
    ) {
      handleGetFirebaseToken();
    }
  }, []);

  return (
    <div>
      <div className="App">
        {/* Used to notify user whether he has provided notification permissions or not */}
        {Notification.permission !== "granted" && (
          <div className="notification-banner">
            <span>The app needs permission to</span>
            <a
              href="#"
              className="notification-banner-link"
              onClick={handleGetFirebaseToken}
            >
              enable push notifications.
            </a>
          </div>
        )}
        <header>
          <h1 className="App-title">
            Web Push Notifications With React And Firebase
          </h1>
        </header>
        <img className="App-logo" src={logo} alt="logo" />
        <div>
          <button
            className="Button"
            onClick={() => {
              setOpen(true);
              setTimeout(() => setOpen(false), 6000);
            }}
          >
            Show Web Push Notification
          </button>
        </div>

        {/* Rendering  Notification from firebase */}

        {notificationPayload.map((notification) => {
          return (
            <>
              {notification?.open && (
                <div className="notification">
                  <div className="push-notification-title">
                    <h1>{notification?.data?.notification?.title}</h1>
                    <button
                      className="close-button"
                      onClick={() => 
                        setNotificationPayload([{ open: false }]);
                      }
                    >
                      X
                    </button>
                  </div>
                  <div>
                    <h1 className="push-notification-text">
                      {notification?.data?.notification?.body}
                    </h1>
                  </div>
                </div>
              )}
            </>
          );
        })}

        {/* Rendering Demo Notification */}

        {open && (
          <div
            className="notification"
            onClick={() => {
              setOpen(false);
            }}
          >
            <div className="push-notification-title">
              <h1>New Message</h1>
              <button
                className="close-button"
                onClick={() => {
                  setOpen(false);
                }}
              >
                X
              </button>
            </div>
            <div>
              <h1 className="push-notification-text">
                Hello Welcome, Today you will learn how to use
                firebase-notifications
              </h1>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;
				
			

Now you can successfully display notifications that you get from firebase.

You can access the FCM Token that is generated using the console now.

FCM Token

Step 4. Show notifications on web in background

To Display your notifications in the background, You need to create a service-worker file in your public folder called firebase-messaging-sw.js and add your firebase configs and Background message handler.

Since the file in the public folder can be accessed by anyone, we need to securely insert our firebase configs in that file. for that we make use of URLSearchParams . So lets add the below mentioned code inside our FirebaseConfig.tsx

				
					const UrlFirebaseConfig = new URLSearchParams(
  {
    apiKey: process.env.REACT_APP_FB_API_KEY,
    authDomain: process.env.REACT_APP_FB_AUTH_DOMAIN,
    projectId: process.env.REACT_APP_FB_PROJECT_ID,
    storageBucket: process.env.REACT_APP_FB_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_FB_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_FB_APP_ID,
  }.toString()
);

const swUrl = `http://localhost:3000/firebase-messaging-sw.js?${UrlFirebaseConfig}`;
				
			

Now in firebase-messaging-sw.js file add the following code to access the firebase configs and also to add a background message handler

				
					/* 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

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');

// Set Firebase configuration, once available
self.addEventListener('fetch', () => {
  try {
    const urlParams = new URLSearchParams(location.search);
    self.firebaseConfig = Object.fromEntries(urlParams);
  } catch (err) {
    console.error('Failed to add event listener', err);
  }

});
// "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();
} catch (err) {
  console.error('Failed to initialize Firebase Messaging', err);
}

// To dispaly background notifications
if (messaging) {
  try {
    messaging.onBackgroundMessage((payload) => {
    console.log('Received background message: ', payload);
    const notificationTitle = payload.notification.title;
    const notificationOptions = { body: payload.notification.body };
    self.registration.showNotification(notificationTitle, notificationOptions);
    });
  } catch (err) {
    console.log(err);
  }
}
				
			

Now your app is capable of handling notifications in background as well as foreground. There is only one thing that is left, Lets see about that in the next section.

Handling Errors When Working With Safari Or iOS

1. Push API not supported in Safari and iOS Safari

As Push API doesn’t support on Safari, so your firebase web notification config will fail and will return an error or blank page.

Supported environments for the Firebase JavaScript SDK

So in order to avoid this we make use of a function called isSupported(). This function Checks if all required APIs exist in the browser. using this you need to initialize messaging only if isSupported is true.

So lets modify our FirebaseConfig.tsx file to add check. Obtain the firebaseapp inside messaging only when isSupported is true

				
					export const messaging = (async () => {
  try {
    const isSupportedBrowser = await isSupported();
    if (isSupportedBrowser) {
      return getMessaging(firebaseApp);
    }
    console.log("Firebase is not supported in this browser");
    return null;
  } catch (err) {
    console.log(err);
    return null;
  }
})();
				
			

This will prevent the firebase crash in your safari or iOS safari browsers.

2. Notification is undefined

In this project , we are making use of Notification API of window interface to check whether Notification permission is granted or not, Since Push API is not supported on Safari this will result in crash of your app.

So in order to avoid this, we need to add few checks wherever we are making use of Notification.permission . So we will modify our App.tsx to include this change.

useEffect in the App.tsx will be

				
					useEffect(() => {
    if (
      "Notification" in window &&
      window.Notification?.permission === "granted"
    ) {
      handleGetFirebaseToken();
    }
  }, []);
				
			

and UI which renders the Permission Banner will include this check

				
					{"Notification" in window && Notification.permission !== "granted" && (
          <div className="notification-banner">
            <span>The app needs permission to</span>
            <a
              href="#"
              className="notification-banner-link"
              onClick={handleGetFirebaseToken}
            >
              enable push notifications.
            </a>
          </div>
        )}
				
			

Yeah, That’s it. You are all set with Web Push Notification using Firebase.

Testing The Firebase Notifications

You can make use of Firebase Console > Engage > Messaging > Create your first campaign test out the notifications. Enter your notification title and and text , Then click on send test message and add FCM token that you got in the console of your react app.

Send Test notifications

Thank you. Happy Coding
You can find the the complete project on Github.