PWA

ChatGPT와 FCM 개발 - Subscription 구현

그랜파 개발자 2024. 9. 19. 12:17

76. FCM 푸시 알림 서비스 기능 분석

푸시 알림 서비스 필요 기능

1. 푸시 알림 구독

  • Notification : 푸시 알림 수신 승인
  • Service Worker :
  • 푸시 알림을 받을 구독자 정보 생성
  • 푸시 알림 구독자 정보를 서버(Backend)에 전송
  • 푸시 알림 수신 및 표시

2. 푸시 알림 보내기

  • Backend에 푸시 알림 요청

3. Backend

  • Client의 Service Worker로부터 푸시 알림 구독자 정보를 받아 DB에 저장
  • 푸시 알림 요청을 받으면 Backend에서 FCM이 구독자에게 푸시 알림 보냄

ChatGPT가 시키는 대로 했습니다.

1. service-worker.js

2. 서비스 워크 등록

프로젝트 만들기를 통해 기본적으로 설치된 registerServiceWorker.js 사용

3. src/notifications.js

4. src/views/SubscribeView.vue

5. 실행

  • Notification permission granted.
    푸시 알림 수신을 승인하였습니다.
  • User is subscribed: PushSubscription {endpoint: . . . }
    푸시 알림 구독을 위한 구독자 정보를 생성하였습니다.
  • Failed to send subscription to server:
    구독자 정보를 서버로 전송하는 것을 실패하였습니다.

6. Source Code

sw.js - Service Worker

// sw.js - Service Worker 
self.addEventListener('push', function(event) {
  const data = event.data.json();
  console.log('[Service Worker] Push Received.', data);

  const title = data.title || 'Notification Title';
  const options = {
    body: data.body || 'Notification Body',
    icon: "/img/push-noti-icon.png",
    badge: "/img/push-badge-icon.png",
    image: "/img/push-news.jpg",
  };

  event.waitUntil(
    self.registration.showNotification(title, options)
  );
});

self.addEventListener('notificationclick', function(event) {
  console.log('[Service Worker] Notification click Received.');

  event.notification.close();

  event.waitUntil(
    clients.openWindow('https://velog.io/@inetsos/posts') 
  );
});

src/notifications.js

// src/notifications.js

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/');

  const rawData = window.atob(base64);
  const outputArray = new Uint8Array(rawData.length);

  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }

  return outputArray;
}

export async function subscribeUserToPush() {
  try {
    const registration = await navigator.serviceWorker.ready;
    const permission = await Notification.requestPermission();

    if (permission === 'granted') {
      console.log('Notification permission granted.');

      const vapidPublicKey = "'YOUR_VAPID_PUBLIC_KEY'";
      const subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
      });

      console.log('User is subscribed:', subscription);

      // Send subscription to server to store it
      await sendSubscriptionToServer(subscription);

      return subscription;

    } else {
      console.warn('Notification permission not granted.');
    }
  } catch (error) {
    console.error('Failed to subscribe the user: ', error);
  }
}

async function sendSubscriptionToServer(subscription) {
  try {
    const response = await fetch('/api/subscribe', {
      method: 'POST',
      body: JSON.stringify(subscription),
      headers: {
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      throw new Error('Failed to send subscription to server.');
    }

    console.log('Subscription sent to server successfully.');
  } catch (error) {
    console.error('Failed to send subscription to server:', error);
  }
}

src/views/SubscribeView.vue

Copy<!-- src/views/SubscribeView.vue -->
<template>
  <v-container>
    <v-card>
      <v-card-title>
        Push Notifications
      </v-card-title>
      <v-btn color="primary" @click="subscribeToNotifications" dark>
        Subscribe to Notifications
      </v-btn>
    </v-card>
  </v-container>
</template>

<script>
import { subscribeUserToPush } from '@/notifications';

export default {
  name: 'NotificationButton',
  methods: {
    async subscribeToNotifications() {
      const subscription = await subscribeUserToPush();
      if (subscription) {
        console.log('User subscribed successfully:', subscription);
      }
    }
  }
};
</script>