fix(gateway): handle reconnects better way

This commit is contained in:
DjDeveloperr 2020-12-04 11:23:10 +05:30
parent 1ca8b4bc67
commit dfb99fb6b9
5 changed files with 67 additions and 32 deletions

11
.vscode/settings.json vendored
View File

@ -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
View File

@ -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'

View File

@ -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()

View File

@ -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,

View File

@ -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)