feat:蓝牙绑定和在线客服
This commit is contained in:
128
src/utils/message-socket.ts
Normal file
128
src/utils/message-socket.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user