엠마의 개발공부일지

FCM을 활용한 web push기능 구현 본문

Stack/etc

FCM을 활용한 web push기능 구현

Emmababy 2023. 4. 6. 14:59
728x90
 스프링 - 리액트 프로젝트에서 웹으로 push알림 보내기

FCM이란?

- Firebase Cloud Messaging(알림push요청을 fcm서버로보내면, 해당 토큰을 가진 유저에게 알림발송)

- 실시간 알림을 받을 수 있기때문에, 웹사이트에서 활용하기 좋음

- 웹 이외에도 IOS, Android에서도 사용가능(사용방법이 각각 조금씩 다름)

 

FCM을 사용하는 이유?

- 비슷한기술로 SSE를 많이 사용하지만, SSE는 단방향통신만 가능하고 FCM은 양방향가능! 추후 확장가능성을 생각할때 - 양방향 기술로 써보고 싶었음.

 

 

전체흐름

- FCM은 여러 언어로 구현할 수 있지만, 이 포스팅은 프론트-TS, 백엔드-Springboot기준으로 작성

FE

1. FCM사이트에서 프로젝트를 생성하고, 토큰을 요청한다

2. FCM토큰을 리턴받는다

BE

3. 토큰을 백엔드로 보낸다(이번플젝에는 로그인하면 프론트로부터 받는것으로 설정함)

4. 토큰은 서버측에서 관리해야하므로 DB에 저장한다

5. 알림발송해야할때 DB에서 토큰을 꺼낸다

6. 메시지객체에 토큰과 내용을 포함하여 FCM으로 보내면

7. FCM에서 토큰 주인에게 바로 web push알람을 보내준다

 

