rx-discord-client
Version:
Reactive client for receiving discord server messages
104 lines (92 loc) • 3.65 kB
text/typescript
import {RawData, WebSocket} from 'ws';
import {Observable, Subscriber} from 'rxjs';
export class DiscordClient extends Observable<{ type: string, data: any }> {
private ws: WebSocket | undefined;
private pingInterval: NodeJS.Timer | undefined;
private sequence: number | null = null;
private constructor(
private token: string,
private reinitializeOnClose = true
) {
super((observer: Subscriber<{ type: string, operation: number, data: any }>) => {
this.init(observer);
const pingInterval = this.pingInterval;
const ws = this.ws;
return {
unsubscribe(): void {
ws?.close();
if (pingInterval) {
clearInterval(pingInterval);
}
}
};
});
}
public static create(token: string): Observable<{ type: string, data: any }> {
return new this(token);
}
private parseMessage(message: RawData): { type: string, operation: number, data: any, sequence: number } {
const parsed = JSON.parse(message.toString());
const type = parsed.t;
const operation = parsed.op;
const data = parsed.d;
const sequence = parsed.s;
return {type, operation, data, sequence};
}
private handleWebsocketMessage(observer: Subscriber<{ type: string, data: any }>, msg: RawData): void {
const {type, operation, data, sequence} = this.parseMessage(msg);
this.sequence = sequence;
switch (operation) {
case 10:
// Initial message received
this.pingInterval = setInterval(() => {
this.ws?.send(JSON.stringify({op: 1, d: this.sequence}))
}, data.heartbeat_interval);
break;
case 1:
// Ping requested
this.ws?.send(JSON.stringify({op: 1, d: this.sequence}));
break;
case 0:
observer.next({type, data});
break;
}
}
private initializeConnection(): void {
this.ws?.send(JSON.stringify({
op: 2,
d: {
token: this.token,
properties: {
$os: 'linux',
$browser: 'chrome',
$device: 'chrome',
}
}
}));
}
private handleWebsocketClose(observer: Subscriber<{ type: string, operation: number, data: any }>, e: any): void {
console.log(`[${new Date().toISOString()}] `, `Connection closed, recreating connection [${e.toString()}]`);
if (this.reinitializeOnClose) {
this.init(observer);
} else {
observer.complete();
}
}
private handleWebsocketError(e: Error): void {
console.log(`[${new Date().toISOString()}] `, `Connection error, closing connection [${e.toString()}]`);
this.ws?.close();
}
private init(observer: Subscriber<{ type: string, operation: number, data: any }>): void {
if (!this.token) {
const error = 'Token is not set';
observer.error(error);
return;
}
this.ws = new WebSocket('wss://gateway.discord.gg/?v=6&encoding=json');
this.ws.on('open', () => this.initializeConnection());
this.ws.on('message', (m) => this.handleWebsocketMessage(observer, m));
this.ws.on('error', (e) => this.handleWebsocketError(e));
this.ws.on('close', (e) => this.handleWebsocketClose(observer, e));
}
}