ws.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { isDevMode } from '@/utils/env'
  2. import { message } from 'antd'
  3. import type { MessageType } from '@/types/typing'
  4. import wsNotice from '@/components/RightContent/Book/components/wsNotice'
  5. import { isNullOrUnDef } from './is'
  6. import { tryChangeWorkStatus } from '@/components/RightContent/Book'
  7. enum ReadyState {
  8. Connecting = 0,
  9. Open = 1,
  10. Closing = 2,
  11. Closed = 3
  12. }
  13. export enum CmdType {
  14. /** 心跳 */
  15. HEART_BEAT = 0,
  16. /** @name 通知、消息 */
  17. NoticeOrMessage = 13,
  18. /** @name 在线员工列表 */
  19. OnlineBook = 12,
  20. /** @name 员工工作状态 */
  21. WorkStatus = 14
  22. }
  23. interface Options {
  24. reconnectLimit?: number
  25. reconnectInterval?: number // ♥跳间隔时长
  26. onOpen?: (event: WebSocketEventMap['open']) => void
  27. onClose?: (event: WebSocketEventMap['close']) => void
  28. onMessage?: (message: WebSocketEventMap['message']) => void
  29. onError?: (event: WebSocketEventMap['error']) => void
  30. }
  31. class Ws {
  32. private uid: string // 用户id
  33. public uri: string // websocket 链接地址
  34. public opts?: Options // websocket 链接相关配置
  35. private isLock: boolean // 断开锁
  36. private reconnectLimit: number // 重连限制锁
  37. private timer: NodeJS.Timeout | undefined // 重连 延时函数
  38. private sTimer: NodeJS.Timeout | undefined // ♥跳 延时函数
  39. private sinTimer: NodeJS.Timeout | undefined // ♥跳
  40. public wsIns: WebSocket | undefined // websocket实例
  41. // 初始化socket,一般在应用启动时初始化一次就好了,或者需要更换wsUrl
  42. public init(uid: string, uri: string, opts?: Options) {
  43. this.uid = uid
  44. this.uri = uri
  45. this.opts = opts
  46. this.reconnectLimit = 0
  47. if (isNullOrUnDef(this.uri)) {
  48. throw new Error('websocket连接地址不能为空')
  49. return
  50. }
  51. if (!this.wsIns || this.wsIns?.readyState === ReadyState.Closed) {
  52. this.connectWs()
  53. }
  54. }
  55. // 利用promise封装,解决ws可能没有实例化
  56. public getInstance(): Promise<WebSocket> {
  57. return new Promise((resolve, reject) => {
  58. if (this.wsIns?.readyState === ReadyState.Open) {
  59. resolve(this.wsIns)
  60. } else if (this.wsIns) {
  61. this.connectWs()
  62. resolve(this.wsIns)
  63. } else {
  64. reject()
  65. }
  66. })
  67. }
  68. private connectWs() {
  69. try {
  70. this.wsIns = new WebSocket(this.uri)
  71. this.wsIns.onmessage = msg => {
  72. this.heartCheck()
  73. // 心跳检测事件, 什么事情都不用干
  74. if (msg.data === 'pong') {
  75. return
  76. }
  77. this.opts?.onMessage?.(JSON.parse(msg.data))
  78. }
  79. this.wsIns.onopen = event => {
  80. // this.heartCheck()
  81. tryChangeWorkStatus(this.uid, window.location.pathname)
  82. this.opts?.onOpen?.(event)
  83. }
  84. this.wsIns.onclose = event => {
  85. this.reconnect()
  86. this.opts?.onClose?.(event)
  87. }
  88. this.wsIns.onerror = err => {
  89. this.reconnect()
  90. this.opts?.onError?.(err)
  91. }
  92. // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  93. window.addEventListener('beforeunload', () => {
  94. this.wsIns?.close()
  95. })
  96. } catch (error) {
  97. if (isDevMode()) {
  98. message.error(error.toString())
  99. }
  100. this.wsIns = undefined
  101. }
  102. }
  103. /** 发送消息 */
  104. public sendMessage(map: MessageType) {
  105. if (this.wsIns?.readyState === ReadyState.Open) {
  106. this.wsIns.send(JSON.stringify(map))
  107. }
  108. }
  109. /** 断线重连 */
  110. public disconnect() {
  111. if (this.wsIns?.readyState === ReadyState.Open) {
  112. this.isLock = true
  113. this.wsIns.close()
  114. }
  115. }
  116. /** ♥跳检测 */
  117. private heartCheck() {
  118. this.sTimer && clearTimeout(this.sTimer)
  119. this.sinTimer && clearInterval(this.sinTimer)
  120. this.sTimer = setTimeout(() => {
  121. // 发送♥跳
  122. this.sendMessage({ cmd: CmdType.HEART_BEAT, dstid: this.uid })
  123. this.sinTimer = setTimeout(() => {
  124. if (this.wsIns.readyState !== ReadyState.Open) {
  125. this.wsIns.close()
  126. }
  127. }, this.opts.reconnectInterval)
  128. }, this.opts.reconnectInterval)
  129. }
  130. // 重连
  131. private reconnect(): void {
  132. if (
  133. this.isLock ||
  134. (this.opts?.reconnectLimit && this.reconnectLimit > this.opts?.reconnectLimit)
  135. ) {
  136. return
  137. }
  138. this.reconnectLimit += 1
  139. this.timer && clearTimeout(this.timer)
  140. this.timer = setTimeout(() => {
  141. this.connectWs()
  142. this.isLock = false
  143. }, 4000)
  144. }
  145. }
  146. export function onMessage(msg: MessageType) {
  147. // 这里执行所有的message监听事件
  148. switch (msg.cmd) {
  149. // 通知、消息
  150. case CmdType.NoticeOrMessage:
  151. wsNotice(msg)
  152. break
  153. // 在线员工列表
  154. case CmdType.OnlineBook:
  155. break
  156. default:
  157. break
  158. }
  159. }
  160. export default new Ws()