CRA + Workbox + Firebase Cloud Messaging all together SOLVED

CRA + Workbox + Firebase Cloud Messaging all together SOLVED

When I've started implementing Push Notifications on my Create React App I've taken google's FCM docs and some youtube tutorials and everything went fine (when I turned on notifications for Chrome on my MacBook).

The idea was NOT TO EJECT from CRA + to keep my custom service worker + to add firebase cloud messaging service worker features but then random issues started to appear.

I'll go quickly through the implementation process well described on few youtube videos just for people that are implementing notifications for the first time:

  1. Create firebase-messaging-sw.js file in the public folder of CRA app


// public/firebase-messaging-sw.js

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

const firebaseConfig = {
 apiKey: "******************",
 authDomain: "*****-*****.firebaseapp.com",
 projectId: "*****-*****",
 storageBucket: "*****-*****.appspot.com",
 messagingSenderId: "322323232323232",
 appId: "***********************************************",
 measurementId: "********************"
};

firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

messaging.setBackgroundMessageHandler(function (payload) {
 const promiseChain = clients
  .matchAll({
   type: "window",
   includeUncontrolled: true,
  })
  .then((windowClients) => {
   for (let i = 0; i < windowClients.length; i++) {
    const windowClient = windowClients[i];
    windowClient.postMessage(payload);
   }
  })
  .then(() => {
   return registration.showNotification("my notification title");
  });
 return promiseChain;
});

self.addEventListener("notificationclick", function (event) {
 console.log(event);
});        

2. Register this service worker in index.js

// index.js

if ('serviceWorker'  in navigator) {
 navigator.serviceWorker.register('./firebase-messaging-sw.js')
  .then(function(registration) {
   console.log('Registration was successful: ', registration)
  })
  .catch(function(e) {
   console.error('Registration has filed', e)
  })
}        

And do not register existing CRA service worker because of collision with FCM sw


// index.js

// Learn more about service workers: https://bit.ly/CRA-PWA
// serviceWorker.register()        

3. Create init-fcm.js and initialize the firebase messaging

// src/init-fcm.js

import firebase from "firebase/app";
import "firebase/messaging";

export let messaging = null

if (firebase.messaging.isSupported()) {
 const initializedFirebaseApp = firebase.initializeApp({
  apiKey: "******************",
  authDomain: "*****-*****.firebaseapp.com",
  projectId: "*****-*****",
  storageBucket: "*****-*****.appspot.com",
  messagingSenderId: "322323232323232",
  appId: "***********************************************",
  measurementId: "********************"
 });

 messaging = initializedFirebaseApp.messaging();
}


export default firebase        

4. In App.js componentDidMount() ask user for permission and subscribe the user, get firebase token and send it to your backend for further use

// App.js componentDidMount()

...

  const token = await messaging.getToken({
    vapidKey: process.env.REACT_APP_VAPIDKEY
  });
  console.log('TOKEN', token)
  notificationsService.subscribeUser(token) // this is backend call

...        


And when send notification via firebase UI to this token you will get it but then starts to get weird.


Problems and solutions

First of all, you have to know that there are two types of messages, notification and data message which can be received in foreground and background.

Notification messages are fetched by the browser and are displayed right away, skipping the service worker in a meantime. With this approach whole control is on the backend. This is not a bad thing but you as a front-end will have less control over the notifications.

Data messages are fetched in service worker and notification is created from there. With this, you can add more logic before create a notification, especially if you want custom actions on the notification message.

#1 The existing custom service worker, implemented using react-app-rewire-workbox, and firebase-messaging-sw.js were in a collision

The main problem without a proper solution on the internet, at last, I didn't found it in this form. All tutorials suggest adding firebase-messaging-sw.js file into the public folder as easy solution, without this file of course below error is thrown.

A bad HTTP response code (404) was received when fetching the script.
Failed to load resource: net::ERR_INVALID_RESPONSE
dcbac67bb39a765db27a.js:1 Service worker registration failed: TypeError: Failed to register a ServiceWorker: A bad HTTP response code (404) was received when fetching the script.        

After reading google's documentation from top to bottom I've spotted a deprecated function useServiceWorker(). I've seen this one before but it looked like some hook to me, since I'm using React component I was ignoring it for a long time. Then I saw that getToken() is now receiving two params in options

getToken(options?: {
  vapidKey?: string;
  serviceWorkerRegistration?: ServiceWorkerRegistration;
}): Promise;        

and that was the solution for this at first unsolvable issue.

#1 Solution

  1. Remove firebase-messaging-sw.js from the public folder.
  2. Remove it's registration in index.js and get serviceWorker.register() line back.
  3. Copy whole content from there to your custom-sw.js file and
  4. In App.js add service worker registration to getToken()

// App.js componentDidMount()

...

if ('serviceWorker' in navigator) {
 navigator.serviceWorker.getRegistration()
  .then(async (reg) => {
   const token = await messaging.getToken({
    vapidKey: process.env.REACT_APP_VAPIDKEY,
    serviceWorkerRegistration: reg
   });
   console.log('TOKEN', token)
   notificationsService.subscribeUser(token)
  })
}

...        


#2 messaging.onBackgroundMessage() is not called even notification is sent successfully

I am not sure why, but if you are using firebase-messaging-sw.js file your data notifications will end up in messaging.onBackgroundMessage() function, after moving whole code into custom service worker along with workbox code, it stopped working.

#2 Solution

Listen to on Push event in the service worker. Code example:


self.addEventListener('push', function(e) {
  const dataObj = e.data.json()
  const {data} = dataObj
  const notificationOptions = {}
  // do some logic to fulfill the notificationOptions
  
  e.waitUntil(
    self.registration.showNotification('Title', notificationOptions)
  );
});        


#3 How to test while on localhost

When you fix the previous issue, you become aware of the fact that sw works only on https and deployed app so testing notifications will be an issue. Don't worry there are solution.

#3 Solution

Get few steps back:

  1. In index.js comment out serviceWorker.register() line and again add registration for file from public folder

if ('serviceWorker'  in navigator) {
 navigator.serviceWorker.register('./firebase-messaging-sw.js')
  .then(function(registration) {
   console.log('Registration was successful: ', registration)
  })
  .catch(function(e) {
   console.error('Registration has filed', e)
  })
}        

2. Create firebase-messaging-sw.js in the public folder and add proper content.

And voila, sw will be registered even you are on localhost. Do whatever you have to do in sw, test and when you are ready for deployment do the steps but backward and build the app.


I hope that I've saved you some time with this knowledge sharing. Feel free to correct me if I'm wrong with some statements. Cheers.


Jad Azzam

Full-Stack JavaScript Engineer | React.js, Next.js, Node.js, Nest.js

3 年

Thank you !

要查看或添加评论,请登录

Bozidar Zecevic的更多文章

  • PWA - "Slede?a velika stvar" koja se ve? desila

    PWA - "Slede?a velika stvar" koja se ve? desila

    Pred scriptum - Post je premala forma da bih napisao sve kao ?elim pa ?u probati ovaj format za promenu i tu ubaciti i…

    1 条评论

社区洞察