From 9066f510e366b9a570457c47a2d8d95cf840d3c9 Mon Sep 17 00:00:00 2001 From: MARCROCK22 Date: Sun, 11 Aug 2024 23:43:33 +0000 Subject: [PATCH] fix: ws reconnect --- package.json | 4 +- pnpm-lock.yaml | 27 --------- src/api/Routes/webhooks.ts | 2 +- src/common/shorters/channels.ts | 2 +- src/common/shorters/members.ts | 2 +- src/websocket/discord/basesocket.ts | 77 +++++++++++++------------- src/websocket/discord/shard.ts | 4 +- src/websocket/discord/socket/custom.ts | 37 +++++++------ src/websocket/discord/worker.ts | 15 ++--- 9 files changed, 67 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 331fbd5..bc5788b 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ "author": "MARCROCK22", "license": "Apache-2.0", "dependencies": { - "magic-bytes.js": "^1.10.0", - "ws": "^8.18.0" + "magic-bytes.js": "^1.10.0" }, "lint-staged": { "*.ts": [ @@ -35,7 +34,6 @@ "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@types/node": "^20.14.11", - "@types/ws": "^8.5.12", "husky": "^9.1.1", "lint-staged": "^15.2.7", "rimraf": "5.0.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9420f86..81d7718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: magic-bytes.js: specifier: ^1.10.0 version: 1.10.0 - ws: - specifier: ^8.18.0 - version: 8.18.0 optionalDependencies: chokidar: specifier: ^3.6.0 @@ -40,9 +37,6 @@ importers: '@types/node': specifier: ^20.14.11 version: 20.14.11 - '@types/ws': - specifier: ^8.5.12 - version: 8.5.12 husky: specifier: ^9.1.1 version: 9.1.1 @@ -209,9 +203,6 @@ packages: '@types/node@20.14.11': resolution: {integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==} - '@types/ws@8.5.12': - resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} - JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -874,18 +865,6 @@ packages: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -1092,10 +1071,6 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/ws@8.5.12': - dependencies: - '@types/node': 20.14.11 - JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -1707,8 +1682,6 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 - ws@8.18.0: {} - y18n@5.0.8: {} yaml@2.4.5: {} diff --git a/src/api/Routes/webhooks.ts b/src/api/Routes/webhooks.ts index e3901a2..b9ed3e2 100644 --- a/src/api/Routes/webhooks.ts +++ b/src/api/Routes/webhooks.ts @@ -65,7 +65,7 @@ export interface WebhookRoutes { >, ): Promise; }; - messages: (id: string | '@original') => { + messages: (id: (string & {}) | '@original') => { get(args?: RestArguments): Promise; patch( args: RestArguments, diff --git a/src/common/shorters/channels.ts b/src/common/shorters/channels.ts index b88900b..91654f2 100644 --- a/src/common/shorters/channels.ts +++ b/src/common/shorters/channels.ts @@ -214,4 +214,4 @@ export class ChannelShorter extends BaseShorter { } } -export type ChannelShorterOptionalParams = Partial<{ guildId: string; reason: string }>; +export type ChannelShorterOptionalParams = Partial<{ guildId: (string & {}) | '@me'; reason: string }>; diff --git a/src/common/shorters/members.ts b/src/common/shorters/members.ts index fcffb38..be608e2 100644 --- a/src/common/shorters/members.ts +++ b/src/common/shorters/members.ts @@ -224,7 +224,7 @@ export class MemberShorter extends BaseShorter { async voice(guildId: string, memberId: '@me', force?: boolean): Promise; async voice(guildId: string, memberId: string, force?: boolean): Promise; - async voice(guildId: string, memberId: string | '@me', force = false) { + async voice(guildId: string, memberId: (string & {}) | '@me', force = false) { if (!force) { const state = await this.client.cache.voiceStates?.get(memberId, guildId); if (state) return state; diff --git a/src/websocket/discord/basesocket.ts b/src/websocket/discord/basesocket.ts index 2d41c10..6a2cfd4 100644 --- a/src/websocket/discord/basesocket.ts +++ b/src/websocket/discord/basesocket.ts @@ -1,60 +1,58 @@ import { randomUUID } from 'node:crypto'; -// import { SeyfertWebSocket } from './socket/custom'; -import { WebSocket as NodeJSWebSocket } from 'ws'; +import { SeyfertWebSocket } from './socket/custom'; export class BaseSocket { - private internal: NodeJSWebSocket | WebSocket; + private internal: SeyfertWebSocket | WebSocket; ping?: () => Promise; constructor(kind: 'ws' | 'bun', url: string) { - this.internal = kind === 'ws' ? new NodeJSWebSocket(url) : new WebSocket(url); - // this.internal = kind === 'ws' ? new SeyfertWebSocket(url) : new WebSocket(url); + this.internal = kind === 'ws' ? new SeyfertWebSocket(url) : new WebSocket(url); - // if (kind === 'ws') { - // const ws = this.internal as NodeJSWebSocket; - // this.ping = ws.waitPing.bind(ws); - // ws.onpong = data => { - // const promise = ws.__promises.get(data); - // if (promise) { - // ws.__promises.delete(data); - // promise?.resolve(); - // } - // }; - // } else { - const ws = this.internal as WebSocket; - this.ping = () => { - return new Promise(res => { - const nonce = randomUUID(); - const start = performance.now(); - const listener = (data: Buffer) => { - if (data.toString() !== nonce) return; - //@ts-expect-error bun support - ws.removeListener('pong', listener); - res(performance.now() - start); - }; - //@ts-expect-error bun support - ws.on('pong', listener); - //@ts-expect-error bun support - ws.ping(nonce); - }); - }; - // } + if (kind === 'ws') { + const ws = this.internal as SeyfertWebSocket; + this.ping = ws.waitPing.bind(ws); + ws.onpong = data => { + const promise = ws.__promises.get(data); + if (promise) { + ws.__promises.delete(data); + promise?.resolve(); + } + }; + } else { + const ws = this.internal as WebSocket; + this.ping = () => { + return new Promise(res => { + const nonce = randomUUID(); + const start = performance.now(); + const listener = (data: Buffer) => { + if (data.toString() !== nonce) return; + //@ts-expect-error + ws.removeListener('pong', listener); + res(performance.now() - start); + }; + //@ts-expect-error + ws.on('pong', listener); + //@ts-expect-error + ws.ping(nonce); + }); + }; + } } - set onopen(callback: NodeJSWebSocket['onopen']) { + set onopen(callback: SeyfertWebSocket['onopen']) { this.internal.onopen = callback; } - set onmessage(callback: NodeJSWebSocket['onmessage']) { + set onmessage(callback: SeyfertWebSocket['onmessage']) { this.internal.onmessage = callback; } - set onclose(callback: NodeJSWebSocket['onclose']) { + set onclose(callback: SeyfertWebSocket['onclose']) { this.internal.onclose = callback; } - set onerror(callback: NodeJSWebSocket['onerror']) { + set onerror(callback: SeyfertWebSocket['onerror']) { this.internal.onerror = callback; } @@ -62,8 +60,7 @@ export class BaseSocket { return this.internal.send(data); } - close(...args: Parameters) { - //@ts-expect-error + close(...args: Parameters) { return this.internal.close(...args); } diff --git a/src/websocket/discord/shard.ts b/src/websocket/discord/shard.ts index 92de029..ef0d80d 100644 --- a/src/websocket/discord/shard.ts +++ b/src/websocket/discord/shard.ts @@ -102,13 +102,13 @@ export class Shard { // @ts-expect-error @types/bun cause erros in compile // biome-ignore lint/correctness/noUndeclaredVariables: /\ bun lol this.websocket = new BaseSocket(typeof Bun === 'undefined' ? 'ws' : 'bun', this.currentGatewayURL); - //@ts-expect-error + this.websocket!.onmessage = ({ data }: { data: string | Buffer }) => { this.handleMessage(data); }; this.websocket!.onclose = (event: { code: number; reason: string }) => this.handleClosed(event); - //@ts-expect-error + this.websocket!.onerror = (event: ErrorEvent) => this.debugger?.error(event); this.websocket!.onopen = () => { diff --git a/src/websocket/discord/socket/custom.ts b/src/websocket/discord/socket/custom.ts index dc7f7d0..1c0457e 100644 --- a/src/websocket/discord/socket/custom.ts +++ b/src/websocket/discord/socket/custom.ts @@ -19,11 +19,9 @@ export class SeyfertWebSocket { code: number; reason: string; } = null; + __closeCalled?: boolean; - constructor( - url: string, - public compress = true, - ) { + constructor(url: string) { const urlParts = new URL(url); this.hostname = urlParts.hostname || ''; this.path = `${urlParts.pathname}${urlParts.search || ''}`; @@ -55,31 +53,30 @@ export class SeyfertWebSocket { } this.socket = socket; - socket.on('readable', () => { - this.handleReadable(); - }); + socket.on('readable', this.handleReadable.bind(this)); - socket.on('close', () => { - this.handleClose(); - }); + socket.on('close', this.handleClose.bind(this)); + + socket.on('error', err => this.onerror(err)); - socket.on('error', err => { - this.onerror(err); - }); this.onopen(); }); + req.on('close', () => { + req.removeAllListeners(); + }); + req.end(); } handleReadable() { // Keep reading until no data, this is useful when two payloads merges. - while (this.socket?.readableLength) { + while (this.socket!.readableLength > 0) { // Read length without consuming the buffer let length = this.readBytes(1, 1) & 127; const slice = length === 126 ? 4 : length === 127 ? 10 : 2; // Check if frame/data is complete - if (this.socket.readableLength < slice) return; // Wait to next cycle if not + if (this.socket!.readableLength < slice) return; // Wait to next cycle if not if (length > 125) { // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 // If length is 126/127, read extended payload length instead @@ -87,7 +84,7 @@ export class SeyfertWebSocket { length = this.readBytes(2, slice - 2); } // Read the frame, ignore data next to it, leave it to next `while` cycle - const frame = this.socket.read(slice + length) as Buffer | null; + const frame = this.socket!.read(slice + length) as Buffer | null; if (!frame) return; // Get fin (0 | 1) const fin = frame[0] >> 7; @@ -147,12 +144,15 @@ export class SeyfertWebSocket { code: body.readUInt16BE(0), reason: body.subarray(2).toString(), }; - this.socket?.destroy(); break; } } handleClose() { + this.socket?.removeAllListeners(); + this.socket?.destroy(); + this.socket = undefined; + if (this.__closeCalled) return; if (!this.__lastError) return this.connect(); this.onclose(this.__lastError); this.__lastError = null; @@ -198,6 +198,7 @@ export class SeyfertWebSocket { onerror(_err: unknown) {} close(code: number, reason: string) { + this.__closeCalled = true; // alloc payload length const buffer = Buffer.alloc(2 + Buffer.byteLength(reason)); // gateway close code @@ -285,6 +286,7 @@ export class SeyfertWebSocket { // Buffer to read let block; while ((block = readable.buffer[blockIndex++])) { + // biome-ignore lint/style/useForOf: why we use biome for (let i = 0; i < block.length; i++) { if (++bitIndex > start) { value *= 256; // shift 8 bits (1 byte) `*= 256 is faster than <<= 8` @@ -300,6 +302,7 @@ export class SeyfertWebSocket { // readable.buffer is kinda a LinkedList let head: ReadableHeadData | undefined = readable.buffer.head; while (head) { + // biome-ignore lint/style/useForOf: why we use biome for (let i = 0; i < head.data.length; i++) { if (++bitIndex > start) { value *= 256; // shift 8 bits (1 byte) `*= 256 is faster than <<= 8` diff --git a/src/websocket/discord/worker.ts b/src/websocket/discord/worker.ts index f53e6c1..89e07d8 100644 --- a/src/websocket/discord/worker.ts +++ b/src/websocket/discord/worker.ts @@ -45,7 +45,7 @@ export type WorkerSendCacheRequest = CreateWorkerMessage< | 'addToRelationship' | 'removeRelationship' | 'removeToRelationship'; - args: any[]; + args: unknown[]; } >; export type WorkerSendShardInfo = CreateWorkerMessage<'SHARD_INFO', WorkerShardInfo & { nonce: string }>; @@ -61,21 +61,15 @@ export type WorkerSendApiRequest = CreateWorkerMessage< nonce: string; } >; -export type WorkerExecuteEval = CreateWorkerMessage< - 'EXECUTE_EVAL', - { - func: string; - nonce: string; - toWorkerId: number; - } ->; + export type WorkerSendEvalResponse = CreateWorkerMessage< 'EVAL_RESPONSE', { - response: any; + response: unknown; nonce: string; } >; + export type WorkerSendEval = CreateWorkerMessage< 'EVAL', { @@ -94,7 +88,6 @@ export type WorkerMessage = | WorkerSendInfo | WorkerReady | WorkerSendApiRequest - | WorkerExecuteEval | WorkerSendEvalResponse | WorkerSendEval | WorkerStart;