들어가기 전

VAPID_PUBLIC_KEY='BAQYVETELyf6ErKyL6sd-K3o-09bcJE_oPGQcualSXO4i0y0sMK9flEGljqDkwj-0zA25ckb56IPIAd969YFTfk'

순서도

graph TD
    A[사용자가 로그인] --> B[구독 버튼 클릭]
    B --> C[Notification과 serviceWorker 지원 여부 확인]
    C --> D{알림 권한 요청}
    D -->|허용| E[서비스 워커 등록 가져오기]
    E --> F{FCM 푸시 알림 구독 VAPID_PUBLIC_KEY 사용}
    F -->|허용| G[서버로 구독 정보 전송]
    G --> H['푸시 알림 구독 완료!' 메시지 표시]
    D -->|거부| I['알림 권한 거부됨.' 메시지 표시]
    F -->|거부| J[구독 실패 처리]

요약 예시 코드

구독시

// 버튼을 이용한 구독 서비스일때
subscribeBtn.addEventListener('click', async () => {
  if ('Notification' in window && navigator.serviceWorker) {
    try {
      const permission = await Notification.requestPermission();
      if (permission === 'granted') {
        const registration = await navigator.serviceWorker.ready;
        const subscription = await registration.pushManager.subscribe({
          userVisibleOnly: true,
          applicationServerKey: urlB64ToUint8Array(
            VAPID_PUBLIC_KEY
          ),
        });

        const response = await fetch(
          '<https://juchum.info/api/push/subscribe>',
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(subscription),
            credentials: 'include',
          },
        );

        if (response.ok) {
          subscribeMessage.textContent = 
            'Subscribed to push notifications!';
        }
      } else {
        subscribeMessage.textContent = 'Notification permission denied.';
      }
    } catch (error) {
      subscribeMessage.textContent = 'Subscription failed.';
    }
  }
});

// 직렬화 코드
const urlB64ToUint8Array = (base64String) => {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/');
  const rawData = window.atob(base64);
  return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}

service worker

//service-worker.js
self.addEventListener('push', (event) => {
  const data = event.data
    ? event.data.json()
    : { title: '알림', body: '내용 없음' };

  self.registration.showNotification(data.title, {
    body: data.body,
    icon: 'icon.png', // 기본 아이콘 설정
  });
});

상세 설명

api/push/subscribe - 파라미터

{
  "endpoint": "string", //현재 디바이스의 FCM 서비스 엔드포인트
  "keys": {
    "p256dh": "string", // base64로 인코딩된 푸시 메시지 암호화용 키
    "auth": "string" // base64로 인코딩된 인증키
  }
}

파라미터 생성 방법

//FCM 서비스 구독을 위한 클라이언트 사이드 코드
//이 subscribe를 그대로 /api/push/subscribe에 보내면 됩니다.
const subscription = await registration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlB64ToUint8Array( 
    VAPID_PUBLIC_KEY,
  ),
});

// 직렬화 코드
const urlB64ToUint8Array = (base64String) => {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/');
  const rawData = window.atob(base64);
  return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}