irclib/src/connector/websocket.connector.ts

97 lines
2.5 KiB
TypeScript

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<IRCConnectorEvents>
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<string, unknown>,
) {
super();
}
connect(): Promise<void> {
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;
// Allow using a custom pinger, otherwise, ping every minute
// to keep socket alive
if (!this.connOpts?.skipPings) {
this.pingInterval = setInterval(() => {
this.lastPingName = Date.now().toString();
this.write('PING :%s', this.lastPingName);
}, (this.connOpts?.wsPingInterval as number) || 60000);
}
resolve();
};
try {
this.socket = new WebSocket(url);
} catch (e: unknown) {
return reject(e);
}
this.socket?.addEventListener('open', onConnect);
this.handle();
});
}
async destroy(): Promise<void> {
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();
// Allow using a custom pinger
if (!this.connOpts?.skipPings) {
if (line.indexOf('PING') === 0) {
this.socket?.send('PONG' + line.substring(4));
return;
}
if (line.includes('PONG') && 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);
});
}
}