import logging from "./logging"; import { getItem } from "./storage"; export const GatewayErrors = { BAD_PAYLOAD: 4001, BAD_AUTH: 4002, AUTHENTICATION_TIMEOUT: 4003, NO_PING: 4004, FLOODING: 4005, ALREADY_AUTHENTICATED: 4006, PAYLOAD_TOO_LARGE: 4007, TOO_MANY_SESSIONS: 4008, }; export const GatewayPayloadType = { Hello: 0, Authenticate: 1, Ready: 2, Ping: 3, ChannelCreate: 110, ChannelUpdate: 111, ChannelDelete: 112, MessageCreate: 120, MessageUpdate: 121, MessageDelete: 122, } export const GatewayEventType = { ...GatewayPayloadType, Open: -5, Close: -4, BadAuth: -3, } const log = logging.logger("Gateway", true); export default { ws: null, authenticated: false, open: false, heartbeatInterval: null, user: null, channels: null, reconnectDelay: 400, reconnectTimeout: null, handlers: new Map(), disableReconnect: false, init(token) { if (!token) { log("no auth token, skipping connection"); this.dispatch(GatewayEventType.BadAuth, 0); return false; } log(`connecting to gateway - gatewayBase: ${getItem("gatewayBase")}`); this.ws = new WebSocket(getItem("gatewayBase")); this.ws.onopen = () => { if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); } this.disableReconnect = false; this.open = true; this.dispatch(GatewayEventType.Open, null); log("open"); }; this.ws.onmessage = (event) => { const payload = JSON.parse(event.data); switch (payload.t) { case GatewayPayloadType.Hello: { this.send({ t: GatewayPayloadType.Authenticate, d: token }); this.heartbeatInterval = setInterval(() => { this.send({ t: GatewayPayloadType.Ping, d: 0 }); }, payload.d.pingInterval); log("hello"); break; } case GatewayPayloadType.Ready: { this.user = payload.d.user; this.channels = payload.d.channels; this.reconnectDelay = 400; log("ready"); break; } } this.dispatch(payload.t, payload.d); }; this.ws.onclose = ({ code }) => { this.authenticated = false; this.user = null; this.channels = null; this.open = false; if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } if (code === GatewayErrors.BAD_AUTH) { this.dispatch(GatewayEventType.BadAuth, 1); if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout); } else if (this.disableReconnect) { if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout); } else { if (this.reconnectDelay < 60000) { this.reconnectDelay *= 2; } this.reconnectTimeout = setTimeout(() => { this.init(token); }, this.reconnectDelay); } this.dispatch(GatewayEventType.Close, code); log("close"); }; this.ws.onerror = (e) => { log("websocket: onerror", e); }; return true; }, send(data) { return this.ws.send(JSON.stringify(data)); }, dispatch(event, payload) { const eventHandlers = this.handlers.get(event); if (!eventHandlers) return; eventHandlers.forEach((e) => { e(payload); }); }, subscribe(event, handler) { if (!this.handlers.get(event)) { this.handlers.set(event, new Set()); } this.handlers.get(event).add(handler); return handler; // can later be used for unsubscribe() }, unsubscribe(event, handler) { const eventHandlers = this.handlers.get(event); if (!eventHandlers) return; eventHandlers.delete(handler); if (eventHandlers.size < 1) { this.handlers.delete(event); } }, close() { this.disableReconnect = true; if (this.ws) this.ws.close(); } };