본문 바로가기
공부/프로그래밍

[React, PWA] 클라이언트에서 웹 푸시(fcm) 설정하기

by demonic_ 2021. 7. 28.
반응형

우선 PWA 에 대해 간단히 알고 넘어가자

PWA(Progressive Web App) 란?

구글 개발자 컨퍼런스인 I/O 2016에 발표된 기술

웹에서 네이티브 앱과 같은 동작을 가능하게 함

홈 화면에 추가 및 푸시 알림, 그리고 캐싱으로 로딩시간 단축 및 성증을 향상시킬 수 있음

PWA를 사용하기 위해서는 Service worker API가 등록되어 있어야 함

 

이번 포스팅은 PWA중 메세지수신(웹푸시) 기능을 알아보려 한다. 클라이언트에서 Token을 생성하고 이것을 서버에서 발송하게 하는게 가장 이상적이지만 이번에는 테스트 용도이니 POSTMAN과 같은 API툴로 전송을 대체한다.

 

 

웹푸시를 사용하기 위해선 우선 2가지 개념을 알아야하는데, 웹 앱이 포그라운드 상태일때와 백그라운드 상태일때의 메세지 처리다. 메시지(푸시)에서 중요한건 앱이 켜져있거나 꺼져있을때로 나뉘는데 이를 포그라운드, 백그라운드라고 한다. 개발 문서를 쭉 봤는데 경험상 느낌은 웹사이트가 열려있고 탭이 활성화 되어 있다면 onMessage 를, 웹사이트가 닫혀있거나 열려있지만 다른 탭을 보고 있다면 onBackgroundMessage를 사용한다. 참고로 onBackgroundMessage 는 Service Worker 에서 작동하게 하는 것이다.

 

자세한 사항은 다음 사이트에서 확인할 수 있다.

https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ko

 

 

간단한 샘플은 다음 github을 확인할 수 있다.

https://github.com/firebase/quickstart-js/tree/master/messaging

 

GitHub - firebase/quickstart-js: Firebase Quickstart Samples for Web

Firebase Quickstart Samples for Web. Contribute to firebase/quickstart-js development by creating an account on GitHub.

github.com

 

 

그럼 우선 시스템 기반은 Firebase 이기 때문에 구글의 Firebase에 프로젝트를 생성해야 한다.

 

 

 

# firebase 에서 프로젝트 생성하기

아래 사이트로 이동한다

https://console.firebase.google.com/

 

로그인 - Google 계정

하나의 계정으로 모든 Google 서비스를 Google 계정으로 로그인

accounts.google.com

 

 

웹 앱을 생성한다

 

애널리틱스를 사용설정하면 다음 단계가 있고, 단순 푸시만 이용할 것이라면 꺼도 된다. 그래도 구글애널리틱스는 여러모로 유용하니 on 해둔다.

 

프로젝트 만들기를 클릭해 마무리한다

 

생성되는데 약간의 시간이 필요하다. 다 되면 다음 화면처럼 완료된다.

 

다음 화면이 메인에서 보일텐데 여기서 웹 앱을 선택한다. 빨갛게 표시한게 웹 이다.

 

다음처럼 앱을 등록하여 생성을 클릭한다.

 

완료가 되면 SDK가 생성되는데 다음처럼 완료된다.

아래 스크립트 내용을 메모장 같은곳에 복사해둔다. 추후 웹 코드에 추가해야 한다

 

콘솔로 이동하여 메인으로 이동한다.

화면에서 톱니바퀴 아이콘을 클릭 -> 프로젝트 설정으로 이동한다

 

설정화면을 들어오면 아래 내 앱 이라고 표시된 곳에 방금 표기된 SDK가 있다.

위에서 복사하지 못했더라면 여기서 복사하면 된다.

 

그럼 이제 PWA를 등록할 사이트에 들어가서 script를 등록한다

위에것에는 message(푸시)관련된건 없다. 그래서 다음도 같이 추가한다.

내 경우 react + nextjs 를 사용하고 있기 때문에 _document.js 에다가 위 스크립트를 추가했다.(각자 상황에 맞게 등록하면 된다)

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/8.8.0/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/8.8.0/firebase-analytics.js"></script>

