Skip to content

Instantly share code, notes, and snippets.

@ninanung
Last active August 17, 2023 00:20
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ninanung/3c3520359abed543a2bb8e09e49212e4 to your computer and use it in GitHub Desktop.
Save ninanung/3c3520359abed543a2bb8e09e49212e4 to your computer and use it in GitHub Desktop.
React에서 FCM을 사용해봅시다.

React에서 FCM사용하기

이 글을 이해하기 위한 선행지식

  • React
  • NPM
  • curl이나 request모듈과 같은 요청관련 지식
  • Javascript에 대한 지식
  • FCM은 https혹은 localhost에서만 동작하며 http에서는 동작하지 않습니다.

많은 회사들이 웹앱을 구축할 때 React를 사용하고 있습니다. 솔직히 우리나라는 아직도 frontend 프레임워크보다는 전통적인 방식의 웹을 선호하는 경향이 있는 것 같지만 스타트업과 같은 많은 젊은 회사들이 React를 사용합니다. 그럴 경우 빠른 웹 구축을 위해 Google이나 AWS의 서비스를 많은 사용하는데요, FCM도 push notification을 빠르게 구축할 수 있게 해주는 Firebase 서비스의 하나입니다. 이름부터 Firebase Cloud Messaging(FCM)이죠. 이 글을 통해서는 아주 간단한 알림서비스 구축에 대해서 다뤄보도록 하겠습니다. 천천히 한번 알아보도록 하죠.

1. 밑작업

Google의 서버스를 이용하는 것이기 때문에 당연히 Google계정과 Firebase 등록이 필요합니다. React를 통한 서비스를 계획중이라면 당연히 npm이나 node.js, package, component에 대한 기본지식은 이미 가지고 있을테니 처음부터 React앱을 만들고 하는 자잘한 설정은 패스하겠습니다.

1-1. 프로젝트 생성

project
먼저 firebase 프로젝트를 생성해 줍니다. 프로젝트 생성에 많은 설정이 필요한 건 아니고 원하는 프로젝트 이름과 서버가 존재할 지역을 설정하면 됩니다. 사실 서버설정도 유럽아니면 미국이라 한국에서는 큰 의미가 없고 큰 차이도 없습니다.

1-2. firebase를 위한 설정값 복사하기

config
웹 앱에 Firebase추가 라고 쓰인 </>모양 버튼을 누르면 Firebase관련 개인설정이 나옵니다.

var config =  { 
	apiKey:  "암호화된 키값", 
	authDomain:  "imap-push-server.firebaseapp.com", 
	databaseURL:  "https://imap-push-server.firebaseio.com", 
	projectId:  "imap-push-server", 
	storageBucket:  "imap-push-server.appspot.com", 
	messagingSenderId:  "숫자로 된 개인 아이디"  
}; 
firebase.initializeApp(config);  

나오는 창에는 HTML사용을 전제로 해서 테그들이 붙어있지만 저희는 React에서의 사용을 전제로 하기 때문에 위와같은 스크립트 부분만 복사합니다. 복사한 설정은 component의 가장 root에 있는 파일에 붙여넣습니다. index.js 혹은 App.jsx 모두 상관 없습니다. 이 설정은 FCM사용시 Firebase서버의 허가를 받는 용도로 계속 사용되기 때문에 따로 js파일을 만들어서 export/import해서 사용해도 무관합니다. 저는 그리 했습니다.

1-3. server key찾아두기

서버키 역시 위에서 복사한 config와 비슷합니다. Firebase의 허가를 받는작업과 실제로 push notification을 요청하는 request에서 사용됩니다. server key
이러한 서버키를 확인하실 수 있는데요, 이 사진은 조금 오래된 것을 사용해서 서버키가 꽤 짧습니다만, 원래를 보시면 굉장히 긴 암호화 키가 쓰여져 있을 겁니다. 위에서 말했듯이 이 키값은 허가를 받는 작업에 쓰이기 때문에 코드에서 인용하는 경우가 조금 있을 겁니다. 1-2에서 했던 것 처럼 따로 import/export해서 사용하는걸 추천합니다. 다만 제작중인 프로젝트를 Github와 같은 곳에 올릴 예정이라면 .gitignore를 통해 해당 설정이나 키값이 있는 파일을 제외시키는걸 잊지 말도록 합시다.