- FE는 FCM사이트(https://console.firebase.google.com/u/0/) 코드스니펫을 받아 fcm으로 토큰요청이 가능!

- BE는 FE로부터 토큰을 건네받았다고 가정하고 아래의 설명 진행!

 

 

 

부연설명

0. FCM을 시작하기위해선 해당 사이트(https://console.firebase.google.com/u/0/)가입 후 프로젝트를 생성해야함.

(하나의 아이디로 프로젝트만들고, 협업을 원하는사람을 초대하는방식으로 진행)

- FE : 프로젝트설정 > 일반 > 내 앱 메뉴에서 자바스크립트 코드를 복사하여 FCM으로 토큰요청하여 받는다

- BE : 프로젝트설정 > 서비스계정 탭> 자바코드(FCM초기화 요청코드)를 복사하여 빈으로 등록, 새 비공개키(json파일)를 다운받아 프로젝트에 ‘resources’경로 바로아래에 저장한다

📌백엔드에서는 프로젝트 실행할 때마다 FCM 초기화요청을 FCM서버로 보내야한다. 그래야 그 다음부터 메시지 발송요청을하면 정상적으로 발송됨

 

1. 초기화 요청 코드 적용

- 초기화는 프로젝트를 실행할때 한번만 실행되면 되므로 설정파일(Webconfig)에 빈으로 등록했다.

- 초기화할때는 나의정보가 포함된상태로 fcm서버에 요청이되는방식인데, 이 정보는 비공개키(json파일)에 포함되어있다. - 비공개키(json파일)의 경로를 변수에 저장해두고, 그안에 내용을 InputStream을 이용해 읽어올 수 있도록 한다

- FirebaseOption부분은 정해진 양식이나, FCM 사이트에서 생성한 프로젝트가 하나일경우 .setProjectId 메서드는 생략해도된다.

- 초기화부분에서 예외가 발생할 수 있으므로 무조건 try-catch로 예외처리를 한다(없으면 에러남!)

    @Value("${com.blackdragon.fcm.path}")
    private String fcmPath;
    
	@Bean
    public FirebaseApp initializer() {
        try {
            log.info("initialized start");
            InputStream inputStream =
                    new ClassPathResource(fcmPath).getInputStream();

            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(inputStream))
                    .setProjectId("heytossme2")
                    .build();
            log.info("initialized completed");
            return FirebaseApp.initializeApp(options);
        } catch (Exception e) {
            log.error("error message = {}", e.getMessage());
        }
        return null;
    }

 

 

2. 메시지발송 메서드를 정의한다.

- 나는 서비스단에 해당 메서드를 정의했다.

- 포인트는 발송되는 이부분! send메서드에 FCM에서 정해놓은 Message형식을 넣어주면 끝!

FirebaseMessaging.getInstance().send(message);

 

- Message의 형식에는 사이트(https://firebase.google.com/docs/cloud-messaging/send-message?hl=ko#java)에서 여러활용방법을 찾아볼 수 있지만, 나의경우에는 가장 기본인 알림내용 + FCM토큰만 포함했다.(필수이기도하고)

- 그리고 알림내용은 WebPushConfig 객체에서 필요한부분만 추가(알림타이틀, 내용) 했다.

- 만약 알림을 보낸내역을 따로 DB에 저장한다면, 발송완료 후 예외가 터지지않을경우에 저장해주면 된다(아래코드참고)

public void sendPush(NotificationRequest request) {
    log.info("request : " + request.getRegistrationToken());
    WebpushConfig webpushConfig = WebpushConfig.builder()
            .setNotification(new WebpushNotification(request.getTitle(), request.getBody()))
            .build();

    Message message = Message.builder()
            .setWebpushConfig(webpushConfig)
            .setToken(request.getFcmToken())
            .build();

    for (var app : FirebaseApp.getApps()) {
        log.info("app list = {}", app.getName());
    }

    try {
        String response = FirebaseMessaging.getInstance().send(message);
        log.info("response : " + response);
    } catch (FirebaseMessagingException e) {
        log.error("fcm을 통한 메시지 push 발송 실패");
        log.error("error code : " + e.getErrorCode());
        log.error("error message : " + e.getMessage());
    } catch (Exception e) {
        log.error("error : " + e.getMessage());
        for (var i : e.getStackTrace()) {
            log.error("error : {}", i);
        }
    }

    Notification notification = Notification.builder()
            .message(request.getBody())
            .type(request.getType())
            .readOrNot(false)
            .item(request.getItem())
            .member(request.getMember())
            .build();

    notificationRepository.save(notification);
}

 

 

3. 해당 메서드 호출

- 이제 필요한 시점에 아래와같이 sendpush메서드를 사용하면 된다

- buyerPush - 이 객체에는 알림을 보낼때 필요한 모든정보를 담아두었다.

notificationService.sendPush(buyerPush);

 

 

 

📌 트러블슈팅

1. Default Error가 발생하는경우
- FCM 프로젝트가 1개일땐 자동으로 Default로 지정, 여러개일땐 각각의 프로젝트id로 지정해줘야하는데 제대로 Initializing이 안되었을 때
- 알림을 받는사람의 토큰값을 잘못저장해놓고 보냈을 때
- 토큰값이 비어있을때


2. 메시지 발송 실패
- 발송되는 메시지의 형태를 갖추었다면 특이 에러는 발생하지 않음
- 만약 fcm토큰이 사용 불가가 된다면 아래와 같은 에러 발생

3. FCM token의 특징

- FCM토큰은 만료기간이 없음
- FCM토큰 사용이 불가한 경우는 FCM프로젝트를 삭제했거나, 현재 사용하는 프로젝트로 부터 받은 private key가 아닌 다른 프로젝트의 키를사용하지않는경우 ⇒ 기기 별로 토큰이 발급 되기 때문에, 아무런 생각 없이 기존 토큰을 쓰는 경우가 있음(=나))
(또는 기존 다른 프로젝트에서 발급 받은 FCM토큰을 교체하지 않고 사용하는 경우
- FCM 토큰은 기기별 로 발급 됨


4. 비공개키(json파일) 경로
- 로컬환경에서 이 파일을 불러오는경로와, 배포환경(AWS, Azure같은..)에서 불러오는 경로는 다름!ㅠㅠ 나의경우는 아래와같았음
- 로컬 : path: src/main/resources/firebase-service-account.json
- 배포환경 : path: file:/home/runner/work/project-BE/project-BE/src/main/resources/firebase-service-account.json

 

다 하고나서보니 정말 간단하고 이렇게 심플하지만..

앱푸시는 레퍼런스가많지만, 웹푸시는 많이 없었다ㅠㅠ..

그나마 웹푸시에 관해 포스팅된글에는 어떤게 필수이고 선택인지에대한 정보가 아쉬워서

필수인것 위주로 작성해보았다.

 

나처럼 오래 삽질하지말고, 빠르게익혀서 빠르게 적용하고 다음을 향해 나아가는분이 있길... 바란다

728x90

'Stack > etc' 카테고리의 다른 글

[Flexbox]  (0) 2022.03.31
[Crystal Report] 크리스탈레포트에 이미지 삽입방법  (0) 2021.07.13
Javascript DOM  (0) 2021.05.06
Web development with Node & Express  (0) 2021.04.26
화요일 공부  (0) 2021.04.06
Comments