FCM을 이용한 브라우저 Push Notification 구현 (2) - Push Notification 구현하기

부루베릐·2023년 4월 6일
1

TIL

목록 보기
8/20

전편에서는 Push Notification 기능이 어떻게 동작하는지 살펴보았다. 알림 기능이 과정을 거쳐 진행되는지 대략적으로 알 수 있었으므로, 이제는 직접 우리 서비스에 적용해보기로 하자. 이를 위해 우리는 FCM을 사용할 것이다.


FCM(Firebase Cloud Messaging) 사용

FCM(참조)은 파이어베이스가 제공하는 메시지를 안정적으로 무료 전송할 수 있는 크로스 플랫폼 메시징 솔루션이다.

왜 파이어베이스 및 FCM을 사용하는가?

파이어베이스는 구글에서 제공하는 모바일 어플리케이션 개발 플랫폼이다. 파이어베이스에서 제공하는 기능들에 대해서는 개발자들이 사소한 부분들까지 신경쓰지 않고도 개발이 가능하다. 인증, 데이터베이스, 푸시 메세지 등, 아래의 이미지에 파이어베이스가 제공하는 기능들이 나열되어 있다.

파이어베이스라는 플랫폼은 개발자들이 편리하게 기능 개발을 할 수 있도록 도와준다. 우리가 푸시 알림 기능을 개발하기 위해선 백엔드 서버와 클라이언트 사이에서 푸시 알림을 전송해주는 역할을 하는 푸시 서비스가 필요하다. 다행히도 파이어베이스는 Cloud Messaging(FCM, Firebase Cloud Messaging)를 통해 푸시 서비스에게 웹 푸시 프로토콜을 보내는 작업을 도와주고, 각 사용자 플랫폼을 구별하여(Android, iOS, 웹 등) 푸시 알림을 각 플랫폼에 적합하게 가공하여 보내주기도 한다. 즉 FCM를 사용하면 좀 더 편리하게 푸시 알림 작업을 할 수 있으므로, 우리도 FCM를 우리 서비스에 적용하기로 했다.

FCM의 구성 요소

  1. 메세지 작성 및 구현 도구
    • 우리 백엔드 서버에서 메세지를 작성하고 구현할 것이다. 그리고 FCM에게 웹 푸시 POST를 요청한다.
  2. FCM 백엔드
    • 백엔드 서버에서 보낸 메세지 요청을 수락한다. 그 후 메세지 ID와 같은 메시지 메타데이터를 생성하여 푸시 알림을 클라이언트에 전달한다.
  3. 플랫폼 수준 전송 레이어
    • 메세지 전송 전에 메세지를 라우팅해서 전송을 처리한다. 만약 필요하다면 클라이언트의 플랫폼 별 구성을 적용하여 각 플랫폼에 맞춰 메세지를 전송할 수 있도록 조치한다. Android 기기용 전송 레이어나 Apple 기기용 푸시 알림 서비스, 웹 앱용 웹 푸시 프로토콜 등이 이 전송 레이어에 포함되어 있다.
  4. 사용자 기기의 FCM SDK
    • 알림이 표시되거나, 사용자 어플리케이션 로직에 따라서 메세지가 처리된다. 나는 프론트엔드 개발자로서 이 부분에 집중하여 구현을 할 것이다.



구현

Push Notification이 어떤 과정을 거쳐 진행되는지 대략적으로 알 수 있었으므로, 이제는 직접 우리 서비스에 적용해보기로 하자. FCM을 사용하여 어떻게 구현할 수 있을까?

세팅

Nuxt에서 Firebase의 세팅은 다음 docs(Nuxt Firebase)에 꽤 자세하게 나와 있다. 이 docs를 참고하여 작업을 진행하였다. 일단 파이어베이스 내에서 프로젝트를 세팅한 다음, 우리 서비스의 클라이언트 코드에서 파이어베이스를 사용할 수 있도록 설정해보자.

