import { IRCConnectorEvents } from '../types/events'; import { IIRCConnector } from '../types/impl.interface'; import { formatstr } from '../utility/formatstr'; import { TypedEventEmitter } from '../utility/typed-event-emitter'; export class IRCWebSocketConnector extends TypedEventEmitter implements IIRCConnector { connected = false; private socket?: WebSocket; private pingInterval?: any; private lastPingName?: string; constructor( public secure: boolean, public host: string, public port?: number, public connOpts?: Record, ) { super(); } connect(): Promise { let url = `ws${this.secure ? 's' : ''}://${this.host}:${this.port || 6667}`; if (this.connOpts?.path) { url += ('/' + this.connOpts.path) as string; } return new Promise((resolve, reject) => { const onConnect = () => { this.connected = true; this.pingInterval = setInterval(() => { this.lastPingName = Date.now().toString(); this.write('PING :%s', this.lastPingName); }, 60000); resolve(); }; try { this.socket = new WebSocket(url); } catch (e: unknown) { return reject(e); } this.socket?.addEventListener('open', onConnect); this.handle(); }); } async destroy(): Promise { this.connected = false; this.socket?.close(); this.socket = undefined; clearInterval(this.pingInterval); } write(format: string, ...args: any[]): void { this.socket?.send(formatstr(format, ...args)); } private handle() { this.socket?.addEventListener('message', (event) => { const line = event.data.toString(); if (line.indexOf('PING') === 0 && !this.connOpts?.skipPings) { this.socket?.send('PONG' + line.substring(4)); return; } if ( line.indexOf('PONG') === 0 && line.includes(' :' + this.lastPingName) ) { return; } this.emit('data', line); }); this.socket?.addEventListener('error', (err) => this.emit('error', err)); this.socket?.addEventListener('close', (err) => { this.connected = false; this.socket = undefined; clearInterval(this.pingInterval); this.emit('close', err.reason); }); } }