+18

[ReactJS] Push notification sử dụng Firebase Cloud Messaging

Xin chào, trong bài viết này mình sẽ thực hiện chức năng push notification trên trang web ReactJS sử dụng Firebase Cloud Messaging (FCM)

Các bước chính

  1. Khởi tạo project ReactJS
  2. Tạo project Firebase và setup FCM
  3. Cấu hình Firebase trong app ReactJS để nhận message push từ FCM
    a. Foreground
    b. Background

1. Khởi tạo project ReactJS

Để khởi tạo project ReactJS đơn giản, mình sử dụng create-react-app:

npx create-react-app demo-fcm --template typescript

Sau đó chạy thử project:

cd demo-fcm
npm start

Kết quả:
image.png

2. Tạo project Firebase và setup FCM

Đăng nhập vào Firebase Console, chọn Add project:
Screen Shot 2022-08-25 at 14.35.01.png
Mình đặt tên là demo-fcm, chọn ContinueCreate project
Screen Shot 2022-08-25 at 14.35.45.png
Đợt 1 chút để project được khởi tạo xong, chọn Continue.
Tại trang chủ của project, bấm vào biểu tượng < /> để setup cho Web
Screen Shot 2022-08-25 at 14.38.45.png
Mình đặt tên là demo-fcm-app, chọn** Register app**
Screen Shot 2022-08-25 at 14.40.50.png
Chọn Continue to console
Tại màn hình chính của project, ở menu bên trái, nhấn vào biểu tượng răng cưa, chọn Project settings. Sau đó chọn tab Cloud Messaging
Screen Shot 2022-08-25 at 14.46.16.png
Ở phần Cloud Messaging API (Legacy) (đang hiện Disable), chọn biểu tượng 3 chấm => Manage API in Google Cloud Console
Ở cửa sổ mở ra, chọn Enable:
Screen Shot 2022-08-25 at 14.47.53.png
Sau đó reload lại trang Project settings, phần Cloud Messaging API (Legacy) đã được Enabled và bên dưới là Server key
Tại phần Web Push certificates, bấm vào Generate key pair: (cái này là VAPID key)
Screen Shot 2022-08-25 at 16.42.08.png
Như vậy là đã cấu hình setup xong project Firebase

3. Cấu hình Firebase trong app ReactJS để nhận message push từ FCM

Trước tiên mình sẽ install package firebase, ở đây mình dùng firebase version 8:

npm install firebase@8.2.0

Tạo file constants.ts trong thư mục src để lưu Firebase config:

//src/constants.ts
export const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: "your_auth_domain",
  projectId: "your_projectId",
  storageBucket: "your_storage_bucket",
  messagingSenderId: "your_messaging_sender_id",
  appId: process.env.REACT_APP_FIREBASE_APP_ID
};

Config lấy từ Project settings, tab General:
Screen Shot 2022-08-25 at 17.15.53.png

a. Foreground

Để nhận foreground message (khi đang ở tab trang web), mình sẽ viết 1 hàm lắng nghe message khi đang focus vào web.
Trong thư mục src, tạo file firebase.ts với nội dung:

import { useEffect } from 'react';
import firebase from "firebase/app";
import "firebase/messaging";
import { firebaseConfig } from './constants';

if (!firebase.apps.length) {
  firebase.initializeApp(firebaseConfig);
} else {
  firebase.app(); // if already initialized, use that one
}

let messaging: firebase.messaging.Messaging;

if (typeof window !== "undefined") {
  if (firebase.messaging.isSupported()) {
    messaging = firebase.messaging();
  }
}

export const getMessagingToken = async () => {
  let currentToken = "";
  if (!messaging) return;
  try {
    currentToken = await messaging.getToken({
      vapidKey: process.env.REACT_APP_FIREBASE_FCM_VAPID_KEY,
    });
    console.log("FCM registration token", currentToken);
  } catch (error) {
    console.log("An error occurred while retrieving token. ", error);
  }
  return currentToken;
};

export const onMessageListener = () =>
  new Promise((resolve) => {
    messaging.onMessage((payload) => {
      resolve(payload);
    });
  });

File này mình định nghĩa hàm onMessageListener để hứng message, hàm này có thể được gọi trong useEffect:
Trong file App.tsx, mình thêm 2 useEffect sau:

