My Boundary As Much As I Experienced

야놀자 부트캠프 파이널 프로젝트 Percent Hotel 회고 3) 사용자에게 체크인 안내, 판매 완료, 정산 완료등을 알려주기 본문

Projects/Yanolja Bootcamp's

야놀자 부트캠프 파이널 프로젝트 Percent Hotel 회고 3) 사용자에게 체크인 안내, 판매 완료, 정산 완료등을 알려주기

Bumang 2024. 3. 5. 20:35

 

안드로이드 체크 안내

 

 

아이폰 판매 완료 알림

 

 

프로젝트 막판 스퍼트, 푸시 알림 구현

약 프로젝트 마감이 5일 남은 시점, 기존 페이지들 QA 테스트 결과 고쳐야할 버그들이 많이 남은 상황에서

'알림 기능', 'pwa 기능', 'SEO 최적화' 등 처리해야될 일들이 너무 많아진 상황이었다..

 

사실 알림 기능과 pwa는 시간 없으면 포기하자고 PM팀이 이야기했다.

사실 이 기능들을 전에 구현해본 경험이 없어서 이게 얼마나 걸릴지 계산이 안 됐지만,

 

'모바일 앱의 존재 이유는 알림이다. 사실 모바일 앱은 푸시알림 머신이다.'

푸시 알림의 중요성을 강조하는 말들을 들어왔기 때문에 선뜻 포기하고 싶지는 않았다.

 

pwa는 전에 구현해본 경험이 있는 팀원분께 맡기고,

나는 fcm을 이용한 푸시 알림 기능을 구현해보기로 하였다.

 

퍼센트 호텔의 알림 정책

현재 퍼센트 호텔의 알림 정책은 이렇다. 

  • 체크인 24시간 전 알림
  • 판매 완료 시 알림
  • 정산 완료 시 알림 설정 (매일 12:00 AM 정산)

사실 알림 정책 로직은 백엔드에서 적절히 처리해서 주기 때문에,

프론트에서는 fcm sdk를 초기화해주고 로그인 시 토큰을 발급받아 서버로 보내주면 된다.

(처음에 담당 백엔드 분께서 로그아웃 시에 서버에 알려줘야 한다고 말씀하셨는데 그럴 필요는 없었다.)

 

 

접근 방식

1. 첫 접근

로그인 시 fcm 토큰을 초기화해야되니

최상위 루트 중 하나인 App.tsx에서 useEffect를 이용하여 로그인 여부 전역 상태를 구독한다.

로그인 여부 전역 상태가 바뀔 때마다 isLoggedIn이 true라면 토큰을 발급해서 서버에 전송하는 방식으로 접근했다.

 

그러나 로그인이 되어있으면 새로고침 혹은 페이지 이동할 때마다 토큰을 발급받는 바보같은 일이 일어났다.

그래서 로그인이 true면서 localStorage에 fcm토큰이 있는지를 확인하도록 하는 방식으로 구현하려 했다.

 

그런데 팀원분께서 차라리 로그인 api함수 호출 성공 시 동기적으로 이어서 fcm토큰 초기화 한 번만 해주면 되지 않냐는 천재적인 제안을 해주셨다. 

 

 

2. 로그인 시에 fcm 토큰을 전달

사실 이게 정석적인 구현 방식인거 같다.

조금만 더 구현 레퍼런스를 찾아봤으면 이렇게 직관적이고 깔끔한 방법을 찾았을텐데

너무 내가 꽃힌 방법으로만 하려고 한 것 같다.

 

[[구현 결과]]

더보기

1. getNotificationPermission 함수에 fcm 초기화 로직을 담아 원하는 곳에 사용하기

이 함수를 만들어서 로그인 후 실행하였다!

이때 onMessaging 함수도 실행되는데 이는 foreground 시 메세지를 받도록 하는 함수이다.

import {
  getMessaging,
  getToken,
  onMessage,
  isSupported,
} from "firebase/messaging";

import { isAccessTokenExpired } from "./checkToken";

import { app } from "@/firebase";

export async function checkAccessToken() {
  const accessToken = localStorage.getItem("accessToken");
  if (accessToken && isAccessTokenExpired(accessToken)) {
    await getNotificationPermission();
  }
}

