Vue PWA mylog

mylog 알림 요청

그랜파 개발자 2024. 11. 28. 06:07

Vue로 PWA 개발 - 그랜파 개발자

Vue 프로젝트 Beta Test : mylog, 일상의 기록

개발이 진행됨에 따라 소스 코드를 계속 추가해 갑니다.

mylog 알림 요청

마이로그는 사용자가 마이로그를 저장하면 구독자에게 알림을 보내려고 합니다.

  • 알림을 받이 위해서 사용자는 알림을 받는 것을 허용해야 합니다. 알림을 허용한 후 어떤 글쓴이에 대해 구독을 요청하면 마이로그는 구독 요청한 글쓴이가 마이로그를 새로 저장하면 알림을 보냅니다.
  • 알림을 차단하면 알림을 받지 않겠다는 것으로 글쓴이에 대한 구독과 관계없이 알림을 보내지 않습니다.
  • 글쓴이에 대해서는 사용자가 구독 요청을 하면 알림을 보내고, 구독 취소를 하면 알림을 보내지 않습니다.

알림 요청을 하면 마이로그는 사용자에게 알림 허용을 요청하고, 사용자가 알림 받는 것을 허용하면 토큰을 받아 firestore에 저장합니다. 토큰을 받기 위해서는 앱이 서비스 워커를 등록한 후 서비스 워커가 활성화되어 있어야 합니다.

1. 알림 요청

Notification.requestPermission()로 알람 허용 요청을 합니다. 사용자가 알림 받는 것을 허용하면 토큰을 얻어서 firestore에 저장합니다. 토큰 등록의 경우 한 사용자가 PC, 모바일 등 여러 기기를 사용할 수 있습니다. 그러므로 각각 기기에 대한 토큰의 등록이 필요합니다.

fcmTokens 컬렉션은 userId를 key로 문서를 만들어 각 사용자의 토큰을 배열로 저장합니다. 그러므로 토큰의 저장을 위해서 먼저 userId에 대한 문서가 있는지 확인하고, 문서가 없다면 하나의 토큰을 가진 문서를 저장하고, 문서가 있다면 문서의 토큰 필드에 토큰을 추가합니다.

async getAndSaveFCMToken({ commit, dispatch }, userId) {
  try { 
    const permission = await Notification.requestPermission();
    if (permission === "granted") {      
      const token = await getToken(messaging, { vapidKey: process.env.VUE_APP_VAPID_KEY });
      if (token) {          
        dispatch('saveFCMToken', { userId, token });
        commit("setGranted", true);
      } else {
        commit('setError', "No registration token available.");
      }
    } else {
      commit('setError', "Notification permission denied.");
    }
  } catch (error) { 
    commit('setError', "An error occurred while getting the FCM token: " + error.message);
  }    
},
async saveFCMToken({ commit }, {userId, token}) {     
  try { 
    const tokenRef = doc(db, 'fcmTokens', userId);
    const tokenDoc = await getDoc(tokenRef);   
    if (tokenDoc.exists()) {
      // 사용자에 대해 등록된 토큰 문서가 있으면    
      const tokens = tokenDoc.data().tokens || [];        
      // Check if the token already exists
      const tokenExists = tokens.some(t => t.token === token);        
      if (!tokenExists) {
        // Add the token with creation date using arrayUnion
        await updateDoc(tokenRef, {
          tokens: arrayUnion({  token: token,  createdAt: new Date()  })
        });
        commit('setLoading', false);
        alert('New FCM token added with creation date.');
      } else {
        commit('setLoading', false);
        alert('FCM token already exists, no action taken.');
      }
    } else {
      // Create a new document if the user does not exist, with the token and creation date
      await setDoc(tokenRef, {
        tokens: [{ token: token,  createdAt: new Date() }]
      });
      //commit('setError', 'User document created and token saved.');
      commit('setLoading', false);
      alert('User document created and token saved.');
    }
  } catch (error) {
    //commit('setError','Error saving FCM token: ${error}');
    commit('setLoading', false);
    alert('Error saving FCM token : ' + error);
  }
},

2. 알림 차단

알림을 차단하기 위해서는 firestore의 fcmTokens 컬렉션에 등록된 토큰을 삭제합니다. 토큰이 삭제되었으므로 알림을 전송할 수 없습니다.

