lunibee 0.1.7 → 0.2.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.
- package/LICENSE +21 -21
- package/README.md +225 -223
- package/dist/builders/commands.d.ts +6 -4
- package/dist/builders/components.d.ts +23 -0
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +120 -24
- package/dist/builders/index.js.map +9 -8
- package/dist/collection/index.d.ts +68 -1
- package/dist/collection/index.js +335 -1
- package/dist/collection/index.js.map +4 -4
- package/dist/core/collector.d.ts +13 -0
- package/dist/core/events.d.ts +112 -2
- package/dist/core/index.d.ts +30 -93
- package/dist/core/index.js +3386 -957
- package/dist/core/index.js.map +48 -24
- package/dist/core/permissions.d.ts +48 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3806 -1193
- package/dist/index.js.map +64 -43
- package/dist/managers/advanced.d.ts +116 -0
- package/dist/managers/base.d.ts +13 -2
- package/dist/managers/emoji.d.ts +1 -1
- package/dist/managers/guild-resources.d.ts +109 -0
- package/dist/managers/guild.d.ts +33 -2
- package/dist/managers/index.d.ts +44 -6
- package/dist/managers/index.js +1186 -150
- package/dist/managers/index.js.map +27 -21
- package/dist/managers/message.d.ts +10 -1
- package/dist/rest/decoder.d.ts +23 -0
- package/dist/rest/errors.d.ts +53 -0
- package/dist/rest/index.d.ts +21 -17
- package/dist/rest/index.js +526 -264
- package/dist/rest/index.js.map +14 -8
- package/dist/rest/limiter.d.ts +42 -0
- package/dist/rest/redis.d.ts +18 -1
- package/dist/rest/route.d.ts +31 -0
- package/dist/rest/routes.d.ts +22 -0
- package/dist/rest/scheduler.d.ts +42 -0
- package/dist/rest/store.d.ts +29 -0
- package/dist/rest/transport.d.ts +40 -0
- package/dist/sharding/bus.d.ts +31 -0
- package/dist/sharding/cluster.d.ts +10 -0
- package/dist/sharding/index.d.ts +39 -2
- package/dist/sharding/index.js +1105 -404
- package/dist/sharding/index.js.map +18 -8
- package/dist/structures/base.d.ts +10 -0
- package/dist/structures/channels.d.ts +56 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +305 -311
- package/dist/structures/index.js.map +18 -14
- package/dist/structures/interactions.d.ts +38 -52
- package/dist/structures/options.d.ts +55 -0
- package/dist/structures/resources.d.ts +3 -3
- package/dist/types/gateway-events.d.ts +132 -0
- package/dist/types/gateway.d.ts +197 -0
- package/dist/types/index.d.ts +53 -260
- package/dist/types/index.js +44 -2
- package/dist/types/index.js.map +5 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/voice/index.d.ts +32 -1
- package/dist/voice/index.js +73 -7
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/close-codes.d.ts +23 -0
- package/dist/ws/decoder.d.ts +58 -0
- package/dist/ws/heartbeat.d.ts +87 -0
- package/dist/ws/index.d.ts +18 -65
- package/dist/ws/index.js +911 -385
- package/dist/ws/index.js.map +15 -5
- package/dist/ws/opcodes.d.ts +14 -0
- package/dist/ws/protocol.d.ts +110 -0
- package/dist/ws/reconnect.d.ts +108 -0
- package/dist/ws/send-budget.d.ts +17 -0
- package/dist/ws/session.d.ts +100 -0
- package/dist/ws/state.d.ts +27 -0
- package/dist/ws/transport.d.ts +80 -0
- package/package.json +21 -20
package/dist/ws/index.js
CHANGED
|
@@ -1,5 +1,709 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// packages/
|
|
2
|
+
// packages/ws/src/session.ts
|
|
3
|
+
class GatewaySession {
|
|
4
|
+
#state = { status: "none" };
|
|
5
|
+
#sequence = null;
|
|
6
|
+
#generation = 0;
|
|
7
|
+
#lastInvalidation;
|
|
8
|
+
get lastInvalidation() {
|
|
9
|
+
return this.#lastInvalidation;
|
|
10
|
+
}
|
|
11
|
+
get generation() {
|
|
12
|
+
return this.#generation;
|
|
13
|
+
}
|
|
14
|
+
get sessionId() {
|
|
15
|
+
return this.#state.status === "established" ? this.#state.sessionId : undefined;
|
|
16
|
+
}
|
|
17
|
+
get resumeURL() {
|
|
18
|
+
return this.#state.status === "established" ? this.#state.resumeURL : undefined;
|
|
19
|
+
}
|
|
20
|
+
get sequence() {
|
|
21
|
+
return this.#sequence;
|
|
22
|
+
}
|
|
23
|
+
get canResume() {
|
|
24
|
+
return this.#state.status === "established" && this.#sequence !== null;
|
|
25
|
+
}
|
|
26
|
+
beginConnection() {
|
|
27
|
+
return ++this.#generation;
|
|
28
|
+
}
|
|
29
|
+
owns(token) {
|
|
30
|
+
return token === this.#generation;
|
|
31
|
+
}
|
|
32
|
+
recordSequence(sequence, token) {
|
|
33
|
+
if (!this.owns(token))
|
|
34
|
+
return false;
|
|
35
|
+
if (!Number.isInteger(sequence))
|
|
36
|
+
return false;
|
|
37
|
+
if (this.#sequence !== null && sequence < this.#sequence)
|
|
38
|
+
return false;
|
|
39
|
+
this.#sequence = sequence;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
activate(payload, token) {
|
|
43
|
+
if (!this.owns(token))
|
|
44
|
+
return false;
|
|
45
|
+
const ready = payload;
|
|
46
|
+
if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string")
|
|
47
|
+
return false;
|
|
48
|
+
this.#state = {
|
|
49
|
+
status: "established",
|
|
50
|
+
sessionId: ready.session_id,
|
|
51
|
+
resumeURL: ready.resume_gateway_url
|
|
52
|
+
};
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
invalidate(token, reason = "explicit") {
|
|
56
|
+
if (!this.owns(token))
|
|
57
|
+
return false;
|
|
58
|
+
this.#lastInvalidation = reason;
|
|
59
|
+
this.#state = { status: "none" };
|
|
60
|
+
this.#sequence = null;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
resumeInfo() {
|
|
64
|
+
if (this.#state.status !== "established")
|
|
65
|
+
return;
|
|
66
|
+
if (this.#sequence === null)
|
|
67
|
+
return;
|
|
68
|
+
const { sessionId, resumeURL } = this.#state;
|
|
69
|
+
return { sessionId, sequence: this.#sequence, resumeURL };
|
|
70
|
+
}
|
|
71
|
+
handshake() {
|
|
72
|
+
const resume = this.resumeInfo();
|
|
73
|
+
return resume ? { type: "resume", ...resume } : { type: "identify" };
|
|
74
|
+
}
|
|
75
|
+
connectURL(fallback) {
|
|
76
|
+
return this.resumeInfo()?.resumeURL ?? fallback;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// packages/ws/src/heartbeat.ts
|
|
81
|
+
var MAX_MONITOR_INTERVAL = 1000;
|
|
82
|
+
|
|
83
|
+
class GatewayHeartbeat {
|
|
84
|
+
#options;
|
|
85
|
+
#now;
|
|
86
|
+
#interval = 0;
|
|
87
|
+
#acknowledged = true;
|
|
88
|
+
#sentAt = 0;
|
|
89
|
+
#latency = -1;
|
|
90
|
+
#lastMessageAt = 0;
|
|
91
|
+
#zombieReported = false;
|
|
92
|
+
#timer;
|
|
93
|
+
#initialTimer;
|
|
94
|
+
#ackTimer;
|
|
95
|
+
#monitorTimer;
|
|
96
|
+
constructor(options) {
|
|
97
|
+
if (options.zombieTimeout <= options.ackTimeout)
|
|
98
|
+
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
99
|
+
this.#options = options;
|
|
100
|
+
this.#now = options.now ?? (() => Date.now());
|
|
101
|
+
}
|
|
102
|
+
get latency() {
|
|
103
|
+
return this.#latency;
|
|
104
|
+
}
|
|
105
|
+
get interval() {
|
|
106
|
+
return this.#interval;
|
|
107
|
+
}
|
|
108
|
+
get acknowledged() {
|
|
109
|
+
return this.#acknowledged;
|
|
110
|
+
}
|
|
111
|
+
get silentFor() {
|
|
112
|
+
return this.#lastMessageAt === 0 ? 0 : this.#now() - this.#lastMessageAt;
|
|
113
|
+
}
|
|
114
|
+
get stalenessDeadline() {
|
|
115
|
+
return Math.max(this.#options.zombieTimeout, this.#interval + this.#options.ackTimeout);
|
|
116
|
+
}
|
|
117
|
+
isHealthy() {
|
|
118
|
+
if (!this.#acknowledged)
|
|
119
|
+
return false;
|
|
120
|
+
if (this.#lastMessageAt === 0)
|
|
121
|
+
return true;
|
|
122
|
+
return this.silentFor < this.stalenessDeadline;
|
|
123
|
+
}
|
|
124
|
+
watch() {
|
|
125
|
+
this.#lastMessageAt = this.#now();
|
|
126
|
+
this.#zombieReported = false;
|
|
127
|
+
this.#startMonitor();
|
|
128
|
+
}
|
|
129
|
+
receivedMessage() {
|
|
130
|
+
this.#lastMessageAt = this.#now();
|
|
131
|
+
this.#zombieReported = false;
|
|
132
|
+
}
|
|
133
|
+
start(interval) {
|
|
134
|
+
this.#clearBeatTimers();
|
|
135
|
+
this.#interval = interval;
|
|
136
|
+
this.#acknowledged = true;
|
|
137
|
+
this.#initialTimer = setTimeout(() => {
|
|
138
|
+
this.#initialTimer = undefined;
|
|
139
|
+
this.sendHeartbeat();
|
|
140
|
+
this.#timer = setInterval(() => this.sendHeartbeat(), interval);
|
|
141
|
+
}, Math.random() * interval);
|
|
142
|
+
}
|
|
143
|
+
stop() {
|
|
144
|
+
this.#clearBeatTimers();
|
|
145
|
+
if (this.#monitorTimer)
|
|
146
|
+
clearInterval(this.#monitorTimer);
|
|
147
|
+
this.#monitorTimer = undefined;
|
|
148
|
+
}
|
|
149
|
+
sendHeartbeat() {
|
|
150
|
+
this.#acknowledged = false;
|
|
151
|
+
this.#sentAt = this.#now();
|
|
152
|
+
if (!this.#options.send(this.#options.sequence())) {
|
|
153
|
+
this.#options.onError(new Error("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.#clearAckTimer();
|
|
157
|
+
this.#ackTimer = setTimeout(() => {
|
|
158
|
+
if (this.#acknowledged)
|
|
159
|
+
return;
|
|
160
|
+
this.#options.onTimeout({
|
|
161
|
+
type: "ack",
|
|
162
|
+
elapsedMs: this.#now() - this.#sentAt
|
|
163
|
+
});
|
|
164
|
+
}, this.#options.ackTimeout);
|
|
165
|
+
}
|
|
166
|
+
acknowledge() {
|
|
167
|
+
if (this.#acknowledged)
|
|
168
|
+
return;
|
|
169
|
+
this.#acknowledged = true;
|
|
170
|
+
this.#latency = this.#now() - this.#sentAt;
|
|
171
|
+
this.#clearAckTimer();
|
|
172
|
+
}
|
|
173
|
+
#startMonitor() {
|
|
174
|
+
if (this.#monitorTimer)
|
|
175
|
+
clearInterval(this.#monitorTimer);
|
|
176
|
+
const interval = Math.max(1, Math.min(MAX_MONITOR_INTERVAL, this.#options.ackTimeout, this.#options.zombieTimeout / 2));
|
|
177
|
+
this.#monitorTimer = setInterval(() => this.#checkStaleness(), interval);
|
|
178
|
+
}
|
|
179
|
+
#checkStaleness() {
|
|
180
|
+
if (!this.#options.isConnected() || this.#lastMessageAt === 0)
|
|
181
|
+
return;
|
|
182
|
+
const silentFor = this.silentFor;
|
|
183
|
+
const deadline = this.stalenessDeadline;
|
|
184
|
+
if (silentFor < deadline || this.#zombieReported)
|
|
185
|
+
return;
|
|
186
|
+
this.#zombieReported = true;
|
|
187
|
+
this.#options.onTimeout({ type: "zombie", silentFor, deadline });
|
|
188
|
+
}
|
|
189
|
+
#clearAckTimer() {
|
|
190
|
+
if (this.#ackTimer)
|
|
191
|
+
clearTimeout(this.#ackTimer);
|
|
192
|
+
this.#ackTimer = undefined;
|
|
193
|
+
}
|
|
194
|
+
#clearBeatTimers() {
|
|
195
|
+
if (this.#timer)
|
|
196
|
+
clearInterval(this.#timer);
|
|
197
|
+
if (this.#initialTimer)
|
|
198
|
+
clearTimeout(this.#initialTimer);
|
|
199
|
+
this.#clearAckTimer();
|
|
200
|
+
this.#timer = undefined;
|
|
201
|
+
this.#initialTimer = undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// packages/ws/src/close-codes.ts
|
|
206
|
+
var GatewayCloseCodes = {
|
|
207
|
+
UnknownError: 4000,
|
|
208
|
+
UnknownOpcode: 4001,
|
|
209
|
+
DecodeError: 4002,
|
|
210
|
+
NotAuthenticated: 4003,
|
|
211
|
+
AuthenticationFailed: 4004,
|
|
212
|
+
AlreadyAuthenticated: 4005,
|
|
213
|
+
InvalidSeq: 4007,
|
|
214
|
+
RateLimited: 4008,
|
|
215
|
+
SessionTimedOut: 4009,
|
|
216
|
+
InvalidShard: 4010,
|
|
217
|
+
ShardingRequired: 4011,
|
|
218
|
+
InvalidAPIVersion: 4012,
|
|
219
|
+
InvalidIntents: 4013,
|
|
220
|
+
DisallowedIntents: 4014
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// packages/ws/src/reconnect.ts
|
|
224
|
+
var FATAL_CLOSE_CODES = [
|
|
225
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
226
|
+
GatewayCloseCodes.InvalidShard,
|
|
227
|
+
GatewayCloseCodes.ShardingRequired,
|
|
228
|
+
GatewayCloseCodes.InvalidAPIVersion,
|
|
229
|
+
GatewayCloseCodes.InvalidIntents,
|
|
230
|
+
GatewayCloseCodes.DisallowedIntents
|
|
231
|
+
];
|
|
232
|
+
var IDENTIFY_CLOSE_CODES = [
|
|
233
|
+
GatewayCloseCodes.InvalidSeq,
|
|
234
|
+
GatewayCloseCodes.SessionTimedOut
|
|
235
|
+
];
|
|
236
|
+
function classifyCloseCode(code, canResume) {
|
|
237
|
+
if (FATAL_CLOSE_CODES.includes(code))
|
|
238
|
+
return "stop";
|
|
239
|
+
if (IDENTIFY_CLOSE_CODES.includes(code))
|
|
240
|
+
return "identify";
|
|
241
|
+
return canResume ? "resume" : "identify";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
class GatewayReconnect {
|
|
245
|
+
#options;
|
|
246
|
+
#random;
|
|
247
|
+
#attempt = 0;
|
|
248
|
+
#timer;
|
|
249
|
+
#promise;
|
|
250
|
+
#resolve;
|
|
251
|
+
#reject;
|
|
252
|
+
constructor(options) {
|
|
253
|
+
this.#options = options;
|
|
254
|
+
this.#random = options.random ?? Math.random;
|
|
255
|
+
}
|
|
256
|
+
get enabled() {
|
|
257
|
+
return this.#options.enabled;
|
|
258
|
+
}
|
|
259
|
+
get attempts() {
|
|
260
|
+
return this.#attempt;
|
|
261
|
+
}
|
|
262
|
+
get pending() {
|
|
263
|
+
return this.#timer !== undefined;
|
|
264
|
+
}
|
|
265
|
+
get exhausted() {
|
|
266
|
+
return this.#attempt >= this.#options.maxAttempts;
|
|
267
|
+
}
|
|
268
|
+
get connecting() {
|
|
269
|
+
return this.#promise !== undefined;
|
|
270
|
+
}
|
|
271
|
+
get attemptPromise() {
|
|
272
|
+
return this.#promise;
|
|
273
|
+
}
|
|
274
|
+
classifyClose(code, canResume) {
|
|
275
|
+
return classifyCloseCode(code, canResume);
|
|
276
|
+
}
|
|
277
|
+
canReconnect() {
|
|
278
|
+
return this.#options.enabled && !this.exhausted;
|
|
279
|
+
}
|
|
280
|
+
schedule(run) {
|
|
281
|
+
if (!this.#options.enabled)
|
|
282
|
+
return { scheduled: false, reason: "disabled" };
|
|
283
|
+
if (this.#timer)
|
|
284
|
+
return { scheduled: false, reason: "pending" };
|
|
285
|
+
if (this.exhausted)
|
|
286
|
+
return { scheduled: false, reason: "exhausted" };
|
|
287
|
+
const attempt = this.#attempt;
|
|
288
|
+
const delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
|
|
289
|
+
const jitter = this.#random() * Math.max(1, delay * 0.25);
|
|
290
|
+
const delayMs = delay + jitter;
|
|
291
|
+
this.#timer = setTimeout(() => {
|
|
292
|
+
this.#timer = undefined;
|
|
293
|
+
run();
|
|
294
|
+
}, delayMs);
|
|
295
|
+
return { scheduled: true, delayMs, attempt };
|
|
296
|
+
}
|
|
297
|
+
cancel() {
|
|
298
|
+
if (this.#timer)
|
|
299
|
+
clearTimeout(this.#timer);
|
|
300
|
+
this.#timer = undefined;
|
|
301
|
+
}
|
|
302
|
+
reset() {
|
|
303
|
+
this.#attempt = 0;
|
|
304
|
+
}
|
|
305
|
+
attempt(start) {
|
|
306
|
+
if (this.#promise)
|
|
307
|
+
return this.#promise;
|
|
308
|
+
this.#promise = new Promise((resolve, reject) => {
|
|
309
|
+
this.#resolve = resolve;
|
|
310
|
+
this.#reject = reject;
|
|
311
|
+
start();
|
|
312
|
+
});
|
|
313
|
+
return this.#promise;
|
|
314
|
+
}
|
|
315
|
+
settle(error) {
|
|
316
|
+
const resolve = this.#resolve;
|
|
317
|
+
const reject = this.#reject;
|
|
318
|
+
this.#resolve = undefined;
|
|
319
|
+
this.#reject = undefined;
|
|
320
|
+
this.#promise = undefined;
|
|
321
|
+
if (error)
|
|
322
|
+
reject?.(error);
|
|
323
|
+
else
|
|
324
|
+
resolve?.();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// packages/ws/src/decoder.ts
|
|
329
|
+
import {
|
|
330
|
+
createInflate,
|
|
331
|
+
constants as zlibConstants
|
|
332
|
+
} from "zlib";
|
|
333
|
+
async function toBytes(data) {
|
|
334
|
+
if (data instanceof Uint8Array)
|
|
335
|
+
return data;
|
|
336
|
+
if (data instanceof ArrayBuffer)
|
|
337
|
+
return new Uint8Array(data);
|
|
338
|
+
if (typeof Blob !== "undefined" && data instanceof Blob)
|
|
339
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
340
|
+
throw new TypeError("Gateway returned an unsupported compressed frame type.");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
class PlainTextDecoder {
|
|
344
|
+
push(data) {
|
|
345
|
+
return [String(data)];
|
|
346
|
+
}
|
|
347
|
+
reset() {}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
class ZlibStreamDecoder {
|
|
351
|
+
#create;
|
|
352
|
+
#inflate;
|
|
353
|
+
#chunks = [];
|
|
354
|
+
#failure;
|
|
355
|
+
#pendingReject;
|
|
356
|
+
constructor(create = () => createInflate()) {
|
|
357
|
+
this.#create = create;
|
|
358
|
+
}
|
|
359
|
+
push(data) {
|
|
360
|
+
if (typeof data === "string")
|
|
361
|
+
return [data];
|
|
362
|
+
return this.#inflateFrame(data);
|
|
363
|
+
}
|
|
364
|
+
async#inflateFrame(data) {
|
|
365
|
+
const chunk = await toBytes(data);
|
|
366
|
+
const inflate = this.#inflate ??= this.#open();
|
|
367
|
+
if (this.#failure)
|
|
368
|
+
throw this.#failure;
|
|
369
|
+
const complete = chunk.length >= 4 && chunk[chunk.length - 4] === 0 && chunk[chunk.length - 3] === 0 && chunk[chunk.length - 2] === 255 && chunk[chunk.length - 1] === 255;
|
|
370
|
+
await this.#awaiting((settle) => inflate.write(chunk, (error) => error ? settle(error) : settle()));
|
|
371
|
+
if (!complete)
|
|
372
|
+
return [];
|
|
373
|
+
await this.#awaiting((settle) => inflate.flush(zlibConstants.Z_SYNC_FLUSH, () => settle()));
|
|
374
|
+
const parts = this.#chunks;
|
|
375
|
+
this.#chunks = [];
|
|
376
|
+
if (parts.length === 0)
|
|
377
|
+
return [];
|
|
378
|
+
const total = parts.reduce((size, part) => size + part.length, 0);
|
|
379
|
+
const merged = new Uint8Array(total);
|
|
380
|
+
let offset = 0;
|
|
381
|
+
for (const part of parts) {
|
|
382
|
+
merged.set(part, offset);
|
|
383
|
+
offset += part.length;
|
|
384
|
+
}
|
|
385
|
+
return [new TextDecoder().decode(merged)];
|
|
386
|
+
}
|
|
387
|
+
reset() {
|
|
388
|
+
const inflate = this.#inflate;
|
|
389
|
+
this.#inflate = undefined;
|
|
390
|
+
this.#chunks = [];
|
|
391
|
+
this.#failure = undefined;
|
|
392
|
+
this.#pendingReject = undefined;
|
|
393
|
+
if (!inflate)
|
|
394
|
+
return;
|
|
395
|
+
inflate.removeAllListeners();
|
|
396
|
+
try {
|
|
397
|
+
inflate.close();
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
#awaiting(run) {
|
|
401
|
+
if (this.#failure)
|
|
402
|
+
return Promise.reject(this.#failure);
|
|
403
|
+
return new Promise((resolve, reject) => {
|
|
404
|
+
this.#pendingReject = reject;
|
|
405
|
+
run((error) => {
|
|
406
|
+
this.#pendingReject = undefined;
|
|
407
|
+
if (error)
|
|
408
|
+
reject(error);
|
|
409
|
+
else if (this.#failure)
|
|
410
|
+
reject(this.#failure);
|
|
411
|
+
else
|
|
412
|
+
resolve();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
#open() {
|
|
417
|
+
const inflate = this.#create();
|
|
418
|
+
inflate.on("data", (chunk) => this.#chunks.push(chunk));
|
|
419
|
+
inflate.on("error", (error) => {
|
|
420
|
+
this.#failure = error;
|
|
421
|
+
const reject = this.#pendingReject;
|
|
422
|
+
this.#pendingReject = undefined;
|
|
423
|
+
reject?.(error);
|
|
424
|
+
});
|
|
425
|
+
return inflate;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function createDecoder(compress) {
|
|
429
|
+
return compress ? new ZlibStreamDecoder : new PlainTextDecoder;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// packages/ws/src/transport.ts
|
|
433
|
+
var OPEN = 1;
|
|
434
|
+
|
|
435
|
+
class WebSocketTransport {
|
|
436
|
+
#handlers;
|
|
437
|
+
#decoder;
|
|
438
|
+
#createSocket;
|
|
439
|
+
#compress;
|
|
440
|
+
#socket;
|
|
441
|
+
#generation = 0;
|
|
442
|
+
#queue = Promise.resolve();
|
|
443
|
+
constructor(options) {
|
|
444
|
+
this.#handlers = options.handlers;
|
|
445
|
+
this.#compress = options.compress ?? false;
|
|
446
|
+
this.#decoder = options.decoder ?? createDecoder(options.compress);
|
|
447
|
+
this.#createSocket = options.createSocket ?? ((url) => new WebSocket(url));
|
|
448
|
+
}
|
|
449
|
+
get connected() {
|
|
450
|
+
return this.#socket?.readyState === OPEN;
|
|
451
|
+
}
|
|
452
|
+
get live() {
|
|
453
|
+
return this.#socket !== undefined && this.#socket.readyState <= OPEN;
|
|
454
|
+
}
|
|
455
|
+
get generation() {
|
|
456
|
+
return this.#generation;
|
|
457
|
+
}
|
|
458
|
+
connect(url) {
|
|
459
|
+
this.#discard(1000, "Superseded by a new connection");
|
|
460
|
+
const generation = ++this.#generation;
|
|
461
|
+
this.#decoder.reset();
|
|
462
|
+
this.#queue = Promise.resolve();
|
|
463
|
+
let socket;
|
|
464
|
+
try {
|
|
465
|
+
socket = this.#createSocket(url);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
return { ok: false, error: this.#asError(error) };
|
|
468
|
+
}
|
|
469
|
+
if (this.#compress) {
|
|
470
|
+
try {
|
|
471
|
+
socket.binaryType = "arraybuffer";
|
|
472
|
+
} catch {}
|
|
473
|
+
}
|
|
474
|
+
this.#socket = socket;
|
|
475
|
+
socket.addEventListener("open", () => {
|
|
476
|
+
if (!this.#owns(generation))
|
|
477
|
+
return;
|
|
478
|
+
this.#handlers.onOpen();
|
|
479
|
+
});
|
|
480
|
+
socket.addEventListener("message", (event) => {
|
|
481
|
+
if (!this.#owns(generation))
|
|
482
|
+
return;
|
|
483
|
+
let decoded;
|
|
484
|
+
try {
|
|
485
|
+
decoded = this.#decoder.push(event.data);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
this.#handlers.onError(this.#asError(error));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!(decoded instanceof Promise)) {
|
|
491
|
+
for (const frame of decoded)
|
|
492
|
+
this.#handlers.onFrame(frame);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
this.#queue = this.#queue.then(async () => {
|
|
496
|
+
const frames = await decoded;
|
|
497
|
+
if (!this.#owns(generation))
|
|
498
|
+
return;
|
|
499
|
+
for (const frame of frames)
|
|
500
|
+
this.#handlers.onFrame(frame);
|
|
501
|
+
}).catch((error) => {
|
|
502
|
+
if (!this.#owns(generation))
|
|
503
|
+
return;
|
|
504
|
+
this.#handlers.onError(this.#asError(error));
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
socket.addEventListener("close", (event) => {
|
|
508
|
+
if (!this.#owns(generation))
|
|
509
|
+
return;
|
|
510
|
+
this.#socket = undefined;
|
|
511
|
+
this.#generation++;
|
|
512
|
+
this.#decoder.reset();
|
|
513
|
+
this.#handlers.onClose(event.code, event.reason ?? "");
|
|
514
|
+
});
|
|
515
|
+
socket.addEventListener("error", () => {
|
|
516
|
+
if (!this.#owns(generation))
|
|
517
|
+
return;
|
|
518
|
+
this.#handlers.onError(new Error("Gateway WebSocket error"));
|
|
519
|
+
});
|
|
520
|
+
return { ok: true };
|
|
521
|
+
}
|
|
522
|
+
send(data) {
|
|
523
|
+
if (!this.connected)
|
|
524
|
+
return false;
|
|
525
|
+
try {
|
|
526
|
+
this.#socket.send(data);
|
|
527
|
+
return true;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
this.#handlers.onError(this.#asError(error));
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
close(code = 1000, reason = "") {
|
|
534
|
+
const socket = this.#socket;
|
|
535
|
+
if (!socket)
|
|
536
|
+
return;
|
|
537
|
+
try {
|
|
538
|
+
socket.close(code, reason);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
this.#handlers.onError(this.#asError(error));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
destroy(code = 1000, reason = "") {
|
|
544
|
+
this.#discard(code, reason);
|
|
545
|
+
this.#decoder.reset();
|
|
546
|
+
}
|
|
547
|
+
#owns(generation) {
|
|
548
|
+
return generation === this.#generation;
|
|
549
|
+
}
|
|
550
|
+
#discard(code, reason) {
|
|
551
|
+
const socket = this.#socket;
|
|
552
|
+
if (!socket)
|
|
553
|
+
return;
|
|
554
|
+
this.#generation++;
|
|
555
|
+
this.#socket = undefined;
|
|
556
|
+
try {
|
|
557
|
+
socket.close(code, reason);
|
|
558
|
+
} catch (error) {
|
|
559
|
+
this.#handlers.onError(this.#asError(error));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
#asError(error) {
|
|
563
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// packages/ws/src/opcodes.ts
|
|
568
|
+
var GatewayOpcodes = {
|
|
569
|
+
Dispatch: 0,
|
|
570
|
+
Heartbeat: 1,
|
|
571
|
+
Identify: 2,
|
|
572
|
+
PresenceUpdate: 3,
|
|
573
|
+
VoiceStateUpdate: 4,
|
|
574
|
+
Resume: 6,
|
|
575
|
+
Reconnect: 7,
|
|
576
|
+
RequestGuildMembers: 8,
|
|
577
|
+
InvalidSession: 9,
|
|
578
|
+
Hello: 10,
|
|
579
|
+
HeartbeatAck: 11
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// packages/ws/src/protocol.ts
|
|
583
|
+
function classifyFrame(raw) {
|
|
584
|
+
let payload;
|
|
585
|
+
try {
|
|
586
|
+
payload = JSON.parse(raw);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
return {
|
|
589
|
+
sequence: null,
|
|
590
|
+
action: {
|
|
591
|
+
type: "invalid",
|
|
592
|
+
violation: "invalid-json",
|
|
593
|
+
message: "Gateway returned invalid JSON",
|
|
594
|
+
cause: error
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
return classifyPayload(payload);
|
|
599
|
+
}
|
|
600
|
+
function classifyPayload(payload) {
|
|
601
|
+
if (payload === null || typeof payload !== "object" || typeof payload.op !== "number")
|
|
602
|
+
return {
|
|
603
|
+
sequence: null,
|
|
604
|
+
action: {
|
|
605
|
+
type: "invalid",
|
|
606
|
+
violation: "invalid-payload",
|
|
607
|
+
message: "Gateway returned an invalid payload"
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
const frame = payload;
|
|
611
|
+
const sequence = typeof frame.s === "number" ? frame.s : null;
|
|
612
|
+
switch (frame.op) {
|
|
613
|
+
case GatewayOpcodes.Dispatch:
|
|
614
|
+
return {
|
|
615
|
+
sequence,
|
|
616
|
+
action: {
|
|
617
|
+
type: "dispatch",
|
|
618
|
+
event: typeof frame.t === "string" ? frame.t : null,
|
|
619
|
+
data: frame.d
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
case GatewayOpcodes.Hello: {
|
|
623
|
+
const interval = frame.d?.heartbeat_interval;
|
|
624
|
+
if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0)
|
|
625
|
+
return {
|
|
626
|
+
sequence,
|
|
627
|
+
action: {
|
|
628
|
+
type: "invalid",
|
|
629
|
+
violation: "invalid-hello",
|
|
630
|
+
message: "Gateway HELLO payload contains an invalid heartbeat interval"
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
return {
|
|
634
|
+
sequence,
|
|
635
|
+
action: { type: "hello", heartbeatInterval: interval }
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
case GatewayOpcodes.Heartbeat:
|
|
639
|
+
return { sequence, action: { type: "heartbeat" } };
|
|
640
|
+
case GatewayOpcodes.HeartbeatAck:
|
|
641
|
+
return {
|
|
642
|
+
sequence,
|
|
643
|
+
action: { type: "heartbeat-ack", data: frame.d }
|
|
644
|
+
};
|
|
645
|
+
case GatewayOpcodes.Reconnect:
|
|
646
|
+
return { sequence, action: { type: "reconnect" } };
|
|
647
|
+
case GatewayOpcodes.InvalidSession:
|
|
648
|
+
return {
|
|
649
|
+
sequence,
|
|
650
|
+
action: {
|
|
651
|
+
type: "invalid-session",
|
|
652
|
+
resumable: frame.d === true
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
default:
|
|
656
|
+
return { sequence, action: { type: "unknown", opcode: frame.op } };
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function identifyPayload(options) {
|
|
660
|
+
const props = options.properties ?? {};
|
|
661
|
+
const os = props.os ?? "Android";
|
|
662
|
+
const browser = props.browser ?? "Discord Android";
|
|
663
|
+
const device = props.device ?? "Discord Android";
|
|
664
|
+
return {
|
|
665
|
+
op: GatewayOpcodes.Identify,
|
|
666
|
+
d: {
|
|
667
|
+
token: options.token,
|
|
668
|
+
intents: options.intents,
|
|
669
|
+
properties: {
|
|
670
|
+
os,
|
|
671
|
+
browser,
|
|
672
|
+
device,
|
|
673
|
+
$os: os,
|
|
674
|
+
$browser: browser,
|
|
675
|
+
$device: device,
|
|
676
|
+
...props
|
|
677
|
+
},
|
|
678
|
+
presence: {
|
|
679
|
+
since: options.presence?.since ?? null,
|
|
680
|
+
activities: options.presence?.activities ?? [],
|
|
681
|
+
status: options.presence?.status ?? "online",
|
|
682
|
+
afk: Boolean(options.presence?.afk)
|
|
683
|
+
},
|
|
684
|
+
shard: [options.shardId, options.shardCount]
|
|
685
|
+
},
|
|
686
|
+
s: null,
|
|
687
|
+
t: null
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function resumePayload(token, resume) {
|
|
691
|
+
return {
|
|
692
|
+
op: GatewayOpcodes.Resume,
|
|
693
|
+
d: {
|
|
694
|
+
token,
|
|
695
|
+
session_id: resume.sessionId,
|
|
696
|
+
seq: resume.sequence
|
|
697
|
+
},
|
|
698
|
+
s: null,
|
|
699
|
+
t: null
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function heartbeatPayload(sequence) {
|
|
703
|
+
return { op: GatewayOpcodes.Heartbeat, d: sequence, s: null, t: null };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// packages/types/src/gateway.ts
|
|
3
707
|
var GatewayIntentBits = {
|
|
4
708
|
Guilds: 1 << 0,
|
|
5
709
|
GuildMembers: 1 << 1,
|
|
@@ -54,6 +758,8 @@ var IntentBits = {
|
|
|
54
758
|
function resolveGatewayIntents(intents) {
|
|
55
759
|
if (typeof intents === "number")
|
|
56
760
|
return intents;
|
|
761
|
+
if (intents && typeof intents === "object" && !Array.isArray(intents))
|
|
762
|
+
return intents.bitfield;
|
|
57
763
|
if (Array.isArray(intents)) {
|
|
58
764
|
return intents.reduce((acc, intent) => {
|
|
59
765
|
return acc | resolveGatewayIntents(intent);
|
|
@@ -73,48 +779,18 @@ function resolveGatewayIntents(intents) {
|
|
|
73
779
|
}
|
|
74
780
|
return 0;
|
|
75
781
|
}
|
|
76
|
-
|
|
77
|
-
// packages/ws/src/index.ts
|
|
78
|
-
var GatewayOpcodes = {
|
|
79
|
-
Dispatch: 0,
|
|
80
|
-
Heartbeat: 1,
|
|
81
|
-
Identify: 2,
|
|
82
|
-
PresenceUpdate: 3,
|
|
83
|
-
VoiceStateUpdate: 4,
|
|
84
|
-
Resume: 6,
|
|
85
|
-
Reconnect: 7,
|
|
86
|
-
RequestGuildMembers: 8,
|
|
87
|
-
InvalidSession: 9,
|
|
88
|
-
Hello: 10,
|
|
89
|
-
HeartbeatAck: 11
|
|
90
|
-
};
|
|
91
|
-
var GatewayCloseCodes = {
|
|
92
|
-
UnknownError: 4000,
|
|
93
|
-
UnknownOpcode: 4001,
|
|
94
|
-
DecodeError: 4002,
|
|
95
|
-
NotAuthenticated: 4003,
|
|
96
|
-
AuthenticationFailed: 4004,
|
|
97
|
-
AlreadyAuthenticated: 4005,
|
|
98
|
-
InvalidSeq: 4007,
|
|
99
|
-
RateLimited: 4008,
|
|
100
|
-
SessionTimedOut: 4009,
|
|
101
|
-
InvalidShard: 4010,
|
|
102
|
-
ShardingRequired: 4011,
|
|
103
|
-
InvalidAPIVersion: 4012,
|
|
104
|
-
InvalidIntents: 4013,
|
|
105
|
-
DisallowedIntents: 4014
|
|
106
|
-
};
|
|
782
|
+
// packages/ws/src/state.ts
|
|
107
783
|
var GatewayState;
|
|
108
|
-
((
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
784
|
+
((GatewayState) => {
|
|
785
|
+
GatewayState["Connect"] = "CONNECT";
|
|
786
|
+
GatewayState["Hello"] = "HELLO";
|
|
787
|
+
GatewayState["Identify"] = "IDENTIFY";
|
|
788
|
+
GatewayState["Resume"] = "RESUME";
|
|
789
|
+
GatewayState["Ready"] = "READY";
|
|
790
|
+
GatewayState["Dispatch"] = "DISPATCH";
|
|
791
|
+
GatewayState["Heartbeat"] = "HEARTBEAT";
|
|
792
|
+
GatewayState["Reconnect"] = "RECONNECT";
|
|
793
|
+
GatewayState["Closed"] = "CLOSED";
|
|
118
794
|
})(GatewayState ||= {});
|
|
119
795
|
class GatewayError extends Error {
|
|
120
796
|
code;
|
|
@@ -124,33 +800,44 @@ class GatewayError extends Error {
|
|
|
124
800
|
this.code = code;
|
|
125
801
|
}
|
|
126
802
|
}
|
|
803
|
+
// packages/ws/src/send-budget.ts
|
|
804
|
+
class SendBudget {
|
|
805
|
+
#timestamps = [];
|
|
806
|
+
#limit;
|
|
807
|
+
#windowMs;
|
|
808
|
+
constructor(limit = 115, windowMs = 60000) {
|
|
809
|
+
this.#limit = limit;
|
|
810
|
+
this.#windowMs = windowMs;
|
|
811
|
+
}
|
|
812
|
+
get used() {
|
|
813
|
+
return this.#timestamps.length;
|
|
814
|
+
}
|
|
815
|
+
allows(privileged, now = Date.now()) {
|
|
816
|
+
while (this.#timestamps.length && now - this.#timestamps[0] >= this.#windowMs)
|
|
817
|
+
this.#timestamps.shift();
|
|
818
|
+
return privileged || this.#timestamps.length < this.#limit;
|
|
819
|
+
}
|
|
820
|
+
record(now = Date.now()) {
|
|
821
|
+
this.#timestamps.push(now);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// packages/ws/src/index.ts
|
|
826
|
+
var RESUMABLE_CLOSE_CODE = 4900;
|
|
827
|
+
var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
|
|
127
828
|
|
|
128
829
|
class Gateway {
|
|
129
830
|
state = "CONNECT" /* Connect */;
|
|
130
831
|
#options;
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#heartbeatTimer;
|
|
136
|
-
#initialHeartbeat;
|
|
137
|
-
#heartbeatAckTimer;
|
|
138
|
-
#heartbeatACK = true;
|
|
139
|
-
#heartbeatInterval = 0;
|
|
140
|
-
#heartbeatSentAt = 0;
|
|
141
|
-
#lastMessageAt = 0;
|
|
142
|
-
#zombieTimer;
|
|
143
|
-
#zombieReported = false;
|
|
832
|
+
#transport;
|
|
833
|
+
#session = new GatewaySession;
|
|
834
|
+
#token = 0;
|
|
835
|
+
#heartbeat;
|
|
144
836
|
#closed = false;
|
|
145
|
-
#
|
|
146
|
-
#reconnectTimer;
|
|
147
|
-
#connectPromise;
|
|
148
|
-
#resolveConnect;
|
|
149
|
-
#rejectConnect;
|
|
837
|
+
#reconnect;
|
|
150
838
|
#listeners = new Map;
|
|
151
|
-
#
|
|
839
|
+
#sendBudget = new SendBudget;
|
|
152
840
|
ping = -1;
|
|
153
|
-
#decompressor;
|
|
154
841
|
constructor(options) {
|
|
155
842
|
if (!options.token?.trim())
|
|
156
843
|
throw new TypeError("A Gateway token is required.");
|
|
@@ -177,41 +864,72 @@ class Gateway {
|
|
|
177
864
|
afk: false,
|
|
178
865
|
since: null
|
|
179
866
|
},
|
|
180
|
-
...options
|
|
867
|
+
...options,
|
|
868
|
+
intents: resolvedIntents
|
|
181
869
|
};
|
|
182
870
|
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
183
871
|
throw new RangeError("Gateway shardId must be within the configured shard count.");
|
|
184
872
|
if (this.#options.zombieTimeout <= this.#options.heartbeatAckTimeout)
|
|
185
873
|
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
874
|
+
this.#transport = new WebSocketTransport({
|
|
875
|
+
compress: this.#options.compress,
|
|
876
|
+
createSocket: options.createSocket,
|
|
877
|
+
handlers: {
|
|
878
|
+
onOpen: () => this.#onOpen(),
|
|
879
|
+
onFrame: (raw) => this.#message(raw, this.#token),
|
|
880
|
+
onClose: (code) => this.#close(code, this.#token),
|
|
881
|
+
onError: (error) => this.#emitError(error instanceof GatewayError ? error : new GatewayError(error.message, undefined, {
|
|
882
|
+
cause: error
|
|
883
|
+
}))
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
this.#reconnect = new GatewayReconnect({
|
|
887
|
+
enabled: this.#options.reconnect,
|
|
888
|
+
maxAttempts: this.#options.maxReconnectAttempts,
|
|
889
|
+
baseDelay: this.#options.reconnectBaseDelay,
|
|
890
|
+
maxDelay: this.#options.reconnectMaxDelay
|
|
891
|
+
});
|
|
892
|
+
this.#heartbeat = new GatewayHeartbeat({
|
|
893
|
+
ackTimeout: this.#options.heartbeatAckTimeout,
|
|
894
|
+
zombieTimeout: this.#options.zombieTimeout,
|
|
895
|
+
send: (sequence) => this.#dispatch(heartbeatPayload(sequence), true),
|
|
896
|
+
sequence: () => this.#session.sequence,
|
|
897
|
+
isConnected: () => !this.#closed && this.#transport.connected,
|
|
898
|
+
onTimeout: (timeout) => this.#onHeartbeatTimeout(timeout),
|
|
899
|
+
onError: (error) => this.#emitError(error)
|
|
900
|
+
});
|
|
186
901
|
}
|
|
187
|
-
|
|
902
|
+
#onHeartbeatTimeout(timeout) {
|
|
903
|
+
if (timeout.type === "zombie") {
|
|
904
|
+
this.#emit("zombie", {
|
|
905
|
+
silentFor: timeout.silentFor,
|
|
906
|
+
timeout: timeout.deadline
|
|
907
|
+
});
|
|
908
|
+
this.#emitError(new GatewayError(`Gateway connection appears stale after ${timeout.silentFor}ms without traffic.`));
|
|
909
|
+
} else {
|
|
910
|
+
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${timeout.elapsedMs}ms.`));
|
|
911
|
+
}
|
|
912
|
+
this.#transport.close(RESUMABLE_CLOSE_CODE, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
|
|
913
|
+
}
|
|
914
|
+
connect(url = DEFAULT_GATEWAY_URL) {
|
|
188
915
|
if (this.state === "CLOSED" /* Closed */)
|
|
189
916
|
throw new GatewayError("Gateway has been permanently closed.");
|
|
190
|
-
|
|
191
|
-
|
|
917
|
+
const inFlight = this.#reconnect.attemptPromise;
|
|
918
|
+
if (inFlight)
|
|
919
|
+
return inFlight;
|
|
920
|
+
if (this.#transport.live)
|
|
921
|
+
return Promise.resolve();
|
|
922
|
+
this.#reconnect.cancel();
|
|
192
923
|
this.#closed = false;
|
|
193
924
|
this.#setState("CONNECT" /* Connect */);
|
|
194
925
|
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
195
|
-
|
|
196
|
-
this.#initDecompressor();
|
|
197
|
-
this.#connectPromise = new Promise((resolve, reject) => {
|
|
198
|
-
this.#resolveConnect = resolve;
|
|
199
|
-
this.#rejectConnect = reject;
|
|
200
|
-
this.#open(connectURL);
|
|
201
|
-
});
|
|
202
|
-
return this.#connectPromise;
|
|
926
|
+
return this.#reconnect.attempt(() => this.#open(connectURL));
|
|
203
927
|
}
|
|
204
928
|
close() {
|
|
205
929
|
this.#closed = true;
|
|
206
930
|
this.#clearTimers();
|
|
207
931
|
this.#settleConnect(new GatewayError("Gateway connection closed before socket open."));
|
|
208
|
-
|
|
209
|
-
this.#ws = undefined;
|
|
210
|
-
try {
|
|
211
|
-
ws?.close(1000, "Client closed connection");
|
|
212
|
-
} catch (error) {
|
|
213
|
-
this.#emitError(error);
|
|
214
|
-
}
|
|
932
|
+
this.#transport.destroy(1000, "Client closed connection");
|
|
215
933
|
this.#setState("CLOSED" /* Closed */);
|
|
216
934
|
}
|
|
217
935
|
emit(event, data) {
|
|
@@ -236,23 +954,17 @@ class Gateway {
|
|
|
236
954
|
return this.#dispatch(payload, false);
|
|
237
955
|
}
|
|
238
956
|
#dispatch(payload, privileged) {
|
|
239
|
-
if (this.#
|
|
957
|
+
if (!this.#transport.connected)
|
|
240
958
|
return false;
|
|
241
959
|
const now = Date.now();
|
|
242
|
-
|
|
243
|
-
this.#sendTimestamps.shift();
|
|
244
|
-
if (!privileged && this.#sendTimestamps.length >= 115) {
|
|
960
|
+
if (!this.#sendBudget.allows(privileged, now)) {
|
|
245
961
|
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
246
962
|
return false;
|
|
247
963
|
}
|
|
248
|
-
|
|
249
|
-
this.#ws.send(JSON.stringify(payload));
|
|
250
|
-
this.#sendTimestamps.push(now);
|
|
251
|
-
return true;
|
|
252
|
-
} catch (error) {
|
|
253
|
-
this.#emitError(error);
|
|
964
|
+
if (!this.#transport.send(JSON.stringify(payload)))
|
|
254
965
|
return false;
|
|
255
|
-
|
|
966
|
+
this.#sendBudget.record(now);
|
|
967
|
+
return true;
|
|
256
968
|
}
|
|
257
969
|
setPresence(data) {
|
|
258
970
|
this.#options.presence = { ...this.#options.presence, ...data };
|
|
@@ -288,127 +1000,80 @@ class Gateway {
|
|
|
288
1000
|
#open(url) {
|
|
289
1001
|
if (this.#closed)
|
|
290
1002
|
return;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
} catch (error) {
|
|
295
|
-
const failure = this.#normalizeError(error);
|
|
296
|
-
this.#emitError(failure);
|
|
297
|
-
if (this.#options.reconnect) {
|
|
298
|
-
this.#setState("RECONNECT" /* Reconnect */);
|
|
299
|
-
this.#scheduleReconnect();
|
|
300
|
-
} else
|
|
301
|
-
this.#settleConnect(failure);
|
|
1003
|
+
this.#token = this.#session.beginConnection();
|
|
1004
|
+
const result = this.#transport.connect(url);
|
|
1005
|
+
if (result.ok)
|
|
302
1006
|
return;
|
|
1007
|
+
const failure = this.#normalizeError(result.error);
|
|
1008
|
+
this.#emitError(failure);
|
|
1009
|
+
if (this.#options.reconnect) {
|
|
1010
|
+
this.#setState("RECONNECT" /* Reconnect */);
|
|
1011
|
+
this.#scheduleReconnect();
|
|
1012
|
+
} else {
|
|
1013
|
+
this.#settleConnect(failure);
|
|
303
1014
|
}
|
|
304
|
-
this.#ws = ws;
|
|
305
|
-
ws.addEventListener("open", () => {
|
|
306
|
-
this.#lastMessageAt = Date.now();
|
|
307
|
-
this.#zombieReported = false;
|
|
308
|
-
this.#startZombieDetection();
|
|
309
|
-
this.#emit("open", undefined);
|
|
310
|
-
this.#settleConnect();
|
|
311
|
-
});
|
|
312
|
-
ws.addEventListener("message", (event) => {
|
|
313
|
-
this.#lastMessageAt = Date.now();
|
|
314
|
-
this.#zombieReported = false;
|
|
315
|
-
if (this.#options.compress && event.data instanceof ArrayBuffer) {
|
|
316
|
-
this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
|
|
317
|
-
} else {
|
|
318
|
-
this.#message(ws, String(event.data));
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
322
|
-
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
323
|
-
}
|
|
324
|
-
#startZombieDetection() {
|
|
325
|
-
if (this.#zombieTimer)
|
|
326
|
-
clearInterval(this.#zombieTimer);
|
|
327
|
-
const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
328
|
-
this.#zombieTimer = setInterval(() => {
|
|
329
|
-
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
330
|
-
return;
|
|
331
|
-
const silentFor = Date.now() - this.#lastMessageAt;
|
|
332
|
-
const deadline = Math.max(this.#options.zombieTimeout, this.#heartbeatInterval + this.#options.heartbeatAckTimeout);
|
|
333
|
-
if (silentFor < deadline || this.#zombieReported)
|
|
334
|
-
return;
|
|
335
|
-
this.#zombieReported = true;
|
|
336
|
-
const error = new GatewayError(`Gateway connection appears stale after ${silentFor}ms without traffic.`);
|
|
337
|
-
this.#emit("zombie", { silentFor, timeout: deadline });
|
|
338
|
-
this.#emitError(error);
|
|
339
|
-
try {
|
|
340
|
-
this.#ws.close(1001, "Zombie Gateway connection");
|
|
341
|
-
} catch (closeError) {
|
|
342
|
-
this.#emitError(closeError);
|
|
343
|
-
}
|
|
344
|
-
}, interval);
|
|
345
1015
|
}
|
|
346
|
-
#
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}));
|
|
354
|
-
ws.close(1002, "Invalid JSON");
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (!payload || typeof payload.op !== "number") {
|
|
358
|
-
this.#emitError(new GatewayError("Gateway returned an invalid payload"));
|
|
359
|
-
ws.close(1002, "Invalid payload");
|
|
1016
|
+
#onOpen() {
|
|
1017
|
+
this.#heartbeat.watch();
|
|
1018
|
+
this.#emit("open", undefined);
|
|
1019
|
+
this.#settleConnect();
|
|
1020
|
+
}
|
|
1021
|
+
#message(raw, token) {
|
|
1022
|
+
if (!this.#session.owns(token))
|
|
360
1023
|
return;
|
|
361
|
-
}
|
|
362
|
-
if (
|
|
363
|
-
this.#sequence
|
|
364
|
-
|
|
365
|
-
|
|
1024
|
+
const { sequence, action } = classifyFrame(raw);
|
|
1025
|
+
if (sequence !== null)
|
|
1026
|
+
this.#session.recordSequence(sequence, token);
|
|
1027
|
+
this.#apply(action, token);
|
|
1028
|
+
}
|
|
1029
|
+
#apply(action, token) {
|
|
1030
|
+
switch (action.type) {
|
|
1031
|
+
case "dispatch":
|
|
366
1032
|
this.#setState("DISPATCH" /* Dispatch */);
|
|
367
|
-
this.#handleDispatch(
|
|
368
|
-
|
|
369
|
-
case
|
|
370
|
-
this.#handleHello(
|
|
371
|
-
|
|
372
|
-
case
|
|
1033
|
+
this.#handleDispatch(action.event, action.data, token);
|
|
1034
|
+
return;
|
|
1035
|
+
case "hello":
|
|
1036
|
+
this.#handleHello(action.heartbeatInterval);
|
|
1037
|
+
return;
|
|
1038
|
+
case "heartbeat":
|
|
373
1039
|
this.#setState("HEARTBEAT" /* Heartbeat */);
|
|
374
|
-
this.#sendHeartbeat();
|
|
375
|
-
|
|
376
|
-
case
|
|
377
|
-
this.#
|
|
378
|
-
this.ping =
|
|
379
|
-
this.#
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this.#resumeURL = undefined;
|
|
391
|
-
}
|
|
392
|
-
this.#emit("invalidSession", resumable);
|
|
393
|
-
ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
394
|
-
break;
|
|
1040
|
+
this.#heartbeat.sendHeartbeat();
|
|
1041
|
+
return;
|
|
1042
|
+
case "heartbeat-ack":
|
|
1043
|
+
this.#heartbeat.acknowledge();
|
|
1044
|
+
this.ping = this.#heartbeat.latency;
|
|
1045
|
+
this.#emit("heartbeatAck", action.data);
|
|
1046
|
+
return;
|
|
1047
|
+
case "reconnect":
|
|
1048
|
+
this.#transport.close(RESUMABLE_CLOSE_CODE, "Server requested reconnect");
|
|
1049
|
+
return;
|
|
1050
|
+
case "invalid-session": {
|
|
1051
|
+
if (!action.resumable)
|
|
1052
|
+
this.#session.invalidate(token, "invalid-session");
|
|
1053
|
+
this.#emit("invalidSession", action.resumable);
|
|
1054
|
+
this.#transport.close(action.resumable ? GatewayCloseCodes.UnknownError : 1000, action.resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
1055
|
+
return;
|
|
395
1056
|
}
|
|
1057
|
+
case "invalid":
|
|
1058
|
+
this.#emitError(new GatewayError(action.message, undefined, action.cause === undefined ? undefined : { cause: action.cause }));
|
|
1059
|
+
this.#transport.close(1002, action.violation === "invalid-json" ? "Invalid JSON" : action.violation === "invalid-hello" ? "Invalid heartbeat interval" : "Invalid payload");
|
|
1060
|
+
return;
|
|
1061
|
+
case "unknown":
|
|
1062
|
+
return;
|
|
396
1063
|
}
|
|
397
1064
|
}
|
|
398
|
-
#handleDispatch(event, data) {
|
|
1065
|
+
#handleDispatch(event, data, token) {
|
|
399
1066
|
if (event === "READY") {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
1067
|
+
if (!this.#session.activate(data, token)) {
|
|
1068
|
+
if (this.#session.owns(token))
|
|
1069
|
+
this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
|
|
403
1070
|
return;
|
|
404
1071
|
}
|
|
405
|
-
this.#
|
|
406
|
-
this.#resumeURL = ready.resume_gateway_url;
|
|
407
|
-
this.#attempt = 0;
|
|
1072
|
+
this.#reconnect.reset();
|
|
408
1073
|
this.#setState("READY" /* Ready */);
|
|
409
1074
|
this.#emit("ready", data);
|
|
410
1075
|
} else if (event === "RESUMED") {
|
|
411
|
-
this.#
|
|
1076
|
+
this.#reconnect.reset();
|
|
412
1077
|
this.#setState("READY" /* Ready */);
|
|
413
1078
|
this.#emit("resumed", data);
|
|
414
1079
|
}
|
|
@@ -417,183 +1082,60 @@ class Gateway {
|
|
|
417
1082
|
return;
|
|
418
1083
|
this.#emit(event ?? "dispatch", data);
|
|
419
1084
|
}
|
|
420
|
-
#handleHello(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
this.#emitError(new GatewayError("Gateway HELLO payload contains an invalid heartbeat interval"));
|
|
424
|
-
this.#ws?.close(1002, "Invalid heartbeat interval");
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
this.#heartbeatInterval = interval;
|
|
428
|
-
this.#setState(this.#sessionId && this.#sequence !== null && this.#resumeURL ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
429
|
-
this.#startHeartbeat(interval);
|
|
1085
|
+
#handleHello(interval) {
|
|
1086
|
+
this.#setState(this.#session.canResume ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
1087
|
+
this.#heartbeat.start(interval);
|
|
430
1088
|
this.#identifyOrResume();
|
|
431
1089
|
}
|
|
432
1090
|
#identifyOrResume() {
|
|
433
|
-
|
|
1091
|
+
const intent = this.#session.handshake();
|
|
1092
|
+
if (intent.type === "resume") {
|
|
434
1093
|
this.#setState("RESUME" /* Resume */);
|
|
435
|
-
this.
|
|
436
|
-
op: GatewayOpcodes.Resume,
|
|
437
|
-
d: {
|
|
438
|
-
token: this.#options.token,
|
|
439
|
-
session_id: this.#sessionId,
|
|
440
|
-
seq: this.#sequence
|
|
441
|
-
},
|
|
442
|
-
s: null,
|
|
443
|
-
t: null
|
|
444
|
-
});
|
|
1094
|
+
this.#dispatch(resumePayload(this.#options.token, intent), true);
|
|
445
1095
|
return;
|
|
446
1096
|
}
|
|
447
1097
|
this.#setState("IDENTIFY" /* Identify */);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
this.send({
|
|
457
|
-
op: GatewayOpcodes.Identify,
|
|
458
|
-
d: {
|
|
459
|
-
token: this.#options.token,
|
|
460
|
-
intents: this.#options.intents,
|
|
461
|
-
properties: {
|
|
462
|
-
os,
|
|
463
|
-
browser,
|
|
464
|
-
device,
|
|
465
|
-
$os: os,
|
|
466
|
-
$browser: browser,
|
|
467
|
-
$device: device,
|
|
468
|
-
...props
|
|
469
|
-
},
|
|
470
|
-
presence: {
|
|
471
|
-
since: this.#options.presence?.since ?? null,
|
|
472
|
-
activities: this.#options.presence?.activities ?? [],
|
|
473
|
-
status: this.#options.presence?.status ?? "online",
|
|
474
|
-
afk: Boolean(this.#options.presence?.afk)
|
|
475
|
-
},
|
|
476
|
-
shard: [this.#options.shardId, this.#options.shardCount]
|
|
477
|
-
},
|
|
478
|
-
s: null,
|
|
479
|
-
t: null
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
#startHeartbeat(interval) {
|
|
483
|
-
this.#clearHeartbeatTimers();
|
|
484
|
-
this.#heartbeatACK = true;
|
|
485
|
-
this.#initialHeartbeat = setTimeout(() => this.#sendHeartbeat(), Math.random() * interval);
|
|
486
|
-
this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), interval);
|
|
487
|
-
}
|
|
488
|
-
#sendHeartbeat() {
|
|
489
|
-
this.#heartbeatACK = false;
|
|
490
|
-
this.#heartbeatSentAt = Date.now();
|
|
491
|
-
if (!this.#dispatch({
|
|
492
|
-
op: GatewayOpcodes.Heartbeat,
|
|
493
|
-
d: this.#sequence,
|
|
494
|
-
s: null,
|
|
495
|
-
t: null
|
|
496
|
-
}, true)) {
|
|
497
|
-
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
this.#clearHeartbeatAckTimer();
|
|
501
|
-
this.#heartbeatAckTimer = setTimeout(() => {
|
|
502
|
-
if (!this.#heartbeatACK) {
|
|
503
|
-
const elapsed = Date.now() - this.#heartbeatSentAt;
|
|
504
|
-
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${elapsed}ms.`));
|
|
505
|
-
try {
|
|
506
|
-
this.#ws?.close(1001, "Heartbeat timeout");
|
|
507
|
-
} catch (error) {
|
|
508
|
-
this.#emitError(error);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}, this.#options.heartbeatAckTimeout);
|
|
1098
|
+
this.#dispatch(identifyPayload({
|
|
1099
|
+
token: this.#options.token,
|
|
1100
|
+
intents: this.#options.intents,
|
|
1101
|
+
shardId: this.#options.shardId,
|
|
1102
|
+
shardCount: this.#options.shardCount,
|
|
1103
|
+
properties: this.#options.properties,
|
|
1104
|
+
presence: this.#options.presence
|
|
1105
|
+
}), true);
|
|
512
1106
|
}
|
|
513
|
-
#close(
|
|
514
|
-
if (this.#ws !== ws)
|
|
515
|
-
return;
|
|
516
|
-
this.#ws = undefined;
|
|
1107
|
+
#close(code, token) {
|
|
517
1108
|
this.#clearTimers();
|
|
518
|
-
const action = this.#
|
|
1109
|
+
const action = this.#reconnect.classifyClose(code, this.#session.canResume);
|
|
519
1110
|
this.#emit("close", { code, action });
|
|
520
|
-
if (this.#closed || !this.#
|
|
1111
|
+
if (this.#closed || !this.#reconnect.enabled || action === "stop") {
|
|
1112
|
+
if (action === "stop")
|
|
1113
|
+
this.#closed = true;
|
|
521
1114
|
this.#setState(this.#closed ? "CLOSED" /* Closed */ : "CONNECT" /* Connect */);
|
|
522
|
-
if (this.#
|
|
523
|
-
this.#
|
|
1115
|
+
if (this.#reconnect.connecting)
|
|
1116
|
+
this.#reconnect.settle(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
|
|
524
1117
|
return;
|
|
525
1118
|
}
|
|
526
|
-
if (action === "identify")
|
|
527
|
-
this.#
|
|
528
|
-
this.#sessionId = undefined;
|
|
529
|
-
this.#resumeURL = undefined;
|
|
530
|
-
}
|
|
1119
|
+
if (action === "identify")
|
|
1120
|
+
this.#session.invalidate(token, "session-timeout");
|
|
531
1121
|
this.#setState("RECONNECT" /* Reconnect */);
|
|
532
1122
|
this.#scheduleReconnect();
|
|
533
1123
|
}
|
|
534
|
-
#closeAction(code) {
|
|
535
|
-
const fatal = [
|
|
536
|
-
GatewayCloseCodes.AuthenticationFailed,
|
|
537
|
-
GatewayCloseCodes.InvalidShard,
|
|
538
|
-
GatewayCloseCodes.ShardingRequired,
|
|
539
|
-
GatewayCloseCodes.InvalidAPIVersion,
|
|
540
|
-
GatewayCloseCodes.InvalidIntents,
|
|
541
|
-
GatewayCloseCodes.DisallowedIntents
|
|
542
|
-
];
|
|
543
|
-
if (fatal.includes(code))
|
|
544
|
-
return "stop";
|
|
545
|
-
if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
|
|
546
|
-
return "identify";
|
|
547
|
-
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
548
|
-
}
|
|
549
1124
|
#scheduleReconnect() {
|
|
550
|
-
if (this.#closed
|
|
1125
|
+
if (this.#closed)
|
|
551
1126
|
return;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
this.#setState("CONNECT" /* Connect */);
|
|
1127
|
+
const result = this.#reconnect.schedule(() => this.#open(this.#session.connectURL(DEFAULT_GATEWAY_URL)));
|
|
1128
|
+
if (result.scheduled || result.reason !== "exhausted")
|
|
555
1129
|
return;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const jitter = Math.random() * Math.max(1, delay * 0.25);
|
|
559
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
560
|
-
this.#reconnectTimer = undefined;
|
|
561
|
-
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
562
|
-
}, delay + jitter);
|
|
1130
|
+
this.#reconnect.settle(new GatewayError("Gateway reconnect attempts exhausted."));
|
|
1131
|
+
this.#setState("CONNECT" /* Connect */);
|
|
563
1132
|
}
|
|
564
1133
|
#settleConnect(error) {
|
|
565
|
-
|
|
566
|
-
const reject = this.#rejectConnect;
|
|
567
|
-
this.#resolveConnect = undefined;
|
|
568
|
-
this.#rejectConnect = undefined;
|
|
569
|
-
this.#connectPromise = undefined;
|
|
570
|
-
if (error)
|
|
571
|
-
reject?.(error);
|
|
572
|
-
else
|
|
573
|
-
resolve?.();
|
|
574
|
-
}
|
|
575
|
-
#clearHeartbeatAckTimer() {
|
|
576
|
-
if (this.#heartbeatAckTimer)
|
|
577
|
-
clearTimeout(this.#heartbeatAckTimer);
|
|
578
|
-
this.#heartbeatAckTimer = undefined;
|
|
579
|
-
}
|
|
580
|
-
#clearHeartbeatTimers() {
|
|
581
|
-
if (this.#heartbeatTimer)
|
|
582
|
-
clearInterval(this.#heartbeatTimer);
|
|
583
|
-
if (this.#initialHeartbeat)
|
|
584
|
-
clearTimeout(this.#initialHeartbeat);
|
|
585
|
-
this.#clearHeartbeatAckTimer();
|
|
586
|
-
this.#heartbeatTimer = undefined;
|
|
587
|
-
this.#initialHeartbeat = undefined;
|
|
1134
|
+
this.#reconnect.settle(error);
|
|
588
1135
|
}
|
|
589
1136
|
#clearTimers() {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (this.#zombieTimer)
|
|
593
|
-
clearInterval(this.#zombieTimer);
|
|
594
|
-
this.#reconnectTimer = undefined;
|
|
595
|
-
this.#zombieTimer = undefined;
|
|
596
|
-
this.#clearHeartbeatTimers();
|
|
1137
|
+
this.#reconnect.cancel();
|
|
1138
|
+
this.#heartbeat.stop();
|
|
597
1139
|
}
|
|
598
1140
|
#normalizeError(error) {
|
|
599
1141
|
return error instanceof GatewayError ? error : new GatewayError("Gateway connection failed.", undefined, {
|
|
@@ -625,45 +1167,29 @@ class Gateway {
|
|
|
625
1167
|
this.state = next;
|
|
626
1168
|
this.#emit("stateChange", { previous, next });
|
|
627
1169
|
}
|
|
628
|
-
#initDecompressor() {
|
|
629
|
-
const ds = new DecompressionStream("deflate-raw");
|
|
630
|
-
this.#decompressor = {
|
|
631
|
-
writer: ds.writable.getWriter(),
|
|
632
|
-
reader: ds.readable.getReader()
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
async#decompress(chunk) {
|
|
636
|
-
if (!this.#decompressor)
|
|
637
|
-
this.#initDecompressor();
|
|
638
|
-
const { writer, reader } = this.#decompressor;
|
|
639
|
-
await writer.write(chunk);
|
|
640
|
-
const parts = [];
|
|
641
|
-
while (true) {
|
|
642
|
-
const { done, value } = await Promise.race([
|
|
643
|
-
reader.read(),
|
|
644
|
-
new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
|
|
645
|
-
]);
|
|
646
|
-
if (done || !value)
|
|
647
|
-
break;
|
|
648
|
-
parts.push(value);
|
|
649
|
-
}
|
|
650
|
-
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
651
|
-
const merged = new Uint8Array(total);
|
|
652
|
-
let offset = 0;
|
|
653
|
-
for (const p of parts) {
|
|
654
|
-
merged.set(p, offset);
|
|
655
|
-
offset += p.length;
|
|
656
|
-
}
|
|
657
|
-
return new TextDecoder().decode(merged);
|
|
658
|
-
}
|
|
659
1170
|
}
|
|
660
1171
|
export {
|
|
1172
|
+
FATAL_CLOSE_CODES,
|
|
661
1173
|
Gateway,
|
|
662
1174
|
GatewayCloseCodes,
|
|
663
1175
|
GatewayError,
|
|
1176
|
+
GatewayHeartbeat,
|
|
664
1177
|
GatewayOpcodes,
|
|
1178
|
+
GatewayReconnect,
|
|
1179
|
+
GatewaySession,
|
|
665
1180
|
GatewayState,
|
|
666
|
-
|
|
1181
|
+
IDENTIFY_CLOSE_CODES,
|
|
1182
|
+
PlainTextDecoder,
|
|
1183
|
+
GatewayState as Status,
|
|
1184
|
+
WebSocketTransport,
|
|
1185
|
+
ZlibStreamDecoder,
|
|
1186
|
+
classifyCloseCode,
|
|
1187
|
+
classifyFrame,
|
|
1188
|
+
classifyPayload,
|
|
1189
|
+
createDecoder,
|
|
1190
|
+
heartbeatPayload,
|
|
1191
|
+
identifyPayload,
|
|
1192
|
+
resumePayload
|
|
667
1193
|
};
|
|
668
1194
|
|
|
669
|
-
//# debugId=
|
|
1195
|
+
//# debugId=DF17709D7B996C6F64756E2164756E21
|