@buape/carbon 0.13.0 → 0.15.0

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