<!-- message(push) 추가 -->
<script src="https://www.gstatic.com/firebasejs/8.8.0/firebase-messaging.js"></script>

 

 

그럼 이제 APP을 등록하는 작업을 하자.

다행히 이것을 등록하기 쉽게 도와주는 오픈소스가 있다. 다음을 설치한다.

npm install firebase --save

참고:

https://www.npmjs.com/package/firebase

 

firebase

Firebase JavaScript library for web and Node.js

www.npmjs.com

 

_app.js 파일을 들어가 다음 코드를 추가한다.

import firebase from 'firebase';
...

// firebase => 현재 푸시용으로 사용
const config = {
    apiKey: "AIzaSyCJUAJq7b82PDFkgbDJkOvXR-KrnYUovqU",
    authDomain: "test-pwa-763bf.firebaseapp.com",
    projectId: "test-pwa-763bf",
    storageBucket: "test-pwa-763bf.appspot.com",
    messagingSenderId: "534393287778",
    appId: "1:534393287778:web:c6a81ffcaaaba9f2798d3b",
    measurementId: "G-7S1P3VZB0N"
}
if(!firebase.apps.length) {
    firebase.initializeApp(config);
} else {
    firebase.app()
}

 

코드를 보면 분기를 태워 initializeApp() 와 app() 을 호출하는데 이것은 다음 에러 때문에 그렇다.

error - FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
at Object.initializeApp (/Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/node_modules/@firebase/app/dist/index.node.cjs.js:389:33)
at eval (webpack-internal:///./src/pages/_app.js:110:50)
at Module../src/pages/_app.js (/Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/.next/server/static/development/pages/_app.js:489:1)
at __webpack_require__ (/Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/.next/server/static/development/pages/_app.js:23:31)
at Object.0 (/Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/.next/server/static/development/pages/_app.js:1212:18)
at __webpack_require__ (/Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/.next/server/static/development/pages/_app.js:23:31)
at /Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/.next/server/static/development/pages/_app.js:91:18
at Object.<anonymous> (/Users/dgpark/git/side-project/kjob/kjob-cor/kjob-frontend/.next/server/static/development/pages/_app.js:94:10)

 

이미 앱이 존재하는데 다시 초기화(initializeApp)을 호출해서 그렇다.

이제 다른 컴포넌트에서 import firebase 를 이용해 사용할 수 있다.

 

 

 

 

 

 

# Service Worker 설정하기

서비스 워커는 브라우저의 백그라운드에서 실행하는 스크립트로써 웹에 접속하지 않을때 또는 다른 탭을 보고 있을 때 여기에 있는 설정을 참고하여 알림을 호출한다.

 

파일의 기본 이름은 firebase-messaging-sw.js 이다. 관련내용은 위에서 링크한 Firebase 문서에 기재되어 있다.

https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ko

 

자바스크립트 클라이언트에서 메시지 수신  |  Firebase

메시지의 동작은 페이지가 포커스를 갖는 포그라운드 상태인지, 백그라운드 상태인지, 다른 탭 뒤에 숨겨져 있는지, 완전히 닫혀 있는지에 따라 다릅니다. 어떠한 경우든 페이지는 onMessage 콜백

firebase.google.com

 

그럼 firebase-message-sw.js 파일을 다음처럼 생성하자.

nextjs 를 쓰고 있기 때문에 정적파일을 관리하는 public 폴더에 위치해두었다.

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

const config = {
    apiKey: "AIzaSyCJUAJq7b82PDFkgbDJkOvXR-KrnYUovqU",
    projectId: "test-pwa-763bf",
    messagingSenderId: "534393287778",
    appId: "1:534393287778:web:c6a81ffcaaaba9f2798d3b"
}
firebase.initializeApp(config);


const messaging = firebase.messaging()

// 이 아래 부분을 설정하는 곳이 종종 보이는데 이걸 하면 같은 알림이 2개 온다.
// messaging.onBackgroundMessage(function (payload) {
//     console.log('[firebase-messaging-sw.js] onBackgroundMessage ', payload)
//     const title = payload.notification.title
//     const options = {
//         body: payload.notification.body,
//         icon: payload.notification.image
//     }
//     return self.registration.showNotification(title, options)
// })

 

생성한 뒤에 localhost:[설정한포트번호]/firebase-message-sw.js 를 입력해서 위 코드가 브라우저에 나오는지 확인해보자. 만약 제대로 설정되어 있지 않아 아무것도 안나온다면 다음과 같은 에러가 발생한다.

FirebaseError: Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker for scope ('http://localhost:5000/firebase-cloud-messaging-push-scope') with script ('http://localhost:5000/firebase-messaging-sw.js'): A bad HTTP response code (404) was received when fetching the script. (messaging/failed-service-worker-registration).
at WindowController.eval (index.esm.js?8071:1451)
at step (tslib.es6.js?0eae:102)
at Object.eval [as throw] (tslib.es6.js?0eae:83)
at rejected (tslib.es6.js?0eae:74)

 

서비스워커가 등록되어 있지 않는다면 토큰을 발급받을때부터 에러를 발생시킨다. 그러니 반드시 등록, URL 로 확인하고 다음으로 넘어가야한다.

 

 

 

 

 

# 토큰 발급받기

이제 토큰을 호출, 발급하는 것을 추가해야 하는데 그전에 Service Worker를 설정해야 한다.

내 경우 메세지 호출 & 토큰발급하는 파일을 새로 생성했다.

FirebaseInit.ts

import firebase from "firebase";

export async function getToken() {
    if(firebase.messaging.isSupported() === false){
        console.log("isSupported: ",  firebase.messaging.isSupported())
        return null;
    }

    const messaging = firebase.messaging()
    const token = await messaging.requestPermission()
        .then(function() {
            return messaging.getToken()
        })
        .then(function(token) {
            return token;
        })
        .catch(function(err) {
            console.debug('에러 : ', err);
            return null;
        })

    console.log("token: ", token)
    return token;
}

그리고 위를 호출할 수 있게 _app.js 에 다음을 추가한다(다른 위치에 두어도 무방)

...
    useEffect(() => {
        firebaseMessageToken();
    }, [])
    // async 를 사용학 위해 메서드로 따로 분리함
    const firebaseMessageToken = async () => {
        let token = await getToken();
        console.log("token === ", token)
        ... //추후 서버에 토큰을 저장하는 기능을 여기에 추가
    }
...

 

 

설정하는 FirebaseInit.ts 파일을 살펴보자.

messaging() 을 통해 메세지 설정을 시작한다.

isSupported()는 브라우저별로 사용가능한지 여부를 확인할 수 있다. 만약 브라우저가 사용불가한 것이라면 false 가 되고 더는 진행되지 않도록 했다.

 

requestPermission 을 해두면 다음과 같은 알림창이 뜬다.

 

 

여기서 승인을 눌렀을때 token을 호출하는 메서드를 실행한다.

만약 거절을 눌렀을땐 다음과 같은 문구가 표기된다.

 

FirebaseError: Messaging: The notification permission was not granted and blocked instead. (messaging/permission-blocked).

한번 허용/차단을 누르면 변경하기 위해 다음으로 이동해 수정해야 한다.(크롬기준)

1) Chrome 열기

2) 오른쪽 상단에 더보기 아이콘 => 설정을 클릭한다.

3) '개인정보 보호 및 보안'에서 사이트 설정을 클릭

4. 알림을 클릭

5. 알림을 차단 또는 허용으로 변경한다.

 

수락되어 있으면 다음처럼 토큰을 발급받는다.

 

 

 

# 토큰 테스트하기

API 테스트 툴로 간단히 호출해보자/. 다음의 조건으로 호출하면 된다.

curl --location --request POST 'https://fcm.googleapis.com/fcm/send' \
--header 'Authorization: key=AAAAfGxSdGI:APA91bFNwmckvr7RYTiB-uVXNlk5-_T1C8yYl6kdellxIGJLWyw4pKnE0g8YIjK0KM5J2YoyboNYXEQd6mln_JjPGKpbwGENHeUBzoAkvkGnx2dV1nWdp9eeOr_ht3mV-P5MgHB4qcxN' \
--header 'Content-Type: application/json' \
--data-raw '{
    "notification": {
        "title": "TEST-PWA",
        "body": "알림이 도착했습니다"
        
    },
    "to": "elwTZJ5eNoSr_sYdB6FHbw:APA91bGn1al3dSKO7MOHu-OWTK-Q8jnGGugt7HffMIWnJs-W925F-Cbxz6wSvkWHcwBsugO_0GVTsKnpC9Yoi8YXSAuhajKv8oAIxroho4cL2IdHmmQwXnig6hGv5JRNY72f3HUmzcVY"
}'

 

