interface SocketOptions { heartbeatInterval?: number; reconnectInterval?: number; maxReconnectAttempts?: number; isReconnect?: boolean; uid?: string; subscribe?: string; } class Socket { url: string; ws: WebSocket | null = null; opts: SocketOptions; reconnectAttempts: number = 0; listeners: { [key: string]: Function[] } = {}; heartbeatInterval: number | null = null; constructor(url: string, opts: SocketOptions = {}) { this.url = url; this.opts = { heartbeatInterval: 2 * 1000, // 心跳间隔 reconnectInterval: 2 * 1000, // 重连间隔 maxReconnectAttempts: 99, // 最大重连次数 isReconnect: true, // 是否需要重连 uid: '', // 用户id subscribe: '', // 订阅的频道 ...opts, }; this.init(); } init() { this.ws = new WebSocket(this.url); this.ws.onopen = this.onOpen.bind(this); this.ws.onmessage = this.onMessage.bind(this); this.ws.onerror = this.onError.bind(this); this.ws.onclose = this.onClose.bind(this); } onOpen(event: Event) { // this.reconnectAttempts=0; this.startSubscribe(); this.startHeartbeat(); this.emit('open', event); } onMessage(event: MessageEvent) { this.emit('message', event); } onError(event: Event) { console.error('WebSocket error:', event); this.emit('error', event); } onClose(event: CloseEvent) { this.stopHeartbeat(); this.emit('close', event); // @ts-ignore if (this.opts.isReconnect) { this.reConnect(); } } reConnect() { // @ts-ignore if (this.reconnectAttempts < this.opts.maxReconnectAttempts) { setTimeout(() => { this.reconnectAttempts++; this.init(); }, this.opts.reconnectInterval); } else { console.error('已到达重连次数最高,请手动刷新重连'); } } startHeartbeat() { if (!this.opts.heartbeatInterval) return; this.heartbeatInterval = window.setInterval(() => { if (this.ws?.readyState === WebSocket.OPEN) { this.send({ id: '', type: 2, from: this.opts.uid, to: 'sys', timestamps: new Date().getTime(), body: 'PING', }); } }, this.opts.heartbeatInterval); } startSubscribe() { // @ts-ignore const moreTime = this.opts.heartbeatInterval + 100; setTimeout(() => { if (!this.opts.subscribe) return; this.send({ id: '', type: 8, from: this.opts.uid, to: this.opts.subscribe, timestamps: new Date().getTime(), body: 'subscribe', }); }, moreTime); } stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } send(data: any) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } else { console.error('WebSocket is not open. Cannot send:', data); } } on(event: string, callback: Function) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); } off(event: string) { if (this.listeners[event]) { delete this.listeners[event]; } } emit(event: string, data: any) { this.listeners[event]?.forEach((callback) => callback(data)); } close() { this.opts.isReconnect = false; this.ws?.close(); this.ws = null; this.stopHeartbeat(); this.listeners = {}; } } export function useSocket(url: string, opts?: SocketOptions) { const socket = new Socket(url, opts); return { socket, close: socket.close.bind(socket), send: socket.send.bind(socket), on: socket.on.bind(socket), off: socket.off.bind(socket), }; }