1-4. npm을 통해 firebase 모듈 설치하기

npm install --save firebase 설명 패스합니다.

2. FCM에 permission요청하고 token발급하기

모든 밑작업을 위한 준비가 되었습니다. 이제부터는 Firebase에 허가를 요청하고 제작중인 앱에대한 token을 받아보도록 하겠습니다. 위에서 제가 index.jsApp.jsx 파일에 config부분을 붙여넣으라고 얘기를 드렸습니다. 저는 App.jsx 에 넣었기 때문에 그것을 기준으로 설명드리겠습니다. 아직 아무 컴포넌트도 없는 App.jsx 파일의 모습은 이러할 겁니다.

import React from 'react';
import firebase from 'firebase'; //firebase모듈을 import해줘야 합니다.

const config =  { 
	apiKey:  "암호화된 키값", 
	authDomain:  "imap-push-server.firebaseapp.com", 
	databaseURL:  "https://imap-push-server.firebaseio.com", 
	projectId:  "imap-push-server", 
	storageBucket:  "imap-push-server.appspot.com", 
	messagingSenderId:  "숫자로 된 개인 아이디"  
}; 
firebase.initializeApp(config);  

class App extends React.Component {
	render() {
		return (
			<h1>Hello World!</h1>
		)
	}
}

export default App;

자 이제 여기부터 시작해 봅시다.

2-1. 허가 요청하기

먼저 firebase모듈을 이용해서 messaging이라는 변수를 새로 만듭니다.

firebase.initializeApp(config);  
const messaging = firebase.messaging();

이렇게 생성된 변수는 사실 permission을 위한 모든 함수를 가지고 있습니다. requestPermission이라는 함수를 사용해 봅시다. javascript의 promise구문을 사용하겠습니다.

firebase.initializeApp(config);  
const messaging = firebase.messaging();

//허가를 요청합니다!
messaging.requestPermission()
.then(function() {
	console.log('허가!');
})
.catch(function(err) {
	console.log('fcm에러 : ', err);
})

허가를 요청하고 허가가 될 경우 허가!를, 오류가 발생하면 해당 오류를 보여주도록 코드를 작성했습니다. 앱을 실행하면 콘솔창에 허가!가 나오는게 보일겁니다. 자 우리는 이제 Firebase의 허가를 받았습니다. 이제 알림을 보내고 받을 수 있을까요? 아닙니다. 허가를 받았다는 증거로 token을 받아야 합니다.

2-2. 토큰 발급받기

토큰 발급은 위에서 작성한 requestPermission에 코드를 조금 추가하면 됩니다. 허가!출력하는 부분부터 코드를 넣어봅시다.

messaging.requestPermission()
.then(function() {
	console.log('허가!');
	return messaging.getToken(); //토큰을 받는 함수를 추가!
})
.catch(function(err) {
	console.log('fcm에러 : ', err);
})

자 이제 토큰을 리턴합니다. 그럼 이 토큰을 받아서 어떻게 해야할까요? 우선은 토큰을 출력해 보도록 합시다.

messaging.requestPermission()
.then(function() {
	console.log('허가!');
	return messaging.getToken(); //토큰을 받는 함수를 추가!
})
.then(function(token) {
	console.log(token); //토큰을 출력!
})
.catch(function(err) {
	console.log('fcm에러 : ', err);
})

이렇게 허가를 받고 토큰을 구해서 콘솔에 출력했습니다. 우선은 브라우저의 개발자도구를 이용해서 출력된 토큰을 따로 저장해 두시기 바랍니다. 나중에 FCM서버에 request를 할때 사용됩니다. 이 단계의 App.jsx는 이런 모습일 겁니다.

