congming_huose-apk/common/utils/appUserWs.js

265 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 用户 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)
}