@discordeno/gateway 19.0.0-next.b213c8b → 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 +15 -6
- package/dist/Shard.d.ts.map +1 -1
- package/dist/Shard.js +131 -86
- package/dist/Shard.js.map +1 -1
- package/dist/manager.d.ts +11 -6
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +58 -37
- 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. */
|
|
@@ -161,6 +166,10 @@ export interface ShardCreateOptions {
|
|
|
161
166
|
connection: ShardGatewayConfig;
|
|
162
167
|
/** The event handlers for events on the shard. */
|
|
163
168
|
events: ShardEvents;
|
|
169
|
+
/** The handler to request a space to make an identify request. */
|
|
170
|
+
requestIdentify?: () => Promise<void>;
|
|
171
|
+
/** The handler to alert the gateway manager that this shard has received a READY event. */
|
|
172
|
+
shardIsReady?: () => Promise<void>;
|
|
164
173
|
}
|
|
165
174
|
export default DiscordenoShard;
|
|
166
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;
|
|
@@ -34,10 +29,21 @@ export class DiscordenoShard {
|
|
|
34
29
|
acknowledged: false,
|
|
35
30
|
interval: 45000
|
|
36
31
|
};
|
|
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
|
+
});
|
|
37
39
|
}
|
|
38
40
|
/** The gateway configuration which is used to connect to Discord. */ get gatewayConfig() {
|
|
39
41
|
return this.connection;
|
|
40
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
|
+
}
|
|
41
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() {
|
|
42
48
|
// * 2 adds extra safety layer for discords OP 1 requests that we need to respond to
|
|
43
49
|
const safeRequests = this.maxRequestsPerRateLimitTick - Math.ceil(this.rateLimitResetInterval / this.heart.interval) * 2;
|
|
@@ -53,8 +59,8 @@ export class DiscordenoShard {
|
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
/** Close the socket connection to discord if present. */ close(code, reason) {
|
|
56
|
-
if (this.socket?.readyState !==
|
|
57
|
-
|
|
62
|
+
if (this.socket?.readyState !== NodeWebSocket.OPEN) return;
|
|
63
|
+
this.socket?.close(code, reason);
|
|
58
64
|
}
|
|
59
65
|
/** Connect the shard with the gateway and start heartbeating. This will not identify the shard to the gateway. */ async connect() {
|
|
60
66
|
// Only set the shard to `Connecting` state,
|
|
@@ -66,20 +72,16 @@ export class DiscordenoShard {
|
|
|
66
72
|
this.state = ShardState.Connecting;
|
|
67
73
|
}
|
|
68
74
|
this.events.connecting?.(this);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
url.searchParams.set('v', this.gatewayConfig.version.toString());
|
|
76
|
-
url.searchParams.set('encoding', 'json');
|
|
77
|
-
}
|
|
78
|
-
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());
|
|
79
80
|
this.socket = socket;
|
|
80
81
|
// TODO: proper event handling
|
|
81
82
|
socket.onerror = (event)=>console.log({
|
|
82
|
-
error: event
|
|
83
|
+
error: event,
|
|
84
|
+
shardId: this.id
|
|
83
85
|
});
|
|
84
86
|
socket.onclose = async (event)=>await this.handleClose(event);
|
|
85
87
|
socket.onmessage = async (message)=>await this.handleMessage(message);
|
|
@@ -102,7 +104,7 @@ export class DiscordenoShard {
|
|
|
102
104
|
// A new identify has been requested even though there is already a connection open.
|
|
103
105
|
// Therefore we need to close the old connection and heartbeating before creating a new one.
|
|
104
106
|
if (this.isOpen()) {
|
|
105
|
-
|
|
107
|
+
logger.debug(`CLOSING EXISTING SHARD: #${this.id}`);
|
|
106
108
|
this.close(ShardSocketCloseCodes.ReIdentifying, 'Re-identifying closure of old connection.');
|
|
107
109
|
}
|
|
108
110
|
this.state = ShardState.Identifying;
|
|
@@ -113,8 +115,6 @@ export class DiscordenoShard {
|
|
|
113
115
|
if (!this.isOpen()) {
|
|
114
116
|
await this.connect();
|
|
115
117
|
}
|
|
116
|
-
// Wait until an identify is free for this this.
|
|
117
|
-
await this.requestIdentify();
|
|
118
118
|
this.send({
|
|
119
119
|
op: GatewayOpcodes.Identify,
|
|
120
120
|
d: {
|
|
@@ -132,6 +132,8 @@ export class DiscordenoShard {
|
|
|
132
132
|
return await new Promise((resolve)=>{
|
|
133
133
|
this.resolves.set('READY', ()=>{
|
|
134
134
|
this.events.identified?.(this);
|
|
135
|
+
// Tells the manager that this shard is ready
|
|
136
|
+
this.shardIsReady();
|
|
135
137
|
resolve();
|
|
136
138
|
});
|
|
137
139
|
// When identifying too fast,
|
|
@@ -144,27 +146,27 @@ export class DiscordenoShard {
|
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
/** Check whether the connection to Discord is currently open. */ isOpen() {
|
|
147
|
-
return this.socket?.readyState ===
|
|
149
|
+
return this.socket?.readyState === NodeWebSocket.OPEN;
|
|
148
150
|
}
|
|
149
151
|
/** Attempt to resume the previous shards session with the gateway. */ async resume() {
|
|
150
|
-
|
|
152
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id}`);
|
|
151
153
|
// It has been requested to resume the Shards session.
|
|
152
154
|
// It's possible that the shard is still connected with Discord's gateway therefore we need to forcefully close it.
|
|
153
155
|
if (this.isOpen()) {
|
|
156
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id} in isOpen`);
|
|
154
157
|
this.close(ShardSocketCloseCodes.ResumeClosingOldConnection, 'Reconnecting the shard, closing old connection.');
|
|
155
158
|
}
|
|
156
159
|
// Shard has never identified, so we cannot resume.
|
|
157
160
|
if (!this.sessionId) {
|
|
158
|
-
|
|
159
|
-
// "GW DEBUG",
|
|
160
|
-
// `[Error] Trying to resume a shard (id: ${shardId}) that was not first identified.`,
|
|
161
|
-
// );
|
|
161
|
+
logger.debug(`[Shard] Trying to resume a shard #${this.id} that was NOT first identified. (No session id found)`);
|
|
162
162
|
return await this.identify();
|
|
163
|
-
// throw new Error(`[SHARD] Trying to resume a shard (id: ${this.id}) which was never identified`);
|
|
164
163
|
}
|
|
165
164
|
this.state = ShardState.Resuming;
|
|
165
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id}, before connecting`);
|
|
166
166
|
// Before we can resume, we need to create a new connection with Discord's gateway.
|
|
167
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}`);
|
|
168
170
|
this.send({
|
|
169
171
|
op: GatewayOpcodes.Resume,
|
|
170
172
|
d: {
|
|
@@ -173,6 +175,7 @@ export class DiscordenoShard {
|
|
|
173
175
|
seq: this.previousSequenceNumber ?? 0
|
|
174
176
|
}
|
|
175
177
|
}, true);
|
|
178
|
+
logger.debug(`[Gateway] Resuming Shard #${this.id} after send resumg`);
|
|
176
179
|
return await new Promise((resolve)=>{
|
|
177
180
|
this.resolves.set('RESUMED', ()=>resolve());
|
|
178
181
|
// If it is attempted to resume with an invalid session id,
|
|
@@ -190,7 +193,7 @@ export class DiscordenoShard {
|
|
|
190
193
|
// Before acquiring a token from the bucket, check whether the shard is currently offline or not.
|
|
191
194
|
// Else bucket and token wait time just get wasted.
|
|
192
195
|
await this.checkOffline(highPriority);
|
|
193
|
-
await this.bucket.acquire(
|
|
196
|
+
await this.bucket.acquire(highPriority);
|
|
194
197
|
// It's possible, that the shard went offline after a token has been acquired from the bucket.
|
|
195
198
|
await this.checkOffline(highPriority);
|
|
196
199
|
this.socket?.send(JSON.stringify(message));
|
|
@@ -228,6 +231,7 @@ export class DiscordenoShard {
|
|
|
228
231
|
case GatewayCloseEventCodes.RateLimited:
|
|
229
232
|
case GatewayCloseEventCodes.SessionTimedOut:
|
|
230
233
|
{
|
|
234
|
+
logger.debug(`[Shard] Gateway connection closing requiring re-identify. Code: ${close.code}`);
|
|
231
235
|
this.state = ShardState.Identifying;
|
|
232
236
|
this.events.disconnected?.(this);
|
|
233
237
|
return await this.identify();
|
|
@@ -251,6 +255,7 @@ export class DiscordenoShard {
|
|
|
251
255
|
case GatewayCloseEventCodes.AlreadyAuthenticated:
|
|
252
256
|
default:
|
|
253
257
|
{
|
|
258
|
+
logger.info(`[Shard] closed shard #${this.id}. Resuming...`);
|
|
254
259
|
this.state = ShardState.Resuming;
|
|
255
260
|
this.events.disconnected?.(this);
|
|
256
261
|
return await this.resume();
|
|
@@ -284,18 +289,22 @@ export class DiscordenoShard {
|
|
|
284
289
|
case GatewayOpcodes.Hello:
|
|
285
290
|
{
|
|
286
291
|
const interval = packet.d.heartbeat_interval;
|
|
292
|
+
logger.debug(`[Gateway] Hello on Shard #${this.id}`);
|
|
287
293
|
this.startHeartbeating(interval);
|
|
288
294
|
if (this.state !== ShardState.Resuming) {
|
|
295
|
+
const currentQueue = [
|
|
296
|
+
...this.bucket.queue
|
|
297
|
+
];
|
|
289
298
|
// HELLO has been send on a non resume action.
|
|
290
299
|
// This means that the shard starts a new session,
|
|
291
300
|
// therefore the rate limit interval has been reset too.
|
|
292
|
-
this.bucket =
|
|
301
|
+
this.bucket = new LeakyBucket({
|
|
293
302
|
max: this.calculateSafeRequests(),
|
|
294
303
|
refillInterval: 60000,
|
|
295
|
-
refillAmount: this.calculateSafeRequests()
|
|
296
|
-
// Waiting acquires should not be lost on a re-identify.
|
|
297
|
-
waiting: this.bucket.waiting
|
|
304
|
+
refillAmount: this.calculateSafeRequests()
|
|
298
305
|
});
|
|
306
|
+
// Queue should not be lost on a re-identify.
|
|
307
|
+
this.bucket.queue.unshift(...currentQueue);
|
|
299
308
|
}
|
|
300
309
|
this.events.hello?.(this);
|
|
301
310
|
break;
|
|
@@ -314,8 +323,8 @@ export class DiscordenoShard {
|
|
|
314
323
|
}
|
|
315
324
|
case GatewayOpcodes.InvalidSession:
|
|
316
325
|
{
|
|
317
|
-
// gateway.debug("GW INVALID_SESSION", { shardId, payload: packet });
|
|
318
326
|
const resumable = packet.d;
|
|
327
|
+
logger.debug(`[Shard] Received Invalid Session for Shard #${this.id} with resumeable as ${resumable.toString()}`);
|
|
319
328
|
this.events.invalidSession?.(this, resumable);
|
|
320
329
|
// We need to wait for a random amount of time between 1 and 5
|
|
321
330
|
// Reference: https://discord.com/developers/docs/topics/gateway#resuming
|
|
@@ -324,7 +333,7 @@ export class DiscordenoShard {
|
|
|
324
333
|
this.resolves.delete('INVALID_SESSION');
|
|
325
334
|
// When resumable is false we need to re-identify
|
|
326
335
|
if (!resumable) {
|
|
327
|
-
await this.
|
|
336
|
+
await this.requestIdentify();
|
|
328
337
|
break;
|
|
329
338
|
}
|
|
330
339
|
// The session is invalid but apparently it is resumable
|
|
@@ -332,25 +341,49 @@ export class DiscordenoShard {
|
|
|
332
341
|
break;
|
|
333
342
|
}
|
|
334
343
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
387
|
}
|
|
355
388
|
// Update the sequence number if it is present
|
|
356
389
|
// `s` can be either `null` or a `number`.
|
|
@@ -358,6 +391,9 @@ export class DiscordenoShard {
|
|
|
358
391
|
if (packet.s !== null) {
|
|
359
392
|
this.previousSequenceNumber = packet.s;
|
|
360
393
|
}
|
|
394
|
+
this.forwardToBot(packet);
|
|
395
|
+
}
|
|
396
|
+
forwardToBot(packet) {
|
|
361
397
|
// The necessary handling required for the Shards connection has been finished.
|
|
362
398
|
// Now the event can be safely forwarded.
|
|
363
399
|
this.events.message?.(this, camelize(packet));
|
|
@@ -381,12 +417,13 @@ export class DiscordenoShard {
|
|
|
381
417
|
// eslint-disable-next-line no-useless-return
|
|
382
418
|
return;
|
|
383
419
|
}
|
|
384
|
-
/** 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() {
|
|
385
|
-
|
|
386
|
-
// return await options.requestIdentify(this.id)
|
|
387
|
-
}
|
|
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() {}
|
|
388
422
|
/** Start sending heartbeat payloads to Discord in the provided interval. */ startHeartbeating(interval) {
|
|
389
|
-
|
|
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);
|
|
390
427
|
this.heart.interval = interval;
|
|
391
428
|
// Only set the shard's state to `Unidentified`
|
|
392
429
|
// if heartbeating has not been started due to an identify or resume action.
|
|
@@ -394,6 +431,7 @@ export class DiscordenoShard {
|
|
|
394
431
|
ShardState.Disconnected,
|
|
395
432
|
ShardState.Offline
|
|
396
433
|
].includes(this.state)) {
|
|
434
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} a`);
|
|
397
435
|
this.state = ShardState.Unidentified;
|
|
398
436
|
}
|
|
399
437
|
// The first heartbeat needs to be send with a random delay between `0` and `interval`
|
|
@@ -402,17 +440,22 @@ export class DiscordenoShard {
|
|
|
402
440
|
// Reference: https://discord.com/developers/docs/topics/gateway#heartbeating
|
|
403
441
|
const jitter = Math.ceil(this.heart.interval * (Math.random() || 0.5));
|
|
404
442
|
this.heart.timeoutId = setTimeout(()=>{
|
|
443
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} b`);
|
|
405
444
|
if (!this.isOpen()) return;
|
|
445
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} c ${this.previousSequenceNumber}`);
|
|
406
446
|
// Using a direct socket.send call here because heartbeat requests are reserved by us.
|
|
407
447
|
this.socket?.send(JSON.stringify({
|
|
408
448
|
op: GatewayOpcodes.Heartbeat,
|
|
409
449
|
d: this.previousSequenceNumber
|
|
410
450
|
}));
|
|
451
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} d`);
|
|
411
452
|
this.heart.lastBeat = Date.now();
|
|
412
453
|
this.heart.acknowledged = false;
|
|
413
454
|
// After the random heartbeat jitter we can start a normal interval.
|
|
414
455
|
this.heart.intervalId = setInterval(async ()=>{
|
|
456
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} e`);
|
|
415
457
|
if (!this.isOpen()) return;
|
|
458
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} f`);
|
|
416
459
|
// gateway.debug("GW DEBUG", `Running setInterval in heartbeat file. Shard: ${shardId}`);
|
|
417
460
|
// gateway.debug("GW HEARTBEATING", { shardId, shard: currentShard });
|
|
418
461
|
// The Shard did not receive a heartbeat ACK from Discord in time,
|
|
@@ -420,15 +463,18 @@ export class DiscordenoShard {
|
|
|
420
463
|
// The Shard needs to start a re-identify action accordingly.
|
|
421
464
|
// Reference: https://discord.com/developers/docs/topics/gateway#heartbeating-example-gateway-heartbeat-ack
|
|
422
465
|
if (!this.heart.acknowledged) {
|
|
466
|
+
logger.debug(`[Shard] Heartbeat not acknowledged for shard #${this.id}.`);
|
|
423
467
|
this.close(ShardSocketCloseCodes.ZombiedConnection, 'Zombied connection, did not receive an heartbeat ACK in time.');
|
|
424
468
|
return await this.identify();
|
|
425
469
|
}
|
|
426
470
|
this.heart.acknowledged = false;
|
|
471
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} g`);
|
|
427
472
|
// Using a direct socket.send call here because heartbeat requests are reserved by us.
|
|
428
473
|
this.socket?.send(JSON.stringify({
|
|
429
474
|
op: GatewayOpcodes.Heartbeat,
|
|
430
475
|
d: this.previousSequenceNumber
|
|
431
476
|
}));
|
|
477
|
+
logger.debug(`[Gateway] Start Heartbeating Shard #${this.id} h`);
|
|
432
478
|
this.heart.lastBeat = Date.now();
|
|
433
479
|
this.events.heartbeat?.(this);
|
|
434
480
|
}, this.heart.interval);
|
|
@@ -457,7 +503,7 @@ export class DiscordenoShard {
|
|
|
457
503
|
* @see {@link https://discord.com/developers/docs/topics/gateway#update-voice-state}
|
|
458
504
|
*/ async joinVoiceChannel(guildId, channelId, options) {
|
|
459
505
|
logger.debug(`[Shard] joinVoiceChannel guildId: ${guildId} channelId: ${channelId}`);
|
|
460
|
-
|
|
506
|
+
await this.send({
|
|
461
507
|
op: GatewayOpcodes.VoiceStateUpdate,
|
|
462
508
|
d: {
|
|
463
509
|
guild_id: guildId.toString(),
|
|
@@ -474,7 +520,7 @@ export class DiscordenoShard {
|
|
|
474
520
|
* @returns Promise<void>
|
|
475
521
|
*/ async editBotStatus(data) {
|
|
476
522
|
logger.debug(`[Shard] editBotStatus data: ${JSON.stringify(data)}`);
|
|
477
|
-
|
|
523
|
+
await this.editShardStatus(data);
|
|
478
524
|
}
|
|
479
525
|
/**
|
|
480
526
|
* Edits the bot's status on one shard.
|
|
@@ -484,7 +530,7 @@ export class DiscordenoShard {
|
|
|
484
530
|
* @returns Promise<void>
|
|
485
531
|
*/ async editShardStatus(data) {
|
|
486
532
|
logger.debug(`[Shard] editShardStatus shardId: ${this.id} -> data: ${JSON.stringify(data)}`);
|
|
487
|
-
|
|
533
|
+
await this.send({
|
|
488
534
|
op: GatewayOpcodes.PresenceUpdate,
|
|
489
535
|
d: {
|
|
490
536
|
since: null,
|
|
@@ -526,7 +572,6 @@ export class DiscordenoShard {
|
|
|
526
572
|
logger.debug(`[Shard] requestMembers guildId: ${guildId} -> setting user limit based on userIds length: ${options.userIds.length}`);
|
|
527
573
|
options.limit = options.userIds.length;
|
|
528
574
|
}
|
|
529
|
-
const nonce = `${guildId}-${Date.now()}`;
|
|
530
575
|
// Gateway does not require caching these requests so directly send and return
|
|
531
576
|
if (!this.cache.requestMembers?.enabled) {
|
|
532
577
|
logger.debug(`[Shard] requestMembers guildId: ${guildId} -> skipping cache -> options ${JSON.stringify(options)}`);
|
|
@@ -539,14 +584,14 @@ export class DiscordenoShard {
|
|
|
539
584
|
limit: options?.limit ?? 0,
|
|
540
585
|
presences: options?.presences ?? false,
|
|
541
586
|
user_ids: options?.userIds?.map((id)=>id.toString()),
|
|
542
|
-
nonce
|
|
587
|
+
nonce: options?.nonce
|
|
543
588
|
}
|
|
544
589
|
});
|
|
545
590
|
return [];
|
|
546
591
|
}
|
|
547
592
|
return await new Promise((resolve)=>{
|
|
548
|
-
this.cache.requestMembers?.pending.set(nonce, {
|
|
549
|
-
nonce,
|
|
593
|
+
if (options?.nonce) this.cache.requestMembers?.pending.set(options.nonce, {
|
|
594
|
+
nonce: options.nonce,
|
|
550
595
|
resolve,
|
|
551
596
|
members: []
|
|
552
597
|
});
|
|
@@ -560,7 +605,7 @@ export class DiscordenoShard {
|
|
|
560
605
|
limit: options?.limit ?? 0,
|
|
561
606
|
presences: options?.presences ?? false,
|
|
562
607
|
user_ids: options?.userIds?.map((id)=>id.toString()),
|
|
563
|
-
nonce
|
|
608
|
+
nonce: options?.nonce
|
|
564
609
|
}
|
|
565
610
|
});
|
|
566
611
|
});
|
|
@@ -578,7 +623,7 @@ export class DiscordenoShard {
|
|
|
578
623
|
* @see {@link https://discord.com/developers/docs/topics/gateway#update-voice-state}
|
|
579
624
|
*/ async leaveVoiceChannel(guildId) {
|
|
580
625
|
logger.debug(`[Shard] leaveVoiceChannel guildId: ${guildId} Shard ${this.id}`);
|
|
581
|
-
|
|
626
|
+
await this.send({
|
|
582
627
|
op: GatewayOpcodes.VoiceStateUpdate,
|
|
583
628
|
d: {
|
|
584
629
|
guild_id: guildId.toString(),
|