index.ts 2.63 KB
import type { UseWebSocketReturn } from '@vueuse/core'
import { useWebSocket } from '@vueuse/core'
import { WebsocketCmd } from './type/service'
import type { WsService, WsSubscriber } from './type/service'
import { useGlobSetting } from '@/hooks/setting'
import { getJwtToken, getShareJwtToken } from '@/utils/auth'
import { isShareMode } from '@/utils/env'

const RECONNECT_INTERVAL = 3000
const MAX_RECONNECT_COUNT = 20
// const WS_IDLE_TIMEOUT = 90000
const MAX_PUBLISH_COMMANDS = 10

export abstract class CmdWrapper {
  abstract hasCommands(): boolean
  abstract clear(): void
  abstract preparePublishCommands(maxCommands: number): CmdWrapper;

  [key: string]: WebsocketCmd | any;
}

export abstract class WebsocketService<T extends WsSubscriber, D = any> implements WsService<T> {
  lastCmdId = 0

  subscribersCount = 0

  subscribersMap = new Map<number, T>()

  socketUrl: string

  data: UseWebSocketReturn<D>['data']

  status: UseWebSocketReturn<D>['status']

  ws: UseWebSocketReturn<D>['ws']

  close: UseWebSocketReturn<D>['close']

  open: UseWebSocketReturn<D>['open']

  send: UseWebSocketReturn<D>['send']

  protected constructor(public cmdWrapper: CmdWrapper) {
    const { socketUrl } = useGlobSetting()
    const { protocol, hostname } = window.location

    let socketProtocol
    let prot
    if (protocol === 'https') {
      socketProtocol = 'wss:'
      prot = 443
    }
    else {
      socketProtocol = 'ws:'
      prot = 80
    }

    this.socketUrl = socketUrl ? `${socketUrl}${isShareMode() ? getShareJwtToken() : getJwtToken()}` : `${socketProtocol}//${hostname}:${prot}`

    const { data, status, ws, close, open, send } = useWebSocket<D>(this.socketUrl, {
      autoReconnect: {
        delay: RECONNECT_INTERVAL,
        retries: MAX_RECONNECT_COUNT,
      },
      immediate: false,

      onMessage: (_ws, event) => {
        if (event.data) {
          const message = JSON.parse(event.data)
          if (message.errorCode) {
            // handle error
          }
          else {
            this.processOnMessage(message)
          }
        }
      },
    })

    this.data = data
    this.status = status
    this.ws = ws
    this.close = close
    this.open = open
    this.send = send
  }

  abstract update(subscriber: T): void

  abstract unsubscribe(subscriber: T): void

  abstract subscribe(subscriber: T): void

  abstract processOnMessage(message: any): void

  protected nextCmdId() {
    this.lastCmdId++
    return this.lastCmdId
  }

  protected publishCommands() {
    const command = this.cmdWrapper.preparePublishCommands(MAX_PUBLISH_COMMANDS)
    this.send(JSON.stringify(command))
  }

  openSocket() {
    this.open()
  }
}