fix(gateway): handle reconnects better way
This commit is contained in:
		
							parent
							
								
									1ca8b4bc67
								
							
						
					
					
						commit
						dfb99fb6b9
					
				
					 5 changed files with 67 additions and 32 deletions
				
			
		
							
								
								
									
										11
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -1,11 +0,0 @@ | ||||||
| { |  | ||||||
|   "deno.enable": true, |  | ||||||
|   "deno.lint": false, |  | ||||||
|   "deno.unstable": false, |  | ||||||
|   "deepscan.enable": true, |  | ||||||
|   "deno.import_intellisense_origins": { |  | ||||||
|     "https://deno.land": true |  | ||||||
|   }, |  | ||||||
|   "editor.tabSize": 2, |  | ||||||
|   "editor.formatOnSave": true |  | ||||||
| } |  | ||||||
							
								
								
									
										14
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -55,7 +55,11 @@ export { Invite } from './src/structures/invite.ts' | ||||||
| export * from './src/structures/member.ts' | export * from './src/structures/member.ts' | ||||||
| export { Message } from './src/structures/message.ts' | export { Message } from './src/structures/message.ts' | ||||||
| export { MessageMentions } from './src/structures/messageMentions.ts' | export { MessageMentions } from './src/structures/messageMentions.ts' | ||||||
| export { Presence, ClientPresence } from './src/structures/presence.ts' | export { | ||||||
|  |   Presence, | ||||||
|  |   ClientPresence, | ||||||
|  |   ActivityTypes | ||||||
|  | } from './src/structures/presence.ts' | ||||||
| export { Role } from './src/structures/role.ts' | export { Role } from './src/structures/role.ts' | ||||||
| export { Snowflake } from './src/structures/snowflake.ts' | export { Snowflake } from './src/structures/snowflake.ts' | ||||||
| export { TextChannel, GuildTextChannel } from './src/structures/textChannel.ts' | export { TextChannel, GuildTextChannel } from './src/structures/textChannel.ts' | ||||||
|  | @ -67,4 +71,12 @@ export { Intents } from './src/utils/intents.ts' | ||||||
| // export { getBuildInfo } from './src/utils/buildInfo.ts'
 | // export { getBuildInfo } from './src/utils/buildInfo.ts'
 | ||||||
