class SmartWebSocket{ socket; url; protocols; debugName; autoreconnect; #events; #reconnecting; constructor(url, protocols = [], autoreconnect = true, debugName = null) { this.url = url; this.protocols = protocols; this.autoreconnect = autoreconnect; this.debugName = debugName; this.#events = {}; this.#reconnecting = false; this.#connect(); } #formatedConsole(method, message, color) { if (color && typeof message == "string") { console[method]("%c%s\t | %c%s", "font-weight: bold; color: cyan;", this.debugName ?? this.url, `color: ${color};`, message); } else { console[method]("%c%s\t | %c%s", "font-weight: bold; color: cyan;", this.debugName ?? this.url, "", message); } } #log(message, color = null) { this.#formatedConsole("log", message, color); } #error(message) { this.#formatedConsole("error", message); } #warn(message) { this.#formatedConsole("warn", message); } #info(message, color = null) { this.#formatedConsole("info", message, color); } #connect() { this.#info("Connecting socket...", "yellow"); this.socket = new WebSocket(this.url, this.protocols); this.socket.addEventListener("open", (eventData) => this.#openHandler(eventData)); this.socket.addEventListener("close", (eventData) => this.#closeHandler(eventData)); this.socket.addEventListener("error", (eventData) => this.#errorHandler(eventData)); this.socket.addEventListener("message", (eventData) => this.#messageHandler(eventData)); } #eventHandler(event, data = null) { if (!(event in this.#events)) { return; } for (let i = 0; i < this.#events[event].length; i++) { this.#events[event][i](data); } } #openHandler(eventData) { this.#info("Socket connected.", "lime"); this.#reconnecting = false; this.#eventHandler("open", eventData); } #closeHandler(eventData) { if (eventData.wasClean) { this.#info("Socket disconnected cleanly."); } else { if (!this.#reconnecting) { this.#warn("Socket disconnected apruptly!"); if (!this.autoreconnect) { this.#warn("autoreconnect is set to false! Socket will not automatically reconnect in case of an unexpected loss of connection."); } } } if (this.autoreconnect) { if (!this.#reconnecting) { this.#info("Automatically reconnecting."); this.#reconnecting = true; } this.reconnect(); } this.#eventHandler("close", eventData); } #errorHandler(eventData) { if (!this.#reconnecting) { this.#error(eventData); } this.#eventHandler("error", eventData); } #messageHandler(eventData) { let jsonObj = false; try { jsonObj = JSON.parse(eventData.data); } catch (err) { this.#warn("received non-JSON data from WebSocket"); jsonObj = false; } if (jsonObj && "event" in jsonObj) { if ("data" in jsonObj) { this.#eventHandler(jsonObj.event, jsonObj.data); } else { this.#eventHandler(jdonObj.event); } } this.#eventHandler("message", eventData); } reconnect() { if (this.socket != undefined && this.socket.readyState < 3) { if (!this.autoreconnect) { this.socket.addEventListener("close", () => this.#connect()); } this.socket.close(); this.#info("Closing socket.", "lime") } else { this.#connect(); } } close() { if (this.socket.readyState == 4) { throw "socket already closed"; } this.autoreconnect = false; this.socket.close(); } isReady() { return this.socket.readyState == 1; } on(event, handler) { if (typeof event != "string") { throw "argument event is expected to be of type string"; } if (typeof handler != "function") { throw "argument handler is expected to be of type function"; } if (event in this.#events) { this.#events[event].push(handler); } else { this.#events[event] = [handler]; } } send(event, data = null) { if (typeof event != "string") { throw "argument event is expected to be of type string"; } this.socket.send(JSON.stringify({event: event, data: data})); } sendRaw(data) { this.socket.send(data); } get events() { return this.#events; } }