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 { Message } from './src/structures/message.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 { Snowflake } from './src/structures/snowflake.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 * from './src/utils/permissions.ts' | ||||
| export { UserFlagsManager } from './src/utils/userFlags.ts' | ||||
| export type { EveryChannelTypes } from './src/utils/getChannelByType.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[] | ||||
| } | ||||
| 
 | ||||
| export const RECONNECT_REASON = 'harmony-reconnect' | ||||
| 
 | ||||
| /** | ||||
|  * Handles Discord gateway connection. | ||||
|  * | ||||
|  | @ -43,6 +45,7 @@ class Gateway { | |||
|   private heartbeatServerResponded = false | ||||
|   client: Client | ||||
|   cache: GatewayCache | ||||
|   private timedIdentify: number | null = null | ||||
| 
 | ||||
|   constructor(client: Client, token: string, intents: GatewayIntents[]) { | ||||
|     this.token = token | ||||
|  | @ -109,10 +112,20 @@ class Gateway { | |||
| 
 | ||||
|       case GatewayOpcodes.INVALID_SESSION: | ||||
|         // Because we know this gonna be bool
 | ||||
|         this.debug(`Invalid Session! Identifying with forced new session`) | ||||
|         // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 | ||||
|         // eslint-disable-next-line @typescript-eslint/promise-function-async
 | ||||
|         setTimeout(() => this.sendIdentify(true), 3000) | ||||
|         this.debug( | ||||
|           `Invalid Session received! Resumeable? ${d === true ? 'Yes' : 'No'}` | ||||
|         ) | ||||
|         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 | ||||
| 
 | ||||
|       case GatewayOpcodes.DISPATCH: { | ||||
|  | @ -151,6 +164,7 @@ class Gateway { | |||
|   } | ||||
| 
 | ||||
|   private async onclose(event: CloseEvent): Promise<void> { | ||||
|     if (event.reason === RECONNECT_REASON) return | ||||
|     this.debug(`Connection Closed with code: ${event.code}`) | ||||
| 
 | ||||
|     if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { | ||||
|  | @ -180,7 +194,7 @@ class Gateway { | |||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.reconnect() | ||||
|     } 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) { | ||||
|       throw new Error("Invalid API Version was used. This shouldn't happen!") | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) { | ||||
|  | @ -191,9 +205,12 @@ class Gateway { | |||
|       this.debug( | ||||
|         '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) | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.reconnect() | ||||
|       await this.reconnect(true) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -216,13 +233,14 @@ class Gateway { | |||
|         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` | ||||
|       ) | ||||
|       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) | ||||
|       if (forceNewSession === undefined || !forceNewSession) { | ||||
|         const sessionIDCached = await this.cache.get('session_id') | ||||
|         if (sessionIDCached !== undefined) { | ||||
|           this.debug(`Found Cached SessionID: ${sessionIDCached}`) | ||||
|           this.sessionID = sessionIDCached | ||||
|           return await this.sendResume() | ||||
|         } | ||||
|     } else this.debug('Skipping /gateway/bot because bot: false') | ||||
| 
 | ||||
|     if (forceNewSession === undefined || !forceNewSession) { | ||||
|       const sessionIDCached = await this.cache.get('session_id') | ||||
|       if (sessionIDCached !== undefined) { | ||||
|         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({ | ||||
|       op: GatewayOpcodes.IDENTIFY, | ||||
|       d: payload | ||||
|  | @ -262,6 +281,10 @@ class Gateway { | |||
|   } | ||||
| 
 | ||||
|   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}`) | ||||
|     if (this.sequenceID === undefined) { | ||||
|       const cached = await this.cache.get('seq') | ||||
|  | @ -276,6 +299,7 @@ class Gateway { | |||
|         seq: this.sequenceID ?? null | ||||
|       } | ||||
|     } | ||||
|     this.debug('Sending Resume payload...') | ||||
|     this.send(resumePayload) | ||||
|   } | ||||
| 
 | ||||
|  | @ -305,13 +329,16 @@ class Gateway { | |||
| 
 | ||||
|   async reconnect(forceNew?: boolean): Promise<void> { | ||||
|     clearInterval(this.heartbeatIntervalID) | ||||
|     if (forceNew === undefined || !forceNew) | ||||
|     if (forceNew === true) { | ||||
|       await this.cache.delete('session_id') | ||||
|     this.close() | ||||
|       await this.cache.delete('seq') | ||||
|     } | ||||
|     this.close(1000, RECONNECT_REASON) | ||||
|     this.initWebsocket() | ||||
|   } | ||||
| 
 | ||||
|   initWebsocket(): void { | ||||
|     this.debug('Initializing WebSocket...') | ||||
|     this.websocket = new WebSocket( | ||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 | ||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, | ||||
|  | @ -324,8 +351,8 @@ class Gateway { | |||
|     this.websocket.onerror = this.onerror.bind(this) | ||||
|   } | ||||
| 
 | ||||
|   close(): void { | ||||
|     this.websocket.close(1000) | ||||
|   close(code: number = 1000, reason?: string): void { | ||||
|     return this.websocket.close(code, reason) | ||||
|   } | ||||
| 
 | ||||
|   send(data: GatewayResponse): boolean { | ||||
|  | @ -362,6 +389,7 @@ class Gateway { | |||
|     if (this.heartbeatServerResponded) { | ||||
|       this.heartbeatServerResponded = false | ||||
|     } else { | ||||
|       this.debug('Found dead connection, reconnecting...') | ||||
|       clearInterval(this.heartbeatIntervalID) | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.reconnect() | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import { Guild } from './guild.ts' | |||
| import { User } from './user.ts' | ||||
| import { Client } from '../models/client.ts' | ||||
| 
 | ||||
| enum ActivityTypes { | ||||
| export enum ActivityTypes { | ||||
|   PLAYING = 0, | ||||
|   STREAMING = 1, | ||||
|   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