diff --git a/src/ws/heartbeat.ts b/src/ws/heartbeat.ts index c9e524224..e8d633011 100644 --- a/src/ws/heartbeat.ts +++ b/src/ws/heartbeat.ts @@ -14,28 +14,44 @@ export function heartbeat(shardId: number, interval: number) { shard.heartbeat.lastSentAt = Date.now(); shard.heartbeat.interval = interval; - shard.heartbeat.intervalId = setInterval(() => { - ws.log("DEBUG", `Running setInterval in heartbeat file.`); - const currentShard = ws.shards.get(shardId); - if (!currentShard) return; - - ws.log("HEARTBEATING", { shardId, shard: currentShard }); - - if ( - currentShard.ws.readyState === WebSocket.CLOSED || - !currentShard.heartbeat.keepAlive - ) { - ws.log("HEARTBEATING_CLOSED", { shardId, shard: currentShard }); - - // STOP THE HEARTBEAT - return clearInterval(currentShard.heartbeat.intervalId); - } - - if (currentShard.ws.readyState !== WebSocket.OPEN) return; - - currentShard.ws.send(JSON.stringify({ - op: DiscordGatewayOpcodes.Heartbeat, - d: currentShard.previousSequenceNumber, - })); - }, interval); + // First heartbeat should be sent bevore + shard.heartbeat.timeoutId = setTimeout( + () => sendHeartbeat(shardId), + Math.floor(shard.heartbeat.interval * Math.random()), + ); +} + +function sendHeartbeat(shardId: number) { + ws.log("DEBUG", `Running setInterval in heartbeat file.`); + const currentShard = ws.shards.get(shardId); + if (!currentShard) return; + + ws.log("HEARTBEATING", { shardId, shard: currentShard }); + + if ( + currentShard.ws.readyState === WebSocket.CLOSED || + !currentShard.heartbeat.keepAlive + ) { + ws.log("HEARTBEATING_CLOSED", { shardId, shard: currentShard }); + + // STOP THE HEARTBEAT + return; + } + + if (currentShard.ws.readyState !== WebSocket.OPEN) { + currentShard.heartbeat.timeoutId = setTimeout( + () => sendHeartbeat(shardId), + currentShard.heartbeat.interval, + ); + } + + currentShard.ws.send(JSON.stringify({ + op: DiscordGatewayOpcodes.Heartbeat, + d: currentShard.previousSequenceNumber, + })); + + currentShard.heartbeat.timeoutId = setTimeout( + () => sendHeartbeat(shardId), + currentShard.heartbeat.interval, + ); } diff --git a/src/ws/identify.ts b/src/ws/identify.ts index e566feb00..5ca156e02 100644 --- a/src/ws/identify.ts +++ b/src/ws/identify.ts @@ -7,7 +7,7 @@ export async function identify(shardId: number, maxShards: number) { // Need to clear the old heartbeat interval const oldShard = ws.shards.get(shardId); if (oldShard) { - clearInterval(oldShard.heartbeat.intervalId); + clearTimeout(oldShard.heartbeat.timeoutId); } // CREATE A SHARD @@ -29,7 +29,7 @@ export async function identify(shardId: number, maxShards: number) { acknowledged: false, keepAlive: false, interval: 0, - intervalId: 0, + timeoutId: 0, }, queue: [], processingQueue: false, diff --git a/src/ws/ws.ts b/src/ws/ws.ts index 2b3006b1e..1ea85c346 100644 --- a/src/ws/ws.ts +++ b/src/ws/ws.ts @@ -138,7 +138,7 @@ export interface DiscordenoShard { /** The interval between heartbeats requested by discord. */ interval: number; /** The id of the interval, useful for stopping the interval if ws closed. */ - intervalId: number; + timeoutId: number; }; /** The items/requestst that are in queue to be sent to this shard websocket. */ queue: WebSocketRequest[]; diff --git a/tests/ws/ws_close.ts b/tests/ws/ws_close.ts index 3b313fc99..7ffcffcbb 100644 --- a/tests/ws/ws_close.ts +++ b/tests/ws/ws_close.ts @@ -7,7 +7,7 @@ Deno.test({ name: "[ws] Close all shards manually.", async fn() { ws.shards.forEach((shard) => { - clearInterval(shard.heartbeat.intervalId); + clearTimeout(shard.heartbeat.timeoutId); shard.ws.close(3064, "Discordeno Testing Finished! Do Not RESUME!"); });