async function getNotificationPermission() {
  const supported = await isSupported();
  if (!supported) {
    console.log("이 브라우저는 FCM을 지원하지 않습니다.");
    return;
  }

  const messaging = getMessaging(app);

  const permission = await Notification.requestPermission();
  if (permission === "denied") {
    console.log("알림 권한 허용 안됨");
    return;
  }

  onMessage(messaging, (payload) => {
    console.log("메시지가 도착했습니다.", payload);
  });

  try {
    const fcmToken = await getToken(messaging, {
      vapidKey: import.meta.env.VITE_FIREBASE_VAPID,
    });

    if (fcmToken) {
      console.log("token: ", fcmToken);
      localStorage.setItem("fcmToken", fcmToken);
    } else {
      console.log("토큰을 가져올 수 없습니다.");
    }

    return fcmToken;
  } catch (error) {
    console.error("FCM 토큰 요청 중 오류 발생:", error);
  }
}

export default getNotificationPermission;

 

 

2. 그리고 백엔드 개발자분이 요청한대로 login 시 fcmToken을 이메일, 패스워드와 같이 보냈다.

  const handleOnSubmit = async (data: FormValues) => {
    const { email, password } = data;

    let fcmToken = await getNotificationPermission();

    if (!fcmToken) {
      fcmToken = localStorage.getItem("fcmToken") ?? "";
    }

    await postLogin({ email, password, fcmToken })
      .then((loginData) => {
        const { memberResponse, tokenResponse } = loginData;
        useUserInfoStore.getState().setUserInfo(memberResponse);

        localStorage.setItem("accessToken", tokenResponse.accessToken);
        localStorage.setItem("refreshToken", tokenResponse.refreshToken);

        if (redirectUrl) {
          navigate(redirectUrl, { replace: true });
          return;
        }

        navigate(PATH.ROOT, { replace: true });
      })
      .catch(() => {
        handleToast(true, [<>아이디 혹은 비밀번호를 확인해주세요</>]);
      });
  };

 

3. 백그라운드에서 메시지 알림을 받기 위해 firebase-messaging-sw.js에서 onBackgroundMessage를 호출

굳이 디자이너님께 파일 요청 안 하고, "저희 로고를 알림 아이콘 크기 맞춰서 사용할까요?" 하고 물어본 뒤 적용했다.

figma 권한을 나에게도 열어줘서 이런 부분은 편했다. 

...
messaging.onBackgroundMessage(function (payload) {
  console.log("Received background message", payload);
  // Customize notification here
  const notificationTitle = payload.data.title;
  const notificationOptions = {
    body: payload.data.message,
    icon: "/icon-192.png",
  };

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

 

 

기타 이슈

1. 맥북 환경설정에 크롬 푸시알림을 차단해서 해멘 경험

내가 옛날에 맥북 환경 설정에서 chrome push 알림 자체를 막아뒀었나보다.

그래놓고 "왜 제대로 한거같은데 알림이 안 오지.."  라며 시간을 많이 소비했다. ㅎㅎ

그런데 조원분께 테스트 메시지를 보내보니 제대로 작동하길래 내 pc 환경의 문제인가보다 싶어 찾아보다 이 이슈를 발견하였다.

 

2. 모바일 사파리와 pwa에서 알림이 오지 않는 이슈

처음에 모바일 크롬은 알림이 오는데, pwa나 모바일 사파리에서는 알림이 오지 않았다!

찾아보니 모바일 사파리는 fcm 지원이 안 된다고 한다.

그리고 아이폰 pwa도 사파리를 기준으로 만들어진거라 fcm 지원이 매끄럽지 않을 수 있다는 말을 들었다.

그러나, pwa 담당하신 분이 어떻게든 pwa에서 알림을 받을 수 있게 만들었다. 이 부분도 조원분께 물어봐서 나중에 포스팅 해보겠다.

 

마무리 회고

모바일 뷰가 메인인 서비스에서 알림은 얼마나 중요한 존재인가... 

알림을 구현해놓고 보니, 훨씬 더 완성도 있는 서비스같아 보인다.

 

또한 누가 옛날에 구현해놓은걸 참고하는건 좋은데 공식문서를 먼저 보는것이 좋다는 것을 느꼈다.

firebase는 버전별로 문법이 자주 바뀌었기 때문에 남의 코드를 보면서 잘 작동하지 않는 것들이 많이 올라와있다는걸 명심해야겠다.

공식 문서에 있는 코드가 가장 최신이고 확실한 코드이다. 응용하더라도 공식문서에 있는 것을 활용하자.

 

지금 돌아보면, 알림 구현할 때 같이 pwa를 구현하던 조원분과 긴밀하게 소통하며
새벽에 "알림 가나요~?", "안드 pwa에서 알림 잘 오나요~?" 식으로 밤을 새웠는데..
고생했지만 결과적으로 잘 구현되니 좋은 기억으로 남았다.