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 | null = null let reconnectTimer: ReturnType | 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 }