diff --git a/src/websocket/discord/shard.ts b/src/websocket/discord/shard.ts index e26bce6..7a02fb9 100644 --- a/src/websocket/discord/shard.ts +++ b/src/websocket/discord/shard.ts @@ -1,5 +1,5 @@ import { inflateSync } from 'node:zlib'; -import { type MakeRequired, MergeOptions, type Logger } from '../../common'; +import { Logger, LogLevels, type MakeRequired, MergeOptions } from '../../common'; import { properties } from '../constants'; import { DynamicBucket } from '../structures'; import { ConnectTimeout } from '../structures/timeout'; @@ -23,6 +23,7 @@ export interface ShardHeart { } export class Shard { + logger: Logger; debugger?: Logger; data: Partial | ShardData = { resume_seq: null, @@ -52,6 +53,11 @@ export class Shard { }, } as ShardOptions); + this.logger = new Logger({ + name: `[Shard #${id}]`, + logLevel: LogLevels.Info, + }); + if (options.debugger) this.debugger = options.debugger; const safe = this.calculateSafeRequests(); @@ -91,7 +97,7 @@ export class Shard { async connect() { await this.connectTimeout.wait(); if (this.isOpen) { - this.debugger?.debug(`[Shard #${this.id}] attempted to connect while open`); + this.debugger?.debug(`[Shard #${this.id}] Attempted to connect while open`); return; } @@ -109,7 +115,7 @@ export class Shard { this.websocket.onclose = (event: { code: number; reason: string }) => this.handleClosed(event); - this.websocket.onerror = (event: ErrorEvent) => this.debugger?.error(event); + this.websocket.onerror = (event: ErrorEvent) => this.logger.error(event); this.websocket.onopen = () => { this.heart.ack = true; @@ -235,7 +241,7 @@ export class Shard { case GatewayOpcodes.InvalidSession: if (packet.d) { if (!this.resumable) { - return this.debugger?.fatal(`[Shard #${this.id}] This is a completely unexpected error message.`); + return this.logger.fatal(`This is a completely unexpected error message.`); } await this.resume(); } else { @@ -269,8 +275,8 @@ export class Shard { protected async handleClosed(close: { code: number; reason: string }) { clearInterval(this.heart.nodeInterval); - this.debugger?.warn( - `[Shard #${this.id}] ${ShardSocketCloseCodes[close.code] ?? GatewayCloseCodes[close.code] ?? close.code} (${close.code})`, + this.logger.warn( + `${ShardSocketCloseCodes[close.code] ?? GatewayCloseCodes[close.code] ?? close.code} (${close.code})`, close.reason, ); @@ -295,7 +301,7 @@ export class Shard { case GatewayCloseCodes.NotAuthenticated: case GatewayCloseCodes.AlreadyAuthenticated: case GatewayCloseCodes.RateLimited: - this.debugger?.info(`[Shard #${this.id}] Trying to reconnect`); + this.logger.info(`Trying to reconnect`); await this.reconnect(); break; @@ -305,11 +311,11 @@ export class Shard { case GatewayCloseCodes.InvalidIntents: case GatewayCloseCodes.InvalidShard: case GatewayCloseCodes.ShardingRequired: - this.debugger?.fatal(`[Shard #${this.id}] cannot reconnect`); + this.logger.fatal(`Cannot reconnect`); break; default: - this.debugger?.warn(`[Shard #${this.id}] Unknown close code, trying to reconnect anyways`); + this.logger.warn(`Unknown close code, trying to reconnect anyways`); await this.reconnect(); break; } @@ -324,10 +330,17 @@ export class Shard { } protected handleMessage(data: string | Buffer) { - if (data instanceof Buffer) { - data = inflateSync(data); + let packet; + try { + if (data instanceof Buffer) { + data = inflateSync(data); + } + packet = JSON.parse(data as string); + } catch (e) { + this.logger.error(e); + return; } - return this.onpacket(JSON.parse(data as string)); + return this.onpacket(packet); } checkOffline(force: boolean) { diff --git a/src/websocket/discord/socket/custom.ts b/src/websocket/discord/socket/custom.ts index bcd3d84..bfb5130 100644 --- a/src/websocket/discord/socket/custom.ts +++ b/src/websocket/discord/socket/custom.ts @@ -28,45 +28,57 @@ export class SeyfertWebSocket { this.connect(); } - private connect() { - const key = randomBytes(16).toString('base64'); - const req = request({ - //discord gateway hostname - hostname: this.hostname, - path: this.path, - headers: { - Connection: 'Upgrade', - Upgrade: 'websocket', - 'Sec-WebSocket-Key': key, - 'Sec-WebSocket-Version': '13', - }, + private connect(retries = 0) { + return new Promise((resolve, rej) => { + const key = randomBytes(16).toString('base64'); + const req = request({ + //discord gateway hostname + hostname: this.hostname, + path: this.path, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Version': '13', + }, + }); + + req.on('upgrade', (res, socket) => { + const hash = createHash('sha1').update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest('base64'); + const accept = res.headers['sec-websocket-accept']; + if (accept !== hash) { + socket.end(() => { + rej(new Error('Invalid sec-websocket-accept header')); + }); + return; + } + this.socket = socket; + + socket.on('readable', this.handleReadable.bind(this)); + + socket.on('close', this.handleClose.bind(this)); + + socket.on('error', err => this.onerror(err)); + resolve(); + this.onopen(); + }); + + req.on('close', () => { + req.removeAllListeners(); + }); + + req.on('error', e => { + if (retries < 5) { + setTimeout(() => { + resolve(this.connect(retries + 1)); + }, 500); + } else { + rej(e); + } + }); + + req.end(); }); - - req.on('upgrade', (res, socket) => { - const hash = createHash('sha1').update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest('base64'); - const accept = res.headers['sec-websocket-accept']; - if (accept !== hash) { - socket.end(() => { - this.onerror(new Error('Invalid sec-websocket-accept header')); - }); - return; - } - this.socket = socket; - - socket.on('readable', this.handleReadable.bind(this)); - - socket.on('close', this.handleClose.bind(this)); - - socket.on('error', err => this.onerror(err)); - - this.onopen(); - }); - - req.on('close', () => { - req.removeAllListeners(); - }); - - req.end(); } handleReadable() { @@ -148,7 +160,7 @@ export class SeyfertWebSocket { } } - handleClose() { + async handleClose() { this.socket?.removeAllListeners(); this.socket?.destroy(); this.socket = undefined;