웹에서 사용자에게 알림을 보내는 법, 그리고 한계점

김채은·2023년 11월 23일
6
post-thumbnail

도입 배경

깜지 팀은 카톡 채팅방을 통해 스터디 챌린지를 진행한다. 사용자들은 매주 서포터즈의 안내 카톡을 받고 문제 제출 미션을 잊지 않고 수행한다. 매주 진행되고 있는 이 일을 자동화하여 깜지 서포터즈 없이도 사용자들이 꾸준히 문제를 제출하는 환경을 만들고 싶었다.

Web Push Notification

Service Worker를 활용하면 Push APINotifications API를 통해 웹에서도 앱처럼 백그라운드 Push 알림을 보낼 수 있다. Push 알림은 Progressive Web App의 필수 조건이기도 하며, 사용자를 재참여(re-engageable)하게 하는 것을 목적으로 한다.

앱을 만들면 되잖아요?

사실 웹 푸시 알림을 받으려면 PWA로 개발된 페이지를 휴대폰에 설치해야 한다. 해외에서는 많이 활용되고 있지만, 국내에서 이런 형태로 앱을 사용하는 것은 대부분의 사용자에게 익숙지 않을 것이다.
그럼에도 불구하고 PWA를 통한 웹 푸시 알림을 개발했던 이유가 있다.

1. 깜지는 PC 환경과 모바일 환경에서 각각 특화된 미션이 있다.

예를 들어, 문제를 제출할 때는 코드나 사진을 첨부하는 일이 빈번하게 발생하기 때문에 PC 환경에서 진행하는 게 편하다. 반면 틈틈이 문제를 풀거나 미션 현황을 확인할 때는 모바일로 접속하는 게 편리하다.

2. 웹과 앱을 모두 지원하기에 비용 이슈가 있다.

프론트엔드 1명, 백엔드 1명, 인프라 엔지니어 1명으로 구성된 팀이었기에 웹으로 기능을 개발하고 배포하는 것만 해도 시간이 모자란 상황이었다. 2주에 한 번씩은 계속해서 개발 사이클이 돌아가면서 기능 개발과 수정이 반복되다 보니, 앱 개발 또는 웹 뷰 개발을 학습해서 도입할 상황이 아니었다.

이러한 이유로 Web Push Notification을 지원하게 되었다.

Service Worker

브라우저가 백그라운드에서 실행시키는 스크립트이다. 기본적으로 웹 애플리케이션, 브라우저, 네트워크 사이에서 프록시 서버 역할을 한다.

특징

  • 페이지의 메인 JavaScript 코드와 독립된 스레드에서 실행되며, DOM 구조에 대한 어떠한 접근도 갖지 않는다(Non-blocking).
  • 이벤트 기반 워커로, 자신이 제어하는 페이지에서 발생하는 이벤트를 수신할 수 있다.

사용 예시

  • 웹에서 네트워크 요청과 같은 이벤트를 가로채어 수정할 수 있고, 다시 페이지로 돌려보낼 수 있다.
  • fetch 이벤트의 중간자 역할로 캐시를 수행할 수 있다.

위치

브라우저와 네트워크 사이 새로운 계층에 존재하기 때문에, 브라우저를 종료해도 네트워크와 통신할 수 있다. 따라서 브라우저가 종료된 상태에서도 푸시 알림을 수신할 수 있는 것이다.

Web Push Notification

백그라운드 웹 푸시 알림을 위해 다음과 같은 컴포넌트들이 필요하다.

  • Service Worker
  • Notification API
  • Push API

진행 단계

  1. 유저에게 Notification API의 requestPersmission 메서드를 사용해 권한을 요청한다.
  2. 권한이 부여됐다면
    • Push API를 사용해서 Service Worker가 Push 서비스를 구독하게 한다.
    • Service Worker가 Push 이벤트를 주시하고 있다.
    • Push 이벤트가 도착하면, Service Worker가 Push 메시지의 정보를 사용해서 Notification API를 통해 알림을 표시한다.
  3. 권한이 거절됐다면
    • 할 수 있는게 없다. 이 부분에 대한 코드 처리가 필요하다.

Web Push Protocol

Push 알림을 수신하는 브라우저, 발송하는 서버 사이 규약이 있어야 상호작용이 가능하다.

  1. 클라이언트는 Push 서비스에게 구독 요청을 한다.
  2. Push 서비스는 클라이언트에게 해당 브라우저의 구독 정보를 전달한다.
  3. 클라이언트는 받은 구독 정보를 서버에게 전달한다. 서버는 구독 정보를 사용자 정보와 함께 저장한다.
  4. 서버가 Push 서비스로 구독 정보와 메시지를 전달한다.
  5. Push 서비스가 클라이언트에게 메시지를 전달한다.

VAPID 키 인증 방식

안전한 Push 메시지를 전송하기 위해 VAPID(Voluntary Application Server Identification) 인증 방식을 사용한다.
서버에서 Push 서비스로 메시지를 전달할 때, VAPID 명세에 따른 정보가 담긴 JWT를 함께 전달하고, 이 JWT를 VAPID의 비공개 키로 서명(암호화)한다.

위 그림에서 암호화 과정을 포함하여 표현했다.

  1. 클라이언트가 구독 요청을 하며, VAPID 공개 키를 전달한다.
  2. 구독 정보를 클라이언트로 전달한다.
  3. 구독 정보를 서버로 전달한다.
  4. 서버는 Push 서비스에게 구독 정보, 메시지와 함께 VAPID 비공개 키로 암호화된 JWT를 전달한다.
  5. VAPID 공개 키로 유효성을 검증하고, 검증된 경우 구독 정보에 해당하는 클라이언트로 메시지를 발송한다.

Firebase Cloud Messaging

