iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
自我挑戰組

我獨自開發 - Supabase 打造全端應用系列 第 26

第二十六關 - 來企排隊: Supabase 快速建立聊天室

  • 分享至 

  • xImage
  •  

聊天室列表

聊天室是一個私密的廣播電台頻道。只有知道正確頻率(例如:訂單ID)的人才能收聽和發話。Supabase Realtime 提供建立這種私密頻道的能力,讓 App 使用者之間可以即時溝通。

核心概念

為了讓程式碼更容易管理,會將所有跟聊天相關的複雜邏輯,都放進一個叫做 useRealtimeChat 的 Hook 檔案。未來任何頁面需要聊天功能時,只要引用這個檔案,而不需要重複撰寫一樣的程式碼。

目標:

  1. 收聽訊息:進入某個聊天室時,要能即時聽到(接收到)別人傳來的訊息。
  2. 發送訊息:說的話(傳送的訊息)要先被記錄下來(存到資料庫),然後再廣播給頻道內的所有人知道。

實作程式碼

同時處理「監聽頻道」和「發送訊息」兩大功能。

// useRealtimeChat.ts

// 定義 ChatMessage 的結構
export interface ChatMessage {
  id: string
  content: string
  user: {
    name: string
  }
  createdAt: string
}

// 為廣播的事件取一個固定的名字
const EVENT_MESSAGE_TYPE = 'message'

/**
 * @param roomName - 聊天的頻道名稱,我們會用訂單ID來當作頻道名稱
 * @param username - 使用者的名稱
 */

export function useRealtimeChat({ roomName, username }: { roomName: string, username: string }) {
  const supabase = createClient()
  const [messages, setMessages] = useState<ChatMessage[]>([])
  const [channel, setChannel] = useState<ReturnType<typeof supabase.channel> | null>(null)
  const [isConnected, setIsConnected] = useState(false)

  //「建立頻道連線」與「監聽訊息」
  useEffect(() => {
    // 1. 根據傳入的 roomName,建立一個新的頻道
    const newChannel = supabase.channel(roomName)

    // 2. 設定這個頻道要監聽的事件
    newChannel
      .on('broadcast', { event: EVENT_MESSAGE_TYPE }, (payload) => {
        // 當收到廣播訊息時,將新訊息加到目前的訊息列表中
        setMessages((current) => [...current, payload.payload as ChatMessage])
      })
      .subscribe(async (status) => {
        // 3. 正式訂閱頻道,開始接收訊息
        if (status === 'SUBSCRIBED') {
          // 如果成功訂閱,就把連線狀態設為 true
          setIsConnected(true)
        }
      })

    // 4. 將建立好的頻道物件存起來,方便後續使用
    setChannel(newChannel)

    // 5. 當元件被銷毀時(例如:使用者離開聊天室頁面),要記得移除頻道連線,節省資源
    return () => {
      supabase.removeChannel(newChannel)
    }
  }, [roomName, username, supabase])

  // 「發送訊息」
  const sendMessage = useCallback(
    async (content: string) => {
      // 如果頻道不存在或尚未連線成功,就不做任何事
      if (!channel || !isConnected) return

      // 準備要發送的訊息物件
      const message: ChatMessage = {
        id: crypto.randomUUID(),
        content,
        user: {
          name: username,
        },
        createdAt: new Date().toISOString(),
      }

      // 核心流程
      // 步驟 1: 將訊息儲存到資料庫
      const { error } = await supabase.from('messages').insert({
        id: message.id,
        room_id: roomName, // 紀錄這個訊息是屬於哪個聊天室的
        user_name: message.user.name,
        content: message.content,
        created_at: message.createdAt,
      })

      // 步驟 2: 透過頻道廣播訊息給所有在線上的使用者
      await channel.send({
        type: 'broadcast',
        event: EVENT_MESSAGE_TYPE,
        payload: message, // 廣播的內容就是我們剛剛建立的訊息物件
      })
    },
    [channel, isConnected, username, roomName, supabase]
  )

  // 將「訊息列表」、「發送訊息的函式」、「連線狀態」回傳給使用這個 Hook 的頁面
  return { messages, sendMessage, isConnected }
}

小結

透過 useRealtimeChat Hook,我們成功地將複雜的邏輯封裝起來。
未來在任何需要聊天功能的頁面,我們只需要簡單地呼叫 useRealtimeChat,就能馬上獲得 messages 列表來顯示,以及一個 sendMessage 函式來發送訊息。

... to be continued

有任何想討論歡迎留言,或需要指正的地方請鞭大力一點,歡迎訂閱、按讚加分享,分享給想要提升開發效率的朋友


上一篇
第二十五關 - 來企排隊: Supabase 快速建立即時通知功能
下一篇
第二十七關 - 來企排隊: Supabase 快速整合 Resend 訂單通知信
系列文
我獨自開發 - Supabase 打造全端應用28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言