watch-party/frontend/lib/reconnecting-web-socket.mjs

59 lines
1.9 KiB
JavaScript

export default class ReconnectingWebSocket {
constructor(url){
if(url instanceof URL) {
this.url = url;
} else {
this.url = new URL(url)
}
this.connected = false;
this._eventTarget = new EventTarget();
this._backoff = 250; // milliseconds, doubled before use
this._lastConnect = 0;
this._socket = null;
this._unsent = [];
this._connect(true);
}
_connect(first) {
if(this._socket) try { this._socket.close() } catch (e) {};
try {
this._socket = new WebSocket(this.url.href);
} catch (e) {
this._reconnecting = false;
return this._reconnect()
}
this._socket.addEventListener("close", () => this._reconnect())
this._socket.addEventListener("error", () => this._reconnect())
this._socket.addEventListener("message", ({data}) => this._eventTarget.dispatchEvent(new MessageEvent("message", {data})))
this._socket.addEventListener("open", e => {
if(first) this._eventTarget.dispatchEvent(new Event("open"));
if(this._reconnecting) this._eventTarget.dispatchEvent(new Event("reconnected"));
this._reconnecting = false;
this._backoff = 250;
this.connected = true;
while(this._unsent.length > 0) this._socket.send(this._unsent.shift())
})
}
_reconnect(){
if(this._reconnecting) return;
this._eventTarget.dispatchEvent(new Event("reconnecting"));
this._reconnecting = true;
this.connected = false;
this._backoff *= 2; // exponential backoff
setTimeout(() => {
this._connect();
}, Math.floor(this._backoff+(Math.random()*this._backoff*0.25)-(this._backoff*0.125)))
}
send(message) {
if(this.connected) {
this._socket.send(message);
} else {
this._unsent.push(message);
}
}
addEventListener(...a) {
return this._eventTarget.addEventListener(...a)
}
removeEventListener(...a) {
return this._eventTarget.removeEventListener(...a)
}
}