async deleteFCMToken({ commit }, userId) {   
  commit('setLoading', true); 
  try { 
    const tokenRef = doc(db, 'fcmTokens', userId);
    const tokenDoc = await getDoc(tokenRef);
    if (tokenDoc.exists()) {
      // 이미 등록된 토근이 있는 경우
      await deleteDoc(tokenRef);
      commit("setGranted", false);
      commit('setLoading', false);
      alert('User token deleted.');
    }
  } catch (error) {
    //commit('setError',"Error delete FCM token: " + error.message);
    commit('setLoading', false);
    alert('Error delete FCM token : ' + error);
  }    
}, 

3. 알림 허용 여부 확인

알림 허용 여부도 fcmTokens 컬렉션의 토큰이 저장되어 있는지 여부로 확인을 합니다.

// 푸시 알림 허용/차단을 위하여 토큰이 저장되어 있는지 확인
async checkFCMToken({ commit }, userId) { 
  const tokenRef = doc(db, 'fcmTokens', userId);
  try { 
    const tokenDoc = await getDoc(tokenRef);      
    if (tokenDoc.exists()) {
      commit("setGranted", true);
    } else {
      commit("setGranted", false);
    }
  } catch (error) {
    commit('setError','Error check FCM token : ' + error.message);
    console.error('Error check FCM token:', error);
  }
},

4. NotificationView.vue

<!-- src/views/NotificationView.vue -->
<template>
  <v-container>
    <v-card class="pa-3">

      <v-card-title style="font-size:1.1em">
        알림 요청
      </v-card-title>

      <v-card-subtitle>
        '알림 요청'을 하면 구독 중인 저자의 새로운 마이로그가 저장되면 알림을 받을 수 있습니다.     
      </v-card-subtitle>

      <v-row>
        <v-col style="text-align: center">   
          <!-- <v-btn color="primary"  @click="requestFCMToken"> 
            <v-icon left>mdi-bell</v-icon> 알림 요청 
          </v-btn> 
           -->
          <v-btn color="primary" @click="requestFCMToken">
            <v-icon left>mdi-bell</v-icon>{{ isGranted ? "알림 차단" : "알림 요청" }}
          </v-btn>

        </v-col>
      </v-row>

    </v-card>

    <!-- v-alert : type="success" "info" "warning"  "error" -->
    <v-alert v-if="error" type="info" dismissible @input="resetErrorMsg" class="my-alert">{{ error }}</v-alert>
    <div class="text-center">
      <v-progress-circular v-if="isLoading" indeterminate></v-progress-circular>
    </div>
      
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from "vuex";

export default {
  name: "NotificationView",
  data() {
    return { 

    };
  },
  async created() {
    const userId = this.$store.state.auth.user.id; 
    if(userId) {
      this.checkFCMToken(userId); // Check if the user is subscribed when the component mounts
    }
  },
  computed: { 
    ...mapGetters('fcm',['isGranted','isLoading','error']),
  },
  methods: {    
    ...mapActions('fcm', ['getAndSaveFCMToken', 'deleteFCMToken', 'checkFCMToken', 'resetError']),
    async requestFCMToken() {
      const userId = this.$store.state.auth.user.id; 
      if(this.isGranted) {
        // 푸시 알림을 허용한 상태 이므로 차단을 실행한다
        try {
          await this.deleteFCMToken(userId);
        } catch (error) {
          console.error("Error requesting FCM token:", error);
        }
      } else {
        // 푸시 알림을 요청하지 않은 상태로 알림 요청을 한다.
        try {          
          await this.getAndSaveFCMToken(userId);
          
          this.ShowNotification();
        } catch (error) {
          console.error("Error requesting FCM token:", error);
        }
      }
      // 알림 요청 버튼의 상태 변경
      //this.checkFCMToken(userId);
    },
    
    resetErrorMsg() {
      this.resetError();
    },

    ShowNotification() {
      const title = "마이로그-일상의 기록";
      const options = {
        body: "알림 서비스 가입을 환영합니다!",
        icon: "/img/push-noti.png",
        badge: "/img/push-badge-icon.png",
        image: "/img/push-image.jpg",
        data: [
           { action: "like", title: "링크를 클릭하세요.", icon: "/img/push-coffee.png", url: '<a href=https://velog.io/@inetsos/posts'>https://velog.io/@inetsos/posts'</a>}
        ],
        vibrate: [500, 100, 500]
      };
      navigator.serviceWorker.ready
      .then(function(swreg) {
        swreg.showNotification(title, options);
      });
    },
  }
};

'Vue PWA mylog' 카테고리의 다른 글

mylog FCM 받기  (1) 2024.12.01
mylog FCM 보내기  (0) 2024.11.30
mylog 서비스 워커 등록  (1) 2024.11.27
mylog backend  (0) 2024.11.25
ManageView.vue  (0) 2024.11.24