import React from 'react';
import firebase from 'firebase'; //firebase모듈을 import해줘야 합니다.

const config =  { 
	apiKey:  "암호화된 키값", 
	authDomain:  "imap-push-server.firebaseapp.com", 
	databaseURL:  "https://imap-push-server.firebaseio.com", 
	projectId:  "imap-push-server", 
	storageBucket:  "imap-push-server.appspot.com", 
	messagingSenderId:  "숫자로 된 개인 아이디"  
}; 
firebase.initializeApp(config);
const messaging = firebase.messaging();

messaging.requestPermission()
.then(function() {
	console.log('허가!');
	return messaging.getToken(); //토큰을 받는 함수를 추가!
})
.then(function(token) {
	console.log(token); //토큰을 출력!
})
.catch(function(err) {
	console.log('fcm에러 : ', err);
})

class App extends React.Component {
	render() {
		return (
			<h1>Hello World!</h1>
		)
	}
}

export default App;

이제는 알림을 받는 부분을 만들어 보도록 하겠습니다.

3. 알림내용 받기

2.부분까지를 통해 FCM서버에서 허가를 받고 토큰을 생성했습니다. 이 단계에서는 그저 FCM서버와 앱을 연결했을 뿐입니다. "나는 알림을 받을거야!" 라고 말하는 부분이 필요합니다. 먼저 그 부분을 작성해 보도록 하겠습니다.

3-1. onMessage함수를 통해 알림내용 받기

위에서 작성했던 코드를 그대로 사용해서 그 밑에 내용을 추가하도록 하겠습니다. 먼저 만들어두었던 messaging변수로 함수를 실행합시다.

messaging.onMessage(function(payload){
	console.log(payload.notification.title);
	console.log(payload.notification.body);
})

해당 코드를 추가했습니다. 이는 알림이 올 경우 이러이러한 작업을 실행하라! 라는 부분입니다. 저는 알림에 포함된 notification데이터에서 titlebody를 받아서 출력했습니다. payload는 알림에 포함된 데이터로 request를 통해 요청할 때 포함시키는 데이터 입니다. 알림을 받아보셔서 아시겠지만, 알림에는 사용자에게 어떠한 사실을 알리기 위한 문자열이 사용되며 titlebody 는 알림의 제목과 내용입니다. 오 이제 다 됐나? 싶겠지만 아직입니다. 서비스워커를 등록해 줘야 합니다. 서비스워커에 대한 자세한 내용은 서비스 워커를 참조해 주세요.

3-2. Service Worker설정하기

서비스 워커 설정이라고 하면 거창해 보이지만 사실 아닙니다. 이 단계에서는 firebase-messaging-sw.js라는 이름의 파일을 만들어서 React프로젝트의 root에 있는 public파일 안에 index.htmlmanifest.json파일 옆에 함께 넣는 것 만으로 충분합니다. 파일의 내용은 아래와 같습니다.

importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
const config =  { 
	apiKey:  "암호화된 키값", 
	authDomain:  "imap-push-server.firebaseapp.com", 
	databaseURL:  "https://imap-push-server.firebaseio.com", 
	projectId:  "imap-push-server", 
	storageBucket:  "imap-push-server.appspot.com", 
	messagingSenderId:  "숫자로 된 개인 아이디"  
}; 
firebase.initializeApp(config);

importScripts를 통해 Firebase에서 필요한 모듈을 가져오고 익숙한 config변수가 보이는군요. 이게 끝입니다. 저장하시면 됩니다.

3-3. 알림을 보내보자

자 이제 알림을 실제로 보내봅시다. React앱을 실행시키고 원하는 방식을 통해서 아래의 정보를 가지는 post형태의 request를 보내봅시다. 저는 curl을 극혐하기 때문에 사용하지 않습니다. 개인적으로는 Insomnia를 추천합니다. REST API테스트에 상당히 유용합니다.

