UNPKG

@dressed/ws

Version:

Communicate with the Discord Gateway

188 lines 6.57 kB
import { GatewayOpcodes, GatewayCloseCodes, GatewayDispatchEvents, } from "discord-api-types/v10"; import assert from "node:assert"; import { platform } from "node:process"; import { parentPort } from "node:worker_threads"; assert(parentPort); const shards = new Map(); const WSCodes = { NormalClose: 1000, GoingAway: 1001, ResumeSession: 3001, }; function connectShard(url, config, resume) { var _a; const ws = new WebSocket(url); const shardId = config.shard.toString(); let seq = (_a = resume === null || resume === void 0 ? void 0 : resume.seq) !== null && _a !== void 0 ? _a : null; let resumeData = resume && { resume_gateway_url: url, session_id: resume.sessionId, }; let beatAcked = true; const shard = { state: "Connecting", heartbeatInterval: null, ws, }; shards.set(shardId, shard); function beat() { if (!beatAcked) { ws.close(WSCodes.ResumeSession, "Failed to ack heartbeat"); return; } beatAcked = false; ws.send(JSON.stringify({ op: GatewayOpcodes.Heartbeat, d: seq })); } ws.onopen = () => { ws.send(JSON.stringify(resume ? { op: GatewayOpcodes.Resume, d: { session_id: resume.sessionId, seq: resume.seq, token: config.token, }, } : { op: GatewayOpcodes.Identify, d: { ...config, properties: { os: platform, browser: "Dressed", device: "Dressed", }, }, })); }; ws.onmessage = (e) => { assert(parentPort); const payload = JSON.parse(e.data); switch (payload.op) { case GatewayOpcodes.Hello: { setTimeout(() => { beat(); shard.state = "Ready"; shard.heartbeatInterval = setInterval(beat, payload.d.heartbeat_interval); }, payload.d.heartbeat_interval * Math.random()); break; } case GatewayOpcodes.InvalidSession: case GatewayOpcodes.Reconnect: { clearInterval(shard.heartbeatInterval); if (payload.op === GatewayOpcodes.InvalidSession && payload.d === false) { shard.ws.close(WSCodes.NormalClose, "Initiating full reset"); } else { shard.ws.close(WSCodes.ResumeSession, "Reconnect message received"); } break; } case GatewayOpcodes.Dispatch: { seq = payload.s; if (payload.t === GatewayDispatchEvents.Ready) { resumeData = { resume_gateway_url: payload.d.resume_gateway_url, session_id: payload.d.session_id, }; } parentPort.postMessage({ type: "dispatch", shard: config.shard, ...payload, }); break; } case GatewayOpcodes.Heartbeat: { beat(); break; } case GatewayOpcodes.HeartbeatAck: { beatAcked = true; break; } } }; ws.onerror = (e) => { console.error(`WebSocket error (shard ${config.shard[0]})`, e); }; ws.onclose = ({ code, reason }) => { if (shard.state === "Disconnecting") { code = WSCodes.GoingAway; } else if (code === WSCodes.GoingAway) { code = WSCodes.NormalClose; } clearInterval(shard.heartbeatInterval); switch (code) { case GatewayCloseCodes.AuthenticationFailed: case GatewayCloseCodes.InvalidShard: case GatewayCloseCodes.ShardingRequired: case GatewayCloseCodes.InvalidAPIVersion: case GatewayCloseCodes.InvalidIntents: case GatewayCloseCodes.DisallowedIntents: case WSCodes.GoingAway: { shards.delete(shardId); console.log(`Connection closed with code ${code} - ${reason || "No reason provided"} (shard ${config.shard[0]})`); break; } case GatewayCloseCodes.UnknownError: case GatewayCloseCodes.UnknownOpcode: case GatewayCloseCodes.DecodeError: case GatewayCloseCodes.AlreadyAuthenticated: case GatewayCloseCodes.RateLimited: case WSCodes.ResumeSession: default: { if (resumeData && seq !== null) { connectShard(resumeData.resume_gateway_url, config, { seq, sessionId: resumeData.session_id, }); break; } } // eslint-disable-next-line no-fallthrough case GatewayCloseCodes.NotAuthenticated: case GatewayCloseCodes.InvalidSeq: case GatewayCloseCodes.SessionTimedOut: case WSCodes.NormalClose: { connectShard(config.bot.url, config); break; } } }; } parentPort.on("message", (data) => { switch (data.type) { case "addShard": { connectShard(data.config.bot.url, data.config); break; } case "removeShard": { const shard = shards.get(data.shardId); if (shard) { shard.state = "Disconnecting"; clearInterval(shard.heartbeatInterval); shard.ws.close(WSCodes.NormalClose, "Shard removed"); shards.delete(data.shardId); } break; } case "emit": { for (const { ws } of shards.values()) { ws.send(JSON.stringify({ op: GatewayOpcodes[data.op], d: data.d })); } break; } } }); parentPort.on("close", () => { for (const shard of shards.values()) { shard.state = "Disconnecting"; clearInterval(shard.heartbeatInterval); shard.ws.close(WSCodes.NormalClose, "Worker closed"); } shards.clear(); }); //# sourceMappingURL=worker.js.map