discord-ws-gateway
Version:
Simple and tiny implementation of Discord's Gateway using web standard APIs
160 lines (158 loc) • 4.08 kB
JavaScript
// package.json
var version = "0.1.1";
var homepage = "https://github.com/Chooks22/discord-ws-gateway";
// src/index.ts
var USER_AGENT = `Discord-Gateway/${version} (+${homepage})`;
function createClient(gatewayUrl, identity) {
return new Client(gatewayUrl, identity);
}
var Client = class {
static create = createClient;
#listeners = /* @__PURE__ */ new Map();
#interval;
#timeout;
socket;
#s = null;
#zombied = false;
#url;
#resume_url;
#session_id;
#identity;
closed = false;
constructor(gatewayUrl, identity) {
this.#url = gatewayUrl;
this.#identity = identity;
void this.#connect(gatewayUrl);
}
#connect(url) {
this.socket = new WebSocket(`${url}?v=10&encoding=json`);
this.socket.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
switch (data.op) {
// Dispatch
case 0: {
this.#s = data.s;
const listeners = this.#listeners.get(data.t);
const listeners_all = this.#listeners.get("ALL");
if (listeners !== void 0) {
listeners.forEach((cb) => cb(data));
}
if (listeners_all !== void 0) {
listeners_all.forEach((cb) => cb(data));
}
if (data.t === "READY") {
this.#session_id = data.d.session_id;
this.#resume_url = data.d.resume_gateway_url;
}
break;
}
// Hello
case 10: {
const interval = data.d.heartbeat_interval;
const heartbeat = () => this.#heartbeat();
this.#timeout = setTimeout(() => {
heartbeat();
this.#interval = setInterval(heartbeat, interval);
}, interval * Math.random());
this.#send({
op: 2,
d: {
...this.#identity,
properties: {
os: this.#identity.properties.os,
browser: `${this.#identity.properties.browser} ${USER_AGENT}`.trim(),
device: `${this.#identity.properties.device} ${USER_AGENT}`.trim()
}
}
});
break;
}
// Heartbeat Ack
case 11: {
this.#zombied = false;
break;
}
// Heartbeat
case 1: {
this.#zombied = false;
this.#heartbeat();
break;
}
// Reconnect
case 7: {
void this.#resume();
break;
}
}
});
return new Promise((res) => {
this.socket.addEventListener("open", res, { once: true });
});
}
#send(data) {
const payload = JSON.stringify(data);
this.socket.send(payload);
}
#heartbeat() {
if (this.#zombied) {
void this.#resume();
return;
}
this.#zombied = true;
this.#send({ op: 1, d: this.#s });
}
async #resume() {
this.close();
await this.#connect(this.#resume_url);
this.#send({
op: 6,
d: {
token: this.#identity.token,
seq: this.#s,
session_id: this.#session_id
}
});
}
/**
* @todo(Chooks22) handle reconnect cases
*/
#reconnect() {
this.close();
void this.#connect(this.#url);
}
addEventListener(event, callback) {
const listeners = this.#listeners.get(event);
if (listeners !== void 0) {
listeners.push(callback);
} else {
this.#listeners.set(event, [callback]);
}
return this;
}
removeEventListener(event, callback) {
const listeners = this.#listeners.get(event);
if (listeners !== void 0) {
if (listeners.length === 1 && listeners[0] === callback) {
this.#listeners.delete(event);
} else {
for (let i = listeners.length; i--; ) {
if (listeners[i] === callback) {
listeners.splice(i, 1);
break;
}
}
}
}
return this;
}
close(code = 1e3) {
this.closed = true;
clearTimeout(this.#timeout);
clearInterval(this.#interval);
this.socket.close(code);
}
};
export {
Client,
createClient
};