|
|
@@ -0,0 +1,239 @@
|
|
|
+interface WebSocketOptions {
|
|
|
+ url: string,
|
|
|
+ protocols?: string | string[]
|
|
|
+ heartbeatMessage?: () => MessageEvent['data'];
|
|
|
+ onOpen?: () => void;
|
|
|
+ onMessage: (data: MessageEvent['data']) => void;
|
|
|
+ onClose?: () => void;
|
|
|
+ onError?: (err: Event) => void;
|
|
|
+ onBeforeReconnect?: (count: number) => void;
|
|
|
+ onReconnect?: () => void;
|
|
|
+}
|
|
|
+
|
|
|
+interface ConnectionOptions {
|
|
|
+ onSuccess?: () => void;
|
|
|
+ onFail?: () => void;
|
|
|
+}
|
|
|
+
|
|
|
+export default class {
|
|
|
+ private socket: WebSocket | null = null; // WebSocket 对象
|
|
|
+ private connectionId = 0;
|
|
|
+ private url;
|
|
|
+ private protocols?: string | string[];
|
|
|
+ private isReconnecting = false; // 是否正在重连
|
|
|
+ private messageTimer = 0; // 消息超时定时器
|
|
|
+ private messageTimeout = 1000 * 15; // 消息超时时间
|
|
|
+ private heartbeatTimer = 0; // 心跳定时器
|
|
|
+ private heartbeatInterval = 1000 * 30; // 心跳间隔时间
|
|
|
+ private reconnectTimer = 0; // 重连定时器
|
|
|
+ private reconnectCount = 0; // 本次已重连次数
|
|
|
+ private reconnectLimit = 10; // 限制重连次数,0 = 无限制
|
|
|
+
|
|
|
+ private heartbeatMessage?: () => string | ArrayBufferLike | Blob | ArrayBufferView; // 心跳消息
|
|
|
+ private onOpen?: () => void; // 连接成功的事件
|
|
|
+ private onMessage: (data: MessageEvent['data']) => void; // 消息事件
|
|
|
+ private onClose?: () => void; // 连接断开的事件
|
|
|
+ private onError?: (err: Event) => void; // 连接发生错误的事件
|
|
|
+ private onBeforeReconnect?: (count: number) => void; // 重连之前的事件
|
|
|
+ private onReconnect?: () => void; // 重连成功之后的事件
|
|
|
+
|
|
|
+ constructor(options: WebSocketOptions) {
|
|
|
+ this.url = options.url
|
|
|
+ this.protocols = options.protocols
|
|
|
+ this.heartbeatMessage = options.heartbeatMessage
|
|
|
+ this.onOpen = options.onOpen
|
|
|
+ this.onMessage = options.onMessage
|
|
|
+ this.onClose = options.onClose
|
|
|
+ this.onError = options.onError
|
|
|
+ this.onBeforeReconnect = options.onBeforeReconnect
|
|
|
+ this.onReconnect = options.onReconnect
|
|
|
+ }
|
|
|
+
|
|
|
+ private isConnecting = false
|
|
|
+ private connectionCallbacks = new Map<symbol, ConnectionOptions>()
|
|
|
+
|
|
|
+ // 查询连接状态
|
|
|
+ isConnected() {
|
|
|
+ return !this.isReconnecting && this.socket?.readyState === WebSocket.OPEN
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建 WebSocket 连接
|
|
|
+ connection(options?: ConnectionOptions) {
|
|
|
+ const key = Symbol()
|
|
|
+
|
|
|
+ if (this.isConnected()) {
|
|
|
+ options?.onSuccess?.()
|
|
|
+ } else {
|
|
|
+ if (options) {
|
|
|
+ this.connectionCallbacks.set(key, options)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.isConnecting) {
|
|
|
+ clearTimeout(this.reconnectTimer)
|
|
|
+ this.stopHeartbeat()
|
|
|
+ this.socket?.close()
|
|
|
+
|
|
|
+ const ws = this.protocols ? new WebSocket(this.url, this.protocols) : new WebSocket(this.url)
|
|
|
+ const currentConnectionId = this.connectionId + 1
|
|
|
+
|
|
|
+ this.connectionId = currentConnectionId
|
|
|
+ this.isConnecting = true
|
|
|
+ console.log(this.url, '正在连接')
|
|
|
+
|
|
|
+ // 连接成功
|
|
|
+ ws.onopen = () => {
|
|
|
+ if (this.connectionId === currentConnectionId) {
|
|
|
+ if (this.reconnectCount) {
|
|
|
+ console.log(this.url, '重连成功')
|
|
|
+ this.onReconnect?.()
|
|
|
+ } else {
|
|
|
+ console.log(this.url, '连接成功')
|
|
|
+ this.onOpen?.()
|
|
|
+ }
|
|
|
+
|
|
|
+ this.reconnectCount = 0
|
|
|
+ this.isConnecting = false
|
|
|
+ this.startHeartbeat()
|
|
|
+
|
|
|
+ this.connectionCallbacks.forEach(({ onSuccess }) => onSuccess?.())
|
|
|
+ this.connectionCallbacks.clear()
|
|
|
+ } else {
|
|
|
+ console.log(this.url, '重复连接')
|
|
|
+ ws.close()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 接收消息
|
|
|
+ ws.onmessage = (event) => {
|
|
|
+ if (this.connectionId === currentConnectionId) {
|
|
|
+ this.stopHeartbeat()
|
|
|
+ this.startHeartbeat()
|
|
|
+ this.onMessage?.(event.data)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 连接断开
|
|
|
+ ws.onclose = () => {
|
|
|
+ // 判断是否当前实例,重连时不处理旧 WebSocket 事件
|
|
|
+ if (this.connectionId === currentConnectionId) {
|
|
|
+ this.stopHeartbeat()
|
|
|
+ this.socket = null
|
|
|
+ this.isReconnecting = false
|
|
|
+ this.isConnecting = false
|
|
|
+ this.reconnect() // 进行重连尝试,直到成功为止
|
|
|
+
|
|
|
+ // 重连失败返回结果
|
|
|
+ if (this.reconnectCount === 0) {
|
|
|
+ console.error(this.url, '连接已断开')
|
|
|
+ this.connectionCallbacks.forEach(({ onFail }) => onFail?.())
|
|
|
+ this.connectionCallbacks.clear()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 连接发生错误
|
|
|
+ ws.onerror = (error) => {
|
|
|
+ if (this.connectionId === currentConnectionId) {
|
|
|
+ console.error(this.url, '连接发生错误')
|
|
|
+ this.onError?.(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.socket = ws
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return () => this.connectionCallbacks.delete(key)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 主动断开连接,断开后不会自动重连
|
|
|
+ disconnect(forced = false) {
|
|
|
+ return new Promise<void>((resolve) => {
|
|
|
+ clearTimeout(this.reconnectTimer)
|
|
|
+ this.stopHeartbeat()
|
|
|
+ this.connectionCallbacks.clear()
|
|
|
+ this.connectionId++
|
|
|
+ this.reconnectCount = 0
|
|
|
+ this.isReconnecting = false
|
|
|
+ this.isConnecting = false
|
|
|
+
|
|
|
+ if (!forced && this.socket) {
|
|
|
+ const listener = () => {
|
|
|
+ console.warn(this.url, '主动断开')
|
|
|
+ this.socket?.removeEventListener('close', listener)
|
|
|
+ this.socket = null
|
|
|
+ this.onClose?.()
|
|
|
+ resolve()
|
|
|
+ }
|
|
|
+ this.socket.addEventListener('close', listener)
|
|
|
+ this.socket.close()
|
|
|
+ } else {
|
|
|
+ console.warn(this.url, '主动断开')
|
|
|
+ this.socket?.close()
|
|
|
+ this.socket = null
|
|
|
+ this.onClose?.()
|
|
|
+ resolve()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送消息
|
|
|
+ send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
|
|
|
+ if (this.socket) {
|
|
|
+ this.socket.send(data)
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 断开重连
|
|
|
+ private reconnect() {
|
|
|
+ if (!this.isReconnecting) {
|
|
|
+ if (this.reconnectCount) {
|
|
|
+ console.warn(this.url, `第${this.reconnectCount}次重连失败`)
|
|
|
+ } else {
|
|
|
+ console.warn(this.url, '连接中断')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重连次数小于限制次数则继续重连
|
|
|
+ if (this.reconnectCount < this.reconnectLimit) {
|
|
|
+ this.isReconnecting = true
|
|
|
+ this.reconnectCount++
|
|
|
+ this.onBeforeReconnect?.(this.reconnectCount)
|
|
|
+
|
|
|
+ // 自动计算每次重试的延时,重试次数越多,延时越大
|
|
|
+ const delay = this.reconnectCount * 5000
|
|
|
+ console.log(this.url, `${delay / 1000}秒后将进行第${this.reconnectCount}次重连`)
|
|
|
+
|
|
|
+ this.reconnectTimer = setTimeout(() => {
|
|
|
+ this.connection()
|
|
|
+ }, delay)
|
|
|
+ } else {
|
|
|
+ this.reconnectCount = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送心跳检测
|
|
|
+ // 待优化:心跳超时后,再发送一次心跳消息,如果仍然超时,再进行重连
|
|
|
+ private startHeartbeat() {
|
|
|
+ const message = this.heartbeatMessage?.()
|
|
|
+ if (message) {
|
|
|
+ this.heartbeatTimer = setTimeout(() => {
|
|
|
+ this.send(message)
|
|
|
+
|
|
|
+ // 如果已经超过或心跳超时时长没有收到心跳回复,则认为网络已经异常,进行断网重连
|
|
|
+ this.messageTimer = setTimeout(() => {
|
|
|
+ console.warn(this.url, '心跳超时')
|
|
|
+ this.reconnect()
|
|
|
+ }, this.messageTimeout)
|
|
|
+ }, this.heartbeatInterval)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 停止心跳检测
|
|
|
+ private stopHeartbeat() {
|
|
|
+ clearTimeout(this.messageTimer)
|
|
|
+ clearTimeout(this.heartbeatTimer)
|
|
|
+ }
|
|
|
+}
|