discord.js-selfbots-v13 0.0.1-security → 3.3.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.
Potentially problematic release.
This version of discord.js-selfbots-v13 might be problematic. Click here for more details.
- package/LICENSE +674 -0
- package/README.md +127 -5
- package/package.json +101 -6
- package/src/WebSocket.js +39 -0
- package/src/client/BaseClient.js +87 -0
- package/src/client/Client.js +1154 -0
- package/src/client/WebhookClient.js +61 -0
- package/src/client/actions/Action.js +115 -0
- package/src/client/actions/ActionsManager.js +72 -0
- package/src/client/actions/ApplicationCommandPermissionsUpdate.js +34 -0
- package/src/client/actions/AutoModerationActionExecution.js +26 -0
- package/src/client/actions/AutoModerationRuleCreate.js +27 -0
- package/src/client/actions/AutoModerationRuleDelete.js +31 -0
- package/src/client/actions/AutoModerationRuleUpdate.js +29 -0
- package/src/client/actions/ChannelCreate.js +23 -0
- package/src/client/actions/ChannelDelete.js +39 -0
- package/src/client/actions/ChannelUpdate.js +34 -0
- package/src/client/actions/GuildAuditLogEntryCreate.js +29 -0
- package/src/client/actions/GuildBanAdd.js +20 -0
- package/src/client/actions/GuildBanRemove.js +25 -0
- package/src/client/actions/GuildChannelsPositionUpdate.js +21 -0
- package/src/client/actions/GuildDelete.js +65 -0
- package/src/client/actions/GuildEmojiCreate.js +20 -0
- package/src/client/actions/GuildEmojiDelete.js +21 -0
- package/src/client/actions/GuildEmojiUpdate.js +20 -0
- package/src/client/actions/GuildEmojisUpdate.js +34 -0
- package/src/client/actions/GuildIntegrationsUpdate.js +19 -0
- package/src/client/actions/GuildMemberRemove.js +33 -0
- package/src/client/actions/GuildMemberUpdate.js +44 -0
- package/src/client/actions/GuildRoleCreate.js +25 -0
- package/src/client/actions/GuildRoleDelete.js +31 -0
- package/src/client/actions/GuildRoleUpdate.js +39 -0
- package/src/client/actions/GuildRolesPositionUpdate.js +21 -0
- package/src/client/actions/GuildScheduledEventCreate.js +27 -0
- package/src/client/actions/GuildScheduledEventDelete.js +31 -0
- package/src/client/actions/GuildScheduledEventUpdate.js +30 -0
- package/src/client/actions/GuildScheduledEventUserAdd.js +32 -0
- package/src/client/actions/GuildScheduledEventUserRemove.js +32 -0
- package/src/client/actions/GuildStickerCreate.js +20 -0
- package/src/client/actions/GuildStickerDelete.js +21 -0
- package/src/client/actions/GuildStickerUpdate.js +20 -0
- package/src/client/actions/GuildStickersUpdate.js +34 -0
- package/src/client/actions/GuildUpdate.js +33 -0
- package/src/client/actions/InteractionCreate.js +115 -0
- package/src/client/actions/InviteCreate.js +28 -0
- package/src/client/actions/InviteDelete.js +30 -0
- package/src/client/actions/MessageCreate.js +50 -0
- package/src/client/actions/MessageDelete.js +32 -0
- package/src/client/actions/MessageDeleteBulk.js +46 -0
- package/src/client/actions/MessageReactionAdd.js +56 -0
- package/src/client/actions/MessageReactionRemove.js +45 -0
- package/src/client/actions/MessageReactionRemoveAll.js +33 -0
- package/src/client/actions/MessageReactionRemoveEmoji.js +28 -0
- package/src/client/actions/MessageUpdate.js +26 -0
- package/src/client/actions/PresenceUpdate.js +45 -0
- package/src/client/actions/StageInstanceCreate.js +28 -0
- package/src/client/actions/StageInstanceDelete.js +33 -0
- package/src/client/actions/StageInstanceUpdate.js +30 -0
- package/src/client/actions/ThreadCreate.js +24 -0
- package/src/client/actions/ThreadDelete.js +32 -0
- package/src/client/actions/ThreadListSync.js +59 -0
- package/src/client/actions/ThreadMemberUpdate.js +30 -0
- package/src/client/actions/ThreadMembersUpdate.js +34 -0
- package/src/client/actions/TypingStart.js +29 -0
- package/src/client/actions/UserUpdate.js +35 -0
- package/src/client/actions/VoiceStateUpdate.js +57 -0
- package/src/client/actions/WebhooksUpdate.js +20 -0
- package/src/client/voice/ClientVoiceManager.js +51 -0
- package/src/client/websocket/WebSocketManager.js +412 -0
- package/src/client/websocket/WebSocketShard.js +908 -0
- package/src/client/websocket/handlers/APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE.js +23 -0
- package/src/client/websocket/handlers/APPLICATION_COMMAND_CREATE.js +18 -0
- package/src/client/websocket/handlers/APPLICATION_COMMAND_DELETE.js +20 -0
- package/src/client/websocket/handlers/APPLICATION_COMMAND_PERMISSIONS_UPDATE.js +5 -0
- package/src/client/websocket/handlers/APPLICATION_COMMAND_UPDATE.js +20 -0
- package/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js +5 -0
- package/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js +5 -0
- package/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js +5 -0
- package/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js +5 -0
- package/src/client/websocket/handlers/CALL_CREATE.js +14 -0
- package/src/client/websocket/handlers/CALL_DELETE.js +11 -0
- package/src/client/websocket/handlers/CALL_UPDATE.js +11 -0
- package/src/client/websocket/handlers/CHANNEL_CREATE.js +5 -0
- package/src/client/websocket/handlers/CHANNEL_DELETE.js +5 -0
- package/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js +22 -0
- package/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js +16 -0
- package/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js +16 -0
- package/src/client/websocket/handlers/CHANNEL_UPDATE.js +16 -0
- package/src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js +11 -0
- package/src/client/websocket/handlers/GUILD_AUDIT_LOG_ENTRY_CREATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_BAN_ADD.js +5 -0
- package/src/client/websocket/handlers/GUILD_BAN_REMOVE.js +5 -0
- package/src/client/websocket/handlers/GUILD_CREATE.js +46 -0
- package/src/client/websocket/handlers/GUILD_DELETE.js +5 -0
- package/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js +39 -0
- package/src/client/websocket/handlers/GUILD_MEMBER_ADD.js +20 -0
- package/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js +55 -0
- package/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js +5 -0
- package/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_ROLE_CREATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_ROLE_DELETE.js +5 -0
- package/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js +5 -0
- package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js +5 -0
- package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js +5 -0
- package/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js +5 -0
- package/src/client/websocket/handlers/GUILD_UPDATE.js +5 -0
- package/src/client/websocket/handlers/INTERACTION_CREATE.js +16 -0
- package/src/client/websocket/handlers/INTERACTION_FAILURE.js +18 -0
- package/src/client/websocket/handlers/INTERACTION_MODAL_CREATE.js +11 -0
- package/src/client/websocket/handlers/INTERACTION_SUCCESS.js +30 -0
- package/src/client/websocket/handlers/INVITE_CREATE.js +5 -0
- package/src/client/websocket/handlers/INVITE_DELETE.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_ACK.js +16 -0
- package/src/client/websocket/handlers/MESSAGE_CREATE.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_DELETE.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js +5 -0
- package/src/client/websocket/handlers/MESSAGE_UPDATE.js +16 -0
- package/src/client/websocket/handlers/PRESENCE_UPDATE.js +5 -0
- package/src/client/websocket/handlers/READY.js +172 -0
- package/src/client/websocket/handlers/RELATIONSHIP_ADD.js +17 -0
- package/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js +15 -0
- package/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js +18 -0
- package/src/client/websocket/handlers/RESUMED.js +14 -0
- package/src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js +5 -0
- package/src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js +5 -0
- package/src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js +5 -0
- package/src/client/websocket/handlers/THREAD_CREATE.js +5 -0
- package/src/client/websocket/handlers/THREAD_DELETE.js +5 -0
- package/src/client/websocket/handlers/THREAD_LIST_SYNC.js +5 -0
- package/src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js +5 -0
- package/src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js +5 -0
- package/src/client/websocket/handlers/THREAD_UPDATE.js +16 -0
- package/src/client/websocket/handlers/TYPING_START.js +5 -0
- package/src/client/websocket/handlers/USER_GUILD_SETTINGS_UPDATE.js +12 -0
- package/src/client/websocket/handlers/USER_NOTE_UPDATE.js +5 -0
- package/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js +9 -0
- package/src/client/websocket/handlers/USER_UPDATE.js +5 -0
- package/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js +6 -0
- package/src/client/websocket/handlers/VOICE_STATE_UPDATE.js +5 -0
- package/src/client/websocket/handlers/WEBHOOKS_UPDATE.js +5 -0
- package/src/client/websocket/handlers/index.js +86 -0
- package/src/errors/DJSError.js +61 -0
- package/src/errors/Messages.js +227 -0
- package/src/errors/index.js +4 -0
- package/src/index.js +190 -0
- package/src/main.js +1 -0
- package/src/managers/ApplicationCommandManager.js +267 -0
- package/src/managers/ApplicationCommandPermissionsManager.js +425 -0
- package/src/managers/AutoModerationRuleManager.js +296 -0
- package/src/managers/BaseGuildEmojiManager.js +80 -0
- package/src/managers/BaseManager.js +19 -0
- package/src/managers/BillingManager.js +66 -0
- package/src/managers/CachedManager.js +71 -0
- package/src/managers/ChannelManager.js +139 -0
- package/src/managers/ClientUserSettingManager.js +490 -0
- package/src/managers/DataManager.js +61 -0
- package/src/managers/DeveloperPortalManager.js +104 -0
- package/src/managers/GuildApplicationCommandManager.js +28 -0
- package/src/managers/GuildBanManager.js +204 -0
- package/src/managers/GuildChannelManager.js +502 -0
- package/src/managers/GuildEmojiManager.js +171 -0
- package/src/managers/GuildEmojiRoleManager.js +118 -0
- package/src/managers/GuildFolderManager.js +24 -0
- package/src/managers/GuildForumThreadManager.js +114 -0
- package/src/managers/GuildInviteManager.js +213 -0
- package/src/managers/GuildManager.js +304 -0
- package/src/managers/GuildMemberManager.js +724 -0
- package/src/managers/GuildMemberRoleManager.js +191 -0
- package/src/managers/GuildScheduledEventManager.js +296 -0
- package/src/managers/GuildSettingManager.js +148 -0
- package/src/managers/GuildStickerManager.js +179 -0
- package/src/managers/GuildTextThreadManager.js +98 -0
- package/src/managers/InteractionManager.js +39 -0
- package/src/managers/MessageManager.js +393 -0
- package/src/managers/PermissionOverwriteManager.js +166 -0
- package/src/managers/PresenceManager.js +58 -0
- package/src/managers/ReactionManager.js +67 -0
- package/src/managers/ReactionUserManager.js +71 -0
- package/src/managers/RelationshipManager.js +258 -0
- package/src/managers/RoleManager.js +352 -0
- package/src/managers/SessionManager.js +57 -0
- package/src/managers/StageInstanceManager.js +162 -0
- package/src/managers/ThreadManager.js +207 -0
- package/src/managers/ThreadMemberManager.js +186 -0
- package/src/managers/UserManager.js +150 -0
- package/src/managers/VoiceStateManager.js +37 -0
- package/src/rest/APIRequest.js +136 -0
- package/src/rest/APIRouter.js +53 -0
- package/src/rest/CaptchaSolver.js +78 -0
- package/src/rest/DiscordAPIError.js +103 -0
- package/src/rest/HTTPError.js +62 -0
- package/src/rest/RESTManager.js +81 -0
- package/src/rest/RateLimitError.js +55 -0
- package/src/rest/RequestHandler.js +446 -0
- package/src/sharding/Shard.js +443 -0
- package/src/sharding/ShardClientUtil.js +275 -0
- package/src/sharding/ShardingManager.js +318 -0
- package/src/structures/AnonymousGuild.js +98 -0
- package/src/structures/ApplicationCommand.js +1028 -0
- package/src/structures/ApplicationRoleConnectionMetadata.js +45 -0
- package/src/structures/AutoModerationActionExecution.js +89 -0
- package/src/structures/AutoModerationRule.js +294 -0
- package/src/structures/AutocompleteInteraction.js +106 -0
- package/src/structures/Base.js +43 -0
- package/src/structures/BaseCommandInteraction.js +211 -0
- package/src/structures/BaseGuild.js +116 -0
- package/src/structures/BaseGuildEmoji.js +56 -0
- package/src/structures/BaseGuildTextChannel.js +193 -0
- package/src/structures/BaseGuildVoiceChannel.js +243 -0
- package/src/structures/BaseMessageComponent.js +114 -0
- package/src/structures/ButtonInteraction.js +11 -0
- package/src/structures/Call.js +58 -0
- package/src/structures/CategoryChannel.js +83 -0
- package/src/structures/Channel.js +271 -0
- package/src/structures/ClientApplication.js +204 -0
- package/src/structures/ClientPresence.js +84 -0
- package/src/structures/ClientUser.js +624 -0
- package/src/structures/CommandInteraction.js +41 -0
- package/src/structures/CommandInteractionOptionResolver.js +276 -0
- package/src/structures/ContextMenuInteraction.js +65 -0
- package/src/structures/DMChannel.js +280 -0
- package/src/structures/DeveloperPortalApplication.js +520 -0
- package/src/structures/DirectoryChannel.js +20 -0
- package/src/structures/Emoji.js +148 -0
- package/src/structures/ForumChannel.js +271 -0
- package/src/structures/Guild.js +1744 -0
- package/src/structures/GuildAuditLogs.js +734 -0
- package/src/structures/GuildBan.js +59 -0
- package/src/structures/GuildBoost.js +108 -0
- package/src/structures/GuildChannel.js +454 -0
- package/src/structures/GuildEmoji.js +161 -0
- package/src/structures/GuildFolder.js +75 -0
- package/src/structures/GuildMember.js +686 -0
- package/src/structures/GuildPreview.js +191 -0
- package/src/structures/GuildPreviewEmoji.js +27 -0
- package/src/structures/GuildScheduledEvent.js +441 -0
- package/src/structures/GuildTemplate.js +236 -0
- package/src/structures/Integration.js +188 -0
- package/src/structures/IntegrationApplication.js +96 -0
- package/src/structures/Interaction.js +351 -0
- package/src/structures/InteractionCollector.js +248 -0
- package/src/structures/InteractionResponse.js +114 -0
- package/src/structures/InteractionWebhook.js +43 -0
- package/src/structures/Invite.js +375 -0
- package/src/structures/InviteGuild.js +23 -0
- package/src/structures/InviteStageInstance.js +86 -0
- package/src/structures/Message.js +1188 -0
- package/src/structures/MessageActionRow.js +103 -0
- package/src/structures/MessageAttachment.js +193 -0
- package/src/structures/MessageButton.js +231 -0
- package/src/structures/MessageCollector.js +146 -0
- package/src/structures/MessageComponentInteraction.js +120 -0
- package/src/structures/MessageContextMenuInteraction.js +20 -0
- package/src/structures/MessageEmbed.js +586 -0
- package/src/structures/MessageMentions.js +272 -0
- package/src/structures/MessagePayload.js +358 -0
- package/src/structures/MessageReaction.js +171 -0
- package/src/structures/MessageSelectMenu.js +391 -0
- package/src/structures/Modal.js +279 -0
- package/src/structures/ModalSubmitFieldsResolver.js +53 -0
- package/src/structures/ModalSubmitInteraction.js +119 -0
- package/src/structures/NewsChannel.js +32 -0
- package/src/structures/OAuth2Guild.js +28 -0
- package/src/structures/PartialGroupDMChannel.js +430 -0
- package/src/structures/PermissionOverwrites.js +196 -0
- package/src/structures/Presence.js +441 -0
- package/src/structures/ReactionCollector.js +229 -0
- package/src/structures/ReactionEmoji.js +31 -0
- package/src/structures/RichPresence.js +722 -0
- package/src/structures/Role.js +515 -0
- package/src/structures/SelectMenuInteraction.js +170 -0
- package/src/structures/Session.js +81 -0
- package/src/structures/StageChannel.js +104 -0
- package/src/structures/StageInstance.js +208 -0
- package/src/structures/Sticker.js +310 -0
- package/src/structures/StickerPack.js +95 -0
- package/src/structures/StoreChannel.js +56 -0
- package/src/structures/Team.js +167 -0
- package/src/structures/TeamMember.js +71 -0
- package/src/structures/TextChannel.js +33 -0
- package/src/structures/TextInputComponent.js +201 -0
- package/src/structures/ThreadChannel.js +626 -0
- package/src/structures/ThreadMember.js +105 -0
- package/src/structures/Typing.js +74 -0
- package/src/structures/User.js +697 -0
- package/src/structures/UserContextMenuInteraction.js +29 -0
- package/src/structures/VoiceChannel.js +110 -0
- package/src/structures/VoiceRegion.js +53 -0
- package/src/structures/VoiceState.js +306 -0
- package/src/structures/WebEmbed.js +401 -0
- package/src/structures/Webhook.js +461 -0
- package/src/structures/WelcomeChannel.js +60 -0
- package/src/structures/WelcomeScreen.js +48 -0
- package/src/structures/Widget.js +87 -0
- package/src/structures/WidgetMember.js +99 -0
- package/src/structures/interfaces/Application.js +190 -0
- package/src/structures/interfaces/Collector.js +300 -0
- package/src/structures/interfaces/InteractionResponses.js +313 -0
- package/src/structures/interfaces/TextBasedChannel.js +566 -0
- package/src/util/ActivityFlags.js +44 -0
- package/src/util/ApplicationFlags.js +74 -0
- package/src/util/BitField.js +170 -0
- package/src/util/ChannelFlags.js +45 -0
- package/src/util/Constants.js +1917 -0
- package/src/util/DataResolver.js +145 -0
- package/src/util/Formatters.js +214 -0
- package/src/util/GuildMemberFlags.js +43 -0
- package/src/util/Intents.js +74 -0
- package/src/util/LimitedCollection.js +131 -0
- package/src/util/MessageFlags.js +54 -0
- package/src/util/Options.js +360 -0
- package/src/util/Permissions.js +187 -0
- package/src/util/PremiumUsageFlags.js +31 -0
- package/src/util/PurchasedFlags.js +31 -0
- package/src/util/RemoteAuth.js +522 -0
- package/src/util/SnowflakeUtil.js +92 -0
- package/src/util/Sweepers.js +466 -0
- package/src/util/SystemChannelFlags.js +55 -0
- package/src/util/ThreadMemberFlags.js +30 -0
- package/src/util/UserFlags.js +104 -0
- package/src/util/Util.js +741 -0
- package/src/util/Voice.js +1456 -0
- package/src/util/arRPC/index.js +229 -0
- package/src/util/arRPC/process/detectable.json +1 -0
- package/src/util/arRPC/process/index.js +102 -0
- package/src/util/arRPC/process/native/index.js +5 -0
- package/src/util/arRPC/process/native/linux.js +37 -0
- package/src/util/arRPC/process/native/win32.js +25 -0
- package/src/util/arRPC/transports/ipc.js +281 -0
- package/src/util/arRPC/transports/websocket.js +128 -0
- package/typings/enums.d.ts +346 -0
- package/typings/index.d.ts +7725 -0
- package/typings/index.test-d.ts +0 -0
- package/typings/rawDataTypes.d.ts +283 -0
@@ -0,0 +1,908 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const EventEmitter = require('node:events');
|
4
|
+
const { setTimeout, setInterval, clearTimeout } = require('node:timers');
|
5
|
+
const WebSocket = require('../../WebSocket');
|
6
|
+
const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants');
|
7
|
+
const Intents = require('../../util/Intents');
|
8
|
+
|
9
|
+
const STATUS_KEYS = Object.keys(Status);
|
10
|
+
const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
|
11
|
+
|
12
|
+
let zlib;
|
13
|
+
|
14
|
+
try {
|
15
|
+
zlib = require('zlib-sync');
|
16
|
+
} catch {} // eslint-disable-line no-empty
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Represents a Shard's WebSocket connection
|
20
|
+
*/
|
21
|
+
class WebSocketShard extends EventEmitter {
|
22
|
+
constructor(manager, id) {
|
23
|
+
super();
|
24
|
+
|
25
|
+
/**
|
26
|
+
* The WebSocketManager of the shard
|
27
|
+
* @type {WebSocketManager}
|
28
|
+
*/
|
29
|
+
this.manager = manager;
|
30
|
+
|
31
|
+
/**
|
32
|
+
* The shard's id
|
33
|
+
* @type {number}
|
34
|
+
*/
|
35
|
+
this.id = id;
|
36
|
+
|
37
|
+
/**
|
38
|
+
* The current status of the shard
|
39
|
+
* @type {Status}
|
40
|
+
*/
|
41
|
+
this.status = Status.IDLE;
|
42
|
+
|
43
|
+
/**
|
44
|
+
* The current sequence of the shard
|
45
|
+
* @type {number}
|
46
|
+
* @private
|
47
|
+
*/
|
48
|
+
this.sequence = -1;
|
49
|
+
|
50
|
+
/**
|
51
|
+
* The sequence of the shard after close
|
52
|
+
* @type {number}
|
53
|
+
* @private
|
54
|
+
*/
|
55
|
+
this.closeSequence = 0;
|
56
|
+
|
57
|
+
/**
|
58
|
+
* The current session id of the shard
|
59
|
+
* @type {?string}
|
60
|
+
* @private
|
61
|
+
*/
|
62
|
+
this.sessionId = null;
|
63
|
+
|
64
|
+
/**
|
65
|
+
* URL to use when resuming
|
66
|
+
* @type {?string}
|
67
|
+
* @private
|
68
|
+
*/
|
69
|
+
this.resumeURL = null;
|
70
|
+
|
71
|
+
/**
|
72
|
+
* The previous heartbeat ping of the shard
|
73
|
+
* @type {number}
|
74
|
+
*/
|
75
|
+
this.ping = -1;
|
76
|
+
|
77
|
+
/**
|
78
|
+
* The last time a ping was sent (a timestamp)
|
79
|
+
* @type {number}
|
80
|
+
* @private
|
81
|
+
*/
|
82
|
+
this.lastPingTimestamp = -1;
|
83
|
+
|
84
|
+
/**
|
85
|
+
* If we received a heartbeat ack back. Used to identify zombie connections
|
86
|
+
* @type {boolean}
|
87
|
+
* @private
|
88
|
+
*/
|
89
|
+
this.lastHeartbeatAcked = true;
|
90
|
+
|
91
|
+
/**
|
92
|
+
* Used to prevent calling {@link WebSocketShard#event:close} twice while closing or terminating the WebSocket.
|
93
|
+
* @type {boolean}
|
94
|
+
* @private
|
95
|
+
*/
|
96
|
+
this.closeEmitted = false;
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Contains the rate limit queue and metadata
|
100
|
+
* @name WebSocketShard#ratelimit
|
101
|
+
* @type {Object}
|
102
|
+
* @private
|
103
|
+
*/
|
104
|
+
Object.defineProperty(this, 'ratelimit', {
|
105
|
+
value: {
|
106
|
+
queue: [],
|
107
|
+
total: 120,
|
108
|
+
remaining: 120,
|
109
|
+
time: 60e3,
|
110
|
+
timer: null,
|
111
|
+
},
|
112
|
+
});
|
113
|
+
|
114
|
+
/**
|
115
|
+
* The WebSocket connection for the current shard
|
116
|
+
* @name WebSocketShard#connection
|
117
|
+
* @type {?WebSocket}
|
118
|
+
* @private
|
119
|
+
*/
|
120
|
+
Object.defineProperty(this, 'connection', { value: null, writable: true });
|
121
|
+
|
122
|
+
/**
|
123
|
+
* @external Inflate
|
124
|
+
* @see {@link https://www.npmjs.com/package/zlib-sync}
|
125
|
+
*/
|
126
|
+
|
127
|
+
/**
|
128
|
+
* The compression to use
|
129
|
+
* @name WebSocketShard#inflate
|
130
|
+
* @type {?Inflate}
|
131
|
+
* @private
|
132
|
+
*/
|
133
|
+
Object.defineProperty(this, 'inflate', { value: null, writable: true });
|
134
|
+
|
135
|
+
/**
|
136
|
+
* The HELLO timeout
|
137
|
+
* @name WebSocketShard#helloTimeout
|
138
|
+
* @type {?NodeJS.Timeout}
|
139
|
+
* @private
|
140
|
+
*/
|
141
|
+
Object.defineProperty(this, 'helloTimeout', { value: null, writable: true });
|
142
|
+
|
143
|
+
/**
|
144
|
+
* The WebSocket timeout.
|
145
|
+
* @name WebSocketShard#wsCloseTimeout
|
146
|
+
* @type {?NodeJS.Timeout}
|
147
|
+
* @private
|
148
|
+
*/
|
149
|
+
Object.defineProperty(this, 'wsCloseTimeout', { value: null, writable: true });
|
150
|
+
|
151
|
+
/**
|
152
|
+
* If the manager attached its event handlers on the shard
|
153
|
+
* @name WebSocketShard#eventsAttached
|
154
|
+
* @type {boolean}
|
155
|
+
* @private
|
156
|
+
*/
|
157
|
+
Object.defineProperty(this, 'eventsAttached', { value: false, writable: true });
|
158
|
+
|
159
|
+
/**
|
160
|
+
* A set of guild ids this shard expects to receive
|
161
|
+
* @name WebSocketShard#expectedGuilds
|
162
|
+
* @type {?Set<string>}
|
163
|
+
* @private
|
164
|
+
*/
|
165
|
+
Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true });
|
166
|
+
|
167
|
+
/**
|
168
|
+
* The ready timeout
|
169
|
+
* @name WebSocketShard#readyTimeout
|
170
|
+
* @type {?NodeJS.Timeout}
|
171
|
+
* @private
|
172
|
+
*/
|
173
|
+
Object.defineProperty(this, 'readyTimeout', { value: null, writable: true });
|
174
|
+
|
175
|
+
/**
|
176
|
+
* Time when the WebSocket connection was opened
|
177
|
+
* @name WebSocketShard#connectedAt
|
178
|
+
* @type {number}
|
179
|
+
* @private
|
180
|
+
*/
|
181
|
+
Object.defineProperty(this, 'connectedAt', { value: 0, writable: true });
|
182
|
+
}
|
183
|
+
|
184
|
+
/**
|
185
|
+
* Emits a debug event.
|
186
|
+
* @param {string} message The debug message
|
187
|
+
* @private
|
188
|
+
*/
|
189
|
+
debug(message) {
|
190
|
+
this.manager.debug(message, this);
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Connects the shard to the gateway.
|
195
|
+
* @private
|
196
|
+
* @returns {Promise<void>} A promise that will resolve if the shard turns ready successfully,
|
197
|
+
* or reject if we couldn't connect
|
198
|
+
*/
|
199
|
+
connect() {
|
200
|
+
const { client } = this.manager;
|
201
|
+
|
202
|
+
const gateway = this.resumeURL ?? this.manager.gateway;
|
203
|
+
|
204
|
+
if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.READY) {
|
205
|
+
return Promise.resolve();
|
206
|
+
}
|
207
|
+
|
208
|
+
return new Promise((resolve, reject) => {
|
209
|
+
const cleanup = () => {
|
210
|
+
this.removeListener(ShardEvents.CLOSE, onClose);
|
211
|
+
this.removeListener(ShardEvents.READY, onReady);
|
212
|
+
this.removeListener(ShardEvents.RESUMED, onResumed);
|
213
|
+
this.removeListener(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
|
214
|
+
this.removeListener(ShardEvents.DESTROYED, onInvalidOrDestroyed);
|
215
|
+
};
|
216
|
+
|
217
|
+
const onReady = () => {
|
218
|
+
cleanup();
|
219
|
+
resolve();
|
220
|
+
};
|
221
|
+
|
222
|
+
const onResumed = () => {
|
223
|
+
cleanup();
|
224
|
+
resolve();
|
225
|
+
};
|
226
|
+
|
227
|
+
const onClose = event => {
|
228
|
+
cleanup();
|
229
|
+
reject(event);
|
230
|
+
};
|
231
|
+
|
232
|
+
const onInvalidOrDestroyed = () => {
|
233
|
+
cleanup();
|
234
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
235
|
+
reject();
|
236
|
+
};
|
237
|
+
|
238
|
+
this.once(ShardEvents.READY, onReady);
|
239
|
+
this.once(ShardEvents.RESUMED, onResumed);
|
240
|
+
this.once(ShardEvents.CLOSE, onClose);
|
241
|
+
this.once(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
|
242
|
+
this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed);
|
243
|
+
|
244
|
+
if (this.connection?.readyState === WebSocket.OPEN) {
|
245
|
+
this.debug('An open connection was found, attempting an immediate identify.');
|
246
|
+
this.identify();
|
247
|
+
return;
|
248
|
+
}
|
249
|
+
|
250
|
+
if (this.connection) {
|
251
|
+
this.debug(`A connection object was found. Cleaning up before continuing.
|
252
|
+
State: ${CONNECTION_STATE[this.connection.readyState]}`);
|
253
|
+
this.destroy({ emit: false });
|
254
|
+
}
|
255
|
+
|
256
|
+
const wsQuery = { v: client.options.ws.version };
|
257
|
+
|
258
|
+
if (zlib) {
|
259
|
+
this.inflate = new zlib.Inflate({
|
260
|
+
chunkSize: 65535,
|
261
|
+
flush: zlib.Z_SYNC_FLUSH,
|
262
|
+
to: WebSocket.encoding === 'json' ? 'string' : '',
|
263
|
+
});
|
264
|
+
wsQuery.compress = 'zlib-stream';
|
265
|
+
}
|
266
|
+
|
267
|
+
this.debug(
|
268
|
+
`[CONNECT]
|
269
|
+
Gateway : ${gateway}
|
270
|
+
Version : ${client.options.ws.version}
|
271
|
+
Encoding : ${WebSocket.encoding}
|
272
|
+
Compression: ${zlib ? 'zlib-stream' : 'none'}
|
273
|
+
Proxy : ${client.options.proxy || 'none'}`,
|
274
|
+
);
|
275
|
+
|
276
|
+
this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING;
|
277
|
+
this.setHelloTimeout();
|
278
|
+
this.setWsCloseTimeout(-1);
|
279
|
+
this.connectedAt = Date.now();
|
280
|
+
|
281
|
+
let args = { handshakeTimeout: 30_000 };
|
282
|
+
if (client.options.proxy.length > 0) {
|
283
|
+
const proxy = require('proxy-agent');
|
284
|
+
args.agent = new proxy(client.options.proxy);
|
285
|
+
this.debug(`Using proxy ${client.options.proxy}`, args);
|
286
|
+
}
|
287
|
+
// Adding a handshake timeout to just make sure no zombie connection appears.
|
288
|
+
const ws = (this.connection = WebSocket.create(gateway, wsQuery, args));
|
289
|
+
ws.onopen = this.onOpen.bind(this);
|
290
|
+
ws.onmessage = this.onMessage.bind(this);
|
291
|
+
ws.onerror = this.onError.bind(this);
|
292
|
+
ws.onclose = this.onClose.bind(this);
|
293
|
+
});
|
294
|
+
}
|
295
|
+
|
296
|
+
/**
|
297
|
+
* Called whenever a connection is opened to the gateway.
|
298
|
+
* @private
|
299
|
+
*/
|
300
|
+
onOpen() {
|
301
|
+
this.debug(`[CONNECTED] Took ${Date.now() - this.connectedAt}ms`);
|
302
|
+
this.status = Status.NEARLY;
|
303
|
+
}
|
304
|
+
|
305
|
+
/**
|
306
|
+
* Called whenever a message is received.
|
307
|
+
* @param {MessageEvent} event Event received
|
308
|
+
* @private
|
309
|
+
*/
|
310
|
+
onMessage({ data }) {
|
311
|
+
let raw;
|
312
|
+
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
313
|
+
if (zlib) {
|
314
|
+
const l = data.length;
|
315
|
+
const flush =
|
316
|
+
l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff;
|
317
|
+
|
318
|
+
this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH);
|
319
|
+
if (!flush) return;
|
320
|
+
raw = this.inflate.result;
|
321
|
+
} else {
|
322
|
+
raw = data;
|
323
|
+
}
|
324
|
+
let packet;
|
325
|
+
try {
|
326
|
+
packet = WebSocket.unpack(raw);
|
327
|
+
} catch (err) {
|
328
|
+
this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
|
329
|
+
return;
|
330
|
+
}
|
331
|
+
this.manager.client.emit(Events.RAW, packet, this.id);
|
332
|
+
if (packet.op === Opcodes.DISPATCH) this.manager.emit(packet.t, packet.d, this.id);
|
333
|
+
this.onPacket(packet);
|
334
|
+
}
|
335
|
+
|
336
|
+
/**
|
337
|
+
* Called whenever an error occurs with the WebSocket.
|
338
|
+
* @param {ErrorEvent} event The error that occurred
|
339
|
+
* @private
|
340
|
+
*/
|
341
|
+
onError(event) {
|
342
|
+
const error = event?.error ?? event;
|
343
|
+
if (!error) return;
|
344
|
+
|
345
|
+
/**
|
346
|
+
* Emitted whenever a shard's WebSocket encounters a connection error.
|
347
|
+
* @event Client#shardError
|
348
|
+
* @param {Error} error The encountered error
|
349
|
+
* @param {number} shardId The shard that encountered this error
|
350
|
+
*/
|
351
|
+
this.manager.client.emit(Events.SHARD_ERROR, error, this.id);
|
352
|
+
}
|
353
|
+
|
354
|
+
/**
|
355
|
+
* @external CloseEvent
|
356
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
|
357
|
+
*/
|
358
|
+
|
359
|
+
/**
|
360
|
+
* @external ErrorEvent
|
361
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent}
|
362
|
+
*/
|
363
|
+
|
364
|
+
/**
|
365
|
+
* @external MessageEvent
|
366
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}
|
367
|
+
*/
|
368
|
+
|
369
|
+
/**
|
370
|
+
* Called whenever a connection to the gateway is closed.
|
371
|
+
* @param {CloseEvent} event Close event that was received
|
372
|
+
* @private
|
373
|
+
*/
|
374
|
+
onClose(event) {
|
375
|
+
this.closeEmitted = true;
|
376
|
+
if (this.sequence !== -1) this.closeSequence = this.sequence;
|
377
|
+
this.sequence = -1;
|
378
|
+
this.setHeartbeatTimer(-1);
|
379
|
+
this.setHelloTimeout(-1);
|
380
|
+
// Clearing the WebSocket close timeout as close was emitted.
|
381
|
+
this.setWsCloseTimeout(-1);
|
382
|
+
// If we still have a connection object, clean up its listeners
|
383
|
+
if (this.connection) {
|
384
|
+
this._cleanupConnection();
|
385
|
+
// Having this after _cleanupConnection to just clean up the connection and not listen to ws.onclose
|
386
|
+
this.destroy({ reset: !this.sessionId, emit: false, log: false });
|
387
|
+
}
|
388
|
+
this.status = Status.DISCONNECTED;
|
389
|
+
this.emitClose(event);
|
390
|
+
}
|
391
|
+
|
392
|
+
/**
|
393
|
+
* This method is responsible to emit close event for this shard.
|
394
|
+
* This method helps the shard reconnect.
|
395
|
+
* @param {CloseEvent} [event] Close event that was received
|
396
|
+
*/
|
397
|
+
emitClose(
|
398
|
+
event = {
|
399
|
+
code: 1011,
|
400
|
+
reason: WSCodes[1011],
|
401
|
+
wasClean: false,
|
402
|
+
},
|
403
|
+
) {
|
404
|
+
this.debug(`[CLOSE]
|
405
|
+
Event Code: ${event.code}
|
406
|
+
Clean : ${event.wasClean}
|
407
|
+
Reason : ${event.reason ?? 'No reason received'}`);
|
408
|
+
/**
|
409
|
+
* Emitted when a shard's WebSocket closes.
|
410
|
+
* @private
|
411
|
+
* @event WebSocketShard#close
|
412
|
+
* @param {CloseEvent} event The received event
|
413
|
+
*/
|
414
|
+
this.emit(ShardEvents.CLOSE, event);
|
415
|
+
}
|
416
|
+
|
417
|
+
/**
|
418
|
+
* Called whenever a packet is received.
|
419
|
+
* @param {Object} packet The received packet
|
420
|
+
* @private
|
421
|
+
*/
|
422
|
+
onPacket(packet) {
|
423
|
+
if (!packet) {
|
424
|
+
this.debug(`Received broken packet: '${packet}'.`);
|
425
|
+
return;
|
426
|
+
}
|
427
|
+
|
428
|
+
switch (packet.t) {
|
429
|
+
case WSEvents.READY:
|
430
|
+
/**
|
431
|
+
* Emitted when the shard receives the READY payload and is now waiting for guilds
|
432
|
+
* @event WebSocketShard#ready
|
433
|
+
*/
|
434
|
+
this.emit(ShardEvents.READY);
|
435
|
+
|
436
|
+
this.sessionId = packet.d.session_id;
|
437
|
+
this.resumeURL = packet.d.resume_gateway_url;
|
438
|
+
this.expectedGuilds = new Set(packet.d.guilds.filter(d => d?.unavailable == true).map(d => d.id));
|
439
|
+
this.status = Status.WAITING_FOR_GUILDS;
|
440
|
+
this.debug(`[READY] Session ${this.sessionId} | ResumeURL ${this.resumeURL}`);
|
441
|
+
this.lastHeartbeatAcked = true;
|
442
|
+
this.sendHeartbeat('ReadyHeartbeat');
|
443
|
+
break;
|
444
|
+
case WSEvents.RESUMED: {
|
445
|
+
/**
|
446
|
+
* Emitted when the shard resumes successfully
|
447
|
+
* @event WebSocketShard#resumed
|
448
|
+
*/
|
449
|
+
this.emit(ShardEvents.RESUMED);
|
450
|
+
|
451
|
+
this.status = Status.READY;
|
452
|
+
const replayed = packet.s - this.closeSequence;
|
453
|
+
this.debug(`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`);
|
454
|
+
this.lastHeartbeatAcked = true;
|
455
|
+
this.sendHeartbeat('ResumeHeartbeat');
|
456
|
+
break;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
if (packet.s > this.sequence) this.sequence = packet.s;
|
461
|
+
|
462
|
+
switch (packet.op) {
|
463
|
+
case Opcodes.HELLO:
|
464
|
+
this.setHelloTimeout(-1);
|
465
|
+
this.setHeartbeatTimer(packet.d.heartbeat_interval);
|
466
|
+
this.identify();
|
467
|
+
break;
|
468
|
+
case Opcodes.RECONNECT:
|
469
|
+
this.debug('[RECONNECT] Discord asked us to reconnect');
|
470
|
+
this.destroy({ closeCode: 4_000 });
|
471
|
+
break;
|
472
|
+
case Opcodes.INVALID_SESSION:
|
473
|
+
this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`);
|
474
|
+
// If we can resume the session, do so immediately
|
475
|
+
if (packet.d) {
|
476
|
+
this.identifyResume();
|
477
|
+
return;
|
478
|
+
}
|
479
|
+
// Reset the sequence
|
480
|
+
this.sequence = -1;
|
481
|
+
// Reset the session id as it's invalid
|
482
|
+
this.sessionId = null;
|
483
|
+
// Set the status to reconnecting
|
484
|
+
this.status = Status.RECONNECTING;
|
485
|
+
// Finally, emit the INVALID_SESSION event
|
486
|
+
/**
|
487
|
+
* Emitted when the session has been invalidated.
|
488
|
+
* @event WebSocketShard#invalidSession
|
489
|
+
*/
|
490
|
+
this.emit(ShardEvents.INVALID_SESSION);
|
491
|
+
break;
|
492
|
+
case Opcodes.HEARTBEAT_ACK:
|
493
|
+
this.ackHeartbeat();
|
494
|
+
break;
|
495
|
+
case Opcodes.HEARTBEAT:
|
496
|
+
this.sendHeartbeat('HeartbeatRequest', true);
|
497
|
+
break;
|
498
|
+
default:
|
499
|
+
this.manager.handlePacket(packet, this);
|
500
|
+
if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) {
|
501
|
+
this.expectedGuilds.delete(packet.d.id);
|
502
|
+
this.checkReady();
|
503
|
+
}
|
504
|
+
}
|
505
|
+
}
|
506
|
+
|
507
|
+
/**
|
508
|
+
* Checks if the shard can be marked as ready
|
509
|
+
* @private
|
510
|
+
*/
|
511
|
+
checkReady() {
|
512
|
+
// Step 0. Clear the ready timeout, if it exists
|
513
|
+
if (this.readyTimeout) {
|
514
|
+
clearTimeout(this.readyTimeout);
|
515
|
+
this.readyTimeout = null;
|
516
|
+
}
|
517
|
+
// Step 1. If we don't have any other guilds pending, we are ready
|
518
|
+
if (!this.expectedGuilds.size) {
|
519
|
+
this.debug('Shard received all its guilds. Marking as fully ready.');
|
520
|
+
this.status = Status.READY;
|
521
|
+
|
522
|
+
/**
|
523
|
+
* Emitted when the shard is fully ready.
|
524
|
+
* This event is emitted if:
|
525
|
+
* * all guilds were received by this shard
|
526
|
+
* * the ready timeout expired, and some guilds are unavailable
|
527
|
+
* @event WebSocketShard#allReady
|
528
|
+
* @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any
|
529
|
+
*/
|
530
|
+
this.emit(ShardEvents.ALL_READY);
|
531
|
+
return;
|
532
|
+
}
|
533
|
+
const hasGuildsIntent = new Intents(this.manager.client.options.intents).has(Intents.FLAGS.GUILDS);
|
534
|
+
// Step 2. Create a timeout that will mark the shard as ready if there are still unavailable guilds
|
535
|
+
// * The timeout is 15 seconds by default
|
536
|
+
// * This can be optionally changed in the client options via the `waitGuildTimeout` option
|
537
|
+
// * a timeout time of zero will skip this timeout, which potentially could cause the Client to miss guilds.
|
538
|
+
|
539
|
+
const { waitGuildTimeout } = this.manager.client.options;
|
540
|
+
|
541
|
+
this.readyTimeout = setTimeout(() => {
|
542
|
+
this.debug(
|
543
|
+
`Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` +
|
544
|
+
`${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${
|
545
|
+
this.expectedGuilds.size
|
546
|
+
}`,
|
547
|
+
);
|
548
|
+
|
549
|
+
this.readyTimeout = null;
|
550
|
+
|
551
|
+
this.status = Status.READY;
|
552
|
+
|
553
|
+
this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
|
554
|
+
// }, hasGuildsIntent && waitGuildTimeout).unref();
|
555
|
+
}, waitGuildTimeout).unref();
|
556
|
+
}
|
557
|
+
|
558
|
+
/**
|
559
|
+
* Sets the HELLO packet timeout.
|
560
|
+
* @param {number} [time] If set to -1, it will clear the hello timeout
|
561
|
+
* @private
|
562
|
+
*/
|
563
|
+
setHelloTimeout(time) {
|
564
|
+
if (time === -1) {
|
565
|
+
if (this.helloTimeout) {
|
566
|
+
this.debug('Clearing the HELLO timeout.');
|
567
|
+
clearTimeout(this.helloTimeout);
|
568
|
+
this.helloTimeout = null;
|
569
|
+
}
|
570
|
+
return;
|
571
|
+
}
|
572
|
+
this.debug('Setting a HELLO timeout for 20s.');
|
573
|
+
this.helloTimeout = setTimeout(() => {
|
574
|
+
this.debug('Did not receive HELLO in time. Destroying and connecting again.');
|
575
|
+
this.destroy({ reset: true, closeCode: 4009 });
|
576
|
+
}, 20_000).unref();
|
577
|
+
}
|
578
|
+
|
579
|
+
/**
|
580
|
+
* Sets the WebSocket Close timeout.
|
581
|
+
* This method is responsible for detecting any zombie connections if the WebSocket fails to close properly.
|
582
|
+
* @param {number} [time] If set to -1, it will clear the timeout
|
583
|
+
* @private
|
584
|
+
*/
|
585
|
+
setWsCloseTimeout(time) {
|
586
|
+
if (this.wsCloseTimeout) {
|
587
|
+
this.debug('[WebSocket] Clearing the close timeout.');
|
588
|
+
clearTimeout(this.wsCloseTimeout);
|
589
|
+
}
|
590
|
+
if (time === -1) {
|
591
|
+
this.wsCloseTimeout = null;
|
592
|
+
return;
|
593
|
+
}
|
594
|
+
this.wsCloseTimeout = setTimeout(() => {
|
595
|
+
this.setWsCloseTimeout(-1);
|
596
|
+
// Check if close event was emitted.
|
597
|
+
if (this.closeEmitted) {
|
598
|
+
this.debug(`[WebSocket] close was already emitted, assuming the connection was closed properly.`);
|
599
|
+
// Setting the variable false to check for zombie connections.
|
600
|
+
this.closeEmitted = false;
|
601
|
+
return;
|
602
|
+
}
|
603
|
+
|
604
|
+
this.debug(
|
605
|
+
// eslint-disable-next-line max-len
|
606
|
+
`[WebSocket] Close Emitted: ${this.closeEmitted} | did not close properly, assuming a zombie connection.\nEmitting close and reconnecting again.`,
|
607
|
+
);
|
608
|
+
|
609
|
+
if (this.connection) this._cleanupConnection();
|
610
|
+
|
611
|
+
this.emitClose({
|
612
|
+
code: 4009,
|
613
|
+
reason: 'Session time out.',
|
614
|
+
wasClean: false,
|
615
|
+
});
|
616
|
+
}, time);
|
617
|
+
}
|
618
|
+
|
619
|
+
/**
|
620
|
+
* Sets the heartbeat timer for this shard.
|
621
|
+
* @param {number} time If -1, clears the interval, any other number sets an interval
|
622
|
+
* @private
|
623
|
+
*/
|
624
|
+
setHeartbeatTimer(time) {
|
625
|
+
if (time === -1) {
|
626
|
+
if (this.heartbeatInterval) {
|
627
|
+
this.debug('Clearing the heartbeat interval.');
|
628
|
+
clearInterval(this.heartbeatInterval);
|
629
|
+
this.heartbeatInterval = null;
|
630
|
+
}
|
631
|
+
return;
|
632
|
+
}
|
633
|
+
this.debug(`Setting a heartbeat interval for ${time}ms.`);
|
634
|
+
// Sanity checks
|
635
|
+
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
636
|
+
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), time).unref();
|
637
|
+
}
|
638
|
+
|
639
|
+
/**
|
640
|
+
* Sends a heartbeat to the WebSocket.
|
641
|
+
* If this shard didn't receive a heartbeat last time, it will destroy it and reconnect
|
642
|
+
* @param {string} [tag='HeartbeatTimer'] What caused this heartbeat to be sent
|
643
|
+
* @param {boolean} [ignoreHeartbeatAck] If we should send the heartbeat forcefully.
|
644
|
+
* @private
|
645
|
+
*/
|
646
|
+
sendHeartbeat(
|
647
|
+
tag = 'HeartbeatTimer',
|
648
|
+
ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status),
|
649
|
+
) {
|
650
|
+
if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
|
651
|
+
this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);
|
652
|
+
} else if (!this.lastHeartbeatAcked) {
|
653
|
+
this.debug(
|
654
|
+
`[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.
|
655
|
+
Status : ${STATUS_KEYS[this.status]}
|
656
|
+
Sequence : ${this.sequence}
|
657
|
+
Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`,
|
658
|
+
);
|
659
|
+
|
660
|
+
this.destroy({ reset: true, closeCode: 4009 });
|
661
|
+
return;
|
662
|
+
}
|
663
|
+
|
664
|
+
this.debug(`[${tag}] Sending a heartbeat.`);
|
665
|
+
this.lastHeartbeatAcked = false;
|
666
|
+
this.lastPingTimestamp = Date.now();
|
667
|
+
this.send({ op: Opcodes.HEARTBEAT, d: this.sequence }, true);
|
668
|
+
}
|
669
|
+
|
670
|
+
/**
|
671
|
+
* Acknowledges a heartbeat.
|
672
|
+
* @private
|
673
|
+
*/
|
674
|
+
ackHeartbeat() {
|
675
|
+
this.lastHeartbeatAcked = true;
|
676
|
+
const latency = Date.now() - this.lastPingTimestamp;
|
677
|
+
this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`);
|
678
|
+
this.ping = latency;
|
679
|
+
}
|
680
|
+
|
681
|
+
/**
|
682
|
+
* Identifies the client on the connection.
|
683
|
+
* @private
|
684
|
+
* @returns {void}
|
685
|
+
*/
|
686
|
+
identify() {
|
687
|
+
return this.sessionId ? this.identifyResume() : this.identifyNew();
|
688
|
+
}
|
689
|
+
|
690
|
+
/**
|
691
|
+
* Identifies as a new connection on the gateway.
|
692
|
+
* @private
|
693
|
+
*/
|
694
|
+
identifyNew() {
|
695
|
+
const { client } = this.manager;
|
696
|
+
if (!client.token) {
|
697
|
+
this.debug('[IDENTIFY] No token available to identify a new session.');
|
698
|
+
return;
|
699
|
+
}
|
700
|
+
|
701
|
+
this.status = Status.IDENTIFYING;
|
702
|
+
|
703
|
+
// Clone the identify payload and assign the token and shard info
|
704
|
+
client.options.ws.properties = Object.assign(client.options.ws.properties, {
|
705
|
+
browser_user_agent: client.options.http.headers['User-Agent'],
|
706
|
+
});
|
707
|
+
Object.keys(client.options.ws.properties)
|
708
|
+
.filter(k => k.startsWith('$'))
|
709
|
+
.forEach(k => {
|
710
|
+
client.options.ws.properties[k.slice(1)] = client.options.ws.properties[k];
|
711
|
+
delete client.options.ws.properties[k];
|
712
|
+
});
|
713
|
+
const d = {
|
714
|
+
...client.options.ws,
|
715
|
+
// Remove, Req by dolfies_person [Reddit]: intents: Intents.resolve(client.options.intents),
|
716
|
+
token: client.token,
|
717
|
+
// Remove: shard: [this.id, Number(client.options.shardCount)],
|
718
|
+
};
|
719
|
+
|
720
|
+
this.debug(
|
721
|
+
`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: ${Intents.resolve(
|
722
|
+
client.options.intents,
|
723
|
+
)} 😊`,
|
724
|
+
);
|
725
|
+
this.send({ op: Opcodes.IDENTIFY, d }, true);
|
726
|
+
}
|
727
|
+
|
728
|
+
/**
|
729
|
+
* Resumes a session on the gateway.
|
730
|
+
* @private
|
731
|
+
*/
|
732
|
+
identifyResume() {
|
733
|
+
if (!this.sessionId) {
|
734
|
+
this.debug('[RESUME] No session id was present; identifying as a new session.');
|
735
|
+
this.identifyNew();
|
736
|
+
return;
|
737
|
+
}
|
738
|
+
|
739
|
+
this.status = Status.RESUMING;
|
740
|
+
|
741
|
+
this.debug(`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`);
|
742
|
+
|
743
|
+
const d = {
|
744
|
+
token: this.manager.client.token,
|
745
|
+
session_id: this.sessionId,
|
746
|
+
seq: this.closeSequence,
|
747
|
+
};
|
748
|
+
|
749
|
+
this.send({ op: Opcodes.RESUME, d }, true);
|
750
|
+
}
|
751
|
+
|
752
|
+
/**
|
753
|
+
* Adds a packet to the queue to be sent to the gateway.
|
754
|
+
* <warn>If you use this method, make sure you understand that you need to provide
|
755
|
+
* a full [Payload](https://discord.com/developers/docs/topics/gateway-events#payload-structure).
|
756
|
+
* Do not use this method if you don't know what you're doing.</warn>
|
757
|
+
* @param {Object} data The full packet to send
|
758
|
+
* @param {boolean} [important=false] If this packet should be added first in queue
|
759
|
+
*/
|
760
|
+
send(data, important = false) {
|
761
|
+
this.ratelimit.queue[important ? 'unshift' : 'push'](data);
|
762
|
+
this.processQueue();
|
763
|
+
}
|
764
|
+
|
765
|
+
/**
|
766
|
+
* Sends data, bypassing the queue.
|
767
|
+
* @param {Object} data Packet to send
|
768
|
+
* @returns {void}
|
769
|
+
* @private
|
770
|
+
*/
|
771
|
+
_send(data) {
|
772
|
+
if (this.connection?.readyState !== WebSocket.OPEN) {
|
773
|
+
this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`);
|
774
|
+
this.destroy({ closeCode: 4_000 });
|
775
|
+
return;
|
776
|
+
}
|
777
|
+
|
778
|
+
this.connection.send(WebSocket.pack(data), err => {
|
779
|
+
if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
|
780
|
+
});
|
781
|
+
}
|
782
|
+
|
783
|
+
/**
|
784
|
+
* Processes the current WebSocket queue.
|
785
|
+
* @returns {void}
|
786
|
+
* @private
|
787
|
+
*/
|
788
|
+
processQueue() {
|
789
|
+
if (this.ratelimit.remaining === 0) return;
|
790
|
+
if (this.ratelimit.queue.length === 0) return;
|
791
|
+
if (this.ratelimit.remaining === this.ratelimit.total) {
|
792
|
+
this.ratelimit.timer = setTimeout(() => {
|
793
|
+
this.ratelimit.remaining = this.ratelimit.total;
|
794
|
+
this.processQueue();
|
795
|
+
}, this.ratelimit.time).unref();
|
796
|
+
}
|
797
|
+
while (this.ratelimit.remaining > 0) {
|
798
|
+
const item = this.ratelimit.queue.shift();
|
799
|
+
if (!item) return;
|
800
|
+
this._send(item);
|
801
|
+
this.ratelimit.remaining--;
|
802
|
+
}
|
803
|
+
}
|
804
|
+
|
805
|
+
/**
|
806
|
+
* Destroys this shard and closes its WebSocket connection.
|
807
|
+
* @param {Object} [options={ closeCode: 1000, reset: false, emit: true, log: true }] Options for destroying the shard
|
808
|
+
* @private
|
809
|
+
*/
|
810
|
+
destroy({ closeCode = 1_000, reset = false, emit = true, log = true } = {}) {
|
811
|
+
if (log) {
|
812
|
+
this.debug(`[DESTROY]
|
813
|
+
Close Code : ${closeCode}
|
814
|
+
Reset : ${reset}
|
815
|
+
Emit DESTROYED: ${emit}`);
|
816
|
+
}
|
817
|
+
|
818
|
+
// Step 0: Remove all timers
|
819
|
+
this.setHeartbeatTimer(-1);
|
820
|
+
this.setHelloTimeout(-1);
|
821
|
+
this.debug(
|
822
|
+
`[WebSocket] Destroy: Attempting to close the WebSocket. | WS State: ${
|
823
|
+
CONNECTION_STATE[this.connection?.readyState ?? WebSocket.CLOSED]
|
824
|
+
}`,
|
825
|
+
);
|
826
|
+
// Step 1: Close the WebSocket connection, if any, otherwise, emit DESTROYED
|
827
|
+
if (this.connection) {
|
828
|
+
// If the connection is currently opened, we will (hopefully) receive close
|
829
|
+
if (this.connection.readyState === WebSocket.OPEN) {
|
830
|
+
this.connection.close(closeCode);
|
831
|
+
this.debug(`[WebSocket] Close: Tried closing. | WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
|
832
|
+
} else {
|
833
|
+
// Connection is not OPEN
|
834
|
+
this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
|
835
|
+
// Attempt to close the connection just in case
|
836
|
+
try {
|
837
|
+
this.connection.close(closeCode);
|
838
|
+
} catch (err) {
|
839
|
+
this.debug(
|
840
|
+
`[WebSocket] Close: Something went wrong while closing the WebSocket: ${
|
841
|
+
err.message || err
|
842
|
+
}. Forcefully terminating the connection | WS State: ${CONNECTION_STATE[this.connection.readyState]}`,
|
843
|
+
);
|
844
|
+
this.connection.terminate();
|
845
|
+
}
|
846
|
+
// Emit the destroyed event if needed
|
847
|
+
if (emit) this._emitDestroyed();
|
848
|
+
}
|
849
|
+
} else if (emit) {
|
850
|
+
// We requested a destroy, but we had no connection. Emit destroyed
|
851
|
+
this._emitDestroyed();
|
852
|
+
}
|
853
|
+
|
854
|
+
this.debug(
|
855
|
+
`[WebSocket] Adding a WebSocket close timeout to ensure a correct WS reconnect.
|
856
|
+
Timeout: ${this.manager.client.options.closeTimeout}ms`,
|
857
|
+
);
|
858
|
+
this.setWsCloseTimeout(this.manager.client.options.closeTimeout);
|
859
|
+
|
860
|
+
// Step 2: Null the connection object
|
861
|
+
this.connection = null;
|
862
|
+
|
863
|
+
// Step 3: Set the shard status to DISCONNECTED
|
864
|
+
this.status = Status.DISCONNECTED;
|
865
|
+
|
866
|
+
// Step 4: Cache the old sequence (use to attempt a resume)
|
867
|
+
if (this.sequence !== -1) this.closeSequence = this.sequence;
|
868
|
+
|
869
|
+
// Step 5: Reset the sequence, resume URL and session id if requested
|
870
|
+
if (reset) {
|
871
|
+
this.resumeURL = null;
|
872
|
+
this.sequence = -1;
|
873
|
+
this.sessionId = null;
|
874
|
+
}
|
875
|
+
|
876
|
+
// Step 6: reset the rate limit data
|
877
|
+
this.ratelimit.remaining = this.ratelimit.total;
|
878
|
+
this.ratelimit.queue.length = 0;
|
879
|
+
if (this.ratelimit.timer) {
|
880
|
+
clearTimeout(this.ratelimit.timer);
|
881
|
+
this.ratelimit.timer = null;
|
882
|
+
}
|
883
|
+
}
|
884
|
+
|
885
|
+
/**
|
886
|
+
* Cleans up the WebSocket connection listeners.
|
887
|
+
* @private
|
888
|
+
*/
|
889
|
+
_cleanupConnection() {
|
890
|
+
this.connection.onopen = this.connection.onclose = this.connection.onmessage = null;
|
891
|
+
this.connection.onerror = () => null;
|
892
|
+
}
|
893
|
+
|
894
|
+
/**
|
895
|
+
* Emits the DESTROYED event on the shard
|
896
|
+
* @private
|
897
|
+
*/
|
898
|
+
_emitDestroyed() {
|
899
|
+
/**
|
900
|
+
* Emitted when a shard is destroyed, but no WebSocket connection was present.
|
901
|
+
* @private
|
902
|
+
* @event WebSocketShard#destroyed
|
903
|
+
*/
|
904
|
+
this.emit(ShardEvents.DESTROYED);
|
905
|
+
}
|
906
|
+
}
|
907
|
+
|
908
|
+
module.exports = WebSocketShard;
|