파이어베이스 자체 세팅(공식 문서 참고)

  1. Firebase Console에서 Firebase 프로젝트를 생성한다.

  2. Firebase 프로젝트 내 앱을 만든다.

  1. 파이어베이스 웹 앱 사용자 인증 정보를 구성한다.
    a. 앞서 살펴 보았던 VAPID(Voluntary Application Server Identification)를 만들기 위해 웹 푸시 인증서 항목의 공개 키/비공개 키 쌍을 만든다. 이 때 클라이언트에서도 파이어베이스 SDK 세팅 시 공개 키를 필요로 하니 기억하고 있자.
b. 이 인증 정보를 통해 어플리케이션에서 푸시 알림을 구독할 수 있다.

프론트 코드 내 파이어베이스 SDK 세팅

파이어베이스와 Nuxt 전용 파이어베이스 라이브러리 설치

npm i firebase @nuxtjs/firebase

nuxt.config.js에 firebase SDK 세팅하기

export default {
  modules: [
    // ...
    '@nuxtjs/firebase',
  ],
  firebase: {
    config: {
      apiKey: '<apiKey>',
      authDomain: '<authDomain>',
      projectId: '<projectId>',
      storageBucket: '<storageBucket>',
      messagingSenderId: '<messagingSenderId>',
      appId: '<appId>',
      measurementId: '<measurementId>',
    },
    services: {
      messaging: {
        fcmPublicVapidKey: '<publicVapidKey>',
      },
    },
  },
}

구독할 FCM 어플리케이션에 대한 정보를 config에 기입한다. 그리고 services 옵션에 messaging 속성을 작성하고 공개 VAPID키를 등록한다. 이를 통해 FCM 어플리케이션과의 연결은 물론 서버 어플리케이션에서 알림을 보낼 때 나를 특정지어줄 수도 있다.

기본적인 세팅은 끝났다. 이제 서비스 워커를 통해서 알림을 받아보도록 하자.


백그라운드 알림 받기 - 서비스 워커 작성하기

FCM이 사용할 서비스 워커는 꼭 firebase-messaging-sw.js라는 이름으로 작성되어야 한다. 서비스 워커 안에서 우리는 파이어베이스 어플리케이션을 초기화하고, 백그라운드 알림 수신 시 알림을 커스텀하도록 한다.

// static/firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/9.17.1/firebase-app-compat.js')
importScripts('https://www.gstatic.com/firebasejs/9.17.1/firebase-messaging-compat.js')

firebase.initializeApp({
  apiKey: '<apiKey>',
  authDomain: '<authDomain>',
  projectId: '<projectId>',
  storageBucket: '<storageBucket>',
  messagingSenderId: '<messagingSenderId>',
  appId: '<appId>',
  measurementId: '<measurementId>'
})

const messaging = firebase.messaging()

messaging.onBackgroundMessage(payload => {
  console.log(
    '[firebase-messaging-sw.js] Received background message ',
    payload,
  )
})

이 코드는 서비스 워커 안에서 파이어베이스 SDK를 가져와 파이어베이스 앱을 초기화해주는 작업을 해 주는 코드이다. 실제로 우리가 nuxt.config.js에서 세팅해준 config 값들이 firebase.initializeApp 메서드의 인자로 들어가 있는 것을 알 수 있다.

// @/serviceWorker.js
messaging.onBackgroundMessage(payload => {
  console.log(
    '[firebase-messaging-sw.js] Received background message ',
    payload,
  )
})

원래는 아래의 코드로 푸시 알림을 커스텀해줄 수 있지만 @nuxtjs/firebase을 사용할 때는 자동으로 처리해준다. 따라서 알림 커스텀 로직을 내가 직접 만들면 사용자에게 같은 알림이 두 번이나 전송되므로 아래 로직은 참고만 해도 될 듯.

// 직접 알림을 커스텀하여 display하는 로직. @nuxtjs/firebase 사용 시에는 필요하지 않다.
onBackgroundMessage(messaging, payload => {
  const notificationTitle = '백그라운드 푸시 알림 제목'
  const notificationOptions = {
    body: '백그라운드 푸시 알림 내용',
    icon: '/firebase-logo.png',
  }

  self.registration.showNotification(notificationTitle, notificationOptions)
})

