// utils/sse.ts import { request } from './request' import { BASE_URL } from '../env' //唯一id const sign = Date.now().toString(36) + Math.random().toString(36).slice(2) const listeners: Record void)[]> = {} let requestTask: any = null let reconnectTimer: any = null let requestTimer: any = null // 不支持TextDecoder的话手动转码utf8 function utf8Decode(uint8: Uint8Array): string { let str = ''; let i = 0; while (i < uint8.length) { const byte1 = uint8[i]; let charCode = byte1; if (byte1 <= 0x7F) { i += 1; } else if (byte1 >= 0xC0 && byte1 <= 0xDF) { const byte2 = uint8[i + 1]; charCode = ((byte1 & 0x1F) << 6) | (byte2 & 0x3F); i += 2; } else if (byte1 >= 0xE0 && byte1 <= 0xEF) { const byte2 = uint8[i + 1]; const byte3 = uint8[i + 2]; charCode = ((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F); i += 3; } else if (byte1 >= 0xF0 && byte1 <= 0xF7) { const byte2 = uint8[i + 1]; const byte3 = uint8[i + 2]; const byte4 = uint8[i + 3]; charCode = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F); i += 4; } str += String.fromCharCode(charCode); } return str; } export function startSSE() { if (requestTimer) return //已经启动过就不重复 // if (wx.getStorageSync("sseId")) leave(); wx.setStorageSync("sseId", sign) requestSSE() //30s连接一次,保持连接 // requestTimer = setInterval(() => { // requestSSE() // }, 30000) } async function requestSSE() { try { requestTask = (wx as any).request({ url: `${BASE_URL}/api/auth/message/subscribe/${sign}`, method: 'GET', enableChunked: true, responseType: 'arraybuffer', header: { 'Content-Type': 'application/json' }, }) requestTask?.onChunkReceived?.((chunk: any) => { const uint8 = new Uint8Array(chunk.data) let chunkStr = '' try { //真机环境不支持TextDecoder // @ts-ignore chunkStr = new TextDecoder('utf-8').decode(uint8); } catch { chunkStr = utf8Decode(uint8); } const lines = chunkStr.trim().split('\n') for (const line of lines) { try { // console.log(JSON.parse(line), 'lllllll') if (JSON.parse(line).msg == 'auto close') { requestSSE() } } catch { if (line.startsWith('data:')) { try { const json = JSON.parse(line.slice(5)) const app = getApp() const data = json.data const eventName = json.event //分类保存 if (data && data.type) { if (!app.globalData.sseStore) app.globalData.sseStore = {} if (!app.globalData.sseStore[data.type]) app.globalData.sseStore[data.type] = [] //可能会传sku对应商品,直播评论,分类的通知 //存入传入的数组 app.globalData.sseStore[data.type].push(data) //最多只保留100条 if (app.globalData.sseStore[data.type].length > 100) { app.globalData.sseStore[data.type].shift() } } if (eventName && listeners[eventName]) { listeners[eventName].forEach(fn => fn(data)) } } catch (err) { console.error('解析返回数据失败:', err) } } } } }) } catch (err) { console.warn('sse请求失败:', err) } requestTask.onError?.((err: any) => { console.warn('sse连接出错,5秒后重连', err) scheduleReconnect() }) requestTask.then?.(() => { console.log('sse连接出错,5秒后重连') scheduleReconnect() }) } //注册事件监听 export function addCustomEventListener(eventName: string, fn: (data: any) => void) { if (!listeners[eventName]) listeners[eventName] = [] listeners[eventName].push(fn) } //移除事件监听 export function removeCustomEventListener(eventName: string, fn: (data: any) => void) { if (!listeners[eventName]) return listeners[eventName] = listeners[eventName].filter(f => f !== fn) if (listeners[eventName].length === 0) delete listeners[eventName] } //加入频道 export async function join(name: string) { const app = getApp(); try { if (app.globalData.joinedArray.includes(name)) { return; //加入过就不加入了 } app.globalData.joinedArray = [name, ...app.globalData.joinedArray]; //为什么在request后就不能push了?? await request({ options: { url: `/auth/message/join`, method: 'POST', data: { id: sign, name } }, isLoading: false }) } catch { app.globalData.joinedArray = app.globalData.joinedArray.filter(item => item !== name); } } //离开频道 export async function leave(name: string) { const app = getApp() try { app.globalData.joinedArray = app.globalData.joinedArray.filter(item => item !== name); await request({ options: { url: `/auth/message/leave`, method: 'POST', data: { id: wx.getStorageSync('sseId'), name } // data: { id: wx.getStorageSync('sseId'), name: String(shopId) } }, isLoading: false }) } catch { app.globalData.joinedArray = [name, ...app.globalData.joinedArray]; } } //重连 function scheduleReconnect() { stopSSE() reconnectTimer = setTimeout(() => { console.log('重连sse中...') startSSE() }, 5000) } //停止sse export function stopSSE() { if (requestTask) { requestTask.abort?.() requestTask = null } clearTimeout(reconnectTimer) } //获取sse的某个分类数据 export function getSSEStore(type: string) { const app = getApp() return app.globalData.sseStore[type] || [] } //删除sse的某个分类数据 export function clearSSEStore(type?: string) { const app = getApp() if (type) { app.globalData.sseStore[type] = [] } else { Object.keys(app.globalData.sseStore).forEach(k => { app.globalData.sseStore[k] = [] }) } }