import WebSocket from 'ws' import { DEFAULT_ORIGIN } from '../../Defaults' import { AbstractSocketClient } from './types' export class WebSocketClient extends AbstractSocketClient { protected socket: WebSocket | null = null get isOpen(): boolean { return this.socket?.readyState === WebSocket.OPEN } get isClosed(): boolean { return this.socket === null || this.socket?.readyState === WebSocket.CLOSED } get isClosing(): boolean { return this.socket === null || this.socket?.readyState === WebSocket.CLOSING } get isConnecting(): boolean { return this.socket?.readyState === WebSocket.CONNECTING } connect() { if (this.socket) { return } this.socket = new WebSocket(this.url, { origin: DEFAULT_ORIGIN, headers: this.config.options?.headers as {}, handshakeTimeout: this.config.connectTimeoutMs, timeout: this.config.connectTimeoutMs, agent: this.config.agent }) // Set max listeners from config (default: 20) // WARNING: 0 disables limit and allows potential memory leaks const maxListeners = this.config.maxWebSocketListeners ?? 20 if (maxListeners === 0) { this.config.logger?.warn('WebSocket setMaxListeners(0) allows UNLIMITED listeners - potential memory leak!') } this.socket.setMaxListeners(maxListeners) const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response'] for (const event of events) { this.socket?.on(event, (...args: any[]) => this.emit(event, ...args)) } } async close() { if (!this.socket) { return } const closePromise = new Promise(resolve => { this.socket?.once('close', resolve) }) this.socket.close() await closePromise this.socket = null } send(str: string | Uint8Array, cb?: (err?: Error) => void): boolean { this.socket?.send(str, cb) return Boolean(this.socket) } }