Web Push Protocol을 쉽게 구현할 수 있게 해주는 솔루션 중 FCM이 있다. 클라우드를 이용해 메시지를 전송할 수 있다. 장점은 무료라는 것과, 특정 플랫폼에 종속적이지 않기 때문에 확장성이 높다는 것이다.

FCM을 이용해서 프론트엔드에서 Web Push Notification을 구현하는 방법에 대해 알아보자.

Firebase SDK 추가 및 초기화

SDK를 import 하고 다음 코드를 작성하면 FCM 라이브러리가 Service Worker를 설정해준다. public 안에 포함시켜야 제대로 동작한다.

  • public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts(
  "https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js"
);

// [1]
const firebaseConfig = {
  apiKey: `${API_KEY}`,
  projectId: `${PROJECT_ID}`,
  messagingSenderId: `${MESSAGING_SENDER_ID}`,
  appId: `${APP_ID}`,
};

firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

// [2]
messaging.onBackgroundMessage(messaging, (payload) => {
  const notificationTitle = "깜지";
  const notificationOptions = {
    body: payload,
    icon: "/kkamji-logo.png",
  };

  self.registration.showNotification(notificationTitle, notificationOptions);
});
  1. config는 Firebase Console에서 확인할 수 있다.
  2. 백그라운드 알림 수신에 대해 설정해주는 부분이며 커스터마이징 가능하다.

포그라운드 메시지 수신

onMessage를 통해 포그라운드에서 메시지를 수신할 수 있다.

  • pages/_app.tsx
import { FirebaseApp, initializeApp } from "firebase/app";
import { getMessaging, onMessage } from "firebase/messaging";

function MyApp(){
  
  useEffect(()=>{
    const app = initializeApp(firebaseConfig);
    
    setNotification(app);
  }, []);

  const setNotification = (app: FirebaseApp) => {
    const messaging = getMessaging(app);
    onMessage(messaging, (payload) => {});
  };
}

Token 전달

로그인한 사용자에게만 알림 전송이 필요하기 때문에, 로그인을 할 때 토큰을 서버로 전달했다. 로그인 환경이 변경되었을 때도 새로운 브라우저에 대한 토큰을 전송할 수 있다.

  • pages/login.tsx
  useEffect(() => {
    const messaging = getMessaging(app);
	
    // [1]
    getToken(messaging, {
      vapidKey: ${VAPID_KEY},
    })
      .then((currentToken) => {
        setFcmToken(currentToken);
      })
      .catch((err) => {});
  }, []);

  const onLoginValid: SubmitHandler<LoginValidForm> = async (data) => {
    const { email, password } = data;

    const loginBody: LoginBody = {
      email,
      password,
      fcmToken,
      platform: navigator.platform,		 // [2]
    };
    mutateLogin({ loginBody });
  };
  1. getToken에서 VAPID 키를 통해 클라이언트를 식별할 수 있는 JWT를 생성하고, 서버에 전달한다.
  2. 당시 iOS에서 Web Push를 지원하고 있지 않아서, 사용자 플랫폼 조사를 위해 해당 정보도 함께 서버에 전달했다.

실제 활용의 어려움

Web Push를 도입하고 사용자들에게 배포해본 결과, 실제로 사용되기 어려운 기술이라는 것을 느꼈다.

1. 배포 당시에 iOS에서 Web Push를 지원하지 않았다.

깜지 유저 중 과반수 이상이 아이폰 유저였기 때문에, 해당 기능을 이용할 수 있는 사용자가 적었다. 하지만 현재는 iOS에서도 Web Push를 지원하기 때문에 다시 기대를 걸어볼 수 있을 것 같다.

2. 웹 사이트를 화면에 설치한다는 경험이 익숙치 않다.

사실 나도 웹 사이트를 바탕화면에 설치해서 사용해본 경험이 많이 없다. 자주 방문하는 웹 사이트는 북마크를 해놓고 들어가는 플로우가 익숙하다. 깜지는 모든 유저가 스터디원처럼 관리되고 있었기 때문에 설치 독려가 충분히 가능했지만, 범용적 서비스의 경우 사용자가 웹을 화면에 설치하도록 만드는 것이 어렵다고 생각한다.

3. PC에서 사용하려면 추가적인 환경 설정이 필요하다.

처음 개발을 했을 때 PC환경에서는 알림을 받을 수 없었다. 그 이유는 PC에서 기본적으로 브라우저 알림이 비허용돼있기 때문이다. 시스템 환경설정에서 다음과 같이 브라우저의 알림을 허용해줘야 알림을 수신할 수 있는데, 웹 사이트의 알림을 수신하기 위해 이 설정을 허용으로 바꿀 유저는 많지 않을 것 같다. 나도 평소에 앱 알림은 대부분 꺼놓는 편이기 때문에.

마무리

PWA를 통해 Web Push Notification을 주는 것은 등장한지 오래되지 않은 기술인 만큼 사용자들이 익숙하게 여기기까지 시간이 걸릴 것 같다.
하지만 PWA는 현재 해외에서 많이 사용되고 있다. 우리나라와 다르게 열악한 네트워크 환경을 가진 국가가 많기 때문에, 오프라인 지원 등의 이점이 있는 PWA의 활용도가 높다고 한다. 그런만큼 해당 기술을 학습한 뒤 직접 서비스를 만들어보고, 한계에 부딪혀보는 것도 좋은 경험이었다고 생각한다.

참고한 아티클

profile
배워서 남주는 개발자 김채은입니다 ( •̀ .̫ •́ )✧

2개의 댓글

comment-user-thumbnail
2024년 4월 13일

감사합니다^^

답글 달기
comment-user-thumbnail
2024년 9월 26일

유용한 정보 감사드립니다.

답글 달기