265 lines
6.6 KiB
JavaScript
265 lines
6.6 KiB
JavaScript
|
|
/**
|
|||
|
|
* 用户 WebSocket 单例:登录后全局一条连接,按 event 分发
|
|||
|
|
* - device_data → appWs:deviceData(设备详情订阅读取)
|
|||
|
|
* - device_online_status → appWs:deviceOnlineStatus(设备列表等)
|
|||
|
|
* - notice → appWs:notice(通知 tab)
|
|||
|
|
* 与首页 DeviceTab 列表批量 subscribe / unsubscribe 及 deviceUserWs mixin 协议一致
|
|||
|
|
*/
|
|||
|
|
// 与 deviceUserWs 同址,改一处即可
|
|||
|
|
const WS_USER_URL = 'wss://eguo.chuantewulian.cn/prod-api/ws/user'
|
|||
|
|
// const WS_USER_URL = 'ws://192.168.1.5:4601/ws/user'
|
|||
|
|
|
|||
|
|
let _task = null
|
|||
|
|
let _socketOpen = false
|
|||
|
|
let _reconnectTimer = null
|
|||
|
|
let _reconnectAttempts = 0
|
|||
|
|
const _maxAttempts = 5
|
|||
|
|
let _reconnectInterval = 3000
|
|||
|
|
let _appActive = true
|
|||
|
|
|
|||
|
|
function isMiniProgramPlatform() {
|
|||
|
|
try {
|
|||
|
|
if (typeof process !== 'undefined' && process.env && process.env.UNI_PLATFORM) {
|
|||
|
|
return String(process.env.UNI_PLATFORM).indexOf('mp-') === 0
|
|||
|
|
}
|
|||
|
|
} catch (e) {}
|
|||
|
|
if (typeof __wxConfig !== 'undefined' && __wxConfig != null) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 与 common/http.interceptor.js buildUrlWithLang 一致:长连接 query 用服务端约定的 locale(如中文为 zh_CN 而非 zh)
|
|||
|
|
*/
|
|||
|
|
function wsLangQueryValue(storedLang) {
|
|||
|
|
const s = String(storedLang || 'en')
|
|||
|
|
switch (s) {
|
|||
|
|
case 'ru':
|
|||
|
|
return 'ru_RU'
|
|||
|
|
case 'zh':
|
|||
|
|
return 'zh_CN'
|
|||
|
|
case 'ja':
|
|||
|
|
return 'ja_JP'
|
|||
|
|
case 'en':
|
|||
|
|
return 'en_US'
|
|||
|
|
default:
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _buildUrl() {
|
|||
|
|
let url = WS_USER_URL
|
|||
|
|
const token = uni.getStorageSync('token') || ''
|
|||
|
|
const lang = wsLangQueryValue(uni.getStorageSync('language') || 'en')
|
|||
|
|
const q = []
|
|||
|
|
if (token) {
|
|||
|
|
q.push('token=' + encodeURIComponent(token))
|
|||
|
|
}
|
|||
|
|
q.push('lang=' + encodeURIComponent(lang))
|
|||
|
|
return url + '?' + q.join('&')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _getConnectOptions() {
|
|||
|
|
const url = _buildUrl()
|
|||
|
|
const token = uni.getStorageSync('token') || ''
|
|||
|
|
if (isMiniProgramPlatform()) {
|
|||
|
|
return { url }
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
url,
|
|||
|
|
header: {
|
|||
|
|
'content-type': 'application/json;charset=UTF-8',
|
|||
|
|
Authorization: token,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _isTaskReady() {
|
|||
|
|
if (!_task || typeof _task.send !== 'function') return false
|
|||
|
|
if (typeof _task.readyState === 'number') {
|
|||
|
|
return _task.readyState === 1
|
|||
|
|
}
|
|||
|
|
if (typeof _task.readyState === 'string') {
|
|||
|
|
return String(_task.readyState).toUpperCase() === 'OPEN'
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _clearReconnectTimer() {
|
|||
|
|
if (_reconnectTimer != null) {
|
|||
|
|
clearTimeout(_reconnectTimer)
|
|||
|
|
_reconnectTimer = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _scheduleReconnect() {
|
|||
|
|
if (!_appActive) return
|
|||
|
|
if (!uni.getStorageSync('token')) return
|
|||
|
|
if (_reconnectAttempts >= _maxAttempts) {
|
|||
|
|
console.log('[appUserWs] 已达最大重连次数,停止')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
_reconnectAttempts++
|
|||
|
|
const delay = _reconnectInterval
|
|||
|
|
console.log(`[appUserWs] 第 ${_reconnectAttempts} 次重连,约 ${delay / 1000}s 后…`)
|
|||
|
|
_clearReconnectTimer()
|
|||
|
|
_reconnectTimer = setTimeout(() => {
|
|||
|
|
_reconnectTimer = null
|
|||
|
|
connectIfLoggedIn(true)
|
|||
|
|
}, delay)
|
|||
|
|
_reconnectInterval = Math.min(_reconnectInterval * 2, 30000)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _onMessage(res) {
|
|||
|
|
let msg
|
|||
|
|
try {
|
|||
|
|
const raw = res && res.data !== undefined && res.data !== null ? res.data : res
|
|||
|
|
msg = typeof raw === 'string' ? JSON.parse(raw) : raw
|
|||
|
|
} catch (e) {
|
|||
|
|
console.log('[appUserWs] 消息解析失败', res)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (!msg || typeof msg !== 'object') return
|
|||
|
|
const event = msg.event
|
|||
|
|
if (event === 'device_data') {
|
|||
|
|
uni.$emit('appWs:deviceData', msg)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (event === 'device_online_status') {
|
|||
|
|
uni.$emit('appWs:deviceOnlineStatus', msg)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (event === 'notice') {
|
|||
|
|
uni.$emit('appWs:notice', msg)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
console.log('[appUserWs] 未处理 event:', event, msg)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _doConnect(isReconnect) {
|
|||
|
|
if (!uni.getStorageSync('token')) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
_clearReconnectTimer()
|
|||
|
|
if (_task) {
|
|||
|
|
try {
|
|||
|
|
_task.close({})
|
|||
|
|
} catch (e) {}
|
|||
|
|
_task = null
|
|||
|
|
}
|
|||
|
|
_socketOpen = false
|
|||
|
|
const opts = _getConnectOptions()
|
|||
|
|
console.log('[appUserWs] 连接中…', opts.url)
|
|||
|
|
_task = uni.connectSocket({
|
|||
|
|
...opts,
|
|||
|
|
success: () => {},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[appUserWs] connectSocket 失败', err)
|
|||
|
|
_task = null
|
|||
|
|
_socketOpen = false
|
|||
|
|
_scheduleReconnect()
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
if (!_task || !_task.onOpen) return
|
|||
|
|
// 用闭包绑定当前条连接:切换语言/重连时旧 socket 的 onClose 晚于新 socket 创建,否则会误把 _task 置空并打挂新线
|
|||
|
|
const sock = _task
|
|||
|
|
_task.onOpen(() => {
|
|||
|
|
if (_task !== sock) return
|
|||
|
|
_reconnectAttempts = 0
|
|||
|
|
_reconnectInterval = 3000
|
|||
|
|
_socketOpen = true
|
|||
|
|
console.log('[appUserWs] 已打开', opts.url)
|
|||
|
|
uni.$emit('appWs:opened', {})
|
|||
|
|
})
|
|||
|
|
_task.onMessage(_onMessage)
|
|||
|
|
_task.onError((err) => {
|
|||
|
|
if (_task !== sock) return
|
|||
|
|
console.error('[appUserWs] onError', err)
|
|||
|
|
_socketOpen = false
|
|||
|
|
_scheduleReconnect()
|
|||
|
|
})
|
|||
|
|
_task.onClose((res) => {
|
|||
|
|
if (_task !== sock) {
|
|||
|
|
// 旧线关闭,主连接已交给新 task,不清理、不重连
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
console.log('[appUserWs] onClose', res)
|
|||
|
|
_socketOpen = false
|
|||
|
|
_task = null
|
|||
|
|
if (_appActive && uni.getStorageSync('token')) {
|
|||
|
|
_scheduleReconnect()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 有 token 时建立/保持连接;无 token 时断开
|
|||
|
|
* @param {boolean} [forceReconnect] 为 true 时强制走完整建连(重连场景)
|
|||
|
|
*/
|
|||
|
|
export function connectIfLoggedIn(forceReconnect) {
|
|||
|
|
if (!uni.getStorageSync('token')) {
|
|||
|
|
disconnect()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
_appActive = true
|
|||
|
|
// 已连接时直接返回,勿 emit appWs:opened,否则监听方会误以为是「新连接」反复触发表层逻辑(如列表多次退订/订阅)
|
|||
|
|
if (!forceReconnect && _socketOpen && _task && _isTaskReady()) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
_doConnect()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function disconnect() {
|
|||
|
|
_appActive = false
|
|||
|
|
_clearReconnectTimer()
|
|||
|
|
_reconnectAttempts = 0
|
|||
|
|
_reconnectInterval = 3000
|
|||
|
|
_socketOpen = false
|
|||
|
|
if (_task) {
|
|||
|
|
try {
|
|||
|
|
_task.close({ success: () => {}, fail: () => {} })
|
|||
|
|
} catch (e) {}
|
|||
|
|
_task = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function isOpen() {
|
|||
|
|
return _socketOpen && _isTaskReady()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送 JSON(subscribe / unsubscribe 等)
|
|||
|
|
*/
|
|||
|
|
export function sendJson(obj, label) {
|
|||
|
|
if (!_isTaskReady()) {
|
|||
|
|
console.warn('[appUserWs] 未就绪,跳过发送', label, obj)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
_task.send({
|
|||
|
|
data: JSON.stringify(obj),
|
|||
|
|
success: () => {
|
|||
|
|
if (label) console.log('[appUserWs] 发送', label, obj)
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[appUserWs] 发送失败', err, obj)
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[appUserWs] send 异常', e)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function setAppActive(active) {
|
|||
|
|
_appActive = active !== false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 语言变更后重连:URL 的 lang 与 Storage 一致,需完整断开再建连,请保证先 setStorageSync('language', …)
|
|||
|
|
* 与 connectIfLoggedIn(true) 等价,命名便于在 i18n 等处明确语义
|
|||
|
|
*/
|
|||
|
|
export function reconnectUserWsForLanguage() {
|
|||
|
|
connectIfLoggedIn(true)
|
|||
|
|
}
|