import { isDevMode } from '@/utils/env' import { message } from 'antd' import type { MessageType } from '@/types/typing' import wsNotice from '@/components/RightContent/Book/components/wsNotice' import { isNullOrUnDef } from './is' import { tryChangeWorkStatus } from '@/components/RightContent/Book' enum ReadyState { Connecting = 0, Open = 1, Closing = 2, Closed = 3 } export enum CmdType { /** 心跳 */ HEART_BEAT = 0, /** @name 通知、消息 */ NoticeOrMessage = 13, /** @name 在线员工列表 */ OnlineBook = 12, /** @name 员工工作状态 */ WorkStatus = 14 } interface Options { reconnectLimit?: number reconnectInterval?: number // ♥跳间隔时长 onOpen?: (event: WebSocketEventMap['open']) => void onClose?: (event: WebSocketEventMap['close']) => void onMessage?: (message: WebSocketEventMap['message']) => void onError?: (event: WebSocketEventMap['error']) => void } class Ws { private uid: string // 用户id public uri: string // websocket 链接地址 public opts?: Options // websocket 链接相关配置 private isLock: boolean // 断开锁 private reconnectLimit: number // 重连限制锁 private timer: NodeJS.Timeout | undefined // 重连 延时函数 private sTimer: NodeJS.Timeout | undefined // ♥跳 延时函数 private sinTimer: NodeJS.Timeout | undefined // ♥跳 public wsIns: WebSocket | undefined // websocket实例 // 初始化socket,一般在应用启动时初始化一次就好了,或者需要更换wsUrl public init(uid: string, uri: string, opts?: Options) { this.uid = uid this.uri = uri this.opts = opts this.reconnectLimit = 0 if (isNullOrUnDef(this.uri)) { throw new Error('websocket连接地址不能为空') return } if (!this.wsIns || this.wsIns?.readyState === ReadyState.Closed) { this.connectWs() } } // 利用promise封装,解决ws可能没有实例化 public getInstance(): Promise { return new Promise((resolve, reject) => { if (this.wsIns?.readyState === ReadyState.Open) { resolve(this.wsIns) } else if (this.wsIns) { this.connectWs() resolve(this.wsIns) } else { reject() } }) } private connectWs() { try { this.wsIns = new WebSocket(this.uri) this.wsIns.onmessage = msg => { this.heartCheck() // 心跳检测事件, 什么事情都不用干 if (msg.data === 'pong') { return } this.opts?.onMessage?.(JSON.parse(msg.data)) } this.wsIns.onopen = event => { // this.heartCheck() tryChangeWorkStatus(this.uid, window.location.pathname) this.opts?.onOpen?.(event) } this.wsIns.onclose = event => { this.reconnect() this.opts?.onClose?.(event) } this.wsIns.onerror = err => { this.reconnect() this.opts?.onError?.(err) } // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.addEventListener('beforeunload', () => { this.wsIns?.close() }) } catch (error) { if (isDevMode()) { message.error(error.toString()) } this.wsIns = undefined } } /** 发送消息 */ public sendMessage(map: MessageType) { if (this.wsIns?.readyState === ReadyState.Open) { this.wsIns.send(JSON.stringify(map)) } } /** 断线重连 */ public disconnect() { if (this.wsIns?.readyState === ReadyState.Open) { this.isLock = true this.wsIns.close() } } /** ♥跳检测 */ private heartCheck() { this.sTimer && clearTimeout(this.sTimer) this.sinTimer && clearInterval(this.sinTimer) this.sTimer = setTimeout(() => { // 发送♥跳 this.sendMessage({ cmd: CmdType.HEART_BEAT, dstid: this.uid }) this.sinTimer = setTimeout(() => { if (this.wsIns.readyState !== ReadyState.Open) { this.wsIns.close() } }, this.opts.reconnectInterval) }, this.opts.reconnectInterval) } // 重连 private reconnect(): void { if ( this.isLock || (this.opts?.reconnectLimit && this.reconnectLimit > this.opts?.reconnectLimit) ) { return } this.reconnectLimit += 1 this.timer && clearTimeout(this.timer) this.timer = setTimeout(() => { this.connectWs() this.isLock = false }, 4000) } } export function onMessage(msg: MessageType) { // 这里执行所有的message监听事件 switch (msg.cmd) { // 通知、消息 case CmdType.NoticeOrMessage: wsNotice(msg) break // 在线员工列表 case CmdType.OnlineBook: break default: break } } export default new Ws()