useEffect(() => {
    getMessagingToken();
  },[])
 useEffect(() => {
   onMessageListener().then(data => {
      console.log("Receive foreground: ",data)
   })
})

useEffect đầu tiên dùng để lấy Registration token từ Firebase
useEffect sau dùng để hứng message được push từ Firebase (lưu ý là useEffect này không có dependencies)
Chạy thử project lên xem nhé:
Trang web sẽ yêu cầu cho phép gửi thông báo, chọn Cho phép, ta sẽ thấy FCM registration token được console.log ra:
Screen Shot 2022-08-25 at 17.42.47.png
Thử bắn message từ firebase (lưu ý là phải đang mở tab của trang web):
Vào Firebase, tại menu bên trái, chọn Engage => Cloud Messaging => Send your first message:
Screen Shot 2022-08-25 at 17.45.21.png
Chọn Send test message, paste token được console.log ra ở trên và chọn +
Screen Shot 2022-08-25 at 17.46.00.png
Chọn Test và message được bắn về trang web:
Screen Shot 2022-08-25 at 17.47.37.png
Như vậy là chức năng push notification foreground đã hoạt động.

b. Background

Để push notification từ background, cần phải có service workers
Ở thư mục gốc, tạo file firebase-messaging-sw.js với nội dung sau:

//src/firebase-messaging-sw.js
import {firebaseConfig} from './src/firebase'
if ('serviceWorker' in navigator) {
  const firebaseConfigParams = new URLSearchParams(firebaseConfig).toString();
  navigator.serviceWorker
    .register(`../firebase-messaging-sw.js?${firebaseConfigParams}`)
    .then(function (registration) {
      console.log('Registration successful, scope is:', registration.scope);
    })
    .catch(function (err) {
      console.log('Service worker registration failed, error:', err);
    });
}

Ở file này mình sẽ register 1 service worker với params là firebaseConfig được import từ constants mà mình trình bày ở phần a.
Trong thư mục public, mình tạo 1 file firebase-messaging-sw.js với nội dung sau:

//public/firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js');

self.addEventListener('fetch', () => {
  const urlParams = new URLSearchParams(location.search);
  self.firebaseConfig = Object.fromEntries(urlParams);
});

const defaultConfig = {
  apiKey: true,
  projectId: true,
  messagingSenderId: true,
  appId: true,
};

firebase.initializeApp(self.firebaseConfig || defaultConfig);
if (firebase.messaging.isSupported()) {
  const messaging = firebase.messaging();
  const channel = new BroadcastChannel('notifications');
  messaging.onBackgroundMessage(function (payload) {
    //can not console.log here
    channel.postMessage(payload);
  });
}

Đây chính là file service worker. File này sẽ initilize Firebase app và hứng message từ background.
Sau khi nhận được background message, mình sẽ muốn console.log ra để xem. Tuy nhiên do luồng service worker chạy độc lập nên sẽ không thể console.log ở trong file này.
Mình sẽ dùng BroadcastChannel để giao tiếp giữa luồng service worker và luồng web:

...
const channel = new BroadcastChannel('notifications');
...
channel.postMessage(payload);

Tiếp theo là ở luồng web, mình nhận data từ BroadcastChannel và console.log ra thôi.
Tại file App.tsx, mình thêm đoạn code vào useEffect đầu tiên, sau khi thêm thì nó sẽ như thế này:

//src/App.tsx
...
useEffect(() => {
    getMessagingToken();
    const channel = new BroadcastChannel("notifications");
    channel.addEventListener("message", (event) => {
      console.log("Receive background: ", event.data);
    });
  },[])
...

Như vậy là trang web đã sẵn sàng nhận background message. Cùng thử xem nhé!
Ở trình duyệt, mình mở sang tab khác, sau đó test push message như ở phần a, và đây là kết quả khi mình quay lại tab trang Web:
Screen Shot 2022-08-26 at 11.23.18.png
Vừa rồi mình đã trình bày chức năng push notification sử dụng Firebase Cloud Messaging (mang tính demo cho phần frontend).
Trong hệ thống thực tế, thường thì chúng ta sẽ lưu registration token dưới database và khi có event thì bắn event sang Firebase thông qua server backend của hệ thống (chẳng hạn như NodeJS thì thường sử dụng firebase-admin)
Cảm ơn mọi người đã đọc bài viết!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.