웹 푸시알림 적용하기(with.FCM)

김상민·2024년 2월 20일
4

PWA

목록 보기
1/1

작년에 했던 프로젝트에서 실패했던 web push 알림을 드디어 성공시켰다. 어떻게 동작하는지를 이해하고 헷갈리는 부분만 넘어가니까 쉽게 할 수 있었다.
블로그들을 찾아봐도 구버전 세팅 방법만 나오고 원리를 알 수 없어서 공식문서를 정말 열심히 봤다.

📂 FCM이란?


Firebase Cloud Messaging(FCM)은 메시지를 안정적으로 무료 전송할 수 있는 크로스 플랫폼 메시징이다.
웹 푸시알림을 사용하기 위해서는 FCM을 이용한다.
클라이언트 앱을 사용하는 기기가 알림을 허용하면 고유한 토큰을 FCM에서 발급받고 이를 통해 FCM에 등록하면 FCM을 통해 메시지를 수신할 수 있다.
즉, FCM은 서버의 요청에 따라 클라이언트에게 메시지를 보낼 수 있도록 하는 중간관리자라고 생각하면 된다.

FCM 세팅하기

1. 프로젝트 만들기

먼저 firebase로 가서 콘솔로 이동하여 그리고 프로젝트 추가를 누르고 시키는데로 하면 된다.
이 글은 firebase v.10.8.0으로 진행했다.

2. SDK 추가

이 부분도 시키는 데로 붙여넣기를 하면 되는데 파일명은 아무거나 하면 되고 위치도 꼭 루트 디렉토리일 필요 없다. src 안에 그냥 하나 만들자.

// src/service/initFirebase.js
import { initializeApp } from "firebase/app";

const firebaseConfig = {
    apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
    authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
    projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
    storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
    appId: import.meta.env.VITE_FIREBASE_APP_ID,
    measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID
};

export const app = initializeApp(firebaseConfig);

그리고 config안의 키 들을 환경변수로 설정한다.
이렇게 하면 app이 firebaseConfig에 따라 초기화된다.
import { initializeApp } from "firebase/app";
firebase 9 버전 이상이라면 꼭 이 코드로 진행하면 되고 8 버전 이하는 코드가 조금 달라진다. 공식문서의 모듈 api가 9버전 이상에서 동작하는 코드다. firebase github snippet은 아직 8버전 코드로 되어있다.

3. Notification 권한 받기

사용자에게 알림을 허용 받아서 고유 토큰을 발급받는 단계다.
이때 먼저 vapid key라는 것을 발급받아야한다.
1. 프로젝트 들어가기
2. 설정 아이콘 > ‘프로젝트 설정’ 들어가기
3. ‘클라우드 메시징’ 들어가기
4. ‘웹 구성’에서 Generate key pair 버튼 누르기

검은색으로 가린 부분이 vapid key이다.
직접 복사해야함! 키쌍 보기 하면 안됨!(여기서 삽질 조금 했음..🥲)
vapid key도 env에 넣고 사용한다. 그리고 서버 개발자에게도 알려줘야함

// src/service/notificationPermission.js
import { getToken } from "firebase/messaging";
import { sendTokenToServer } from "./api";
import { registerServiceWorker } from "./registerServiceWorker";

export async function handleAllowNotification() {
    registerServiceWorker(); // 나중에 설명
    try {
        const permission = await Notification.requestPermission();

        if (permission === "granted") {
            const token = await getToken(messaging, {
                vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY
            });
            if (token) {
                sendTokenToServer(token);// (토큰을 서버로 전송하는 로직)
            } else {
                alert(
                    "토큰 등록이 불가능 합니다. 생성하려면 권한을 허용해주세요"
                );
            }
        } else if (permission === "denied") {
            alert(
                "web push 권한이 차단되었습니다. 알림을 사용하시려면 권한을 허용해주세요"
            );
        }
    } catch (error) {
        console.error("푸시 토큰 가져오는 중에 에러 발생", error);
    }
}

handleAllowNotification 이라는 함수를 만들어서 로그인 한 유저의 첫 화면에서 토큰 발급을 요청하도록 했다.
토큰을 출력해서 복사해두면 fcm에서 알림 받기 테스트를 해볼 수도 있다.

