@buape/carbon 0.13.0 → 0.15.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 +1 -1
- package/dist/package.json +12 -8
- package/dist/src/abstracts/BaseCommand.d.ts +19 -3
- package/dist/src/abstracts/BaseCommand.d.ts.map +1 -1
- package/dist/src/abstracts/BaseCommand.js +55 -3
- package/dist/src/abstracts/BaseCommand.js.map +1 -1
- package/dist/src/abstracts/BaseComponent.d.ts +2 -2
- package/dist/src/abstracts/BaseComponent.d.ts.map +1 -1
- package/dist/src/abstracts/BaseInteraction.d.ts +52 -0
- package/dist/src/abstracts/BaseInteraction.d.ts.map +1 -1
- package/dist/src/abstracts/BaseInteraction.js +43 -5
- package/dist/src/abstracts/BaseInteraction.js.map +1 -1
- package/dist/src/abstracts/BaseListener.d.ts +7 -4
- package/dist/src/abstracts/BaseListener.d.ts.map +1 -1
- package/dist/src/abstracts/BaseListener.js.map +1 -1
- package/dist/src/abstracts/BaseModalComponent.d.ts +3 -10
- package/dist/src/abstracts/BaseModalComponent.d.ts.map +1 -1
- package/dist/src/abstracts/BaseModalComponent.js.map +1 -1
- package/dist/src/abstracts/Plugin.d.ts +6 -0
- package/dist/src/abstracts/Plugin.d.ts.map +1 -1
- package/dist/src/abstracts/Plugin.js.map +1 -1
- package/dist/src/adapters/bun/index.d.ts +2 -2
- package/dist/src/adapters/bun/index.d.ts.map +1 -1
- package/dist/src/adapters/bun/index.js +6 -0
- package/dist/src/adapters/bun/index.js.map +1 -1
- package/dist/src/adapters/fetch/index.d.ts.map +1 -1
- package/dist/src/adapters/fetch/index.js +6 -0
- package/dist/src/adapters/fetch/index.js.map +1 -1
- package/dist/src/classes/Client.d.ts +111 -9
- package/dist/src/classes/Client.d.ts.map +1 -1
- package/dist/src/classes/Client.js +237 -23
- package/dist/src/classes/Client.js.map +1 -1
- package/dist/src/classes/EntryPointCommand.d.ts +11 -0
- package/dist/src/classes/EntryPointCommand.d.ts.map +1 -0
- package/dist/src/classes/EntryPointCommand.js +13 -0
- package/dist/src/classes/EntryPointCommand.js.map +1 -0
- package/dist/src/classes/Listener.d.ts +85 -83
- package/dist/src/classes/Listener.d.ts.map +1 -1
- package/dist/src/classes/Listener.js +14 -0
- package/dist/src/classes/Listener.js.map +1 -1
- package/dist/src/classes/Modal.d.ts +2 -3
- package/dist/src/classes/Modal.d.ts.map +1 -1
- package/dist/src/classes/Modal.js.map +1 -1
- package/dist/src/classes/RequestClient.d.ts +99 -8
- package/dist/src/classes/RequestClient.d.ts.map +1 -1
- package/dist/src/classes/RequestClient.js +406 -137
- package/dist/src/classes/RequestClient.js.map +1 -1
- package/dist/src/classes/components/Checkbox.d.ts +12 -0
- package/dist/src/classes/components/Checkbox.d.ts.map +1 -0
- package/dist/src/classes/components/Checkbox.js +20 -0
- package/dist/src/classes/components/Checkbox.js.map +1 -0
- package/dist/src/classes/components/CheckboxGroup.d.ts +24 -0
- package/dist/src/classes/components/CheckboxGroup.d.ts.map +1 -0
- package/dist/src/classes/components/CheckboxGroup.js +37 -0
- package/dist/src/classes/components/CheckboxGroup.js.map +1 -0
- package/dist/src/classes/components/Container.d.ts +6 -2
- package/dist/src/classes/components/Container.d.ts.map +1 -1
- package/dist/src/classes/components/Container.js +11 -0
- package/dist/src/classes/components/Container.js.map +1 -1
- package/dist/src/classes/components/File.d.ts +2 -2
- package/dist/src/classes/components/File.d.ts.map +1 -1
- package/dist/src/classes/components/File.js +4 -1
- package/dist/src/classes/components/File.js.map +1 -1
- package/dist/src/classes/components/FileUpload.d.ts +3 -2
- package/dist/src/classes/components/FileUpload.d.ts.map +1 -1
- package/dist/src/classes/components/FileUpload.js +2 -2
- package/dist/src/classes/components/FileUpload.js.map +1 -1
- package/dist/src/classes/components/Label.d.ts +9 -5
- package/dist/src/classes/components/Label.d.ts.map +1 -1
- package/dist/src/classes/components/Label.js +1 -2
- package/dist/src/classes/components/Label.js.map +1 -1
- package/dist/src/classes/components/MediaGallery.d.ts +8 -13
- package/dist/src/classes/components/MediaGallery.d.ts.map +1 -1
- package/dist/src/classes/components/MediaGallery.js +5 -0
- package/dist/src/classes/components/MediaGallery.js.map +1 -1
- package/dist/src/classes/components/RadioGroup.d.ts +24 -0
- package/dist/src/classes/components/RadioGroup.d.ts.map +1 -0
- package/dist/src/classes/components/RadioGroup.js +33 -0
- package/dist/src/classes/components/RadioGroup.js.map +1 -0
- package/dist/src/classes/components/Row.d.ts.map +1 -1
- package/dist/src/classes/components/Row.js +2 -3
- package/dist/src/classes/components/Row.js.map +1 -1
- package/dist/src/classes/components/Section.d.ts +4 -3
- package/dist/src/classes/components/Section.d.ts.map +1 -1
- package/dist/src/classes/components/Section.js +22 -0
- package/dist/src/classes/components/Section.js.map +1 -1
- package/dist/src/classes/components/TextDisplay.d.ts +2 -2
- package/dist/src/classes/components/TextDisplay.d.ts.map +1 -1
- package/dist/src/classes/components/TextDisplay.js +3 -0
- package/dist/src/classes/components/TextDisplay.js.map +1 -1
- package/dist/src/classes/components/Thumbnail.d.ts +3 -2
- package/dist/src/classes/components/Thumbnail.d.ts.map +1 -1
- package/dist/src/classes/components/Thumbnail.js +11 -0
- package/dist/src/classes/components/Thumbnail.js.map +1 -1
- package/dist/src/errors/RatelimitError.d.ts +4 -1
- package/dist/src/errors/RatelimitError.d.ts.map +1 -1
- package/dist/src/errors/RatelimitError.js +7 -1
- package/dist/src/errors/RatelimitError.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/internals/AutocompleteInteraction.d.ts +1 -1
- package/dist/src/internals/AutocompleteInteraction.d.ts.map +1 -1
- package/dist/src/internals/BoundedExecutor.d.ts +12 -0
- package/dist/src/internals/BoundedExecutor.d.ts.map +1 -0
- package/dist/src/internals/BoundedExecutor.js +60 -0
- package/dist/src/internals/BoundedExecutor.js.map +1 -0
- package/dist/src/internals/CommandHandler.d.ts.map +1 -1
- package/dist/src/internals/CommandHandler.js +53 -6
- package/dist/src/internals/CommandHandler.js.map +1 -1
- package/dist/src/internals/CommandInteraction.d.ts +4 -0
- package/dist/src/internals/CommandInteraction.d.ts.map +1 -1
- package/dist/src/internals/CommandInteraction.js +30 -0
- package/dist/src/internals/CommandInteraction.js.map +1 -1
- package/dist/src/internals/ComponentHandler.d.ts +5 -10
- package/dist/src/internals/ComponentHandler.d.ts.map +1 -1
- package/dist/src/internals/ComponentHandler.js +53 -38
- package/dist/src/internals/ComponentHandler.js.map +1 -1
- package/dist/src/internals/EmojiHandler.d.ts.map +1 -1
- package/dist/src/internals/EmojiHandler.js +0 -1
- package/dist/src/internals/EmojiHandler.js.map +1 -1
- package/dist/src/internals/EventHandler.d.ts +28 -2
- package/dist/src/internals/EventHandler.d.ts.map +1 -1
- package/dist/src/internals/EventHandler.js +14 -11
- package/dist/src/internals/EventHandler.js.map +1 -1
- package/dist/src/internals/EventQueue.d.ts +90 -0
- package/dist/src/internals/EventQueue.d.ts.map +1 -0
- package/dist/src/internals/EventQueue.js +363 -0
- package/dist/src/internals/EventQueue.js.map +1 -0
- package/dist/src/internals/FieldsHandler.d.ts.map +1 -1
- package/dist/src/internals/FieldsHandler.js +10 -1
- package/dist/src/internals/FieldsHandler.js.map +1 -1
- package/dist/src/internals/OptionsHandler.d.ts +9 -1
- package/dist/src/internals/OptionsHandler.d.ts.map +1 -1
- package/dist/src/internals/OptionsHandler.js +20 -1
- package/dist/src/internals/OptionsHandler.js.map +1 -1
- package/dist/src/internals/RequestBody.d.ts +6 -0
- package/dist/src/internals/RequestBody.d.ts.map +1 -0
- package/dist/src/internals/RequestBody.js +74 -0
- package/dist/src/internals/RequestBody.js.map +1 -0
- package/dist/src/internals/RequestScheduler.d.ts +131 -0
- package/dist/src/internals/RequestScheduler.d.ts.map +1 -0
- package/dist/src/internals/RequestScheduler.js +245 -0
- package/dist/src/internals/RequestScheduler.js.map +1 -0
- package/dist/src/internals/TemporaryListenerManager.d.ts +20 -0
- package/dist/src/internals/TemporaryListenerManager.d.ts.map +1 -0
- package/dist/src/internals/TemporaryListenerManager.js +59 -0
- package/dist/src/internals/TemporaryListenerManager.js.map +1 -0
- package/dist/src/permissions.d.ts +1 -0
- package/dist/src/permissions.d.ts.map +1 -1
- package/dist/src/plugins/client-manager/ClientManager.d.ts +30 -2
- package/dist/src/plugins/client-manager/ClientManager.d.ts.map +1 -1
- package/dist/src/plugins/client-manager/ClientManager.js +93 -16
- package/dist/src/plugins/client-manager/ClientManager.js.map +1 -1
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayDurableObject.d.ts +43 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayDurableObject.d.ts.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayDurableObject.js +210 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayDurableObject.js.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayPlugin.d.ts +16 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayPlugin.d.ts.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayPlugin.js +129 -0
- package/dist/src/plugins/cloudflare-gateway/CloudflareGatewayPlugin.js.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/index.d.ts +4 -0
- package/dist/src/plugins/cloudflare-gateway/index.d.ts.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/index.js +4 -0
- package/dist/src/plugins/cloudflare-gateway/index.js.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/types.d.ts +63 -0
- package/dist/src/plugins/cloudflare-gateway/types.d.ts.map +1 -0
- package/dist/src/plugins/cloudflare-gateway/types.js +2 -0
- package/dist/src/plugins/cloudflare-gateway/types.js.map +1 -0
- package/dist/src/plugins/gateway/BabyCache.d.ts +13 -4
- package/dist/src/plugins/gateway/BabyCache.d.ts.map +1 -1
- package/dist/src/plugins/gateway/BabyCache.js +47 -0
- package/dist/src/plugins/gateway/BabyCache.js.map +1 -1
- package/dist/src/plugins/gateway/GatewayPlugin.d.ts +111 -19
- package/dist/src/plugins/gateway/GatewayPlugin.d.ts.map +1 -1
- package/dist/src/plugins/gateway/GatewayPlugin.js +605 -234
- package/dist/src/plugins/gateway/GatewayPlugin.js.map +1 -1
- package/dist/src/plugins/gateway/types.d.ts +33 -1
- package/dist/src/plugins/gateway/types.d.ts.map +1 -1
- package/dist/src/plugins/gateway/types.js +21 -0
- package/dist/src/plugins/gateway/types.js.map +1 -1
- package/dist/src/plugins/gateway/utils/heartbeat.d.ts +1 -0
- package/dist/src/plugins/gateway/utils/heartbeat.d.ts.map +1 -1
- package/dist/src/plugins/gateway/utils/heartbeat.js +23 -12
- package/dist/src/plugins/gateway/utils/heartbeat.js.map +1 -1
- package/dist/src/plugins/gateway/utils/payload.d.ts.map +1 -1
- package/dist/src/plugins/gateway/utils/payload.js +0 -4
- package/dist/src/plugins/gateway/utils/payload.js.map +1 -1
- package/dist/src/plugins/gateway-forwarder/GatewayForwarderPlugin.d.ts +56 -3
- package/dist/src/plugins/gateway-forwarder/GatewayForwarderPlugin.d.ts.map +1 -1
- package/dist/src/plugins/gateway-forwarder/GatewayForwarderPlugin.js +224 -17
- package/dist/src/plugins/gateway-forwarder/GatewayForwarderPlugin.js.map +1 -1
- package/dist/src/plugins/paginator/index.d.ts +1 -1
- package/dist/src/plugins/paginator/index.d.ts.map +1 -1
- package/dist/src/plugins/paginator/index.js +1 -1
- package/dist/src/plugins/paginator/index.js.map +1 -1
- package/dist/src/plugins/voice/GuildDeleteListener.d.ts +7 -0
- package/dist/src/plugins/voice/GuildDeleteListener.d.ts.map +1 -0
- package/dist/src/plugins/voice/GuildDeleteListener.js +11 -0
- package/dist/src/plugins/voice/GuildDeleteListener.js.map +1 -0
- package/dist/src/plugins/voice/VoicePlugin.d.ts +15 -0
- package/dist/src/plugins/voice/VoicePlugin.d.ts.map +1 -0
- package/dist/src/plugins/voice/VoicePlugin.js +58 -0
- package/dist/src/plugins/voice/VoicePlugin.js.map +1 -0
- package/dist/src/plugins/voice/VoiceServerUpdateListener.d.ts +10 -0
- package/dist/src/plugins/voice/VoiceServerUpdateListener.d.ts.map +1 -0
- package/dist/src/plugins/voice/VoiceServerUpdateListener.js +16 -0
- package/dist/src/plugins/voice/VoiceServerUpdateListener.js.map +1 -0
- package/dist/src/plugins/voice/VoiceStateUpdateListener.d.ts +10 -0
- package/dist/src/plugins/voice/VoiceStateUpdateListener.d.ts.map +1 -0
- package/dist/src/plugins/voice/VoiceStateUpdateListener.js +21 -0
- package/dist/src/plugins/voice/VoiceStateUpdateListener.js.map +1 -0
- package/dist/src/plugins/voice/index.d.ts +2 -0
- package/dist/src/plugins/voice/index.d.ts.map +1 -0
- package/dist/src/plugins/voice/index.js +2 -0
- package/dist/src/plugins/voice/index.js.map +1 -0
- package/dist/src/structures/DmChannel.d.ts +4 -0
- package/dist/src/structures/DmChannel.d.ts.map +1 -1
- package/dist/src/structures/DmChannel.js +6 -0
- package/dist/src/structures/DmChannel.js.map +1 -1
- package/dist/src/structures/GroupDmChannel.d.ts +4 -0
- package/dist/src/structures/GroupDmChannel.d.ts.map +1 -1
- package/dist/src/structures/GroupDmChannel.js +6 -0
- package/dist/src/structures/GroupDmChannel.js.map +1 -1
- package/dist/src/structures/Guild.js +1 -1
- package/dist/src/structures/Guild.js.map +1 -1
- package/dist/src/structures/GuildMember.d.ts +10 -5
- package/dist/src/structures/GuildMember.d.ts.map +1 -1
- package/dist/src/structures/GuildMember.js.map +1 -1
- package/dist/src/structures/Poll.d.ts +1 -1
- package/dist/src/structures/Poll.d.ts.map +1 -1
- package/dist/src/structures/User.d.ts +1 -1
- package/dist/src/structures/User.d.ts.map +1 -1
- package/dist/src/structures/User.js +3 -3
- package/dist/src/structures/User.js.map +1 -1
- package/dist/src/structures/Webhook.d.ts +4 -2
- package/dist/src/structures/Webhook.d.ts.map +1 -1
- package/dist/src/structures/Webhook.js +42 -29
- package/dist/src/structures/Webhook.js.map +1 -1
- package/dist/src/types/commandMiddleware.d.ts +64 -0
- package/dist/src/types/commandMiddleware.d.ts.map +1 -0
- package/dist/src/types/commandMiddleware.js +2 -0
- package/dist/src/types/commandMiddleware.js.map +1 -0
- package/dist/src/types/index.d.ts +58 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -0
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/listeners.d.ts +15 -4
- package/dist/src/types/listeners.d.ts.map +1 -1
- package/dist/src/types/listeners.js.map +1 -1
- package/dist/src/utils/LRUCache.d.ts +46 -0
- package/dist/src/utils/LRUCache.d.ts.map +1 -0
- package/dist/src/utils/LRUCache.js +78 -0
- package/dist/src/utils/LRUCache.js.map +1 -0
- package/dist/src/utils/customIdParser.d.ts.map +1 -1
- package/dist/src/utils/customIdParser.js +6 -2
- package/dist/src/utils/customIdParser.js.map +1 -1
- package/dist/src/utils/payload.d.ts.map +1 -1
- package/dist/src/utils/payload.js +18 -1
- package/dist/src/utils/payload.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -8
- package/dist/src/classes/ApplicationManager.d.ts +0 -88
- package/dist/src/classes/ApplicationManager.d.ts.map +0 -1
- package/dist/src/classes/ApplicationManager.js +0 -179
- package/dist/src/classes/ApplicationManager.js.map +0 -1
- package/dist/src/classes/ClientWithCaching.d.ts +0 -51
- package/dist/src/classes/ClientWithCaching.d.ts.map +0 -1
- package/dist/src/classes/ClientWithCaching.js +0 -108
- package/dist/src/classes/ClientWithCaching.js.map +0 -1
- package/dist/src/classes/components/ModalChannelSelectMenu.d.ts +0 -62
- package/dist/src/classes/components/ModalChannelSelectMenu.d.ts.map +0 -1
- package/dist/src/classes/components/ModalChannelSelectMenu.js +0 -73
- package/dist/src/classes/components/ModalChannelSelectMenu.js.map +0 -1
- package/dist/src/classes/components/ModalMentionableSelectMenu.d.ts +0 -58
- package/dist/src/classes/components/ModalMentionableSelectMenu.d.ts.map +0 -1
- package/dist/src/classes/components/ModalMentionableSelectMenu.js +0 -68
- package/dist/src/classes/components/ModalMentionableSelectMenu.js.map +0 -1
- package/dist/src/classes/components/ModalRoleSelectMenu.d.ts +0 -58
- package/dist/src/classes/components/ModalRoleSelectMenu.d.ts.map +0 -1
- package/dist/src/classes/components/ModalRoleSelectMenu.js +0 -68
- package/dist/src/classes/components/ModalRoleSelectMenu.js.map +0 -1
- package/dist/src/classes/components/ModalStringSelectMenu.d.ts +0 -58
- package/dist/src/classes/components/ModalStringSelectMenu.d.ts.map +0 -1
- package/dist/src/classes/components/ModalStringSelectMenu.js +0 -64
- package/dist/src/classes/components/ModalStringSelectMenu.js.map +0 -1
- package/dist/src/classes/components/ModalUserSelectMenu.d.ts +0 -58
- package/dist/src/classes/components/ModalUserSelectMenu.d.ts.map +0 -1
- package/dist/src/classes/components/ModalUserSelectMenu.js +0 -68
- package/dist/src/classes/components/ModalUserSelectMenu.js.map +0 -1
- package/dist/src/internals/Cache.d.ts +0 -76
- package/dist/src/internals/Cache.d.ts.map +0 -1
- package/dist/src/internals/Cache.js +0 -122
- package/dist/src/internals/Cache.js.map +0 -1
- package/dist/src/plugins/multi-app/ApplicationManager.d.ts +0 -120
- package/dist/src/plugins/multi-app/ApplicationManager.d.ts.map +0 -1
- package/dist/src/plugins/multi-app/ApplicationManager.js +0 -207
- package/dist/src/plugins/multi-app/ApplicationManager.js.map +0 -1
- package/dist/src/plugins/multi-app/index.d.ts +0 -2
- package/dist/src/plugins/multi-app/index.d.ts.map +0 -1
- package/dist/src/plugins/multi-app/index.js +0 -2
- package/dist/src/plugins/multi-app/index.js.map +0 -1
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
3
|
import { Plugin } from "../../abstracts/Plugin.js";
|
|
4
|
-
import { ListenerEvent } from "../../types/index.js";
|
|
5
4
|
import { BabyCache } from "./BabyCache.js";
|
|
6
5
|
import { InteractionEventListener } from "./InteractionEventListener.js";
|
|
7
|
-
import {
|
|
6
|
+
import { fatalGatewayCloseCodes, GatewayIntents, GatewayOpcodes, listenerEvents, nonResumableGatewayCloseCodes, reconnectDefaults } from "./types.js";
|
|
8
7
|
import { startHeartbeat, stopHeartbeat } from "./utils/heartbeat.js";
|
|
9
8
|
import { ConnectionMonitor } from "./utils/monitor.js";
|
|
10
9
|
import { createIdentifyPayload, createRequestGuildMembersPayload, createResumePayload, createUpdatePresencePayload, createUpdateVoiceStatePayload, validatePayload } from "./utils/payload.js";
|
|
11
10
|
import { GatewayRateLimit } from "./utils/rateLimit.js";
|
|
11
|
+
const textDecoder = new TextDecoder();
|
|
12
|
+
const socketOpenState = 1;
|
|
13
|
+
const nodeRequire = typeof process !== "undefined" && process.versions?.node
|
|
14
|
+
? createRequire(import.meta.url)
|
|
15
|
+
: null;
|
|
12
16
|
export class GatewayPlugin extends Plugin {
|
|
13
17
|
id = "gateway";
|
|
14
18
|
client;
|
|
@@ -18,6 +22,7 @@ export class GatewayPlugin extends Plugin {
|
|
|
18
22
|
monitor;
|
|
19
23
|
rateLimit;
|
|
20
24
|
heartbeatInterval;
|
|
25
|
+
firstHeartbeatTimeout;
|
|
21
26
|
sequence = null;
|
|
22
27
|
lastHeartbeatAck = true;
|
|
23
28
|
emitter;
|
|
@@ -28,15 +33,22 @@ export class GatewayPlugin extends Plugin {
|
|
|
28
33
|
isConnected = false;
|
|
29
34
|
pings = [];
|
|
30
35
|
babyCache;
|
|
36
|
+
reconnectTimeout;
|
|
37
|
+
isConnecting = false;
|
|
38
|
+
socketGeneration = 0;
|
|
39
|
+
shouldReconnect = false;
|
|
40
|
+
nextConnectionShouldResume = false;
|
|
41
|
+
silentSocketClosures = new WeakSet();
|
|
42
|
+
consecutiveResumeFailures = 0;
|
|
31
43
|
constructor(options, gatewayInfo) {
|
|
32
44
|
super();
|
|
33
45
|
this.options = {
|
|
34
46
|
reconnect: {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
maxDelay: 30000
|
|
47
|
+
...reconnectDefaults,
|
|
48
|
+
...options.reconnect
|
|
38
49
|
},
|
|
39
|
-
...options
|
|
50
|
+
...options,
|
|
51
|
+
intents: options.intents ?? 0
|
|
40
52
|
};
|
|
41
53
|
this.state = {
|
|
42
54
|
sequence: null,
|
|
@@ -56,305 +68,315 @@ export class GatewayPlugin extends Plugin {
|
|
|
56
68
|
? this.pings.reduce((a, b) => a + b, 0) / this.pings.length
|
|
57
69
|
: null;
|
|
58
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Bootstraps gateway metadata and opens the initial websocket connection.
|
|
73
|
+
*/
|
|
59
74
|
async registerClient(client) {
|
|
60
75
|
this.client = client;
|
|
61
76
|
if (!this.gatewayInfo) {
|
|
77
|
+
let response;
|
|
62
78
|
try {
|
|
63
|
-
|
|
79
|
+
response = await fetch("https://discord.com/api/v10/gateway/bot", {
|
|
64
80
|
headers: {
|
|
65
81
|
Authorization: `Bot ${client.options.token}`
|
|
66
82
|
}
|
|
67
83
|
});
|
|
68
|
-
this.gatewayInfo = (await response.json());
|
|
69
84
|
}
|
|
70
85
|
catch (error) {
|
|
71
|
-
throw new Error(`Failed to get gateway information from Discord: ${error instanceof Error ? error.message : String(error)}
|
|
86
|
+
throw new Error(`Failed to get gateway information from Discord: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
72
87
|
}
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`Failed to get gateway information from Discord: ${response.status} ${response.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
this.gatewayInfo = (await response.json());
|
|
73
92
|
}
|
|
74
|
-
// Set shard information on the client
|
|
75
93
|
if (this.options.shard) {
|
|
76
94
|
client.shardId = this.options.shard[0];
|
|
77
95
|
client.totalShards = this.options.shard[1];
|
|
78
96
|
}
|
|
79
97
|
if (this.options.autoInteractions) {
|
|
80
|
-
this.client
|
|
98
|
+
this.client.registerListener(new InteractionEventListener());
|
|
81
99
|
}
|
|
82
|
-
this.
|
|
100
|
+
this.shouldReconnect = true;
|
|
101
|
+
this.connect(false);
|
|
83
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Opens a new websocket connection and prepares either IDENTIFY or RESUME on HELLO.
|
|
105
|
+
*/
|
|
84
106
|
connect(resume = false) {
|
|
85
|
-
this.
|
|
86
|
-
|
|
107
|
+
if (this.isConnecting) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.shouldReconnect = true;
|
|
111
|
+
this.clearReconnectTimeout();
|
|
112
|
+
stopHeartbeat(this);
|
|
113
|
+
this.lastHeartbeatAck = true;
|
|
114
|
+
const oldSocket = this.ws;
|
|
115
|
+
if (oldSocket) {
|
|
116
|
+
this.silentSocketClosures.add(oldSocket);
|
|
117
|
+
this.closeSocketImmediately(oldSocket);
|
|
118
|
+
}
|
|
119
|
+
const baseUrl = resume && this.state.resumeGatewayUrl
|
|
87
120
|
? this.state.resumeGatewayUrl
|
|
88
121
|
: (this.gatewayInfo?.url ??
|
|
89
122
|
this.options.url ??
|
|
90
|
-
"wss://gateway.discord.gg
|
|
123
|
+
"wss://gateway.discord.gg/");
|
|
124
|
+
const url = this.ensureGatewayParams(baseUrl);
|
|
125
|
+
this.nextConnectionShouldResume = resume;
|
|
126
|
+
this.socketGeneration++;
|
|
91
127
|
this.ws = this.createWebSocket(url);
|
|
128
|
+
this.isConnecting = true;
|
|
129
|
+
this.isConnected = false;
|
|
92
130
|
this.setupWebSocket();
|
|
93
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Stops heartbeats, clears reconnect state, and closes the active socket intentionally.
|
|
134
|
+
*/
|
|
94
135
|
disconnect() {
|
|
136
|
+
this.shouldReconnect = false;
|
|
137
|
+
this.clearReconnectTimeout();
|
|
95
138
|
stopHeartbeat(this);
|
|
139
|
+
this.lastHeartbeatAck = true;
|
|
96
140
|
this.monitor.resetUptime();
|
|
97
|
-
this.
|
|
141
|
+
this.rateLimit.reset();
|
|
142
|
+
if (this.ws) {
|
|
143
|
+
this.silentSocketClosures.add(this.ws);
|
|
144
|
+
this.ws.close(1000, "Client disconnect");
|
|
145
|
+
}
|
|
98
146
|
this.ws = null;
|
|
147
|
+
this.isConnecting = false;
|
|
99
148
|
this.isConnected = false;
|
|
149
|
+
this.reconnectAttempts = 0;
|
|
150
|
+
this.consecutiveResumeFailures = 0;
|
|
100
151
|
this.pings = [];
|
|
101
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Creates the websocket instance for a gateway URL. Override in tests if needed.
|
|
155
|
+
*/
|
|
102
156
|
createWebSocket(url) {
|
|
103
157
|
if (!url) {
|
|
104
158
|
throw new Error("Gateway URL is required");
|
|
105
159
|
}
|
|
106
|
-
|
|
160
|
+
const socket = this.options.webSocketFactory
|
|
161
|
+
? this.options.webSocketFactory(url)
|
|
162
|
+
: (() => {
|
|
163
|
+
if (nodeRequire) {
|
|
164
|
+
try {
|
|
165
|
+
const wsModule = nodeRequire("ws");
|
|
166
|
+
const nodeWebSocket = typeof wsModule.WebSocket === "function"
|
|
167
|
+
? wsModule.WebSocket
|
|
168
|
+
: typeof wsModule.default === "function"
|
|
169
|
+
? wsModule.default
|
|
170
|
+
: null;
|
|
171
|
+
if (nodeWebSocket) {
|
|
172
|
+
return new nodeWebSocket(url);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// fall through to global WebSocket
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (typeof globalThis.WebSocket === "function") {
|
|
180
|
+
return new globalThis.WebSocket(url);
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
})();
|
|
184
|
+
if (!socket) {
|
|
185
|
+
throw new Error("No WebSocket implementation available. Provide GatewayPluginOptions.webSocketFactory or install 'ws'.");
|
|
186
|
+
}
|
|
187
|
+
if ("binaryType" in socket) {
|
|
188
|
+
try {
|
|
189
|
+
;
|
|
190
|
+
socket.binaryType = "arraybuffer";
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Ignore runtimes that expose a readonly binaryType.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return socket;
|
|
107
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Attaches websocket lifecycle handlers for open, message, close, and error.
|
|
200
|
+
*/
|
|
108
201
|
setupWebSocket() {
|
|
109
|
-
if (!this.ws)
|
|
202
|
+
if (!this.ws) {
|
|
110
203
|
return;
|
|
111
|
-
|
|
112
|
-
this.ws
|
|
113
|
-
|
|
114
|
-
|
|
204
|
+
}
|
|
205
|
+
const socket = this.ws;
|
|
206
|
+
const generation = this.socketGeneration;
|
|
207
|
+
this.onSocketEvent(socket, "open", () => {
|
|
208
|
+
if (!this.isCurrentSocket(socket, generation)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.isConnecting = false;
|
|
212
|
+
this.emitter.emit("debug", "Gateway websocket opened");
|
|
115
213
|
});
|
|
116
|
-
this.
|
|
214
|
+
this.onSocketEvent(socket, "message", (incoming) => {
|
|
215
|
+
if (!this.isCurrentSocket(socket, generation)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
117
218
|
this.monitor.recordMessageReceived();
|
|
118
|
-
const
|
|
219
|
+
const payloadText = this.getMessageText(incoming);
|
|
220
|
+
const payload = payloadText ? validatePayload(payloadText) : null;
|
|
119
221
|
if (!payload) {
|
|
120
222
|
this.monitor.recordError();
|
|
121
|
-
this.emitter.emit("error", new Error("Invalid gateway payload
|
|
223
|
+
this.emitter.emit("error", new Error("Invalid gateway payload"));
|
|
122
224
|
return;
|
|
123
225
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.sequence = s;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
startHeartbeat(this, {
|
|
132
|
-
interval,
|
|
133
|
-
reconnectCallback: () => {
|
|
134
|
-
if (closed) {
|
|
135
|
-
throw new Error("Attempted to reconnect zombie connection after disconnecting first (this shouldn't be possible)");
|
|
136
|
-
}
|
|
137
|
-
closed = true;
|
|
138
|
-
this.handleZombieConnection();
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
if (this.canResume()) {
|
|
142
|
-
this.resume();
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
this.identify();
|
|
146
|
-
}
|
|
147
|
-
this.isConnected = true;
|
|
226
|
+
if (payload.s !== null && payload.s !== undefined) {
|
|
227
|
+
this.sequence = payload.s;
|
|
228
|
+
this.state.sequence = payload.s;
|
|
229
|
+
}
|
|
230
|
+
switch (payload.op) {
|
|
231
|
+
case GatewayOpcodes.Hello:
|
|
232
|
+
this.handleHello(payload.d, generation);
|
|
148
233
|
break;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.lastHeartbeatAck = true;
|
|
152
|
-
this.monitor.recordHeartbeatAck();
|
|
153
|
-
// Record the latency for ping averaging
|
|
154
|
-
const latency = this.monitor.getMetrics().latency;
|
|
155
|
-
if (latency > 0) {
|
|
156
|
-
this.pings.push(latency);
|
|
157
|
-
// Keep only the last 10 pings to prevent unbounded growth
|
|
158
|
-
if (this.pings.length > 10) {
|
|
159
|
-
this.pings.shift();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
234
|
+
case GatewayOpcodes.HeartbeatAck:
|
|
235
|
+
this.handleHeartbeatAck();
|
|
162
236
|
break;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const payload1 = payload;
|
|
166
|
-
const t1 = payload1.t;
|
|
167
|
-
try {
|
|
168
|
-
if (!Object.values(ListenerEvent).includes(t1)) {
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
if (t1 === "READY") {
|
|
172
|
-
const readyData = d;
|
|
173
|
-
this.state.sessionId = readyData.session_id;
|
|
174
|
-
this.state.resumeGatewayUrl = readyData.resume_gateway_url;
|
|
175
|
-
}
|
|
176
|
-
if (t && this.client) {
|
|
177
|
-
if (!this.options.eventFilter || this.options.eventFilter?.(t1)) {
|
|
178
|
-
if (t1 === "READY") {
|
|
179
|
-
const readyData = d;
|
|
180
|
-
readyData.guilds.forEach((guild) => {
|
|
181
|
-
this.babyCache.guildCache.set(guild.id, {
|
|
182
|
-
available: false,
|
|
183
|
-
lastEvent: Date.now()
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
if (t1 === "GUILD_CREATE") {
|
|
188
|
-
const guildCreateData = d;
|
|
189
|
-
const existingGuild = this.babyCache.guildCache.get(guildCreateData.id);
|
|
190
|
-
if (existingGuild && !existingGuild.available) {
|
|
191
|
-
this.babyCache.guildCache.set(guildCreateData.id, {
|
|
192
|
-
available: true,
|
|
193
|
-
lastEvent: Date.now()
|
|
194
|
-
});
|
|
195
|
-
this.client.eventHandler.handleEvent({
|
|
196
|
-
...guildCreateData,
|
|
197
|
-
clientId: this.client.options.clientId
|
|
198
|
-
}, "GUILD_AVAILABLE");
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (t1 === "GUILD_DELETE") {
|
|
203
|
-
const guildDeleteData = d;
|
|
204
|
-
const existingGuild = this.babyCache.guildCache.get(guildDeleteData.id);
|
|
205
|
-
if (existingGuild?.available && guildDeleteData.unavailable) {
|
|
206
|
-
this.babyCache.guildCache.set(guildDeleteData.id, {
|
|
207
|
-
available: false,
|
|
208
|
-
lastEvent: Date.now()
|
|
209
|
-
});
|
|
210
|
-
this.client.eventHandler.handleEvent({
|
|
211
|
-
...guildDeleteData,
|
|
212
|
-
clientId: this.client.options.clientId
|
|
213
|
-
}, "GUILD_UNAVAILABLE");
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
this.client.eventHandler.handleEvent({ ...payload1.d, clientId: this.client.options.clientId }, t1);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
catch (err) {
|
|
222
|
-
console.error(err);
|
|
223
|
-
}
|
|
237
|
+
case GatewayOpcodes.Heartbeat:
|
|
238
|
+
this.sendHeartbeatNow();
|
|
224
239
|
break;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (canResume && this.canResume()) {
|
|
231
|
-
this.connect(true);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
this.state.sessionId = null;
|
|
235
|
-
this.state.resumeGatewayUrl = null;
|
|
236
|
-
this.state.sequence = null;
|
|
237
|
-
this.sequence = null;
|
|
238
|
-
this.pings = [];
|
|
239
|
-
this.connect(false);
|
|
240
|
-
}
|
|
241
|
-
}, 5000);
|
|
240
|
+
case GatewayOpcodes.Dispatch:
|
|
241
|
+
this.handleDispatch(payload);
|
|
242
|
+
break;
|
|
243
|
+
case GatewayOpcodes.InvalidSession:
|
|
244
|
+
this.handleInvalidSession(payload.d);
|
|
242
245
|
break;
|
|
243
|
-
}
|
|
244
246
|
case GatewayOpcodes.Reconnect:
|
|
245
|
-
|
|
246
|
-
throw new Error("Attempted to reconnect gateway after disconnecting first (this shouldn't be possible)");
|
|
247
|
-
}
|
|
248
|
-
closed = true;
|
|
249
|
-
this.state.sequence = this.sequence;
|
|
250
|
-
this.ws?.close(3024);
|
|
251
|
-
this.handleReconnect();
|
|
247
|
+
this.handleReconnectOpcode();
|
|
252
248
|
break;
|
|
253
249
|
}
|
|
254
250
|
});
|
|
255
|
-
this.
|
|
256
|
-
this.
|
|
257
|
-
this.monitor.recordReconnect();
|
|
258
|
-
if (closed)
|
|
251
|
+
this.onSocketEvent(socket, "close", (incoming) => {
|
|
252
|
+
if (!this.isCurrentSocket(socket, generation)) {
|
|
259
253
|
return;
|
|
260
|
-
|
|
254
|
+
}
|
|
255
|
+
this.isConnecting = false;
|
|
256
|
+
this.isConnected = false;
|
|
257
|
+
stopHeartbeat(this);
|
|
258
|
+
this.lastHeartbeatAck = true;
|
|
259
|
+
const wasSilentClose = this.silentSocketClosures.has(socket);
|
|
260
|
+
if (wasSilentClose) {
|
|
261
|
+
this.silentSocketClosures.delete(socket);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const code = this.getCloseCode(incoming);
|
|
265
|
+
this.monitor.recordReconnect();
|
|
266
|
+
this.emitter.emit("debug", `Gateway websocket closed: ${code}`);
|
|
261
267
|
this.handleClose(code);
|
|
262
268
|
});
|
|
263
|
-
this.
|
|
269
|
+
this.onSocketEvent(socket, "error", (incoming) => {
|
|
270
|
+
if (!this.isCurrentSocket(socket, generation)) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.isConnecting = false;
|
|
264
274
|
this.monitor.recordError();
|
|
265
|
-
this.emitter.emit("error",
|
|
275
|
+
this.emitter.emit("error", this.getSocketError(incoming));
|
|
266
276
|
});
|
|
267
277
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
this.monitor.destroy();
|
|
278
|
+
/**
|
|
279
|
+
* Handles close codes and decides whether reconnection should resume or re-identify.
|
|
280
|
+
*/
|
|
281
|
+
handleClose(code) {
|
|
282
|
+
if (!this.shouldReconnect) {
|
|
274
283
|
return;
|
|
275
284
|
}
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
case GatewayCloseCodes.DisallowedIntents:
|
|
282
|
-
case GatewayCloseCodes.ShardingRequired: {
|
|
283
|
-
this.emitter.emit("error", new Error(`Fatal Gateway error: ${options.code}`));
|
|
284
|
-
this.reconnectAttempts = maxAttempts;
|
|
285
|
-
this.monitor.destroy();
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
case GatewayCloseCodes.InvalidSeq:
|
|
289
|
-
case GatewayCloseCodes.SessionTimedOut: {
|
|
290
|
-
this.state.sessionId = null;
|
|
291
|
-
this.state.resumeGatewayUrl = null;
|
|
292
|
-
this.state.sequence = null;
|
|
293
|
-
this.sequence = null;
|
|
294
|
-
this.pings = [];
|
|
295
|
-
options.forceNoResume = true;
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
285
|
+
if (fatalGatewayCloseCodes.has(code)) {
|
|
286
|
+
this.shouldReconnect = false;
|
|
287
|
+
this.emitter.emit("error", new Error(`Fatal gateway close code: ${code}`));
|
|
288
|
+
this.disconnect();
|
|
289
|
+
return;
|
|
299
290
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (options.isZombieConnection) {
|
|
303
|
-
this.monitor.recordZombieConnection();
|
|
291
|
+
if (nonResumableGatewayCloseCodes.has(code)) {
|
|
292
|
+
this.resetSessionState();
|
|
304
293
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.handleReconnectionAttempt({ code });
|
|
294
|
+
this.scheduleReconnect({
|
|
295
|
+
code,
|
|
296
|
+
reason: "close",
|
|
297
|
+
preferResume: !nonResumableGatewayCloseCodes.has(code)
|
|
298
|
+
});
|
|
311
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Handles missing heartbeat acknowledgements by forcing a reconnect flow.
|
|
302
|
+
*/
|
|
312
303
|
handleZombieConnection() {
|
|
313
|
-
this.
|
|
304
|
+
this.monitor.recordZombieConnection();
|
|
305
|
+
this.scheduleReconnect({
|
|
306
|
+
reason: "zombie",
|
|
307
|
+
preferResume: true
|
|
308
|
+
});
|
|
309
|
+
this.reconnectWithSocketRestart();
|
|
314
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Compatibility wrapper that maps to reconnect opcode handling.
|
|
313
|
+
*/
|
|
315
314
|
handleReconnect() {
|
|
316
|
-
this.
|
|
315
|
+
this.handleReconnectOpcode();
|
|
317
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Returns whether session_id and sequence are both available for RESUME.
|
|
319
|
+
*/
|
|
318
320
|
canResume() {
|
|
319
|
-
return Boolean(this.state.sessionId && this.sequence);
|
|
321
|
+
return Boolean(this.state.sessionId && this.sequence !== null);
|
|
320
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Sends a RESUME payload using cached session_id and latest sequence.
|
|
325
|
+
*/
|
|
321
326
|
resume() {
|
|
322
|
-
if (!this.client || !this.state.sessionId || this.
|
|
327
|
+
if (!this.client || !this.state.sessionId || this.sequence === null) {
|
|
323
328
|
return;
|
|
329
|
+
}
|
|
324
330
|
const payload = createResumePayload({
|
|
325
331
|
token: this.client.options.token,
|
|
326
332
|
sessionId: this.state.sessionId,
|
|
327
|
-
sequence: this.
|
|
333
|
+
sequence: this.sequence
|
|
328
334
|
});
|
|
329
335
|
this.send(payload, true);
|
|
330
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Sends a gateway payload with size and rate-limit safeguards.
|
|
339
|
+
*/
|
|
331
340
|
send(payload, skipRateLimit = false) {
|
|
332
|
-
if (this.ws
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
341
|
+
if (!this.ws || this.ws.readyState !== socketOpenState) {
|
|
342
|
+
throw new Error("Gateway websocket is not open");
|
|
343
|
+
}
|
|
344
|
+
const isEssentialEvent = payload.op === GatewayOpcodes.Heartbeat ||
|
|
345
|
+
payload.op === GatewayOpcodes.Identify ||
|
|
346
|
+
payload.op === GatewayOpcodes.Resume;
|
|
347
|
+
if (!skipRateLimit && !isEssentialEvent && !this.rateLimit.canSend()) {
|
|
348
|
+
throw new Error(`Gateway rate limit exceeded. ${this.rateLimit.getRemainingEvents()} events remaining. Reset in ${this.rateLimit.getResetTime()}ms`);
|
|
349
|
+
}
|
|
350
|
+
const encodedPayload = JSON.stringify(payload);
|
|
351
|
+
const payloadSize = typeof Buffer !== "undefined"
|
|
352
|
+
? Buffer.byteLength(encodedPayload, "utf8")
|
|
353
|
+
: new TextEncoder().encode(encodedPayload).byteLength;
|
|
354
|
+
if (payloadSize > 4096) {
|
|
355
|
+
throw new Error("Gateway payload exceeds 4096-byte Discord limit");
|
|
356
|
+
}
|
|
357
|
+
this.ws.send(encodedPayload);
|
|
358
|
+
this.monitor.recordMessageSent();
|
|
359
|
+
if (!isEssentialEvent) {
|
|
360
|
+
this.rateLimit.recordEvent();
|
|
361
|
+
}
|
|
362
|
+
if (payload.op === GatewayOpcodes.Heartbeat) {
|
|
363
|
+
this.monitor.recordHeartbeat();
|
|
348
364
|
}
|
|
349
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Sends an IDENTIFY payload for a fresh gateway session.
|
|
368
|
+
*/
|
|
350
369
|
identify() {
|
|
351
|
-
if (!this.client)
|
|
370
|
+
if (!this.client) {
|
|
352
371
|
return;
|
|
372
|
+
}
|
|
353
373
|
const payload = createIdentifyPayload({
|
|
354
374
|
token: this.client.options.token,
|
|
355
375
|
intents: this.options.intents,
|
|
356
376
|
properties: {
|
|
357
|
-
os: process
|
|
377
|
+
os: typeof process !== "undefined" && process?.platform
|
|
378
|
+
? process.platform
|
|
379
|
+
: "unknown",
|
|
358
380
|
browser: "@buape/carbon - https://carbon.buape.com",
|
|
359
381
|
device: "@buape/carbon - https://carbon.buape.com"
|
|
360
382
|
},
|
|
@@ -363,30 +385,25 @@ export class GatewayPlugin extends Plugin {
|
|
|
363
385
|
this.send(payload, true);
|
|
364
386
|
}
|
|
365
387
|
/**
|
|
366
|
-
*
|
|
367
|
-
* @param data Presence data to update
|
|
388
|
+
* Updates bot presence over the gateway connection.
|
|
368
389
|
*/
|
|
369
390
|
updatePresence(data) {
|
|
370
391
|
if (!this.isConnected) {
|
|
371
392
|
throw new Error("Gateway is not connected");
|
|
372
393
|
}
|
|
373
|
-
|
|
374
|
-
this.send(payload);
|
|
394
|
+
this.send(createUpdatePresencePayload(data));
|
|
375
395
|
}
|
|
376
396
|
/**
|
|
377
|
-
*
|
|
378
|
-
* @param data Voice state data to update
|
|
397
|
+
* Updates bot voice state for a guild over the gateway connection.
|
|
379
398
|
*/
|
|
380
399
|
updateVoiceState(data) {
|
|
381
400
|
if (!this.isConnected) {
|
|
382
401
|
throw new Error("Gateway is not connected");
|
|
383
402
|
}
|
|
384
|
-
|
|
385
|
-
this.send(payload);
|
|
403
|
+
this.send(createUpdateVoiceStatePayload(data));
|
|
386
404
|
}
|
|
387
405
|
/**
|
|
388
|
-
*
|
|
389
|
-
* @param data Guild members request data
|
|
406
|
+
* Requests guild members and validates required intents/options.
|
|
390
407
|
*/
|
|
391
408
|
requestGuildMembers(data) {
|
|
392
409
|
if (!this.isConnected) {
|
|
@@ -405,11 +422,10 @@ export class GatewayPlugin extends Plugin {
|
|
|
405
422
|
if (!data.query && data.query !== "" && !data.user_ids) {
|
|
406
423
|
throw new Error("Either 'query' or 'user_ids' field is required for requestGuildMembers");
|
|
407
424
|
}
|
|
408
|
-
|
|
409
|
-
this.send(payload);
|
|
425
|
+
this.send(createRequestGuildMembersPayload(data));
|
|
410
426
|
}
|
|
411
427
|
/**
|
|
412
|
-
*
|
|
428
|
+
* Returns the current outbound gateway rate-limit snapshot.
|
|
413
429
|
*/
|
|
414
430
|
getRateLimitStatus() {
|
|
415
431
|
return {
|
|
@@ -419,7 +435,7 @@ export class GatewayPlugin extends Plugin {
|
|
|
419
435
|
};
|
|
420
436
|
}
|
|
421
437
|
/**
|
|
422
|
-
*
|
|
438
|
+
* Returns helpers describing which intents are currently enabled.
|
|
423
439
|
*/
|
|
424
440
|
getIntentsInfo() {
|
|
425
441
|
return {
|
|
@@ -432,11 +448,366 @@ export class GatewayPlugin extends Plugin {
|
|
|
432
448
|
};
|
|
433
449
|
}
|
|
434
450
|
/**
|
|
435
|
-
*
|
|
436
|
-
* @param intent The intent to check
|
|
451
|
+
* Checks if a specific intent bit is enabled.
|
|
437
452
|
*/
|
|
438
453
|
hasIntent(intent) {
|
|
439
454
|
return (this.options.intents & intent) !== 0;
|
|
440
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Guards handlers from acting on stale websocket instances.
|
|
458
|
+
*/
|
|
459
|
+
isCurrentSocket(socket, generation) {
|
|
460
|
+
return this.ws === socket && this.socketGeneration === generation;
|
|
461
|
+
}
|
|
462
|
+
onSocketEvent(socket, event, handler) {
|
|
463
|
+
if (typeof socket.on === "function") {
|
|
464
|
+
socket.on(event, (...args) => {
|
|
465
|
+
if (args.length === 0) {
|
|
466
|
+
handler(undefined);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
handler(args.length === 1 ? args[0] : args);
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (typeof socket.addEventListener === "function") {
|
|
474
|
+
socket.addEventListener(event, (incoming) => {
|
|
475
|
+
handler(incoming);
|
|
476
|
+
});
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
throw new Error("WebSocket implementation does not support event listeners");
|
|
480
|
+
}
|
|
481
|
+
getMessageText(incoming) {
|
|
482
|
+
const payload = this.extractSocketPayload(incoming);
|
|
483
|
+
if (typeof payload === "string") {
|
|
484
|
+
return payload;
|
|
485
|
+
}
|
|
486
|
+
if (payload instanceof ArrayBuffer) {
|
|
487
|
+
return textDecoder.decode(new Uint8Array(payload));
|
|
488
|
+
}
|
|
489
|
+
if (ArrayBuffer.isView(payload)) {
|
|
490
|
+
return textDecoder.decode(new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength));
|
|
491
|
+
}
|
|
492
|
+
if (payload && typeof payload === "object" && "toString" in payload) {
|
|
493
|
+
const text = String(payload);
|
|
494
|
+
return text === "[object Object]" ? null : text;
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
extractSocketPayload(incoming) {
|
|
499
|
+
if (Array.isArray(incoming)) {
|
|
500
|
+
return incoming[0];
|
|
501
|
+
}
|
|
502
|
+
if (incoming && typeof incoming === "object" && "data" in incoming) {
|
|
503
|
+
return incoming.data;
|
|
504
|
+
}
|
|
505
|
+
return incoming;
|
|
506
|
+
}
|
|
507
|
+
getCloseCode(incoming) {
|
|
508
|
+
if (Array.isArray(incoming)) {
|
|
509
|
+
const [code] = incoming;
|
|
510
|
+
if (typeof code === "number") {
|
|
511
|
+
return code;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (incoming && typeof incoming === "object" && "code" in incoming) {
|
|
515
|
+
const code = incoming.code;
|
|
516
|
+
if (typeof code === "number") {
|
|
517
|
+
return code;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return 1000;
|
|
521
|
+
}
|
|
522
|
+
getSocketError(incoming) {
|
|
523
|
+
const payload = this.extractSocketPayload(incoming);
|
|
524
|
+
if (payload instanceof Error) {
|
|
525
|
+
return payload;
|
|
526
|
+
}
|
|
527
|
+
if (payload && typeof payload === "object" && "error" in payload) {
|
|
528
|
+
const nested = payload.error;
|
|
529
|
+
if (nested instanceof Error) {
|
|
530
|
+
return nested;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return new Error(typeof payload === "string"
|
|
534
|
+
? payload
|
|
535
|
+
: "Gateway socket emitted an unknown error");
|
|
536
|
+
}
|
|
537
|
+
closeSocketImmediately(socket) {
|
|
538
|
+
if (typeof socket.terminate === "function") {
|
|
539
|
+
socket.terminate();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
socket.close(1000, "Gateway reconnect");
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Processes HELLO, starts heartbeat scheduling, then sends RESUME or IDENTIFY.
|
|
546
|
+
*/
|
|
547
|
+
handleHello(data, generation) {
|
|
548
|
+
const heartbeatInterval = data?.heartbeat_interval;
|
|
549
|
+
if (typeof heartbeatInterval !== "number" || heartbeatInterval <= 0) {
|
|
550
|
+
this.monitor.recordError();
|
|
551
|
+
this.emitter.emit("error", new Error("Gateway HELLO missing heartbeat"));
|
|
552
|
+
this.handleZombieConnection();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
startHeartbeat(this, {
|
|
556
|
+
interval: heartbeatInterval,
|
|
557
|
+
reconnectCallback: () => {
|
|
558
|
+
if (this.socketGeneration !== generation) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this.handleZombieConnection();
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
const shouldResume = this.nextConnectionShouldResume && this.canResume();
|
|
565
|
+
this.nextConnectionShouldResume = false;
|
|
566
|
+
try {
|
|
567
|
+
if (shouldResume) {
|
|
568
|
+
this.resume();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
this.identify();
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
this.handleZombieConnection();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Marks heartbeat acknowledged and updates rolling ping metrics.
|
|
579
|
+
*/
|
|
580
|
+
handleHeartbeatAck() {
|
|
581
|
+
this.lastHeartbeatAck = true;
|
|
582
|
+
this.monitor.recordHeartbeatAck();
|
|
583
|
+
const latency = this.monitor.getMetrics().latency;
|
|
584
|
+
if (latency > 0) {
|
|
585
|
+
this.pings.push(latency);
|
|
586
|
+
if (this.pings.length > 10) {
|
|
587
|
+
this.pings.shift();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Immediately sends a heartbeat in response to gateway heartbeat requests.
|
|
593
|
+
*/
|
|
594
|
+
sendHeartbeatNow() {
|
|
595
|
+
this.lastHeartbeatAck = false;
|
|
596
|
+
try {
|
|
597
|
+
this.send({
|
|
598
|
+
op: GatewayOpcodes.Heartbeat,
|
|
599
|
+
d: this.sequence
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
this.handleZombieConnection();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Processes dispatch events, session caching, and Carbon event forwarding.
|
|
608
|
+
*/
|
|
609
|
+
handleDispatch(payload) {
|
|
610
|
+
const type = payload.t;
|
|
611
|
+
if (!listenerEvents.has(type)) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (type === "READY") {
|
|
615
|
+
const readyData = payload.d;
|
|
616
|
+
this.state.sessionId = readyData.session_id;
|
|
617
|
+
this.state.resumeGatewayUrl = readyData.resume_gateway_url;
|
|
618
|
+
}
|
|
619
|
+
if (type === "READY" || type === "RESUMED") {
|
|
620
|
+
this.isConnected = true;
|
|
621
|
+
this.reconnectAttempts = 0;
|
|
622
|
+
this.consecutiveResumeFailures = 0;
|
|
623
|
+
}
|
|
624
|
+
if (!this.client) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (this.options.eventFilter && !this.options.eventFilter(type)) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (type === "READY") {
|
|
631
|
+
const readyData = payload.d;
|
|
632
|
+
readyData.guilds.forEach((guild) => {
|
|
633
|
+
this.babyCache.setGuild(guild.id, {
|
|
634
|
+
available: false,
|
|
635
|
+
lastEvent: Date.now()
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
if (type === "GUILD_CREATE") {
|
|
640
|
+
const guildCreateData = payload.d;
|
|
641
|
+
const existingGuild = this.babyCache.getGuild(guildCreateData.id);
|
|
642
|
+
if (existingGuild && !existingGuild.available) {
|
|
643
|
+
this.babyCache.setGuild(guildCreateData.id, {
|
|
644
|
+
available: true,
|
|
645
|
+
lastEvent: Date.now()
|
|
646
|
+
});
|
|
647
|
+
this.client.eventHandler.handleEvent({
|
|
648
|
+
...guildCreateData,
|
|
649
|
+
clientId: this.client.options.clientId
|
|
650
|
+
}, "GUILD_AVAILABLE");
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (type === "GUILD_DELETE") {
|
|
655
|
+
const guildDeleteData = payload.d;
|
|
656
|
+
const existingGuild = this.babyCache.getGuild(guildDeleteData.id);
|
|
657
|
+
if (existingGuild?.available && guildDeleteData.unavailable) {
|
|
658
|
+
this.babyCache.setGuild(guildDeleteData.id, {
|
|
659
|
+
available: false,
|
|
660
|
+
lastEvent: Date.now()
|
|
661
|
+
});
|
|
662
|
+
this.client.eventHandler.handleEvent({
|
|
663
|
+
...guildDeleteData,
|
|
664
|
+
clientId: this.client.options.clientId
|
|
665
|
+
}, "GUILD_UNAVAILABLE");
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (!guildDeleteData.unavailable) {
|
|
669
|
+
this.babyCache.removeGuild(guildDeleteData.id);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
this.client.eventHandler.handleEvent({ ...payload.d, clientId: this.client.options.clientId }, type);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Handles INVALID_SESSION and schedules reconnect with Discord-compliant delay.
|
|
676
|
+
*/
|
|
677
|
+
handleInvalidSession(data) {
|
|
678
|
+
const isResumable = Boolean(data) && this.canResume();
|
|
679
|
+
if (!isResumable) {
|
|
680
|
+
this.resetSessionState();
|
|
681
|
+
}
|
|
682
|
+
const discordRequiredDelay = 1000 + Math.floor(Math.random() * 4000);
|
|
683
|
+
this.scheduleReconnect({
|
|
684
|
+
reason: "invalid-session",
|
|
685
|
+
preferResume: isResumable,
|
|
686
|
+
minDelayMs: discordRequiredDelay
|
|
687
|
+
});
|
|
688
|
+
this.reconnectWithSocketRestart();
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Handles RECONNECT opcode by scheduling reconnect with resume preference.
|
|
692
|
+
*/
|
|
693
|
+
handleReconnectOpcode() {
|
|
694
|
+
this.scheduleReconnect({
|
|
695
|
+
reason: "reconnect-opcode",
|
|
696
|
+
preferResume: true,
|
|
697
|
+
allowImmediateFirstAttempt: true
|
|
698
|
+
});
|
|
699
|
+
this.reconnectWithSocketRestart();
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Terminates the current socket after reconnect has been scheduled.
|
|
703
|
+
*/
|
|
704
|
+
reconnectWithSocketRestart() {
|
|
705
|
+
stopHeartbeat(this);
|
|
706
|
+
this.lastHeartbeatAck = true;
|
|
707
|
+
if (!this.ws) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
this.silentSocketClosures.add(this.ws);
|
|
711
|
+
this.closeSocketImmediately(this.ws);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Schedules a single reconnect attempt with backoff and attempt limits.
|
|
715
|
+
*/
|
|
716
|
+
scheduleReconnect(options) {
|
|
717
|
+
if (!this.shouldReconnect || this.reconnectTimeout || this.isConnecting) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const maxAttempts = this.options.reconnect?.maxAttempts ?? reconnectDefaults.maxAttempts;
|
|
721
|
+
if (Number.isFinite(maxAttempts) && this.reconnectAttempts >= maxAttempts) {
|
|
722
|
+
this.shouldReconnect = false;
|
|
723
|
+
this.emitter.emit("error", new Error(`Max reconnect attempts (${maxAttempts}) reached${options.code ? ` after close code ${options.code}` : ""}`));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
let shouldResume = options.preferResume && this.canResume();
|
|
727
|
+
const resumeFailureThreshold = 3;
|
|
728
|
+
if (shouldResume &&
|
|
729
|
+
this.consecutiveResumeFailures >= resumeFailureThreshold) {
|
|
730
|
+
this.resetSessionState();
|
|
731
|
+
shouldResume = false;
|
|
732
|
+
this.emitter.emit("debug", `Gateway forcing fresh IDENTIFY after ${resumeFailureThreshold} failed resume attempts`);
|
|
733
|
+
}
|
|
734
|
+
const delay = this.computeReconnectDelay(options);
|
|
735
|
+
if (shouldResume) {
|
|
736
|
+
this.consecutiveResumeFailures++;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
this.consecutiveResumeFailures = 0;
|
|
740
|
+
}
|
|
741
|
+
this.reconnectAttempts++;
|
|
742
|
+
this.emitter.emit("debug", `Gateway reconnect scheduled in ${delay}ms (${options.reason}, resume=${String(shouldResume)})`);
|
|
743
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
744
|
+
this.reconnectTimeout = undefined;
|
|
745
|
+
this.connect(shouldResume);
|
|
746
|
+
}, delay);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Computes exponential reconnect delay with jitter and optional minimum delay.
|
|
750
|
+
*/
|
|
751
|
+
computeReconnectDelay(options) {
|
|
752
|
+
const baseDelay = this.options.reconnect?.baseDelay ?? reconnectDefaults.baseDelay;
|
|
753
|
+
const maxDelay = this.options.reconnect?.maxDelay ?? reconnectDefaults.maxDelay;
|
|
754
|
+
if (options.allowImmediateFirstAttempt &&
|
|
755
|
+
this.reconnectAttempts === 0 &&
|
|
756
|
+
(options.minDelayMs ?? 0) === 0) {
|
|
757
|
+
return 0;
|
|
758
|
+
}
|
|
759
|
+
const exponentialDelay = Math.min(baseDelay * 2 ** this.reconnectAttempts, maxDelay);
|
|
760
|
+
const jitterFactor = 0.85 + Math.random() * 0.3;
|
|
761
|
+
const jitteredDelay = Math.floor(exponentialDelay * jitterFactor);
|
|
762
|
+
return Math.max(options.minDelayMs ?? 0, jitteredDelay);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Clears any pending reconnect timer.
|
|
766
|
+
*/
|
|
767
|
+
clearReconnectTimeout() {
|
|
768
|
+
if (!this.reconnectTimeout) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
clearTimeout(this.reconnectTimeout);
|
|
772
|
+
this.reconnectTimeout = undefined;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Clears resume-related session state after non-resumable failures.
|
|
776
|
+
*/
|
|
777
|
+
resetSessionState() {
|
|
778
|
+
this.state.sessionId = null;
|
|
779
|
+
this.state.resumeGatewayUrl = null;
|
|
780
|
+
this.state.sequence = null;
|
|
781
|
+
this.sequence = null;
|
|
782
|
+
this.consecutiveResumeFailures = 0;
|
|
783
|
+
this.pings = [];
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Ensures gateway URLs include v=10 and encoding=json query parameters.
|
|
787
|
+
*/
|
|
788
|
+
ensureGatewayParams(url) {
|
|
789
|
+
try {
|
|
790
|
+
const parsed = new URL(url);
|
|
791
|
+
if (!parsed.searchParams.get("v")) {
|
|
792
|
+
parsed.searchParams.set("v", "10");
|
|
793
|
+
}
|
|
794
|
+
if (!parsed.searchParams.get("encoding")) {
|
|
795
|
+
parsed.searchParams.set("encoding", "json");
|
|
796
|
+
}
|
|
797
|
+
return parsed.toString();
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
const hasQuery = url.includes("?");
|
|
801
|
+
const hasV = url.includes("v=");
|
|
802
|
+
const hasEncoding = url.includes("encoding=");
|
|
803
|
+
const separator = hasQuery ? "&" : "?";
|
|
804
|
+
const parts = [];
|
|
805
|
+
if (!hasV)
|
|
806
|
+
parts.push("v=10");
|
|
807
|
+
if (!hasEncoding)
|
|
808
|
+
parts.push("encoding=json");
|
|
809
|
+
return parts.length ? `${url}${separator}${parts.join("&")}` : url;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
441
812
|
}
|
|
442
813
|
//# sourceMappingURL=GatewayPlugin.js.map
|