Files
hazardousWaste_web/src/utils/ws.js

176 lines
3.6 KiB
JavaScript
Raw Normal View History

2026-04-14 11:37:37 +08:00
import { getToken } from '@/utils/auth'
let ws = null
let reconnectTimer = null
let manualClose = false
2026-04-15 08:32:42 +08:00
let retryCount = 0
const subscribers = new Set()
2026-04-14 11:37:37 +08:00
const WS_PATH = '/ws'
2026-04-15 08:32:42 +08:00
const MAX_RECONNECT_DELAY = 30000
2026-04-14 11:37:37 +08:00
function getWsBaseUrl() {
const baseApi = import.meta.env.VITE_APP_BASE_API || ''
if (/^https?:\/\//.test(baseApi)) {
return baseApi.replace(/^http/, 'ws').replace(/\/$/, '')
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
return `${protocol}//${window.location.host}${baseApi}`.replace(/\/$/, '')
}
2026-04-15 08:32:42 +08:00
function buildWsUrl() {
2026-04-14 11:37:37 +08:00
const url = new URL(`${getWsBaseUrl()}${WS_PATH}`)
const token = getToken()
if (token) {
url.searchParams.set('token', token)
}
return url.toString()
}
2026-04-15 08:32:42 +08:00
export function connectWs(onMessage) {
2026-04-22 15:41:41 +08:00
console.trace('connectWs called')
2026-04-15 08:32:42 +08:00
if (!getToken()) {
console.warn('[WebSocket] missing token')
2026-04-14 11:37:37 +08:00
return
}
if (typeof onMessage === 'function') {
subscribers.add(onMessage)
}
2026-04-14 11:37:37 +08:00
manualClose = false
clearReconnectTimer()
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
return
}
2026-04-15 08:32:42 +08:00
ws = new WebSocket(buildWsUrl())
2026-04-14 11:37:37 +08:00
ws.onopen = () => {
2026-04-15 08:32:42 +08:00
retryCount = 0
console.log('[WebSocket] connected')
2026-04-14 11:37:37 +08:00
}
ws.onmessage = (event) => {
let data = event.data
try {
data = JSON.parse(event.data)
} catch (e) {
2026-04-15 08:32:42 +08:00
// Keep plain text messages as-is.
2026-04-14 11:37:37 +08:00
}
subscribers.forEach((handler) => {
try {
handler(data, event)
} catch (error) {
console.error('[WebSocket] subscriber error', error)
}
})
2026-04-14 11:37:37 +08:00
}
2026-04-22 15:41:41 +08:00
ws.onclose = (event) => {
console.log('[WebSocket] closed', { code: event.code, reason: event.reason })
2026-04-14 11:37:37 +08:00
ws = null
2026-04-22 15:41:41 +08:00
if (isNonRetryableClose(event)) {
console.warn('[WebSocket] stop reconnect because close reason is not retryable', {
code: event.code,
reason: event.reason
})
retryCount = 0
clearReconnectTimer()
return
}
if (!manualClose && subscribers.size > 0) {
2026-04-14 11:37:37 +08:00
reconnectTimer = setTimeout(() => {
2026-04-15 08:32:42 +08:00
console.log('[WebSocket] reconnecting')
connectWs()
2026-04-15 08:32:42 +08:00
}, getReconnectDelay())
2026-04-14 11:37:37 +08:00
}
}
ws.onerror = (error) => {
2026-04-15 08:32:42 +08:00
console.error('[WebSocket] error', error)
2026-04-14 11:37:37 +08:00
}
}
export function closeWs(onMessage) {
if (typeof onMessage === 'function') {
subscribers.delete(onMessage)
} else {
subscribers.clear()
}
if (subscribers.size > 0) {
return
}
2026-04-14 11:37:37 +08:00
manualClose = true
2026-04-15 08:32:42 +08:00
retryCount = 0
2026-04-14 11:37:37 +08:00
clearReconnectTimer()
if (ws) {
ws.close()
ws = null
}
}
export function reconnectWs(onMessage) {
if (typeof onMessage === 'function') {
subscribers.add(onMessage)
}
manualClose = false
retryCount = 0
clearReconnectTimer()
if (ws) {
ws.close()
ws = null
}
2026-04-15 08:32:42 +08:00
manualClose = false
connectWs()
2026-04-15 08:32:42 +08:00
}
2026-04-14 11:37:37 +08:00
export function sendWs(data) {
if (!ws || ws.readyState !== WebSocket.OPEN) {
2026-04-15 08:32:42 +08:00
console.warn('[WebSocket] send failed, socket is not open')
2026-04-14 11:37:37 +08:00
return false
}
ws.send(typeof data === 'string' ? data : JSON.stringify(data))
return true
}
export function getWsState() {
return ws ? ws.readyState : WebSocket.CLOSED
}
2026-04-15 08:32:42 +08:00
function getReconnectDelay() {
retryCount += 1
return Math.min(5000 * retryCount, MAX_RECONNECT_DELAY)
}
2026-04-14 11:37:37 +08:00
function clearReconnectTimer() {
if (reconnectTimer) {
clearTimeout(reconnectTimer)
reconnectTimer = null
}
2026-04-15 08:32:42 +08:00
}
2026-04-22 15:41:41 +08:00
function isNonRetryableClose(event) {
const reason = (event?.reason || '').toLowerCase()
return (
reason.includes('invalid token') ||
reason.includes('missing token') ||
reason.includes('deptid is null')
)
}