[NestJS] FireBase Cloud Message 연동하기(feat. 외부 API 연동)

리미·2021년 4월 10일
2
post-thumbnail

이 문서에서는 firebase cloud message 요청 엔드포인트가 POST https://fcm.googleapis.com/fcm/send 라면 legacy라고 얘기하겠다.
현재 프로젝트엔 firebase cloud message가 legacy 요청 방식+Classic ASP으로 되어있어서
요청방식을 최신화 시키고 NestJS로 변경하는 것이 목표였다

여기엔 firebase push 전송할때 필요한 코드만 적어놓았고 dto와 interface, 그외 상관없는 함수들은 생략하고 작성하였다.

legacy 요청 방식의 최신화

https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ko
위에 링크를 참고하여 기존방식에서 업데이트 하면된다.

https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages
payload가 각각 무엇을 뜻하는지 내가 원하는게 있는지 찾고싶다면, 이 링크를 참고하면된다

2. 전송 요청의 승인 업데이트

1번의 서버 엔드포인트 업데이트는 일단 코드를 짤때 넣을것이니 2번부터 설명하도록하겠다.

우리는 AWS서버를 사용하고 있으니 ADC 방식은 불가능하고 해당 FireBase 서비스 계정에서 JSON 파일을 받아다가 쓰겠다
아래의 링크를 참고하도록하자
https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ko#provide-credentials-manually


이렇게 콘솔로 접근하면 프로젝트들이 보이는데, 본인이 원하는 프로젝트로 들어간다.

해당 프로젝트의 프로젝트설정으로 가면 서비스 계정이라는 메뉴가 보이는데 거기로 접근하면

이렇게 나오는데, 여기에서 새 비공개키 생성을 누르면 키 생성이 나오는데 그걸 클릭하면 JSON파일이 받아진다.

생성된 JSON파일은 프로젝트에 직접 삽입시 보안상의 문제가 있으므로,
AWS Secrets Manager(https://aws.amazon.com/ko/secrets-manager/)나 환경변수 등을 이용해 외부에서 끌어올수 있게하자

필자의 환경은 Secrets Manager를 사용하는게 더 관리가 안됄것같아서
docker나 쿠버쪽 환경변수에 넣어주었다

이제 코드를 짜보자

NestJS는 기본적으로 지원하는 axios기반(python의 requests같은것)의 http 요청 모듈이 있으므로, 그걸 사용하도록한다.
https://docs.nestjs.com/techniques/http-module

사전설치

먼저, googleapis를 설치하도록하자

npm install googleapis

module.ts

그리고 추가할 app의 module에 HttpsModule을 import 해준다

// app-push.module.ts
import { HttpModule, Module } from '@nestjs/common';

@Module({
  imports: [HttpModule],
  ...
})

service.ts

그리고 service에 추가하여 사용한다.

서비스의 메인이되는 부분

import { HttpService, Injectable } from '@nestjs/common';
import { google } from 'googleapis'; // access token 발행시 사용

@Injectable()
export class AppPushService {
  // app push 서비스
  constructor(private http: HttpService) 
  // HttpService를 http로 정의
  async sendPush(dto: AppPushDto) {{}
    //controller에서 호출하는 app push send 
    const message = this.buildMessage(dto); // 받은 data를 firebase에 맞는 payload로 변경하는 함수
    const response = await this.sendFcmMessage(message); // 가공된 payload를 기반으로 firebase에 전달하여 push 발송
    return this.responseData(response);	// 정해진 응답값으로 반환
  }

FireBase에 전달하여 Push를 발송하는 함수

  private sendFcmMessage(fcmMessage): Promise<fcmResponse> {
    const http = this.http;
    return this.getAccessToken()
      .then(async function (accessToken) {
        // token 발행을 성공할 경우 발행된 token으로 http 모듈을 사용하여 전송해준다
        const url = AppPushEnum.HOST + AppPushEnum.PATH;
        const headersRequest = {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + accessToken,
        };
        const response = await http
          .post(url, JSON.stringify(fcmMessage), {
            headers: headersRequest,
          })
          .toPromise();
        return response.data;
      })
      .catch((e: fcmErrorResponse) => {
        return { error: e.message };
      });
  }

toPromise를 사용하기위해 http.postawait를 추가하고 then functionasync로 지정하고 return하였다.

좀더 유연하게 사용하기위해 fcmResponsefcmErrorResponse interface를 정의하여 타입으로 지정하였다.

받아온 서비스계정 JSON을 이용하여 access token을 발행하는 함수

// app-push.service.ts
  private getAccessToken() {
    const FIREBASE_CLIENT_EMAIL = process.env.FIREBASE_CLIENT_EMAIL;
    const FIREBASE_PRIVATE_KEY = process.env.FIREBASE_PRIVATE_KEY;
    return new Promise(function (resolve, reject) {
      const jwtClient = new google.auth.JWT(
        FIREBASE_CLIENT_EMAIL,
        null,
        FIREBASE_PRIVATE_KEY.replace(/\\n/gm, '\n'),
        [ApppushEnum.MESSAGING_SCOPE],
        null,
      );
      jwtClient.authorize(function (err, tokens) {
        if (err) {
          reject(err);
          return;
        }
        resolve(tokens.access_token);
      });
    });
  }

특별한 이슈는 환경변수process.env.FIREBASE_PRIVATE_KEY로 바로 받아서 쓸때 readonly로 받아온다는 점 참고
FIREBASE_PRIVATE_KEY\n기호를 사용하고있어서 이게 자동으로 줄바꿈으로 변환을 해주는것같다 string '\n'로 replace해주어야하므로 변수로 지정하였다.

const FIREBASE_CLIENT_EMAIL = process.env.FIREBASE_CLIENT_EMAIL;
const FIREBASE_PRIVATE_KEY = process.env.FIREBASE_PRIVATE_KEY;

이렇게 열심히 가공하고
로컬에 돌려보고 기기에 잘 전송되는지 확인하고
내 입맛대로 만들어주면 끗

profile
Python이 하고싶은데 자꾸 Flutter 시켜서 빡쳐서 만든 블로그

0개의 댓글