hopefully make the DiscordClient connection more robust

This commit is contained in:
hippoz 2022-02-03 20:41:23 +02:00
parent fe04b99c35
commit 0950241d3b
Signed by: hippoz
GPG key ID: 7C52899193467641
2 changed files with 84 additions and 34 deletions

View file

@ -7,10 +7,16 @@ const opcodes = {
EVENT: 0, EVENT: 0,
CLIENT_HEARTBEAT: 1, CLIENT_HEARTBEAT: 1,
IDENTIFY: 2, IDENTIFY: 2,
RESUME: 6,
INVALID_SESSION: 9,
HELLO: 10, HELLO: 10,
HEARTBEAT_ACK: 11, HEARTBEAT_ACK: 11,
}; };
const reconnectOnCloseCodes = [
1000, 1001, 4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009
];
class DiscordClient extends EventEmitter { class DiscordClient extends EventEmitter {
constructor(token, { intents, baseDomain="discord.com", gatewayUrl="wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream", apiBase="https://discord.com/api/v9" } = {}) { constructor(token, { intents, baseDomain="discord.com", gatewayUrl="wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream", apiBase="https://discord.com/api/v9" } = {}) {
super(); super();
@ -50,6 +56,37 @@ class DiscordClient extends EventEmitter {
}, this._heartbeatIntervalTime); }, this._heartbeatIntervalTime);
} }
_getIdentifyPayload() {
return {
token: this.token,
intents: this.intents,
properties: {
"$os": "linux",
"$browser": "generic",
"$device": "generic"
},
presence: {
since: Date.now(),
activities: [
{
type: 2, // LISTENING
name: "the voices"
}
],
status: "online",
afk: false
}
};
}
_getResumePayload() {
return {
token: this.token,
session_id: this.sessionId,
seq: this.seq
};
}
_handleGatewayMessage(ws, message) { _handleGatewayMessage(ws, message) {
try { try {
message = JSON.parse(message); message = JSON.parse(message);
@ -68,29 +105,18 @@ class DiscordClient extends EventEmitter {
case opcodes.HELLO: { case opcodes.HELLO: {
this._setHeartbeat(payload.heartbeat_interval); this._setHeartbeat(payload.heartbeat_interval);
ws.send(JSON.stringify({ if (this.resuming) {
op: opcodes.IDENTIFY, console.warn("DiscordClient: resuming...");
d: { ws.send(JSON.stringify({
token: this.token, op: opcodes.RESUME,
intents: this.intents, d: this._getResumePayload()
properties: { }));
"$os": "linux", } else {
"$browser": "generic", ws.send(JSON.stringify({
"$device": "generic" op: opcodes.IDENTIFY,
}, d: this._getIdentifyPayload()
presence: { }));
since: Date.now(), }
activities: [
{
type: 2, // LISTENING
name: "the voices"
}
],
status: "online",
afk: false
}
}
}));
break; break;
} }
@ -183,6 +209,23 @@ class DiscordClient extends EventEmitter {
break; break;
} }
case opcodes.INVALID_SESSION: {
if (message.d) {
// connection is resumable, we are going to resume the connection
this.resuming = true;
this.connect();
} else {
// connection is not resumable, wait some time and then send a new IDENTIFY payload
setTimeout(() => {
ws.send(JSON.stringify({
op: opcodes.IDENTIFY,
d: this._getIdentifyPayload()
}));
}, 3500);
}
break;
}
default: { default: {
console.warn(`warn: DiscordClient: got unhandled opcode "${message.op}"`); console.warn(`warn: DiscordClient: got unhandled opcode "${message.op}"`);
break; break;
@ -191,6 +234,12 @@ class DiscordClient extends EventEmitter {
} }
connect() { connect() {
if (this.ws) {
this.ws.removeAllListeners();
this.ws.close();
this.ws = null;
}
const ws = new WebSocket(this.gatewayUrl); const ws = new WebSocket(this.gatewayUrl);
this.ws = ws; this.ws = ws;
@ -208,16 +257,22 @@ class DiscordClient extends EventEmitter {
ws.on("close", (code, reason) => { ws.on("close", (code, reason) => {
reason = reason.toString(); reason = reason.toString();
this.emit("close", code, reason);
console.error(`DiscordClient: on \`close\`: disconnected from gateway: code \`${code}\`, reason \`${reason}\``); console.error(`DiscordClient: on \`close\`: disconnected from gateway: code \`${code}\`, reason \`${reason}\``);
this.emit("close", code, reason);
this._setHeartbeat(-1);
if (reconnectOnCloseCodes.includes(code)) {
this.resuming = true;
this.connect();
}
}); });
ws.on("error", (e) => { ws.on("error", (e) => {
console.error("DiscordClient: websocket error:", e); console.error("DiscordClient: websocket error:", e);
console.log("DiscordClient: reconnecting in a couple of miliseconds"); console.log("DiscordClient: reconnecting?");
setTimeout(() => { this._setHeartbeat(-1);
this.connect(); this.resuming = true;
}, 400); this.connect();
}); });
} }

View file

@ -13,7 +13,7 @@ export function wait(time, shouldReject=false) {
}); });
} }
bot.on("READY", () => { bot.once("READY", () => {
watchedGuildIds.forEach(id => { watchedGuildIds.forEach(id => {
const watchedGuild = new WatchedGuild(); const watchedGuild = new WatchedGuild();
watchedGuild.upstreamGuildId = id; watchedGuild.upstreamGuildId = id;
@ -21,8 +21,3 @@ bot.on("READY", () => {
guildMap.set(id, watchedGuild); guildMap.set(id, watchedGuild);
}); });
}); });
bot.on("close", (code, reason) => {
console.log("bot: connection closed, reconnecting...");
bot.connect();
});