discord-selfbot-v13.js 0.0.1-security → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of discord-selfbot-v13.js might be problematic. Click here for more details.
- package/LICENSE +674 -0
- package/README.md +119 -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 +42 -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;
|