const option = {
	method: 'POST',
	url: 'https://fcm.googleapis.com/fcm/send', //FCM서버의 주소입니다. 그대로 쓰시면 됩니다.
	json: {
		'to': '', //2-2에서 복사해놓은 토큰을 사용합니다.
		'notification': { //꼭 notification일 필요는 없습니다. data든 뭐든 바꿔도 됩니다.
			'title': 'hello', //알림의 제목에 해당하는 부분입니다.
			'body': 'world!', //알림의 본문에 해당하는 부분입니다.
		}
		//title이나 body이외에도 다른 옵션들이 많으니 찾아보시기 바랍니다.
	},
	headers: {
		'Content-Type': 'application/json',
		'Authorization': 'key=' //위에서 찾았던 서버키 앞에 'key='을 붙여서 사용합니다.
	}
}

요청을 보내셨나요? 보냈다면 알림이 오거나 콘솔창에 정보가 출력되었을 겁니다. 웹 페이지를 전면으로 켜놓지 않으신 분들은 알림을, 켜놓으신 분들은 정보를 출력합니다. 음? 왜 알림은 안오고 정보만 출력되죠? 라고 하시겠지만, push 알림은 기본적으로 background에서 사용자가 해당 앱을 사용하지 않고 있을 때 알려줘야 할 내용이 있다면 공지를 하는 역할을 합니다. 카톡으로 예를 들면, 카톡방에서 메시지를 계속 보고있는데 이미 보고있는 메시지에 대해서 사용자에게 또 알림을 줘야 할 필요가 없겠죠? 결국 해당 웹앱을 열어놓고 다른 웹을 보고있거나 다른 프로그램을 실행하는 중일 때 알림이 보내지게 됩니다.

현재 파일의 상태는 이러합니다.

App.js

import React from 'react';
import firebase from 'firebase'; //firebase모듈을 import해줘야 합니다.

const config =  { 
	apiKey:  "암호화된 키값", 
	authDomain:  "imap-push-server.firebaseapp.com", 
	databaseURL:  "https://imap-push-server.firebaseio.com", 
	projectId:  "imap-push-server", 
	storageBucket:  "imap-push-server.appspot.com", 
	messagingSenderId:  "숫자로 된 개인 아이디"  
}; 
firebase.initializeApp(config);
const messaging = firebase.messaging();

messaging.requestPermission()
.then(function() {
	console.log('허가!');
	return messaging.getToken(); //토큰을 받는 함수를 추가!
})
.then(function(token) {
	console.log(token); //토큰을 출력!
})
.catch(function(err) {
	console.log('fcm에러 : ', err);
})

messaging.onMessage(function(payload){
	console.log(payload.notification.title);
	console.log(payload.notification.body);
})

class App extends React.Component {
	render() {
		return (
			<h1>Hello World!</h1>
		)
	}
}

export default App;

firebase-messaging-sw.js

importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
const config =  { 
	apiKey:  "암호화된 키값", 
	authDomain:  "imap-push-server.firebaseapp.com", 
	databaseURL:  "https://imap-push-server.firebaseio.com", 
	projectId:  "imap-push-server", 
	storageBucket:  "imap-push-server.appspot.com", 
	messagingSenderId:  "숫자로 된 개인 아이디"  
}; 
firebase.initializeApp(config);

4. 유용한 것들

저희는 이미 React에 관련 설정을 입력하고 알림을 보내고 받아 봤습니다. 그것도 아주 간단하게 말이죠. 하지만 이것 말고도 Firebase나 FCM의 기능은 많습니다. FCM에서 설정하면 좋을 만한 것들을 몇개 설명해 보도록 하겠습니다.

4-1. onTokenRefresh()

이 함수는 on이 붙은만큼 어떠한 변화에 대응하는 함수입니다. 2-1에서 만들었던 messaging변수에 포함되어 있죠. 이름에서 예상을 하셨을지도 모르지만 이 함수는 원래 발급받은 토큰이 사라지고 새롭게 생성되었을 때 자동으로 그 토큰을 리턴하게 되어있는 함수입니다.

