seyfert 4.3.0 → 4.3.1-dev-24757643741.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,8 @@ export declare class Shard {
22
22
  websocket: BaseSocket | null;
23
23
  connectTimeout: ConnectTimeout;
24
24
  heart: ShardHeart;
25
+ private isConnecting;
26
+ private reconnectPromise;
25
27
  bucket: DynamicBucket;
26
28
  offlineSendQueue: ((_?: unknown) => void)[];
27
29
  pendingGuilds?: Set<string>;
@@ -44,6 +46,7 @@ export declare class Shard {
44
46
  heartbeat(requested: boolean): void;
45
47
  disconnect(code?: ShardSocketCloseCodes): void;
46
48
  reconnect(code?: ShardSocketCloseCodes): Promise<void>;
49
+ private flushOfflineSendQueue;
47
50
  onpacket(packet: GatewayReceivePayload): void | Promise<void>;
48
51
  requestGuildMember(options: Omit<GatewayRequestGuildMembersDataWithQuery, 'nonce'> | Omit<GatewayRequestGuildMembersDataWithUserIds, 'nonce'>): Promise<{
49
52
  members: APIGuildMember[];
@@ -22,6 +22,8 @@ class Shard {
22
22
  interval: 30e3,
23
23
  ack: true,
24
24
  };
25
+ isConnecting = false;
26
+ reconnectPromise;
25
27
  bucket;
26
28
  offlineSendQueue = [];
27
29
  pendingGuilds;
@@ -74,9 +76,15 @@ class Shard {
74
76
  return this.websocket.ping();
75
77
  }
76
78
  async connect() {
79
+ if (this.isConnecting) {
80
+ this.debugger?.debug(`[Shard #${this.id}] Already connecting, skipping`);
81
+ return;
82
+ }
83
+ this.isConnecting = true;
77
84
  await this.connectTimeout.wait();
78
85
  if (this.isOpen) {
79
86
  this.debugger?.debug(`[Shard #${this.id}] Attempted to connect while open`);
87
+ this.isConnecting = false;
80
88
  return;
81
89
  }
82
90
  clearTimeout(this.heart.nodeInterval);
@@ -90,6 +98,7 @@ class Shard {
90
98
  this.websocket.onclose = (event) => this.handleClosed(event);
91
99
  this.websocket.onerror = (event) => this.logger.error(event);
92
100
  this.websocket.onopen = () => {
101
+ this.isConnecting = false;
93
102
  this.heart.ack = true;
94
103
  void this.options.onShardReconnect?.({ shardId: this.id });
95
104
  };
@@ -159,14 +168,24 @@ class Shard {
159
168
  clearTimeout(this.connectionTimeout);
160
169
  this.connectionTimeout = undefined;
161
170
  clearTimeout(this.heart.ackTimeout);
171
+ this.isConnecting = false;
162
172
  this.debugger?.info(`[Shard #${this.id}] Disconnecting`);
163
173
  this.close(code, 'Shard down request');
164
174
  }
165
175
  async reconnect(code = shared_1.ShardSocketCloseCodes.Reconnect) {
166
- this.debugger?.info(`[Shard #${this.id}] Reconnecting`);
167
- this.disconnect(code);
168
- await (0, common_1.delay)(this.options.reconnectTimeout);
169
- await this.connect();
176
+ return (this.reconnectPromise ??= (async () => {
177
+ this.debugger?.info(`[Shard #${this.id}] Reconnecting`);
178
+ this.disconnect(code);
179
+ await (0, common_1.delay)(this.options.reconnectTimeout);
180
+ await this.connect();
181
+ })().finally(() => {
182
+ this.reconnectPromise = undefined;
183
+ }));
184
+ }
185
+ flushOfflineSendQueue() {
186
+ const queue = this.offlineSendQueue.splice(0);
187
+ for (const resolve of queue)
188
+ resolve();
170
189
  }
171
190
  onpacket(packet) {
172
191
  if (packet.s !== null) {
@@ -217,8 +236,7 @@ class Shard {
217
236
  clearTimeout(this.connectionTimeout);
218
237
  this.connectionTimeout = undefined;
219
238
  this.isReady = true;
220
- for (const resolve of this.offlineSendQueue.splice(0))
221
- resolve();
239
+ this.flushOfflineSendQueue();
222
240
  this.options.handlePayload(this.id, packet);
223
241
  }
224
242
  break;
@@ -230,8 +248,7 @@ class Shard {
230
248
  }
231
249
  this.data.resume_gateway_url = packet.d.resume_gateway_url;
232
250
  this.data.session_id = packet.d.session_id;
233
- for (const resolve of this.offlineSendQueue.splice(0))
234
- resolve();
251
+ this.flushOfflineSendQueue();
235
252
  this.options.handlePayload(this.id, packet);
236
253
  if (this.pendingGuilds?.size === 0) {
237
254
  this.isReady = true;
@@ -427,7 +444,7 @@ class Shard {
427
444
  }
428
445
  close(code, reason) {
429
446
  clearInterval(this.heart.nodeInterval);
430
- if (!this.isOpen) {
447
+ if (!this.websocket) {
431
448
  return this.debugger?.warn(`[Shard #${this.id}] Is not open, reason:`, reason);
432
449
  }
433
450
  this.debugger?.debug(`[Shard #${this.id}] Called close with reason:`, reason);
@@ -1,3 +1,4 @@
1
+ import type { ClientRequest } from 'node:http';
1
2
  import type { Socket } from 'node:net';
2
3
  export declare class SeyfertWebSocket {
3
4
  #private;
@@ -15,6 +16,8 @@ export declare class SeyfertWebSocket {
15
16
  reason: string;
16
17
  };
17
18
  __closeCalled?: boolean;
19
+ request?: ClientRequest;
20
+ retryTimeout?: NodeJS.Timeout;
18
21
  constructor(url: string);
19
22
  private connect;
20
23
  handleReadable(): void;
@@ -13,6 +13,9 @@ class SeyfertWebSocket {
13
13
  __promises = new Map();
14
14
  __lastError = null;
15
15
  __closeCalled;
16
+ request;
17
+ retryTimeout;
18
+ #closeEmitted = false;
16
19
  constructor(url) {
17
20
  const urlParts = new URL(url);
18
21
  this.hostname = urlParts.hostname || '';
@@ -33,7 +36,16 @@ class SeyfertWebSocket {
33
36
  'Sec-WebSocket-Version': '13',
34
37
  },
35
38
  });
39
+ this.request = req;
36
40
  req.on('upgrade', (res, socket) => {
41
+ if (this.request === req) {
42
+ this.request = undefined;
43
+ }
44
+ if (this.__closeCalled) {
45
+ socket.destroy();
46
+ resolve();
47
+ return;
48
+ }
37
49
  const hash = (0, node_crypto_1.createHash)('sha1').update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest('base64');
38
50
  const accept = res.headers['sec-websocket-accept'];
39
51
  if (accept !== hash) {
@@ -52,20 +64,34 @@ class SeyfertWebSocket {
52
64
  this.onopen();
53
65
  });
54
66
  req.on('close', () => {
67
+ if (this.request === req) {
68
+ this.request = undefined;
69
+ }
55
70
  req.removeAllListeners();
56
71
  });
57
72
  req.on('error', e => {
73
+ if (this.request === req) {
74
+ this.request = undefined;
75
+ }
76
+ if (this.__closeCalled) {
77
+ resolve();
78
+ return;
79
+ }
58
80
  if (retries < 5) {
59
- setTimeout(() => {
81
+ this.retryTimeout = setTimeout(() => {
82
+ this.retryTimeout = undefined;
83
+ if (this.__closeCalled) {
84
+ resolve();
85
+ return;
86
+ }
60
87
  resolve(this.connect(retries + 1));
61
88
  }, 500);
89
+ return;
62
90
  }
63
- else {
64
- this.onerror(e);
65
- setTimeout(() => {
66
- resolve(this.connect(0));
67
- }, 5e3);
68
- }
91
+ this.onerror(e);
92
+ this.__lastError = { code: 1006, reason: e.message };
93
+ resolve();
94
+ this.#emitClose(this.__lastError);
69
95
  });
70
96
  req.end();
71
97
  });
@@ -135,19 +161,19 @@ class SeyfertWebSocket {
135
161
  this.onmessage({ data: body });
136
162
  }
137
163
  break;
138
- // pong
164
+ // ping
139
165
  case 0x9:
140
166
  this.onping(body.toString());
141
167
  break;
142
- // ping
168
+ // pong
143
169
  case 0xa:
144
170
  this.onpong(body.toString());
145
171
  break;
146
172
  // close
147
173
  case 0x8:
148
174
  this.__lastError = {
149
- code: body.readUInt16BE(0),
150
- reason: body.subarray(2).toString(),
175
+ code: body.length >= 2 ? body.readUInt16BE(0) : 1005,
176
+ reason: body.length > 2 ? body.subarray(2).toString() : '',
151
177
  };
152
178
  break;
153
179
  }
@@ -158,9 +184,10 @@ class SeyfertWebSocket {
158
184
  this.socket = undefined;
159
185
  if (this.__closeCalled)
160
186
  return;
161
- if (!this.__lastError)
162
- return this.connect();
163
- this.onclose(this.__lastError);
187
+ this.#emitClose(this.__lastError ?? {
188
+ code: 1006,
189
+ reason: 'Socket closed without a close frame',
190
+ });
164
191
  this.__lastError = null;
165
192
  }
166
193
  send(data) {
@@ -193,7 +220,7 @@ class SeyfertWebSocket {
193
220
  this.socket?.write(frame);
194
221
  }
195
222
  onping(_data) {
196
- //
223
+ this.pong(_data);
197
224
  }
198
225
  onpong(_data) {
199
226
  //
@@ -212,6 +239,10 @@ class SeyfertWebSocket {
212
239
  }
213
240
  close(code, reason) {
214
241
  this.__closeCalled = true;
242
+ clearTimeout(this.retryTimeout);
243
+ this.retryTimeout = undefined;
244
+ this.request?.destroy();
245
+ this.request = undefined;
215
246
  // alloc payload length
216
247
  const buffer = Buffer.alloc(2 + Buffer.byteLength(reason));
217
248
  // gateway close code
@@ -221,7 +252,13 @@ class SeyfertWebSocket {
221
252
  // message, close opcode
222
253
  this._write(buffer, 0x8);
223
254
  this.socket?.end();
224
- this.onclose({ code, reason });
255
+ this.#emitClose({ code, reason });
256
+ }
257
+ #emitClose(close) {
258
+ if (this.#closeEmitted)
259
+ return;
260
+ this.#closeEmitted = true;
261
+ this.onclose(close);
225
262
  }
226
263
  pong(data) {
227
264
  //send pong opcode (10)
@@ -259,6 +296,8 @@ class SeyfertWebSocket {
259
296
  return id;
260
297
  }
261
298
  get readyState() {
299
+ if (this.request || this.retryTimeout)
300
+ return 0;
262
301
  return ['opening', 'open', 'closed', 'closed'].indexOf(this.socket?.readyState ?? 'closed');
263
302
  }
264
303
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seyfert",
3
- "version": "4.3.0",
3
+ "version": "4.3.1-dev-24757643741.0",
4
4
  "description": "The most advanced framework for discord bots",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
@@ -70,4 +70,4 @@
70
70
  "esbuild"
71
71
  ]
72
72
  }
73
- }
73
+ }