Skip to content

Instantly share code, notes, and snippets.

@TheArmagan
Last active June 30, 2021 21:07
Show Gist options
  • Save TheArmagan/2078f6275ac0627dc44619a8b6046b88 to your computer and use it in GitHub Desktop.
Save TheArmagan/2078f6275ac0627dc44619a8b6046b88 to your computer and use it in GitHub Desktop.
NODEJS ReconnectingWebSocket
const { EventEmitter } = require("events");
const WebSocket = require("ws");
class ReconnectingWebSocket {
/** @type {string} */
address;
/** @type {WebSocket.ClientOptions} */
clientOptions;
/** @type {EventEmitter} */
events;
/** @type {WebSocket} */
ws;
reconnectIn = 1000;
sendBuffer = [];
connected = false;
connecting = false;
waitingToConnect = false;
#manualClose = false;
/**
*
* @param {{address:string|()=>string,clientOptions:WebSocket.ClientOptions,reconnectIn:number}} param0
*/
constructor({ address, clientOptions = {}, reconnectIn = 3000 }) {
this.address = address;
this.clientOptions = clientOptions;
this.reconnectIn = reconnectIn;
this.events = new EventEmitter();
this.#tryToSendBuffer();
}
#tryToSendBuffer = () => {
if (this.sendBuffer.length != 0 && this.connected) {
let [data, res, rej] = this.sendBuffer.shift();
this.ws.send(data, (err) => {
data = 0;
if (err) return rej(err);
return res(true);
});
setTimeout(() => {
if (this.sendBuffer.length != 0) this.#tryToSendBuffer();
}, 0)
}
}
onMessage(cb) {
this.events.addListener("message", cb);
}
offMessage(cb) {
this.events.removeListener("message", cb);
}
send(data) {
return new Promise((res, rej) => {
if (!this.connected || this.connecting) {
this.sendBuffer.push([data, res, rej]);
this.#tryToSendBuffer();
return;
}
this.ws.send(data, (err) => {
data = 0;
if (err) return rej(err);
return res(true);
});
})
}
connect() {
return new Promise(async (res, rej) => {
if (this.connected || this.connecting) return;
this.connecting = true;
let address = this.address;
if (typeof this.address == "function") address = await this.address();
this.ws = new WebSocket(address, this.clientOptions);
this.ws.addEventListener("close", (data) => {
this.events.emit("close", data);
this.ws.removeAllListeners();
this.ws = null;
this.connected = false;
this.connecting = false;
this.waitingToConnect = false;
if (!this.#manualClose) {
this.waitingToConnect = true;
setTimeout(() => {
this.waitingToConnect = false;
this.connect();
}, this.reconnectIn)
}
});
this.ws.addEventListener("error", (data) => {
this.events.emit("error", data);
this.connecting = false;
});
this.ws.addEventListener("open", (data) => {
this.events.emit("connected", data);
this.connected = true;
this.#manualClose = false;
this.connecting = false;
this.#tryToSendBuffer();
});
this.ws.addEventListener("message", (data) => {
this.events.emit("message", data);
});
this.ws.addEventListener("open", res, { once: true });
})
}
close() {
return new Promise((res) => {
this.#manualClose = true;
this.sendBuffer = [];
this.ws.close();
res();
})
}
}
module.exports = ReconnectingWebSocket;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment