129 lines
3.1 KiB
TypeScript
129 lines
3.1 KiB
TypeScript
import { getStoredToken } from '@/utils/auth-token'
|
|
import type { MessagePushPayload } from '@/types/message'
|
|
|
|
const HEARTBEAT_INTERVAL_MS = 25000
|
|
const RECONNECT_DELAY_MS = 25000
|
|
|
|
let socketTask: UniApp.SocketTask | null = null
|
|
let heartbeatTimer: ReturnType<typeof setInterval> | null = null
|
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
let connecting = false
|
|
let manualClosed = false
|
|
|
|
const listeners = new Set<(payload: MessagePushPayload) => void>()
|
|
|
|
function resolveMessageWsBase(): string {
|
|
const configured = import.meta.env.VITE_MESSAGE_WS_URL?.trim()
|
|
if (configured) {
|
|
return configured.replace(/[?#].*$/, '').replace(/\/$/, '')
|
|
}
|
|
|
|
const envBase = import.meta.env.VITE_API_BASE_URL?.trim() || ''
|
|
const fallback = import.meta.env.VITE_API_PROXY_TARGET?.trim() || 'http://192.168.0.222:8082'
|
|
const httpBase = envBase || fallback
|
|
const wsBase = httpBase.replace(/^https/i, 'wss').replace(/^http/i, 'ws')
|
|
return `${wsBase}/app/ws/message`
|
|
}
|
|
|
|
function resolveMessageWsUrl(token: string): string {
|
|
const base = resolveMessageWsBase()
|
|
return `${base}?token=${encodeURIComponent(token)}`
|
|
}
|
|
|
|
function stopHeartbeat() {
|
|
if (heartbeatTimer) clearInterval(heartbeatTimer)
|
|
heartbeatTimer = null
|
|
}
|
|
|
|
function startHeartbeat() {
|
|
stopHeartbeat()
|
|
heartbeatTimer = setInterval(() => {
|
|
socketTask?.send({ data: 'ping' })
|
|
}, HEARTBEAT_INTERVAL_MS)
|
|
}
|
|
|
|
function clearReconnectTimer() {
|
|
if (reconnectTimer) clearTimeout(reconnectTimer)
|
|
reconnectTimer = null
|
|
}
|
|
|
|
function scheduleReconnect() {
|
|
if (manualClosed || !getStoredToken()) return
|
|
clearReconnectTimer()
|
|
reconnectTimer = setTimeout(() => {
|
|
connectMessageSocket()
|
|
}, RECONNECT_DELAY_MS)
|
|
}
|
|
|
|
function cleanupSocketTask() {
|
|
stopHeartbeat()
|
|
socketTask = null
|
|
connecting = false
|
|
}
|
|
|
|
function handleSocketMessage(raw: string) {
|
|
if (!raw || raw === 'ping' || raw === 'pong') return
|
|
try {
|
|
const data = JSON.parse(raw) as MessagePushPayload
|
|
listeners.forEach((listener) => listener(data))
|
|
if (data.title) {
|
|
uni.showToast({ title: data.title, icon: 'none' })
|
|
}
|
|
} catch {
|
|
// ignore invalid payload
|
|
}
|
|
}
|
|
|
|
export function onMessagePush(listener: (payload: MessagePushPayload) => void) {
|
|
listeners.add(listener)
|
|
return () => listeners.delete(listener)
|
|
}
|
|
|
|
export function connectMessageSocket() {
|
|
const token = getStoredToken()
|
|
if (!token || connecting || socketTask) return
|
|
|
|
manualClosed = false
|
|
connecting = true
|
|
clearReconnectTimer()
|
|
|
|
const task = uni.connectSocket({
|
|
url: resolveMessageWsUrl(token),
|
|
complete: () => {
|
|
connecting = false
|
|
},
|
|
})
|
|
|
|
socketTask = task
|
|
|
|
task.onOpen(() => {
|
|
startHeartbeat()
|
|
})
|
|
|
|
task.onMessage((res) => {
|
|
const raw = typeof res.data === 'string' ? res.data : ''
|
|
handleSocketMessage(raw)
|
|
})
|
|
|
|
task.onClose(() => {
|
|
cleanupSocketTask()
|
|
scheduleReconnect()
|
|
})
|
|
|
|
task.onError(() => {
|
|
cleanupSocketTask()
|
|
scheduleReconnect()
|
|
})
|
|
}
|
|
|
|
export function disconnectMessageSocket() {
|
|
manualClosed = true
|
|
clearReconnectTimer()
|
|
stopHeartbeat()
|
|
if (socketTask) {
|
|
socketTask.close({})
|
|
}
|
|
socketTask = null
|
|
connecting = false
|
|
}
|