import { sessionGet } from '@/utils/cache-session-utils'; import { getToken } from './auth-token'; import { localGet } from '@/utils/cache-local-utils'; import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants'; import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants'; /**连接参数类型 */ export type OptionsType = { /**WebSocket服务地址 */ url: string; /**地址栏参数 */ params?: Record; /**onopen事件的回调函数 */ onopen?: Function; /**message事件的回调函数 */ onmessage: (data: Record) => void; /**error事件的回调函数 */ onerror: Function; /**close事件的回调函数 */ onclose?: (code: number) => void; /**心跳周期 若为0则不启用 */ heartTimer?: number; /**重连等待 若为0则不启用 */ reconnectTimer?: number; }; /** * WebSocket 使用方法 * * import WS from '@/plugins/ws-websocket.ts'; * * const ws = new WS(); * * 创建连接 * const options: OptionsType = { }; * ws.connect(options); * * 手动关闭 * ws.close(); */ export class WS { /**ws 实例 */ private ws: WebSocket | null = null; /**ws 连接参数 */ private options: OptionsType | null = null; /**心跳调度器 */ private heartInterval: number = 0; /**重连定时器 */ private reconnectTimeout: number = 0; /** * 构造函数 * @param {object} params 构造函数参数 */ constructor(options?: OptionsType) { if (!window.WebSocket) { // 检测浏览器支持 console.error('Sorry! Browser does not support websocket'); return; } options && this.connect(options); } /** * 创建链接 * @param {object} options 连接参数 */ public connect(options: OptionsType) { this.options = options; try { if (!options.url.startsWith('ws')) { const uri = options.url.startsWith('/') ? options.url : `/${options.url}`; // 兼容旧前端可改配置文件 const wsUrl = import.meta.env.PROD ? sessionGet('wsUrl') || import.meta.env.VITE_API_BASE_URL : '/socket'; if (wsUrl.startsWith('ws')) { options.url = `${wsUrl}${uri}`; } else if (wsUrl.startsWith('https')) { options.url = `${wsUrl.replace('https', 'wss')}${uri}`; } else if (wsUrl.startsWith('http')) { options.url = `${wsUrl.replace('http', 'ws')}${uri}`; } else { const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; options.url = `${protocol}${location.host}${wsUrl}${uri}`; } } // 地址栏参数 let params = Object.assign({}, options.params, { // 设置 token [TOKEN_RESPONSE_FIELD]: getToken(), // 多语言 ['language']: localGet(CACHE_LOCAL_I18N) || 'en_US', }); let paramStr = ''; for (const key in params) { const value = params[key]; // 空字符或未定义的值不作为参数发送 if (value === '' || value === undefined) continue; paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`; } if (paramStr && paramStr.startsWith('&')) { options.url = `${options.url}?${paramStr.substring(1)}`; } const ws = new WebSocket(options.url, 'omc-ws'); // 用于指定连接成功后的回调函数。 ws.onopen = ev => { if (options.heartTimer && options.heartTimer > 0) { this.heartCheck(options.heartTimer); } if (typeof options.onopen === 'function') { options.onopen(ev); } }; // 用于指定当从服务器接受到信息时的回调函数。 ws.onmessage = ev => { // 解析文本消息 if (ev.type === 'message') { const data = ev.data; try { const jsonData = JSON.parse(data); if (typeof options.onmessage === 'function') { options.onmessage(jsonData); } } catch (error) { console.error('websocket message formatting error', error); } } }; // 用于指定连接关闭后的回调函数。 ws.onclose = ev => { if (typeof options.onclose === 'function') { options.onclose(ev.code); } }; // 用于指定连接失败后的回调函数。 ws.onerror = ev => { console.error('websocket connection anomaly', ev); if (typeof options.onerror === 'function') { options.onerror(ev); } if (options.reconnectTimer && options.reconnectTimer > 0) { this.reconnect(options.reconnectTimer); } }; this.ws = ws; } catch (error) { if (typeof options.onerror === 'function') { options.onerror(error); } if (options.reconnectTimer && options.reconnectTimer > 0) { this.reconnect(options.reconnectTimer); } } } /** * 发送消息 * @param data JSON数据 * @returns */ public send(data: Record): boolean { if (!this.ws) { console.warn('websocket unavailable'); return false; } // 非正常状态关闭 if ( this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING ) { this.close(); return false; } // 正在连接时 if (this.ws.readyState === WebSocket.CONNECTING) { setTimeout(() => { this.ws && this.ws.send(JSON.stringify(data)); }, 1000); } // 在连接的 if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); return true; } return false; } /**连接状态 * * WebSocket.OPEN * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState) */ public state(): number { if (!this.ws) { return -1; } return this.ws.readyState; } // 手动关闭socket public close() { this.heartInterval && clearInterval(this.heartInterval); this.reconnectTimeout && clearTimeout(this.reconnectTimeout); if (!this.ws) { console.warn('websocket unavailable'); return; } this.ws.close(1000, 'user initiated closure'); } // 周期性发送ping 保活 private heartCheck(heartTimer: number) { this.heartInterval = window.setInterval(() => { this.send({ requestId: `${Date.now()}`, type: 'ping', }); }, heartTimer); } // 断线重连 private reconnect(reconnectTimer: number) { if (this.reconnectTimeout > 0) return; clearTimeout(this.reconnectTimeout); this.reconnectTimeout = window.setTimeout(() => { if (this.options) { this.connect(this.options); this.reconnectTimeout = 0; } }, reconnectTimer); } }