Files
InfiniteAPI/src/Socket/Client/websocket.ts
T
Claude 6f75e5ac21 feat(socket): add configurable maxListeners to prevent memory leaks
Based on RSocket's battle-tested configuration:

- Add maxWebSocketListeners config option (default: 20)
  - 8 base WS events + 10 dynamic listeners + 2 buffer slots
- Add maxSocketClientListeners config option (default: 50)
- Replace dangerous setMaxListeners(0) with configurable limits
- Add warning log if user explicitly sets limit to 0

BREAKING: Previous behavior used setMaxListeners(0) which removed
all limits. Now defaults to safe limits but can be overridden via config.
2026-01-20 12:36:51 +00:00

70 lines
1.8 KiB
TypeScript

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<void>(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)
}
}