Vue3, Firebase 프로젝트 - 채팅앱 VSignal

13. Vue3 Firebase 프로젝트 채팅앱 VSignal - 메시지를 언제 읽었는지 표시하기

그랜파 개발자 2025. 3. 24. 02:04

메시지를 언제 읽었는지 표시하기 (예: "5분 전에 읽음")

상대가 메시지를 읽었으면 읽은 시간을 기록하고,
이것을 메시지를 보낸 사람에게 보여주면 좋겠습니다.

읽은 메시지는 "5분 전에 읽음" 같은 상대적 시간으로 표시하고
UI에서 메시지가 실시간으로 업데이트 하도록 기능을 구현합니다.

1. Firebase에 메시지 읽은 시간 저장

상대방이 채팅방을 열면 메시지를 "읽음" 상태로 변경 + 읽은 시간 저장
status: "read"와 함께 "readAt" (읽은 시간) 필드 추가

📌 Firebase 메시지 구조 예시

"messages": {
  "chatRoomId": {
    "messageId1": {
      "text": "안녕!",
      "sender": "user1",
      "receiver": "user2",
      "timestamp": 1710020000000,
      "status": "read",
      "readAt": 1710020500000
    }
  }
}

✔ "readAt" 필드 추가하여 읽은 시간 기록

2. Store에서 메시지 읽은 시간 처리

📌 store/chatStore.js 수정

import { defineStore } from "pinia";
import { getDatabase, ref, onValue, update, serverTimestamp } from "firebase/database";
import { auth } from "@/firebase";
import { formatDistanceToNow } from "date-fns"; // ⏳ 상대적 시간 변환

export const useChatStore = defineStore("chat", {
  state: () => ({
    messages: [],
  }),

  actions: {
    // ✅ 채팅방 메시지 가져오기
    listenForMessages(chatRoomId) {
      const db = getDatabase();
      const messagesRef = ref(db, `messages/${chatRoomId}`);

      onValue(messagesRef, (snapshot) => {
        if (snapshot.exists()) {
          this.messages = Object.entries(snapshot.val()).map(([id, msg]) => ({
            id,
            ...msg,
          }));
        } else {
          this.messages = [];
        }
      });
    },

    // ✅ 메시지를 읽음 상태로 변경 + 읽은 시간 저장
    markMessagesAsRead(chatRoomId) {
      const db = getDatabase();
      const user = auth.currentUser;
      if (!user) return;

      this.messages.forEach((message) => {
        if (message.receiver === user.uid && message.status === "unread") {
          const messageRef = ref(db, `messages/${chatRoomId}/${message.id}`);
          update(messageRef, {
            status: "read",
            readAt: serverTimestamp(), // 🔥 읽은 시간 저장
          });
        }
      });
    },
  },

  getters: {
    // ✅ 메시지 읽은 시간을 상대적 표현으로 변환 (예: "5분 전에 읽음")
    getReadTime: () => (message) => {
      if (!message.readAt) return "";
      return formatDistanceToNow(new Date(message.readAt), { addSuffix: true });
    },
  },
});

✔ markMessagesAsRead(chatRoomId) → 메시지 읽을 때 "readAt"을 현재 시간으로 저장
✔ getReadTime() → "5분 전에 읽음" 같은 상대적 시간 표시

3. UI에서 메시지 읽은 시간 표시

📌 ChatRoom.vue 수정

<script setup>
import { useChatStore } from "@/store/chatStore";
import { useRoute } from "vue-router";
import { onMounted, watch } from "vue";

const chatStore = useChatStore();
const route = useRoute();
const chatRoomId = route.params.chatRoomId;

onMounted(() => {
  chatStore.listenForMessages(chatRoomId);
});

// ✅ 메시지 목록이 업데이트될 때 읽음 처리 실행
watch(() => chatStore.messages, () => {
  chatStore.markMessagesAsRead(chatRoomId);
});
</script>

<template>
  <v-card>
    <v-card-title>1:1 채팅</v-card-title>
    <v-divider></v-divider>

    <v-card-text>
      <v-list>
        <v-list-item
          v-for="message in chatStore.messages"
          :key="message.id"
          :class="{ 'read-message': message.status === 'read' }"
        >
          <v-list-item-content>
            <v-list-item-title>{{ message.text }}</v-list-item-title>
            <v-list-item-subtitle>
              {{ message.sender === 'me' ? "보낸 메시지" : "받은 메시지" }}
            </v-list-item-subtitle>
          </v-list-item-content>

          <v-list-item-action v-if="message.sender === 'me'">
            <v-icon :color="message.status === 'read' ? 'blue' : 'grey'">
              mdi-check
            </v-icon>
            <span v-if="message.status === 'read'" class="read-time">
              {{ chatStore.getReadTime(message) }}
            </span>
          </v-list-item-action>
        </v-list-item>
      </v-list>
    </v-card-text>
  </v-card>
</template>

<style>
.read-message {
  background-color: #e3f2fd;
}
.read-time {
  font-size: 12px;
  color: gray;
  margin-left: 5px;
}
</style>

✔ 읽은 메시지에는 "5분 전에 읽음" 표시
✔ 상대방이 읽으면 파란 체크(mdi-check) + 읽은 시간 표시

🎯 결론

✅ 상대방이 메시지를 읽으면 Firebase에 읽은 시간(readAt) 기록
✅ 읽은 메시지는 "5분 전에 읽음" 같은 상대적 시간으로 표시
✅ UI에서 메시지가 실시간으로 업데이트됨 🚀