congming_huose-apk/common/utils/appUserWs.js

265 lines
6.6 KiB
JavaScript
Raw Normal View History

/**
* 用户 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()
}
/**
* 发送 JSONsubscribe / 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)
}