62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { useLocation } from 'react-router-dom'
|
|
import { useQueryClient } from '@tanstack/react-query'
|
|
import { useAuthStore } from '@/app/store/auth'
|
|
import { toast } from '@/app/store/toast'
|
|
import type { Customer, Message } from '@/types'
|
|
import { getWebSocketTicket } from '@/services/api/auth'
|
|
|
|
export function GlobalRealtime() {
|
|
const token = useAuthStore((state) => state.token)
|
|
const location = useLocation()
|
|
const queryClient = useQueryClient()
|
|
|
|
useEffect(() => {
|
|
if (!token) return
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
let socket: WebSocket | undefined
|
|
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
|
|
let stopped = false
|
|
let retryDelay = 500
|
|
|
|
const connect = async () => {
|
|
if (stopped) return
|
|
try {
|
|
const { ticket } = await getWebSocketTicket()
|
|
if (stopped) return
|
|
socket = new WebSocket(`${protocol}//${window.location.host}/api/ws?ticket=${encodeURIComponent(ticket)}`)
|
|
} catch {
|
|
reconnectTimer = setTimeout(() => { void connect() }, retryDelay)
|
|
retryDelay = Math.min(retryDelay * 2, 10_000)
|
|
return
|
|
}
|
|
socket.onopen = () => { retryDelay = 500 }
|
|
socket.onmessage = (raw) => {
|
|
let event: { type?: string; message?: Message; customer?: Customer; conversationId?: string }
|
|
try { event = JSON.parse(String(raw.data)) } catch { return }
|
|
if (event.type !== 'message.created' || event.message?.sender !== 'customer') return
|
|
void queryClient.invalidateQueries({ queryKey: ['notifications'] })
|
|
void queryClient.invalidateQueries({ queryKey: ['conversations'] })
|
|
if (!location.pathname.startsWith('/inbox')) {
|
|
const preview = event.message.translatedText || event.message.originalText || `[${event.message.type}]`
|
|
toast.info(`${event.customer?.name || event.message.senderName || 'Telegram 客户'} 发来新消息`, preview.slice(0, 120))
|
|
}
|
|
}
|
|
socket.onerror = () => socket?.close()
|
|
socket.onclose = () => {
|
|
if (stopped) return
|
|
reconnectTimer = setTimeout(() => { void connect() }, retryDelay)
|
|
retryDelay = Math.min(retryDelay * 2, 10_000)
|
|
}
|
|
}
|
|
void connect()
|
|
return () => {
|
|
stopped = true
|
|
if (reconnectTimer) clearTimeout(reconnectTimer)
|
|
socket?.close()
|
|
}
|
|
}, [token, location.pathname, queryClient])
|
|
|
|
return null
|
|
}
|