messaging.onTokenRefresh(function() {
	messaging.getToken()
	.then(function(refreshedToken) {
		console.log(refreshedToken);
		console.log('Token refreshed.');
	})
	.catch(function(err) {
		console.log('Unable to retrieve refreshed token ', err);
	});
});

익숙한 getToken함수가 보이는군요. 그렇습니다. 이 함수는 이 글을 기준으로 하자면 지금까지 설명한 App.jsx에 들어가게 됩니다. 이 함수를 통해 웹앱은 쉽게 새롭게 바뀐 토큰을 구할 수 있습니다.

4-2. setBackgroundMessageHandler()

우리는 onMessage함수를 통해서 알림을 받았습니다. 하지만 이는 아주 기본적인 형태로, 사실 서비스 워커 파일(firebase-messaging-sw.js)에 알림을 컨트롤 할 수 있는 몇가지 함수가 있습니다. 이 함수는 그중 하나로 웹이 백그라운드에서 구동중일때 알림을 어떻게 할지에 대한 설정입니다.

firebase.initializeApp(config);
const messaging = firebase.messaging();

messaging.setBackgroundMessageHandler(function(payload) {
	const title  =  payload.notification.title;
	const options  = {
		body: payload.notification.body,
	};
	return self.registration.showNotification(title, options);
})

사실 onMessage와 비슷합니다. payload를 받는것도 그렇고 말이죠. 하지만 이 함수에서는 self.registration.showNotification()을 통해 직접 알림을 보내도록 합니다. title이나 option에 대한 부분은 이해가 가실겁니다. 기본적인 payload 데이터 입니다. 이 코드에서는 백그라운드에서 웹이 구동중일때 알림을 보내는 요청이 들어올 경우 그냥 그대로 다시 알림을 보내도록 했습니다. 필요할 경우 여러가지 동작을 지시할 수 있을 겁니다.

4-3. click_action

지금까지는 알림을 통해 보내는 옵션은 title과 body가 끝이었습니다. 사실 이것 말고도 더 있다고 얘기를 드리긴 했지만 한가지 꽤 중요한 옵션이 있으니 이 기회에 알아보도록 합시다. 유튜브나 기타 많은 웹앱들이 알림을 보냅니다. 그 알림을 클릭할 수 있다는 걸 아시나요? 보통은 그 알림에 연관된 페이지로 redirect시킵니다. click_action은 어느 페이지로 redirect시킬지를 설정하는 옵션입니다. url을 입력하면 되죠.

const option = {
	method: 'POST',
	url: 'https://fcm.googleapis.com/fcm/send',
	json: {
		'to': '',
		'notification': {
			'title': 'hello',
			'body': 'world!',
			'click_action': 'url', //이 부분에 원하는 url을 넣습니다.
		}
	},
	headers: {
		'Content-Type': 'application/json',
		'Authorization': 'key'
	}
}

역시나 익숙한 코드죠? request시 보내는 json데이터 안에 같이 넣어주면 됩니다.

자, 여기까지 해서 공식적으로 유용할 만한 것들을 얘기해 보았습니다. React라고 해서 딱히 별거 없습니다. 그냥 위에서 설명한 코드를 앱이 실행될 때 읽도록 할 수만 있다면 어디에 넣어도 크게 상관이 없죠. 저는 redux를 사용하기 위해서 App.js파일의 componentWillMount라이프 사이클 안에 넣었습니다. 잘 작동합니다.

5. 기타

여기서부터는 개인적인 팁입니다.

5-1. redux-persist 모듈

위의 글을 통해 토큰을 받는 부분을 설명했습니다. 이 글을 통해서는 토큰을 그냥 콘솔로 받아서 request시 붙여넣고 사용했지만 실제 프로그램에서는 DB든 어디든 저장소를 잡아서 저장을 해야 합니다. 그럴 때 사용하면 좋은 모듈이 바로 redux-persist입니다. 이 모듈은 React에서 사용하는 redux의 state를 휘발성 데이터가 아니라 계속 존재하는 데이터로 만들어 줍니다. 마치 세션과 같이 말이죠. 저는 이 모듈을 이용해서 redux에 토큰을 저장할 state를 만들고 토큰 생성시(혹은 위에서 설명한 재생성시에도) 값을 저장하도록 했습니다. 그렇게 하면 원하는 부분에서 기존의 react-redux 방식을 통해 손쉽게 데이터를 가져오거나 저장할 수 있습니다. 코드를 참고하셔도 됩니다.

