import Config from "./config" export type AutoReconnectOptions = boolean | { maxRetries?: number retryInterval?: number onMaxRetriesReached?: Function } export enum ConnectionStatus { Disconnected = 'DISCONNECTED', Connected = 'CONNECTED', Error = 'ERROR' } class SocketService { private static instance: SocketService | null = null private ws: WebSocket | null = null private listeners: Record = {} private autoReconnect: AutoReconnectOptions = true private times: any = null private retries: number = 0 private connectionStatus: ConnectionStatus = ConnectionStatus.Disconnected private constructor() { this.connect() } public static getInstance(): SocketService { if (!SocketService.instance) { SocketService.instance = new SocketService() } return SocketService.instance } public setAutoReconnectOptions(options: AutoReconnectOptions) { this.autoReconnect = options } public connect() { this.ws = new WebSocket(Config.ws) this.ws.onopen = () => { this.connectionStatus = ConnectionStatus.Connected this.emit('connected', null) this.hert() } this.ws.onerror = () => { this.connectionStatus = ConnectionStatus.Error clearTimeout(this.times) this.emit('error', null) } this.ws.onclose = () => { this.connectionStatus = ConnectionStatus.Disconnected clearTimeout(this.times) this.emit('disconnected', null) if (this.shouldReconnect()) { setTimeout(() => this.connect(), this.getRetryInterval()) } } this.ws.onmessage = (event) => { this.emit('message', event.data) } } public hert(){ this.times = setTimeout(() => this.send({"type":"heartbeat"}), 3000) } private shouldReconnect(): boolean { if (typeof this.autoReconnect === 'boolean') { return this.autoReconnect } else if (this.autoReconnect) { const { maxRetries } = this.autoReconnect if (maxRetries !== undefined) { if (this.retries < maxRetries) { this.retries++ return true } else if (this.retries >= maxRetries) { this.autoReconnect.onMaxRetriesReached && this.autoReconnect.onMaxRetriesReached() return false } } } return false } private getRetryInterval(): number { if (typeof this.autoReconnect === 'boolean') { return 1000 } else if (this.autoReconnect && this.autoReconnect.retryInterval) { return this.autoReconnect.retryInterval } return 1000 } public send(data: any) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)) } else { console.error('WebSocket 连接未打开') } } public close() { if (this.ws) { this.ws.close() SocketService.instance = null } } private emit(event: string, data: any) { if (!this.listeners[event]) { return } this.listeners[event].forEach((listener) => listener(data)) } public on(event: string, listener: Function) { if (!this.listeners[event]) { this.listeners[event] = [] } this.listeners[event].push(listener) if (event === 'connected' && this.connectionStatus === ConnectionStatus.Connected) { listener() } } public off(event: string, listener: Function) { if (!this.listeners[event]) { return } this.listeners[event] = this.listeners[event].filter((l) => l !== listener) } public getConnectionStatus(): ConnectionStatus { return this.connectionStatus } public watchConnectionStatus(callback: (status: ConnectionStatus) => void) { this.on('connected', () => callback(ConnectionStatus.Connected)) this.on('disconnected', () => callback(ConnectionStatus.Disconnected)) this.on('error', () => callback(ConnectionStatus.Error)) } } export default SocketService