푸시 알림 구독하고 토큰 서버에 보내기

자, 서비스 워커에서 파이어베이스 어플리케이션을 초기화했으므로, 이제 클라이언트 코드에서 파이어베이스 메세지 인스턴스 $fire 전역변수 내 messaging 속성을 통하여 접근할 수 있다.

this.$fire.messaging

이 객체를 살펴보면 다음과 같은 구성 요소를 갖는 것을 알 수 있는데,

클라이언트 사용자가 알림 구독을 완료했다면 파이어베이스 앱으로부터 token을 받아 이 토큰을 백엔드 서버에 넘겨주어야 한다. 만약 백엔드 서버가 A라는 유저에게 푸시 알림을 보내고 싶다면 A 유저의 토큰을 가지고 있어야 사용자를 특정할 수 있기 때문이다.

Nuxt의 layout 코드의 mounted 로직에서 푸시 알림 구독을 구현하기로 하자.

async mounted() {
  try {
    await Notification.requestPermission()
    this.fcmToken = await this.$fire.messaging.getToken()
  } catch (err) {
    console.error(err)
  }

  // API 호출을 통해서 토큰 서버로 넘기기
  axios.POST('/fcm/token', {
    token: this.fcmToken,	
  })
},

Notification.requestPermission()을 통해 사용자 브라우저에서 푸시 알림 설정이 어떻게 되어 있는지를 파악한다. 만약 granted가 아니라면 에러를 호출하기 때문에 따로 에러 핸들링은 하지 않았다(필요할 듯 하다).

await Notification.requestPermission()

사용자에게 알림 권한을 요청하는 메세지가 온다. 여기서 허용을 누르면 푸시 알림이 granted로 되고, 만약 granted로 되어 있다면 getToken() 메서드를 통해 파이어베이스 앱에서 token을 받아올 수 있다.

const currentToken = await this.$fire.messaging.getToken()

이제 이 토큰을 서버에 보내어 서버에서 이 사용자에게 푸시 알림을 보내고 싶을 때 사용할 수 있도록 한다.


foreground에서도 알림 display하기

앞서 serviceWorker.js에서 클라이언트 서비스가 background에 있을 때 알림을 보여줄 수 있도록 커스텀할 수 있다고 하였다. 만약 이 서비스가 활성화되어 있을 때, 즉 foreground에서 작동할 때도 알림을 보여주기 위해서는 다음과 같이 onMessage() 메서드 안에서 Notification API를 사용하여 직접 제작하여야 한다.

methods: {
  async handlePushNotificationOnForeground() {
    this.$fire.messaging.onMessage(payload => {
      const notificationTitle = payload.notification.title
      const notificationOptions = {
        body: payload.notification.body,
        image: payload.notification.image,
        icon: payload.notification.icon,
      }
      const notification = new Notification(
        notificationTitle,
        notificationOptions,
      )
      notification.onclick = function (event) {
        event.preventDefault()
        console.log('notification clicked!')
        notification.close()
      }
    })
  },
}

마무리

이제 인하우스 서비스에서 답글을 입력하면 푸시 알림을 받을 수 있다!

물론 백엔드 단에서 이루어지는 로직들도 굉장히 많지만, 일단 여기까지가 프론트엔드 개발자로서 내가 구현한 로직들이다.

사실 완벽한 구현이라고 하기는 어렵다. 그 중 토큰 관리가 미흡한 편이다. 지금은 서버에서 토큰을 저장하지 않고 매번 클라이언트가 로그인 할 때 새로 보내주고 있다. 서버에 토큰을 저장하고 리프레시 등의 관리를 해야 좀 더 제대로 된 알림 구현이 되지 않을까 한다.

이것 이외에도 앞으로 남은 과제는 다음과 같다.

  • 사용자가 아예 오프라인일 때 받은 알림들을 온라인되었을 때 보여주기
  • 알림 권한이 granted가 아닐 때의 에러 핸들링

0개의 댓글