import React, { Component } from 'react';
import firebase from 'firebase';
import RouterRoot from './router_root';
import fcm from './fcm_config/fcm_config'; //관련설정을 따로 저장한 파일 가져오기
import './App.css';
import {connect} from 'react-redux';
import {bindActionCreators} from  'redux';
import * as actions from './store/action';

const  mapStateToProps  = (state) => {
	return {
		fcm_cloud_messaging_token:  state.fcm_cloud_messaging_token, //저장된 토큰
	}
}
const  mapDispatchToProps  = (dispatch) => {
	return  bindActionCreators({
		insert_token:  actions.insert_token, //토큰을 저장하는 redux action입니다.
	}, dispatch)
}

class  App  extends  Component {
	componentWillMount() {
		firebase.initializeApp(fcm.config);
		const insert_token = this.props.insert_token;
		const messaging = firebase.messaging();
		messaging.requestPermission().then(function() {
			console.log('have permissin');
			return  messaging.getToken();
		}).then(function(token) {
			insert_token(token); //토큰을 이 부분에서 저장합니다.
		}).catch(function(err) {
			console.log('fcm error : ', err);
		})
		messaging.onTokenRefresh(function() {
			messaging.getToken().then(function(refreshedToken) {
				insert_token(refreshedToken); //토큰이 재 생성될 경우 다시 저장
				console.log('Token refreshed.');
			}).catch(function(err) {
				console.log('Unable to retrieve refreshed token ', err);
			});
		});
		messaging.onMessage(function(payload) {
			alert('Got a ' + payload.notification.title + '\n' + payload.notification.body);
		})
	}
	render() {
		return (
			<div className='App'>
				hello world!
			</div>
		);
	}
}

export default connect(mapStateToProps, mapDispatchToProps)(App);

이렇게 하면 앱 실행시 자동으로 토큰을 저장합니다.

5-2. request 모듈

워낙 유명한 API request 모듈입니다. 사실 위에서 사용했던 request관련 코드도 여기에 사용합니다. 해당 모듈을 사용하기는 분은 참고하실만한 코드입니다. 혹은 이 모듈 사용을 고민해 보시기 바랍니다.

import request from 'request';

const option = {
	method: 'POST',
	url: 'https://fcm.googleapis.com/fcm/send',
	json: {
		'to': token,
		'notification': {
			'title': 'hello',
			'body': 'world!',
			'click_action': 'url', //이 부분에 원하는 url을 넣습니다.
		}
	},
	headers: {
		'Content-Type': 'application/json',
		'Authorization': key
	}
}

request(option, (err, res, body) => {
	if(err) console.log(err); //에러가 발생할 경우 에러를 출력
	else console.log(body); //제대로 요청이 되었을 경우 response의 데이터를 출력
})

이 코드를 이용하면 push 알림을 요청할 수 있습니다. 참고하시기 바랍니다.

참고링크

이 글은 위의 링크에서 많은 정보를 받았음을 밝힙니다.

@drllr
Copy link

drllr commented Dec 1, 2020

감사합니다!

@JoDMsoluth
Copy link

좋은 정보 감사드립니다!! ㅎ

@LeeSeongJinCa
Copy link

좋은 정보 감사합니다.
질문이 있습니다.
Safari, Safari On iOS 에서도 4-2 에서 설명해주신 showNotification 이 동작하나요?
ServiceWorkerRegistration API: showNotification 에서는 아직 showNotification 이 Safari 를 지원하지 않고 있다고 합니다.
Safari 에서 Push Notification 기능을 제공하고 싶은데 도무지 방법이 떠오르지 않네요. :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment