mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-01 20:46:08 +00:00
fix: ws reconnect
This commit is contained in:
parent
084c85368e
commit
9066f510e3
@ -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",
|
||||
|
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
@ -65,7 +65,7 @@ export interface WebhookRoutes {
|
||||
>,
|
||||
): Promise<RESTPostAPIWebhookWithTokenGitHubResult | RESTPostAPIWebhookWithTokenGitHubWaitResult>;
|
||||
};
|
||||
messages: (id: string | '@original') => {
|
||||
messages: (id: (string & {}) | '@original') => {
|
||||
get(args?: RestArguments<ProxyRequestMethod.Get>): Promise<RESTGetAPIWebhookWithTokenMessageResult>;
|
||||
patch(
|
||||
args: RestArguments<ProxyRequestMethod.Patch, RESTPatchAPIWebhookWithTokenMessageJSONBody>,
|
||||
|
@ -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 }>;
|
||||
|
@ -224,7 +224,7 @@ export class MemberShorter extends BaseShorter {
|
||||
|
||||
async voice(guildId: string, memberId: '@me', force?: boolean): Promise<VoiceStateStructure>;
|
||||
async voice(guildId: string, memberId: string, force?: boolean): Promise<VoiceStateStructure>;
|
||||
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;
|
||||
|
@ -1,27 +1,25 @@
|
||||
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<number>;
|
||||
|
||||
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 {
|
||||
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<number>(res => {
|
||||
@ -29,32 +27,32 @@ export class BaseSocket {
|
||||
const start = performance.now();
|
||||
const listener = (data: Buffer) => {
|
||||
if (data.toString() !== nonce) return;
|
||||
//@ts-expect-error bun support
|
||||
//@ts-expect-error
|
||||
ws.removeListener('pong', listener);
|
||||
res(performance.now() - start);
|
||||
};
|
||||
//@ts-expect-error bun support
|
||||
//@ts-expect-error
|
||||
ws.on('pong', listener);
|
||||
//@ts-expect-error bun support
|
||||
//@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<NodeJSWebSocket['close']>) {
|
||||
//@ts-expect-error
|
||||
close(...args: Parameters<SeyfertWebSocket['close']>) {
|
||||
return this.internal.close(...args);
|
||||
}
|
||||
|
||||
|
@ -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 = () => {
|
||||
|
@ -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`
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user