위 스팩을 풀어보면 아래와 같다.

 

주소: https://fcm.googleapis.com/fcm/send

메서드: POST

Authorization 값은 Firebase 콘솔에서 확인해야 한다.(아래그림 참조)

 

아래 서버키라고 되어있는 부분을 복사한 뒤 Header 에 key=[복사한 키값] 을 입력해야 한다 (아래 참조)

 

to 에는 위에서 발급받은 토큰을 입력한다.

그리고 notification 내에 title 과 body에 전송할 내용을 채운 후 전송한다.

그 외 추가데이터를 보낼 수 있는데(data 에 포함). 자세한 것은 다음 문서를 참조.

https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ko

 

FCM 메시지 정보  |  Firebase

Firebase 클라우드 메시징(FCM)은 다양한 메시징 옵션과 기능을 제공합니다. 이 페이지의 정보는 다양한 유형의 FCM 메시지에 관한 이해를 돕고 FCM으로 구현할 수 있는 기능을 소개하기 위한 내용입

firebase.google.com

완료되면 다음과 같이 message_id 를 받을 수 있다.

결과창

{
    "multicast_id": 2813324488901719365,
    "success": 1,
    "failure": 0,
    "canonical_ids": 0,
    "results": [
        {
            "message_id": "0:1627180574341920%cc9b4facf9fd7ecd"
        }
    ]
}

 

그리고 다음과 같은 알림창이 뜬다.

 

한가지 주의해야 할 점은 위 알림창을 받으려면 브라우저에서 사이트를 닫거나 현재 활성화된 탭이 다른탭이어야 한다. 활성화되어 있다면 아무런 반응이 없다.

 

그럼 창을 보고 있는 상태에서 알림을 처리하려면 어떻게 하는게 좋을까? 이때 사용하는것이 messaging.onMessage 이다.

 

위에 토큰을 발급받는 곳에서 다음을 추가하자.(FirebaseInit.ts 파일)

import firebase from "firebase";

export async function getToken() {
    if(firebase.messaging.isSupported() === false){
        console.log("isSupported: ",  firebase.messaging.isSupported())
        return null;
    }

    const messaging = firebase.messaging()
    const token = await messaging.requestPermission()
        .then(function() {
            return messaging.getToken()
        })
        .then(function(token) {
            messaging.onMessage(payload => {
                alert("알림:" + payload.notification.body);
            })
            return token;
        })
        .catch(function(err) {
            console.debug('에러 : ', err);
            return null;
        })

    console.log("token: ", token)
    return token;
}

 

이제 창이 활성화된 상태에서 다시 호출해보자.

이렇게 처리가 가능하며, 디자인을 추가하여 레이어팝업으로 띄우는것도 가능할 것이다.

 

 

끝.

 

 

 

참조:

https://velog.io/@skygl/FCM-Spring-Boot%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%9B%B9-%ED%91%B8%EC%8B%9C-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

FCM, Spring Boot를 사용하여 웹 푸시 기능 구현하기

Firebase firebase Firebase는 웹과 모바일 개발에 필요한 기능을 제공하는 BaaS(BackEnd as a Service) 이다. 백엔드 서버의 인프라들을 제공해주고 많은 기능들을 지원한다. 머신러닝 사용자 인증 파일 저장

velog.io

https://gist.github.com/ninanung/3c3520359abed543a2bb8e09e49212e4

 

React에서 FCM을 사용해봅시다.

React에서 FCM을 사용해봅시다. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ko

 

자바스크립트 클라이언트에서 메시지 수신  |  Firebase

메시지의 동작은 페이지가 포커스를 갖는 포그라운드 상태인지, 백그라운드 상태인지, 다른 탭 뒤에 숨겨져 있는지, 완전히 닫혀 있는지에 따라 다릅니다. 어떠한 경우든 페이지는 onMessage 콜백

firebase.google.com

 

반응형

댓글