wsClient.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { notification } from 'ant-design-vue'
  2. import i18n from '@/locales'
  3. import router from '@/router'
  4. import { useNotificationStore } from '@/stores/notification'
  5. const PING_INTERVAL = 10000
  6. const RECONNECT_DELAY = 5000
  7. function resolveCallType(callType) {
  8. if (Array.isArray(callType)) return callType[0] || 'mesh'
  9. return callType || 'mesh'
  10. }
  11. function getWsUrl(token) {
  12. const path = import.meta.env.VITE_PATH || import.meta.env.VITE_API_BASE_URL || ''
  13. let host = window.location.host
  14. if (path) {
  15. try {
  16. host = new URL(path).host
  17. } catch {
  18. // keep current host
  19. }
  20. }
  21. const protocol = import.meta.env.DEV || path.startsWith('https') || window.location.protocol === 'https:'
  22. ? 'wss:'
  23. : 'ws:'
  24. return `${protocol}//${host}/ucenter/ws/${encodeURIComponent(token)}`
  25. }
  26. class WsClient {
  27. ws = null
  28. pingTimer = null
  29. reconnectTimer = null
  30. manualClose = false
  31. currentToken = ''
  32. connect(token) {
  33. if (!token) return
  34. if (this.currentToken === token && this.ws?.readyState === WebSocket.OPEN) return
  35. this.disconnect(true)
  36. this.currentToken = token
  37. this.manualClose = false
  38. try {
  39. this.ws = new WebSocket(getWsUrl(token))
  40. } catch (err) {
  41. console.error('[ws] connect failed', err)
  42. this.scheduleReconnect()
  43. return
  44. }
  45. this.ws.onopen = () => {
  46. this.startPing()
  47. }
  48. this.ws.onmessage = (event) => {
  49. try {
  50. const message = JSON.parse(event.data)
  51. this.handleMessage(message)
  52. } catch (err) {
  53. console.error('[ws] parse message failed', err)
  54. }
  55. }
  56. this.ws.onclose = () => {
  57. this.stopPing()
  58. if (!this.manualClose) {
  59. this.scheduleReconnect()
  60. }
  61. }
  62. this.ws.onerror = () => {
  63. this.stopPing()
  64. this.ws?.close()
  65. }
  66. }
  67. handleMessage(message) {
  68. const { command, content } = message || {}
  69. if (command === 'open' || command === 'pong') return
  70. if (command === 'scene_compute_done') {
  71. const data = content?.data || {}
  72. const notificationStore = useNotificationStore()
  73. notificationStore.markUnread(data)
  74. const { t } = i18n.global
  75. const key = `scene_compute_done_${data.num || ''}_${Date.now()}`
  76. notification.open({
  77. key,
  78. message: t('notification.sceneComputeDoneTitle'),
  79. description: t('notification.sceneComputeDoneDesc', {
  80. title: data.title || data.num || '',
  81. }),
  82. duration: 0,
  83. onClick: () => {
  84. const type = resolveCallType(data.callType)
  85. const num = data.num
  86. if (num) {
  87. router.push({ path: `/sceneomore/${type}/${num}` })
  88. }
  89. notification.close(key)
  90. notificationStore.clearUnread()
  91. },
  92. })
  93. }
  94. }
  95. sendPing() {
  96. if (this.ws?.readyState !== WebSocket.OPEN) return
  97. this.ws.send(JSON.stringify({ command: 'ping', content: {} }))
  98. }
  99. startPing() {
  100. this.stopPing()
  101. this.sendPing()
  102. this.pingTimer = setInterval(() => this.sendPing(), PING_INTERVAL)
  103. }
  104. stopPing() {
  105. if (this.pingTimer) {
  106. clearInterval(this.pingTimer)
  107. this.pingTimer = null
  108. }
  109. }
  110. scheduleReconnect() {
  111. if (this.reconnectTimer || !this.currentToken || this.manualClose) return
  112. this.reconnectTimer = setTimeout(() => {
  113. this.reconnectTimer = null
  114. if (!this.currentToken || this.manualClose) return
  115. this.connect(this.currentToken)
  116. }, RECONNECT_DELAY)
  117. }
  118. disconnect(resetToken = false) {
  119. this.manualClose = true
  120. this.stopPing()
  121. if (this.reconnectTimer) {
  122. clearTimeout(this.reconnectTimer)
  123. this.reconnectTimer = null
  124. }
  125. if (this.ws) {
  126. this.ws.close()
  127. this.ws = null
  128. }
  129. if (resetToken) {
  130. this.currentToken = ''
  131. }
  132. }
  133. }
  134. let client = null
  135. export function getWsClient() {
  136. if (!client) {
  137. client = new WsClient()
  138. }
  139. return client
  140. }