| export * from './src/utils/permissions.ts' | export * from './src/utils/permissions.ts' | ||||||
| export { UserFlagsManager } from './src/utils/userFlags.ts' | export { UserFlagsManager } from './src/utils/userFlags.ts' | ||||||
|  | export type { EveryChannelTypes } from './src/utils/getChannelByType.ts' | ||||||
| export * from './src/utils/bitfield.ts' | export * from './src/utils/bitfield.ts' | ||||||
|  | export type { | ||||||
|  |   ActivityGame, | ||||||
|  |   ClientActivity, | ||||||
|  |   ClientStatus, | ||||||
|  |   StatusType | ||||||
|  | } from './src/types/presence.ts' | ||||||
|  | export { ChannelTypes } from './src/types/channel.ts' | ||||||
|  |  | ||||||
|  | @ -24,6 +24,8 @@ export interface RequestMembersOptions { | ||||||
|   users?: string[] |   users?: string[] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export const RECONNECT_REASON = 'harmony-reconnect' | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Handles Discord gateway connection. |  * Handles Discord gateway connection. | ||||||
|  * |  * | ||||||
|  | @ -43,6 +45,7 @@ class Gateway { | ||||||
|   private heartbeatServerResponded = false |   private heartbeatServerResponded = false | ||||||
|   client: Client |   client: Client | ||||||
|   cache: GatewayCache |   cache: GatewayCache | ||||||
|  |   private timedIdentify: number | null = null | ||||||
| 
 | 
 | ||||||
|   constructor(client: Client, token: string, intents: GatewayIntents[]) { |   constructor(client: Client, token: string, intents: GatewayIntents[]) { | ||||||
|     this.token = token |     this.token = token | ||||||
|  | @ -109,10 +112,20 @@ class Gateway { | ||||||
| 
 | 
 | ||||||
|       case GatewayOpcodes.INVALID_SESSION: |       case GatewayOpcodes.INVALID_SESSION: | ||||||
|         // Because we know this gonna be bool
 |         // Because we know this gonna be bool
 | ||||||
|         this.debug(`Invalid Session! Identifying with forced new session`) |         this.debug( | ||||||
|         // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 |           `Invalid Session received! Resumeable? ${d === true ? 'Yes' : 'No'}` | ||||||
|         // eslint-disable-next-line @typescript-eslint/promise-function-async
 |         ) | ||||||
|         setTimeout(() => this.sendIdentify(true), 3000) |         if (d !== true) { | ||||||
|  |           this.debug(`Session was invalidated, deleting from cache`) | ||||||
|  |           await this.cache.delete('session_id') | ||||||
|  |           await this.cache.delete('seq') | ||||||
|  |           this.sessionID = undefined | ||||||
|  |           this.sequenceID = undefined | ||||||
|  |         } | ||||||
|  |         this.timedIdentify = setTimeout(async () => { | ||||||
|  |           this.timedIdentify = null | ||||||
|  |           await this.sendIdentify(!(d as boolean)) | ||||||
|  |         }, 5000) | ||||||
|         break |         break | ||||||
| 
 | 
 | ||||||
|       case GatewayOpcodes.DISPATCH: { |       case GatewayOpcodes.DISPATCH: { | ||||||
|  | @ -151,6 +164,7 @@ class Gateway { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async onclose(event: CloseEvent): Promise<void> { |   private async onclose(event: CloseEvent): Promise<void> { | ||||||
|  |     if (event.reason === RECONNECT_REASON) return | ||||||
|     this.debug(`Connection Closed with code: ${event.code}`) |     this.debug(`Connection Closed with code: ${event.code}`) | ||||||
| 
 | 
 | ||||||
|     if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { |     if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { | ||||||
|  | @ -180,7 +194,7 @@ class Gateway { | ||||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|       this.reconnect() |       this.reconnect() | ||||||
|     } else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) { |     } else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) { | ||||||
|       throw new Error("Couldn't connect. Sharding is requried!") |       throw new Error("Couldn't connect. Sharding is required!") | ||||||
|     } else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) { |     } else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) { | ||||||
|       throw new Error("Invalid API Version was used. This shouldn't happen!") |       throw new Error("Invalid API Version was used. This shouldn't happen!") | ||||||
|     } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) { |     } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) { | ||||||
|  | @ -191,9 +205,12 @@ class Gateway { | ||||||
|       this.debug( |       this.debug( | ||||||
|         'Unknown Close code, probably connection error. Reconnecting in 5s.' |         'Unknown Close code, probably connection error. Reconnecting in 5s.' | ||||||
|       ) |       ) | ||||||
|  |       if (this.timedIdentify !== null) { | ||||||
|  |         clearTimeout(this.timedIdentify) | ||||||
|  |         this.debug('Timed Identify found. Cleared timeout.') | ||||||
|  |       } | ||||||
|       await delay(5000) |       await delay(5000) | ||||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |       await this.reconnect(true) | ||||||
|       this.reconnect() |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -216,13 +233,14 @@ class Gateway { | ||||||
|         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` |         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` | ||||||
|       ) |       ) | ||||||
|       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) |       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) | ||||||
|       if (forceNewSession === undefined || !forceNewSession) { |     } else this.debug('Skipping /gateway/bot because bot: false') | ||||||
|         const sessionIDCached = await this.cache.get('session_id') | 
 | ||||||
|         if (sessionIDCached !== undefined) { |     if (forceNewSession === undefined || !forceNewSession) { | ||||||
|           this.debug(`Found Cached SessionID: ${sessionIDCached}`) |       const sessionIDCached = await this.cache.get('session_id') | ||||||
|           this.sessionID = sessionIDCached |       if (sessionIDCached !== undefined) { | ||||||
|           return await this.sendResume() |         this.debug(`Found Cached SessionID: ${sessionIDCached}`) | ||||||
|         } |         this.sessionID = sessionIDCached | ||||||
|  |         return await this.sendResume() | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -255,6 +273,7 @@ class Gateway { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     this.debug('Sending Identify payload...') | ||||||
|     this.send({ |     this.send({ | ||||||
|       op: GatewayOpcodes.IDENTIFY, |       op: GatewayOpcodes.IDENTIFY, | ||||||
|       d: payload |       d: payload | ||||||
|  | @ -262,6 +281,10 @@ class Gateway { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async sendResume(): Promise<void> { |   private async sendResume(): Promise<void> { | ||||||
|  |     if (this.sessionID === undefined) { | ||||||
|  |       this.sessionID = await this.cache.get('session_id') | ||||||
|  |       if (this.sessionID === undefined) return await this.sendIdentify() | ||||||
|  |     } | ||||||
|     this.debug(`Preparing to resume with Session: ${this.sessionID}`) |     this.debug(`Preparing to resume with Session: ${this.sessionID}`) | ||||||
|     if (this.sequenceID === undefined) { |     if (this.sequenceID === undefined) { | ||||||
|       const cached = await this.cache.get('seq') |       const cached = await this.cache.get('seq') | ||||||
|  | @ -276,6 +299,7 @@ class Gateway { | ||||||
|         seq: this.sequenceID ?? null |         seq: this.sequenceID ?? null | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     this.debug('Sending Resume payload...') | ||||||
|     this.send(resumePayload) |     this.send(resumePayload) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -305,13 +329,16 @@ class Gateway { | ||||||
| 
 | 
 | ||||||
|   async reconnect(forceNew?: boolean): Promise<void> { |   async reconnect(forceNew?: boolean): Promise<void> { | ||||||
|     clearInterval(this.heartbeatIntervalID) |     clearInterval(this.heartbeatIntervalID) | ||||||
|     if (forceNew === undefined || !forceNew) |     if (forceNew === true) { | ||||||
|       await this.cache.delete('session_id') |       await this.cache.delete('session_id') | ||||||
|     this.close() |       await this.cache.delete('seq') | ||||||
|  |     } | ||||||
|  |     this.close(1000, RECONNECT_REASON) | ||||||
|     this.initWebsocket() |     this.initWebsocket() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   initWebsocket(): void { |   initWebsocket(): void { | ||||||
|  |     this.debug('Initializing WebSocket...') | ||||||
|     this.websocket = new WebSocket( |     this.websocket = new WebSocket( | ||||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 |       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 | ||||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, |       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, | ||||||
|  | @ -324,8 +351,8 @@ class Gateway { | ||||||
|     this.websocket.onerror = this.onerror.bind(this) |     this.websocket.onerror = this.onerror.bind(this) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   close(): void { |   close(code: number = 1000, reason?: string): void { | ||||||
|     this.websocket.close(1000) |     return this.websocket.close(code, reason) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   send(data: GatewayResponse): boolean { |   send(data: GatewayResponse): boolean { | ||||||
|  | @ -362,6 +389,7 @@ class Gateway { | ||||||
|     if (this.heartbeatServerResponded) { |     if (this.heartbeatServerResponded) { | ||||||
|       this.heartbeatServerResponded = false |       this.heartbeatServerResponded = false | ||||||
|     } else { |     } else { | ||||||
|  |       this.debug('Found dead connection, reconnecting...') | ||||||
|       clearInterval(this.heartbeatIntervalID) |       clearInterval(this.heartbeatIntervalID) | ||||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|       this.reconnect() |       this.reconnect() | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ import { Guild } from './guild.ts' | ||||||
| import { User } from './user.ts' | import { User } from './user.ts' | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| 
 | 
 | ||||||
| enum ActivityTypes { | export enum ActivityTypes { | ||||||
|   PLAYING = 0, |   PLAYING = 0, | ||||||
|   STREAMING = 1, |   STREAMING = 1, | ||||||
|   LISTENING = 2, |   LISTENING = 2, | ||||||
|  |  | ||||||
|  | @ -92,4 +92,10 @@ client.on('messageCreate', async (msg: Message) => { | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| client.connect(TOKEN, Intents.None) | client.connect(TOKEN, Intents.All) | ||||||
|  | 
 | ||||||
|  | // OLD: Was a way to reproduce reconnect infinite loop
 | ||||||
|  | // setTimeout(() => {
 | ||||||
|  | //   console.log('[DEBUG] Reconnect')
 | ||||||
|  | //   client.gateway?.reconnect()
 | ||||||
|  | // }, 1000 * 4)
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue