@discordeno/gateway 19.0.0-next.b0c482b → 19.0.0-next.b2f65c2
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.
- package/dist/Shard.d.ts +13 -6
- package/dist/Shard.d.ts.map +1 -1
- package/dist/Shard.js +128 -81
- package/dist/Shard.js.map +1 -1
- package/dist/manager.d.ts +10 -5
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +54 -40
- package/dist/manager.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -16
package/dist/Shard.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AtLeastOne, BigString, Camelize, DiscordGatewayPayload, DiscordMember, RequestGuildMembers } from '@discordeno/types';
|
|
2
|
-
import { Collection } from '@discordeno/utils';
|
|
3
|
-
import
|
|
2
|
+
import { Collection, LeakyBucket } from '@discordeno/utils';
|
|
3
|
+
import NodeWebSocket from 'ws';
|
|
4
4
|
import type { RequestMemberRequest } from './manager.js';
|
|
5
5
|
import type { BotStatusUpdate, ShardEvents, ShardGatewayConfig, ShardHeart, ShardSocketRequest, StatusUpdate, UpdateVoiceState } from './types.js';
|
|
6
6
|
import { ShardState } from './types.js';
|
|
@@ -20,7 +20,7 @@ export declare class DiscordenoShard {
|
|
|
20
20
|
/** Current session id of the shard if present. */
|
|
21
21
|
sessionId?: string;
|
|
22
22
|
/** This contains the WebSocket connection to Discord, if currently connected. */
|
|
23
|
-
socket?:
|
|
23
|
+
socket?: NodeWebSocket;
|
|
24
24
|
/** Current internal state of the this. */
|
|
25
25
|
state: ShardState;
|
|
26
26
|
/** The url provided by discord to use when resuming a connection for this this. */
|
|
@@ -32,7 +32,7 @@ export declare class DiscordenoShard {
|
|
|
32
32
|
/** Resolve internal waiting states. Mapped by SelectedEvents => ResolveFunction */
|
|
33
33
|
resolves: Map<"READY" | "RESUMED" | "INVALID_SESSION", (payload: DiscordGatewayPayload) => void>;
|
|
34
34
|
/** Shard bucket. Only access this if you know what you are doing. Bucket for handling shard request rate limits. */
|
|
35
|
-
bucket:
|
|
35
|
+
bucket: LeakyBucket;
|
|
36
36
|
/** This managers cache related settings. */
|
|
37
37
|
cache: {
|
|
38
38
|
requestMembers: {
|
|
@@ -48,6 +48,8 @@ export declare class DiscordenoShard {
|
|
|
48
48
|
constructor(options: ShardCreateOptions);
|
|
49
49
|
/** The gateway configuration which is used to connect to Discord. */
|
|
50
50
|
get gatewayConfig(): ShardGatewayConfig;
|
|
51
|
+
/** The url to connect to. Intially this is the discord gateway url, and then is switched to resume gateway url once a READY is received. */
|
|
52
|
+
get connectionUrl(): string;
|
|
51
53
|
/** Calculate the amount of requests which can safely be made per rate limit interval, before the gateway gets disconnected due to an exceeded rate limit. */
|
|
52
54
|
calculateSafeRequests(): number;
|
|
53
55
|
checkOffline(highPriority: boolean): Promise<void>;
|
|
@@ -68,11 +70,12 @@ export declare class DiscordenoShard {
|
|
|
68
70
|
/** Shutdown the this. Forcefully disconnect the shard from Discord. The shard may not attempt to reconnect with Discord. */
|
|
69
71
|
shutdown(): Promise<void>;
|
|
70
72
|
/** Handle a gateway connection close. */
|
|
71
|
-
handleClose(close:
|
|
73
|
+
handleClose(close: NodeWebSocket.CloseEvent): Promise<void>;
|
|
72
74
|
/** Handles a incoming gateway packet. */
|
|
73
75
|
handleDiscordPacket(packet: DiscordGatewayPayload): Promise<void>;
|
|
76
|
+
forwardToBot(packet: DiscordGatewayPayload): void;
|
|
74
77
|
/** Handle an incoming gateway message. */
|
|
75
|
-
handleMessage(message:
|
|
78
|
+
handleMessage(message: NodeWebSocket.MessageEvent): Promise<void>;
|
|
76
79
|
/**
|
|
77
80
|
* Override in order to make the shards presence.
|
|
78
81
|
* async in case devs create the presence based on eg. database values.
|
|
@@ -81,6 +84,8 @@ export declare class DiscordenoShard {
|
|
|
81
84
|
makePresence(): Promise<BotStatusUpdate | undefined>;
|
|
82
85
|
/** This function communicates with the management process, in order to know whether its free to identify. When this function resolves, this means that the shard is allowed to send an identify payload to discord. */
|
|
83
86
|
requestIdentify(): Promise<void>;
|
|
87
|
+
/** This function communicates with the management process, in order to tell it can identify the next shard. */
|
|
88
|
+
shardIsReady(): Promise<void>;
|
|
84
89
|
/** Start sending heartbeat payloads to Discord in the provided interval. */
|
|
85
90
|
startHeartbeating(interval: number): void;
|
|
86
91
|
/** Stop the heartbeating process with discord. */
|
|
@@ -163,6 +168,8 @@ export interface ShardCreateOptions {
|
|
|
163
168
|
events: ShardEvents;
|
|
164
169
|
/** The handler to request a space to make an identify request. */
|
|
165
170
|
requestIdentify?: () => Promise<void>;
|
|
171
|
+
/** The handler to alert the gateway manager that this shard has received a READY event. */
|
|
172
|
+
shardIsReady?: () => Promise<void>;
|
|
166
173
|
}
|
|
167
174
|
export default DiscordenoShard;
|
|
168
175
|
//# sourceMappingURL=Shard.d.ts.map
|
package/dist/Shard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Shard.d.ts","sourceRoot":"","sources":["../src/Shard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Shard.d.ts","sourceRoot":"","sources":["../src/Shard.ts"],"names":[],"mappings":"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"}
|
package/dist/Shard.js
CHANGED
|
@@ -1,32 +1,27 @@
|
|
|
1
|
-
import { GatewayCloseEventCodes, GatewayIntents, GatewayOpcodes } from '@discordeno/types';
|
|
2
|
-
import {
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-confusing-void-expression */ import { GatewayCloseEventCodes, GatewayIntents, GatewayOpcodes } from '@discordeno/types';
|
|
2
|
+
import { Collection, LeakyBucket, camelize, delay, logger } from '@discordeno/utils';
|
|
3
3
|
import { inflateSync } from 'node:zlib';
|
|
4
|
-
import
|
|
4
|
+
import NodeWebSocket from 'ws';
|
|
5
5
|
import { ShardSocketCloseCodes, ShardState } from './types.js';
|
|
6
6
|
export class DiscordenoShard {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
20
|
-
/** This managers cache related settings. */ cache = {
|
|
21
|
-
requestMembers: {
|
|
22
|
-
/**
|
|
7
|
+
constructor(options){
|
|
8
|
+
/** The maximum of requests which can be send to discord per rate limit tick. Typically this value should not be changed. */ this.maxRequestsPerRateLimitTick = 120;
|
|
9
|
+
/** The previous payload sequence number. */ this.previousSequenceNumber = null;
|
|
10
|
+
/** In which interval (in milliseconds) the gateway resets it's rate limit. */ this.rateLimitResetInterval = 60000;
|
|
11
|
+
/** Current internal state of the this. */ this.state = ShardState.Offline;
|
|
12
|
+
/** The url provided by discord to use when resuming a connection for this this. */ this.resumeGatewayUrl = '';
|
|
13
|
+
/** The shard related event handlers. */ this.events = {};
|
|
14
|
+
/** Cache for pending gateway requests which should have been send while the gateway went offline. */ this.offlineSendQueue = [];
|
|
15
|
+
/** Resolve internal waiting states. Mapped by SelectedEvents => ResolveFunction */ this.resolves = new Map();
|
|
16
|
+
/** This managers cache related settings. */ this.cache = {
|
|
17
|
+
requestMembers: {
|
|
18
|
+
/**
|
|
23
19
|
* Whether or not request member requests should be cached.
|
|
24
20
|
* @default false
|
|
25
21
|
*/ enabled: false,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
constructor(options){
|
|
22
|
+
/** The pending requests. */ pending: new Collection()
|
|
23
|
+
}
|
|
24
|
+
};
|
|
30
25
|
this.id = options.id;
|
|
31
26
|
this.connection = options.connection;
|
|
32
27
|
this.events = options.events;
|
|
@@ -35,10 +30,20 @@ export class DiscordenoShard {
|
|
|
35
30
|
interval: 45000
|
|
36
31
|
};
|
|
37
32
|
if (options.requestIdentify) this.requestIdentify = options.requestIdentify;
|
|
33
|
+
if (options.shardIsReady) this.shardIsReady = options.shardIsReady;
|
|
34
|
+
this.bucket = new LeakyBucket({
|
|
35
|
+
max: this.calculateSafeRequests(),
|
|
36
|
+
refillAmount: this.calculateSafeRequests(),
|
|
37
|
+
refillInterval: 60000
|
|
38
|
+
});
|
|
38
39
|
}
|
|
39
40
|
/** The gateway configuration which is used to connect to Discord. */ get gatewayConfig() {
|
|
40
41
|
return this.connection;
|
|
41
42
|
}
|
|
43
|
+
/** The url to connect to. Intially this is the discord gateway url, and then is switched to resume gateway url once a READY is received. */ get connectionUrl() {
|
|
44
|
+
// Use || and not ?? here. ?? will cause a bug.
|
|
45
|
+
return this.resumeGatewayUrl || this.gatewayConfig.url;
|
|
46
|
+
}
|
|
42
47
|
/** Calculate the amount of requests which can safely be made per rate limit interval, before the gateway gets disconnected due to an exceeded rate limit. */ calculateSafeRequests() {
|
|
43
48
|
// * 2 adds extra safety layer for discords OP 1 requests that we need to respond to
|
|
44
49
|
const safeRequests = this.maxRequestsPerRateLimitTick - Math.ceil(this.rateLimitResetInterval / this.heart.interval) * 2;
|
|
@@ -54,8 +59,8 @@ export class DiscordenoShard {
|
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
/** Close the socket connection to discord if present. */ close(code, reason) {
|
|
57
|
-
if (this.socket?.readyState !==
|
|
58
|
-
|
|
62
|
+
if (this.socket?.readyState !== NodeWebSocket.OPEN) return;
|
|
63
|
+
this.socket?.close(code, reason);
|
|
59
64
|
}
|
|
60
65
|
/** Connect the shard with the gateway and start heartbeating. This will not identify the shard to the gateway. */ async connect() {
|
|
61
66
|
// Only set the shard to `Connecting` state,
|
|
@@ -67,20 +72,16 @@ export class DiscordenoShard {
|
|
|
67
72
|
this.state = ShardState.Connecting;
|
|
68
73
|
}
|
|
69
74
|
this.events.connecting?.(this);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
url.searchParams.set('v', this.gatewayConfig.version.toString());
|
|
77
|
-
url.searchParams.set('encoding', 'json');
|
|
78
|
-
}
|
|
79
|
-
const socket = new WebSocket(url.toString());
|
|
75
|
+
const url = new URL(this.connectionUrl);
|
|
76
|
+
url.searchParams.set('v', this.gatewayConfig.version.toString());
|
|
77
|
+
url.searchParams.set('encoding', 'json');
|
|
78
|
+
const socket = // @ts-expect-error Deno
|
|
79
|
+
globalThis.Deno !== undefined && Reflect.has(globalThis, 'Deno') ? new WebSocket(url.toString()) : new NodeWebSocket(url.toString());
|
|
80
80
|
this.socket = socket;
|
|
81
81
|
// TODO: proper event handling
|
|
82
82
|
socket.onerror = (event)=>console.log({
|
|
83
|
-
error: event
|
|
83
|
+
error: event,
|
|
84
|
+
shardId: this.id
|
|
84
85
|
});
|
|
85
86
|
socket.onclose = async (event)=>await this.handleClose(event);
|
|
86
87
|
socket.onmessage = async (message)=>await this.handleMessage(message);
|
|
@@ -114,8 +115,6 @@ export class DiscordenoShard {
|
|
|
114
115
|
if (!this.isOpen()) {
|
|
115
116
|
await this.connect();
|
|
116
117
|
}
|
|
117
|
-
// Wait until an identify is free for this this.
|
|
118
|
-
await this.requestIdentify();
|
|
119
118
|
this.send({
|
|
120
119
|
op: GatewayOpcodes.Identify,
|
|
121
120
|
d: {
|
|
@@ -133,6 +132,8 @@ export class DiscordenoShard {
|
|
|
133
132
|
return await new Promise((resolve)=>{
|
|
134
133
|
this.resolves.set('READY', ()=>{
|
|
135
134
|
this.events.identified?.(this);
|
|
135
|
+
// Tells the manager that this shard is ready
|
|
136
|
+
this.shardIsReady();
|
|
136
137
|
resolve();
|
|
137
138
|
});
|
|
138
139
|
// When identifying too fast,
|
|
@@ -145,27 +146,27 @@ export class DiscordenoShard {
|
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
148
|
/** Check whether the connection to Discord is currently open. */ isOpen() {
|
|
148
|
-
return this.socket?.readyState ===
|
|
149
|
+
return this.socket?.readyState === NodeWebSocket.OPEN;
|
|
149
150
|
}
|
|
150
151
|
/** Attempt to resume the previous shards session with the gateway. */ async resume() {
|
|
151
|
-
|
|
152
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id}`);
|
|
152
153
|
// It has been requested to resume the Shards session.
|
|
153
154
|
// It's possible that the shard is still connected with Discord's gateway therefore we need to forcefully close it.
|
|
154
155
|
if (this.isOpen()) {
|
|
156
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id} in isOpen`);
|
|
155
157
|
this.close(ShardSocketCloseCodes.ResumeClosingOldConnection, 'Reconnecting the shard, closing old connection.');
|
|
156
158
|
}
|
|
157
159
|
// Shard has never identified, so we cannot resume.
|
|
158
160
|
if (!this.sessionId) {
|
|
159
|
-
|
|
160
|
-
// "GW DEBUG",
|
|
161
|
-
// `[Error] Trying to resume a shard (id: ${shardId}) that was not first identified.`,
|
|
162
|
-
// );
|
|
161
|
+
logger.debug(`[Shard] Trying to resume a shard #${this.id} that was NOT first identified. (No session id found)`);
|
|
163
162
|
return await this.identify();
|
|
164
|
-
// throw new Error(`[SHARD] Trying to resume a shard (id: ${this.id}) which was never identified`);
|
|
165
163
|
}
|
|
166
164
|
this.state = ShardState.Resuming;
|
|
165
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id}, before connecting`);
|
|
167
166
|
// Before we can resume, we need to create a new connection with Discord's gateway.
|
|
168
167
|
await this.connect();
|
|
168
|
+
logger.debug(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
169
|
+
`[Gateway] Resuming Shard #${this.id}, after connecting. ${this.gatewayConfig.token} | ${this.sessionId} | ${this.previousSequenceNumber}`);
|
|
169
170
|
this.send({
|
|
170
171
|
op: GatewayOpcodes.Resume,
|
|
171
172
|
d: {
|
|
@@ -174,6 +175,7 @@ export class DiscordenoShard {
|
|
|
174
175
|
seq: this.previousSequenceNumber ?? 0
|
|
175
176
|
}
|
|
176
177
|
}, true);
|
|
178
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id} after send resumg`);
|
|
177
179
|
return await new Promise((resolve)=>{
|
|
178
180
|
this.resolves.set('RESUMED', ()=>resolve());
|
|
179
181
|
// If it is attempted to resume with an invalid session id,
|
|
@@ -191,7 +193,7 @@ export class DiscordenoShard {
|
|
|
191
193
|
// Before acquiring a token from the bucket, check whether the shard is currently offline or not.
|
|
192
194
|
// Else bucket and token wait time just get wasted.
|
|
193
195
|
await this.checkOffline(highPriority);
|
|
194
|
-
await this.bucket.acquire(
|
|
196
|
+
await this.bucket.acquire(highPriority);
|
|
195
197
|
// It's possible, that the shard went offline after a token has been acquired from the bucket.
|
|
196
198
|
await this.checkOffline(highPriority);
|
|
197
199
|
this.socket?.send(JSON.stringify(message));
|
|
@@ -229,6 +231,7 @@ export class DiscordenoShard {
|
|
|
229
231
|
case GatewayCloseEventCodes.RateLimited:
|
|
230
232
|
case GatewayCloseEventCodes.SessionTimedOut:
|
|
231
233
|
{
|
|
234
|
+
logger.debug(`[Shard] Gateway connection closing requiring re-identify. Code: ${close.code}`);
|
|
232
235
|
this.state = ShardState.Identifying;
|
|
233
236
|
this.events.disconnected?.(this);
|
|
234
237
|
return await this.identify();
|
|
@@ -252,6 +255,7 @@ export class DiscordenoShard {
|
|
|
252
255
|
case GatewayCloseEventCodes.AlreadyAuthenticated:
|
|
253
256
|
default:
|
|
254
257
|
{
|
|
258
|
+
logger.info(`[Shard] closed shard #${this.id}. Resuming...`);
|
|
255
259
|
this.state = ShardState.Resuming;
|
|
256
260
|
this.events.disconnected?.(this);
|
|
257
261
|
return await this.resume();
|
|
@@ -285,18 +289,22 @@ export class DiscordenoShard {
|
|
|
285
289
|
case GatewayOpcodes.Hello:
|
|
286
290
|
{
|
|
287
291
|
const interval = packet.d.heartbeat_interval;
|
|
292
|
+
logger.debug(`[Gateway] Hello on Shard #${this.id}`);
|
|
288
293
|
this.startHeartbeating(interval);
|
|
289
294
|
if (this.state !== ShardState.Resuming) {
|
|
295
|
+
const currentQueue = [
|
|
296
|
+
...this.bucket.queue
|
|
297
|
+
];
|
|
290
298
|
// HELLO has been send on a non resume action.
|
|
291
299
|
// This means that the shard starts a new session,
|
|
292
300
|
// therefore the rate limit interval has been reset too.
|
|
293
|
-
this.bucket =
|
|
301
|
+
this.bucket = new LeakyBucket({
|
|
294
302
|
max: this.calculateSafeRequests(),
|
|
295
303
|
refillInterval: 60000,
|
|
296
|
-
refillAmount: this.calculateSafeRequests()
|
|
297
|
-
// Waiting acquires should not be lost on a re-identify.
|
|
298
|
-
waiting: this.bucket.waiting
|
|
304
|
+
refillAmount: this.calculateSafeRequests()
|
|
299
305
|
});
|
|
306
|
+
// Queue should not be lost on a re-identify.
|
|
307
|
+
this.bucket.queue.unshift(...currentQueue);
|
|
300
308
|
}
|
|
301
309
|
this.events.hello?.(this);
|
|
302
310
|
break;
|
|
@@ -315,8 +323,8 @@ export class DiscordenoShard {
|
|
|
315
323
|
}
|
|
316
324
|
case GatewayOpcodes.InvalidSession:
|
|
317
325
|
{
|
|
318
|
-
// gateway.debug("GW INVALID_SESSION", { shardId, payload: packet });
|
|
319
326
|
const resumable = packet.d;
|
|
327
|
+
logger.debug(`[Shard] Received Invalid Session for Shard #${this.id} with resumeable as ${resumable.toString()}`);
|
|
320
328
|
this.events.invalidSession?.(this, resumable);
|
|
321
329
|
// We need to wait for a random amount of time between 1 and 5
|
|
322
330
|
// Reference: https://discord.com/developers/docs/topics/gateway#resuming
|
|
@@ -325,7 +333,7 @@ export class DiscordenoShard {
|
|
|
325
333
|
this.resolves.delete('INVALID_SESSION');
|
|
326
334
|
// When resumable is false we need to re-identify
|
|
327
335
|
if (!resumable) {
|
|
328
|
-
await this.
|
|
336
|
+
await this.requestIdentify();
|
|
329
337
|
break;
|
|
330
338
|
}
|
|
331
339
|
// The session is invalid but apparently it is resumable
|
|
@@ -333,25 +341,49 @@ export class DiscordenoShard {
|
|
|
333
341
|
break;
|
|
334
342
|
}
|
|
335
343
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
344
|
+
switch(packet.t){
|
|
345
|
+
case 'RESUMED':
|
|
346
|
+
this.state = ShardState.Connected;
|
|
347
|
+
this.events.resumed?.(this);
|
|
348
|
+
// Continue the requests which have been queued since the shard went offline.
|
|
349
|
+
this.offlineSendQueue.map((resolve)=>resolve());
|
|
350
|
+
this.resolves.get('RESUMED')?.(packet);
|
|
351
|
+
this.resolves.delete('RESUMED');
|
|
352
|
+
break;
|
|
353
|
+
case 'READY':
|
|
354
|
+
{
|
|
355
|
+
// Important for future resumes.
|
|
356
|
+
const payload = packet.d;
|
|
357
|
+
this.resumeGatewayUrl = payload.resume_gateway_url;
|
|
358
|
+
this.sessionId = payload.session_id;
|
|
359
|
+
this.state = ShardState.Connected;
|
|
360
|
+
// Continue the requests which have been queued since the shard went offline.
|
|
361
|
+
// Important when this is a re-identify
|
|
362
|
+
this.offlineSendQueue.map((resolve)=>resolve());
|
|
363
|
+
this.resolves.get('READY')?.(packet);
|
|
364
|
+
this.resolves.delete('READY');
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
case 'GUILD_MEMBERS_CHUNK':
|
|
368
|
+
{
|
|
369
|
+
// If it's not enabled skip checks.
|
|
370
|
+
if (!this.cache.requestMembers.enabled) break;
|
|
371
|
+
const payload = packet.d;
|
|
372
|
+
// If this request has non nonce, skip checks.
|
|
373
|
+
if (!payload.nonce) break;
|
|
374
|
+
const pending = this.cache.requestMembers.pending.get(payload.nonce);
|
|
375
|
+
if (!pending) break;
|
|
376
|
+
// If this is not the final chunk, just save to cache.
|
|
377
|
+
if (payload.chunk_index + 1 < payload.chunk_count) {
|
|
378
|
+
pending.members.push(...payload.members);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
// Resolve the promise that all requests are done.
|
|
382
|
+
pending.resolve(camelize(pending.members));
|
|
383
|
+
// Delete the cache to clean up once its done.
|
|
384
|
+
this.cache.requestMembers.pending.delete(payload.nonce);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
355
387
|
}
|
|
356
388
|
// Update the sequence number if it is present
|
|
357
389
|
// `s` can be either `null` or a `number`.
|
|
@@ -359,6 +391,9 @@ export class DiscordenoShard {
|
|
|
359
391
|
if (packet.s !== null) {
|
|
360
392
|
this.previousSequenceNumber = packet.s;
|
|
361
393
|
}
|
|
394
|
+
this.forwardToBot(packet);
|
|
395
|
+
}
|
|
396
|
+
forwardToBot(packet) {
|
|
362
397
|
// The necessary handling required for the Shards connection has been finished.
|
|
363
398
|
// Now the event can be safely forwarded.
|
|
364
399
|
this.events.message?.(this, camelize(packet));
|
|
@@ -383,8 +418,12 @@ export class DiscordenoShard {
|
|
|
383
418
|
return;
|
|
384
419
|
}
|
|
385
420
|
/** This function communicates with the management process, in order to know whether its free to identify. When this function resolves, this means that the shard is allowed to send an identify payload to discord. */ async requestIdentify() {}
|
|
421
|
+
/** This function communicates with the management process, in order to tell it can identify the next shard. */ async shardIsReady() {}
|
|
386
422
|
/** Start sending heartbeat payloads to Discord in the provided interval. */ startHeartbeating(interval) {
|
|
387
|
-
|
|
423
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id}`);
|
|
424
|
+
// If old heartbeast exist like after resume, clear the old ones.
|
|
425
|
+
if (this.heart.intervalId) clearInterval(this.heart.intervalId);
|
|
426
|
+
if (this.heart.timeoutId) clearTimeout(this.heart.timeoutId);
|
|
388
427
|
this.heart.interval = interval;
|
|
389
428
|
// Only set the shard's state to `Unidentified`
|
|
390
429
|
// if heartbeating has not been started due to an identify or resume action.
|
|
@@ -392,6 +431,7 @@ export class DiscordenoShard {
|
|
|
392
431
|
ShardState.Disconnected,
|
|
393
432
|
ShardState.Offline
|
|
394
433
|
].includes(this.state)) {
|
|
434
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} a`);
|
|
395
435
|
this.state = ShardState.Unidentified;
|
|
396
436
|
}
|
|
397
437
|
// The first heartbeat needs to be send with a random delay between `0` and `interval`
|
|
@@ -400,17 +440,22 @@ export class DiscordenoShard {
|
|
|
400
440
|
// Reference: https://discord.com/developers/docs/topics/gateway#heartbeating
|
|
401
441
|
const jitter = Math.ceil(this.heart.interval * (Math.random() || 0.5));
|
|
402
442
|
this.heart.timeoutId = setTimeout(()=>{
|
|
443
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} b`);
|
|
403
444
|
if (!this.isOpen()) return;
|
|
445
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} c ${this.previousSequenceNumber}`);
|
|
404
446
|
// Using a direct socket.send call here because heartbeat requests are reserved by us.
|
|
405
447
|
this.socket?.send(JSON.stringify({
|
|
406
448
|
op: GatewayOpcodes.Heartbeat,
|
|
407
449
|
d: this.previousSequenceNumber
|
|
408
450
|
}));
|
|
451
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} d`);
|
|
409
452
|
this.heart.lastBeat = Date.now();
|
|
410
453
|
this.heart.acknowledged = false;
|
|
411
454
|
// After the random heartbeat jitter we can start a normal interval.
|
|
412
455
|
this.heart.intervalId = setInterval(async ()=>{
|
|
456
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} e`);
|
|
413
457
|
if (!this.isOpen()) return;
|
|
458
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} f`);
|
|
414
459
|
// gateway.debug("GW DEBUG", `Running setInterval in heartbeat file. Shard: ${shardId}`);
|
|
415
460
|
// gateway.debug("GW HEARTBEATING", { shardId, shard: currentShard });
|
|
416
461
|
// The Shard did not receive a heartbeat ACK from Discord in time,
|
|
@@ -418,15 +463,18 @@ export class DiscordenoShard {
|
|
|
418
463
|
// The Shard needs to start a re-identify action accordingly.
|
|
419
464
|
// Reference: https://discord.com/developers/docs/topics/gateway#heartbeating-example-gateway-heartbeat-ack
|
|
420
465
|
if (!this.heart.acknowledged) {
|
|
466
|
+
logger.debug(`[Shard] Heartbeat not acknowledged for shard #${this.id}.`);
|
|
421
467
|
this.close(ShardSocketCloseCodes.ZombiedConnection, 'Zombied connection, did not receive an heartbeat ACK in time.');
|
|
422
468
|
return await this.identify();
|
|
423
469
|
}
|
|
424
470
|
this.heart.acknowledged = false;
|
|
471
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} g`);
|
|
425
472
|
// Using a direct socket.send call here because heartbeat requests are reserved by us.
|
|
426
473
|
this.socket?.send(JSON.stringify({
|
|
427
474
|
op: GatewayOpcodes.Heartbeat,
|
|
428
475
|
d: this.previousSequenceNumber
|
|
429
476
|
}));
|
|
477
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} h`);
|
|
430
478
|
this.heart.lastBeat = Date.now();
|
|
431
479
|
this.events.heartbeat?.(this);
|
|
432
480
|
}, this.heart.interval);
|
|
@@ -455,7 +503,7 @@ export class DiscordenoShard {
|
|
|
455
503
|
* @see {@link https://discord.com/developers/docs/topics/gateway#update-voice-state}
|
|
456
504
|
*/ async joinVoiceChannel(guildId, channelId, options) {
|
|
457
505
|
logger.debug(`[Shard] joinVoiceChannel guildId: ${guildId} channelId: ${channelId}`);
|
|
458
|
-
|
|
506
|
+
await this.send({
|
|
459
507
|
op: GatewayOpcodes.VoiceStateUpdate,
|
|
460
508
|
d: {
|
|
461
509
|
guild_id: guildId.toString(),
|
|
@@ -472,7 +520,7 @@ export class DiscordenoShard {
|
|
|
472
520
|
* @returns Promise<void>
|
|
473
521
|
*/ async editBotStatus(data) {
|
|
474
522
|
logger.debug(`[Shard] editBotStatus data: ${JSON.stringify(data)}`);
|
|
475
|
-
|
|
523
|
+
await this.editShardStatus(data);
|
|
476
524
|
}
|
|
477
525
|
/**
|
|
478
526
|
* Edits the bot's status on one shard.
|
|
@@ -482,7 +530,7 @@ export class DiscordenoShard {
|
|
|
482
530
|
* @returns Promise<void>
|
|
483
531
|
*/ async editShardStatus(data) {
|
|
484
532
|
logger.debug(`[Shard] editShardStatus shardId: ${this.id} -> data: ${JSON.stringify(data)}`);
|
|
485
|
-
|
|
533
|
+
await this.send({
|
|
486
534
|
op: GatewayOpcodes.PresenceUpdate,
|
|
487
535
|
d: {
|
|
488
536
|
since: null,
|
|
@@ -524,7 +572,6 @@ export class DiscordenoShard {
|
|
|
524
572
|
logger.debug(`[Shard] requestMembers guildId: ${guildId} -> setting user limit based on userIds length: ${options.userIds.length}`);
|
|
525
573
|
options.limit = options.userIds.length;
|
|
526
574
|
}
|
|
527
|
-
const nonce = `${guildId}-${Date.now()}`;
|
|
528
575
|
// Gateway does not require caching these requests so directly send and return
|
|
529
576
|
if (!this.cache.requestMembers?.enabled) {
|
|
530
577
|
logger.debug(`[Shard] requestMembers guildId: ${guildId} -> skipping cache -> options ${JSON.stringify(options)}`);
|
|
@@ -537,14 +584,14 @@ export class DiscordenoShard {
|
|
|
537
584
|
limit: options?.limit ?? 0,
|
|
538
585
|
presences: options?.presences ?? false,
|
|
539
586
|
user_ids: options?.userIds?.map((id)=>id.toString()),
|
|
540
|
-
nonce
|
|
587
|
+
nonce: options?.nonce
|
|
541
588
|
}
|
|
542
589
|
});
|
|
543
590
|
return [];
|
|
544
591
|
}
|
|
545
592
|
return await new Promise((resolve)=>{
|
|
546
|
-
this.cache.requestMembers?.pending.set(nonce, {
|
|
547
|
-
nonce,
|
|
593
|
+
if (options?.nonce) this.cache.requestMembers?.pending.set(options.nonce, {
|
|
594
|
+
nonce: options.nonce,
|
|
548
595
|
resolve,
|
|
549
596
|
members: []
|
|
550
597
|
});
|
|
@@ -558,7 +605,7 @@ export class DiscordenoShard {
|
|
|
558
605
|
limit: options?.limit ?? 0,
|
|
559
606
|
presences: options?.presences ?? false,
|
|
560
607
|
user_ids: options?.userIds?.map((id)=>id.toString()),
|
|
561
|
-
nonce
|
|
608
|
+
nonce: options?.nonce
|
|
562
609
|
}
|
|
563
610
|
});
|
|
564
611
|
});
|
|
@@ -576,7 +623,7 @@ export class DiscordenoShard {
|
|
|
576
623
|
* @see {@link https://discord.com/developers/docs/topics/gateway#update-voice-state}
|
|
577
624
|
*/ async leaveVoiceChannel(guildId) {
|
|
578
625
|
logger.debug(`[Shard] leaveVoiceChannel guildId: ${guildId} Shard ${this.id}`);
|
|
579
|
-
|
|
626
|
+
await this.send({
|
|
580
627
|
op: GatewayOpcodes.VoiceStateUpdate,
|
|
581
628
|
d: {
|
|
582
629
|
guild_id: guildId.toString(),
|