📨 메세지 수신 설정

먼저 메시지를 수신할 때 두가지 버전이 있다는 걸 잘 이해해야 헤메지 않는다.
앱이 켜져 있을 때 메시지를 수신하는 foreground message, 그리고 앱이 백그라운드에 있을 때 메시지를 수신하는 background message가 있다.

foreground message

foreground의 경우에는 만들고 있는 서비스 코드에서 동작하기 때문에 그 전처럼 src에서 설정을 한다.

// src/service/foregroundMessage.js
import { getMessaging, onMessage } from "firebase/messaging";
import { app } from "./initFirebase";

const messaging = getMessaging(app);

onMessage(messaging, (payload) => {
    // console.log("알림 도착 ", payload);
    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body
    };

    if (Notification.permission === "granted") {
        new Notification(notificationTitle, notificationOptions);
    }
});

background message

background는 service worker에 의해서만 컨트롤 된다.
service worker 파일은 항상 public 폴더에 있어야한다.
firebase 문서에는 이 부분에 대한 설명이 부족해서 한참을 헤맸다..
네임스페이스(8버전 이하)코드를 보면 아래와 같이 사용할 수 있는 코드가 나온다.

// /public/firebase-messaging-sw.js
importScripts(
    "https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js"
);
importScripts(
    "https://www.gstatic.com/firebasejs/10.8.0/firebase-messaging-compat.js"
);

self.addEventListener("install", function (e) {
    self.skipWaiting();
});

self.addEventListener("activate", function (e) {
    console.log("fcm service worker가 실행되었습니다.");
});

const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
};

firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
    const notificationTitle = payload.title;
    const notificationOptions = {
        body: payload.body
        // icon: payload.icon
    };
    self.registration.showNotification(notificationTitle, notificationOptions);
});

public 폴더에서는 모듈이 동작하지 않기 때문에 일반적인 import는 할 수 없고 import script를 이용한다. 그리고 env도 사용할 수 없기 때문에 string으로 직접 넣어준다.
여기서도 한 번 더 app을 초기화 해줘야 동작하는데 이 부분도 문서에는 없어서 에러코드를 보면서 하나씩 고치다가 알게되었다.
아마 더 좋은 방법이 있을수도..

vite에서 pwa plugin을 설치하면 서비스 워커 코드도 src에서 다룰 수 있는 것 같은데 아직 안해봐서 잘 모르겠다.

그리고 위의 코드를 실행시켜 주는 서비스 워커를 등록해준다..

export function registerServiceWorker() {
    if ("serviceWorker" in navigator) {
        window.addEventListener("load", function () {
            navigator.serviceWorker
                .register("/firebase-messaging-sw.js")
                .then(function (registration) {
                    console.log(
                        "Service Worker가 scope에 등록되었습니다.:",
                        registration.scope
                    );
                })
                .catch(function (err) {
                    console.log("Service Worker 등록 실패:", err);
                });
        });
    }
}

좀 찾아보면 직접 script 코드에 넣어서 서비스가 시작될 때 동작하도록 하라는데 나는 로그인 된 사용자에게만 push 알림을 보낼거라 알림 권한 설정할 때 실행되도록 했다. (나중에 바꿀 수도..😁)

이렇게 하고 나서 백그라운드 앱에 테스트 메시지 보내기 문서를 참고해서 테스트 메시지를 보내면 이렇게 알림이 오는 걸 볼 수 있다.

회고

PWA에 관심을 갖고 부터 알고는 있었지만 직접 만드는 것에 막연한 두려움이 있었는데 문서를 차근차근 읽으면서 하다보니 생각보다 별거 아니라는 것을 느꼈다.
다음에는 PWA로 만들어서 진짜 앱처럼 동작하게 만들어 볼 생각이다.
그리고 service worker가 어떻게 동작하는지도 조금 더 알아보면 좋을 것 같다.

참고

https://firebase.google.com/docs/cloud-messaging/js/client?hl=ko
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

profile
성장하는 웹 프론트엔드 개발자 입니다.

0개의 댓글