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…
Reference in a new issue