FE

React PWA에서 푸시 알림 구현하기: 사용자 경험을 한 단계 업그레이드하세요

올바른생활부터 2024. 12. 29. 13:04
728x90
반응형
SMALL
목차

1. 배경

2. 개발 과정

3. 결과

1. 배경

사용자들에게 REracle 서비스의 업데이트 및 공지사항을 실시간으로 받아볼 수 있는 알림 기능을 제공하는 것이 필요했었습니다. 그래서 PWA의 기능인 PUSH 알림 기능을 구현하게된 내용을 소개해드리겠습니다.

제가 만든 REracle 서비스에서 push 알림을 코드를 참고할 수 있습니다.

2. 개발 과정

  1. PWA 구현
    • service-worker.js 파일을 생성하여 PWA의 기본 기능을 구현하여 오프라인 지원, 앱 설치 기능 등 PWA의 핵심 기능을 추가했습니다.
  2. 푸시 알림 구현
    • firebase-messaging-sw.js 파일을 생성하여 백그라운드 메시지 처리를 구현하였고, NotificationComponent를 만들어 포그라운드 알림 수신 및 권한 요청 로직을 구현하여 웹, 앱에 처음 들어갔을 때 Notification API 알림 수신 및 권한 요청 메시지가 요청되도록 했습니다.
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="src/assets/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Recycle</title>

    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap"
      rel="stylesheet"
    />
    <link
      rel="apple-touch-icon"
      href="/apple-touch-icon-180x180.png"
      sizes="180x180"
    />
    <link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF" />
    <meta name="theme-color" content="#ffffff" />
  </head>
  <body>
    <div id="root"></div>
    <script src="/firebase-messaging-sw.js"></script>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

2.1 Service Worker 등록

// /public/service-worker.js
// 서비스 워커 등록 코드 예시

function registerServiceWorker() {
  if (typeof window !== "undefined") {
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker
        .register("/firebase-messaging-sw.js")
        .then((registration) => {
          console.log("Service Worker Registered");
          console.dir(registration);
        });
    }
  }
}
registerServiceWorker();

  • PWA로 웹/앱을 구현했다고 가정한 후 firebase-messaging-sw.js 파일을 생성하여 Firebase Cloud Messaging을 위한 서비스 워커를 등록해줍니다.
  1. **typeof window !== "undefined"**로 브라우저 환경인지 확인합니다.
  2. **"serviceWorker" in navigator**로 브라우저가 서비스 워커를 지원하는지 확인합니다.
  3. **navigator.serviceWorker.register()**를 사용하여 /firebase-messaging-sw.js 파일을 서비스 워커로 등록합니다.
  4. 등록이 성공하면 콘솔에 메시지를 출력하고 등록 객체의 상세 정보를 표시합니다.

2.2 Firebase 서비스 워커 구현

// /public/firebase-messaging-sw.js

importScripts(
  "<https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js>"
);
importScripts(
  "<https://www.gstatic.com/firebasejs/9.6.1/firebase-messaging-compat.js>"
);

const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: "",
};
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
  const title = payload.notification.title + " (onBackgroundMessage)";
  const notificationOptions = {
    body: payload.notification.body,
    icon: "/REracle.svg",
  };

  self.registration.showNotification(title, notificationOptions);
});

self.addEventListener("notificationclick", function (event) {
  event.notification.close();

  const redirectUrl = event?.notification?.data?.redirectUrl;

  event.waitUntil(
    clients
      .matchAll({
        type: "window",
      })
      .then(function (clientList) {
        for (const client of clientList) {
          if (client.url === redirectUrl && "focus" in client) {
            return client.focus();
          }
        }
        if (clients.openWindow) {
          return clients.openWindow(redirectUrl);
        }
      })
  );
});
  • 이 코드에서는 Firebase 앱과 메시징 서비스를 초기화합니다. **importScripts**를 사용하여 필요한 Firebase 스크립트를 가져오고, 설정 객체로 Firebase 앱을 초기화한 후 메시징 인스턴스를 생성합니다.

2.3 Firebase 설정

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";

const firebaseConfig = {
	// Firebase 설정 정보
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const storage = getStorage(app);

export { app, auth, db, storage };

2.4 Notification Component 작성

// /components/NotificationComponent

import { useEffect } from "react";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { app, auth, db } from "@/firebase";
import { doc, setDoc } from "firebase/firestore";

const NotificationWebApi = () => {
  useEffect(() => {
    const messaging = getMessaging(app);

    /**
     * @description 알림 권한 요청
     * @description Firestore에 유저의 FCM Token 저장 코드 추가
     */
    const requestPermission = async () => {
      const permission = await Notification.requestPermission();
      if (permission === "granted") {
        const token = await getToken(messaging, {
          vapidKey:
            ".....", // 생성된 VAPID 키 사용
        });
        console.log("FCM Token:", token);
        const user = auth.currentUser;
        if (user) {
          const userDoc = doc(db, "users", user.uid);
          await setDoc(userDoc, { fcmToken: token }, { merge: true });
        }
      }
    };

    /**
     * @description 포그라운드 알림 수신
     */
    onMessage(messaging, (payload) => {
      console.log("Message received. ", payload);
      if (
        payload.notification &&
        payload.notification.title &&
        payload.notification.body
      ) {
        new Notification(payload.notification.title, {
          body: payload.notification.body,
          icon: "/icons/icon-96.png",
        });
      }
    });

    requestPermission();
  }, []);

  return <div></div>;
};

export default NotificationWebApi;

FCM 토큰 관리

  • requestPermission 함수에서는 먼저 사용자로부터 알림 권한을 요청합니다. 권한이 부여되면, FCM 토큰을 생성합니다.
  • 회원가입된 해당 사용자의 Firestore 에 FCM 토큰을 저장합니다.

포그라운드 메시지 처리

  • onMessage 리스너를 설정하여 앱이 활성 상태일 때 수신되는 메시지 처리합니다.
  • 메시지에 알림 정보(제목과 내용)가 포함되어 있다면, 브라우저의 Notification API를 사용하여 알림을 표시합니다.
import { useEffect } from "react";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import "@shoelace-style/shoelace/dist/themes/light.css";
// import Loading from "./pages/Loading";
import { routes } from "./router/routes";
import { saveWasteCategories } from "./lib/utils/firestoreService";
import NotificationComponent from "./components/NotificationComponent";

const router = createBrowserRouter(routes);

const App = () => {
  useEffect(() => {
    saveWasteCategories();
  }, []);

  return (
    <>
      <div className="absolute top-1/2 left-1/2  transform -translate-x-1/2 -translate-y-1/2 flex flex-col w-full max-w-[440px] h-full max-h-[920px] bg-white">
        <RouterProvider router={router} />
        <NotificationComponent />
      </div>

    </>
  );
};

export default App;

  • App 컴포넌트에 **NotificationComponent**를 추가함으로써, 푸시 알림 기능을 전체 애플리케이션에 걸쳐 사용할 수 있게 됩니다.

3. 결과

Firebase Cloud Messaging 홈페이지에 들어가서 메시지를 보내면 PWA로 푸시 알림 시스템을 통해 사용자들은 REracle 서비스 주요 업데이트 및 공지사항을 실시간으로 받아볼 수 있습니다.

밑의 사진은 제가 test메시지를 푸시 알림으로 웹과 앱에 알림을 보낸 것을 확인할 수 있습니다.

참고

728x90
반응형
LIST