Skip to content

Instantly share code, notes, and snippets.

@aubreyrjones
Created January 2, 2022 19:14
Show Gist options
  • Save aubreyrjones/b3b429e5e853a430236d04640d6e8afb to your computer and use it in GitHub Desktop.
Save aubreyrjones/b3b429e5e853a430236d04640d6e8afb to your computer and use it in GitHub Desktop.
Websocket event client
function createEnum(values) {
const enumObject = {};
for (const val of values) {
enumObject[val] = val;
}
return Object.freeze(enumObject);
}
const ChannelState = createEnum(['Created', 'Connecting', 'Authorizing', 'Subscribed', 'Error', 'Closed']);
class WSChannel {
constructor(url, channel) {
this.socket = null;
this.channel = channel;
this.url = url;
this.connectCallback = null;
this.handlers = {};
this.state = ChannelState.Created;
this.preConnectQueue = [];
this.handle("_challenge", data => {
this.onChallenge(data.nonce);
});
this.handle("_subbed", () => { this.onSubbed(); });
}
connect(callback = null) {
this.connectCallback = callback;
this._reconnect();
}
_reconnect() {
this.state = ChannelState.Connecting;
console.info("Connect initiated.");
this.socket = new WebSocket(this.url);
this.socket.binaryType = "arraybuffer";
this.socket.addEventListener('open', (event) => { this.onConnect(event); });
this.socket.addEventListener('message', (event) => { this.onMessage(event); });
this.socket.addEventListener('error', (event) => { this.onError(event); });
this.socket.addEventListener('close', (event) => { this.onClose(event); });
}
onClose(event) {
if (this.state == ChannelState.Subscribed) {
console.error("Subscribed channel closed by server.", event);
}
this.state = ChannelState.Closed;
}
onError(event) {
console.log("Websocket error", event);
this.state = ChannelState.Error;
}
onConnect(event) {
console.info("Socket open.");
}
onChallenge(nonce) {
this.state = ChannelState.Authorizing;
let url = "/ws/auth?char_id=" + this.channel + "&nonce=" + nonce;
fetch(url).then(async response => {
let hash = await response.json();
this.send('_response', {"value": hash});
});
}
onSubbed() {
console.info("Connected to peers.");
this.state = ChannelState.Subscribed;
if (this.connectCallback) this.connectCallback();
if (this.preConnectQueue.length > 0) {
for (let m of this.preConnectQueue) {
this.socket.send(m);
}
this.preConnectQueue.length = 0; // lordy, js is weird.
}
}
onMessage(event) {
try {
let msg = JSON.parse(event.data);
if ('ev' in msg && msg['ev'] in this.handlers) {
this.handlers[msg['ev']](msg['data']);
}
}
catch (err) {
console.error("Can't decode websocket event: ", err);
}
}
send(eventName, payload) {
let msg = {
ev: eventName,
data: payload || {}
};
let msgString = JSON.stringify(msg);
if (this.state == ChannelState.Subscribed && this.socket.readyState == 1) { // we're subscribed and the socket's good
this.socket.send(msgString);
}
else { // if we're not connected, push this into a queue to send after connection.
this.preConnectQueue.push(msgString);
// if we're trying to send events in one of these states, then let's try to reconnect.
if (this.state == ChannelState.Subscribed || this.state == ChannelState.Closed || this.state == ChannelState.Error) {
this.state = ChannelState.Error;
this._reconnect();
}
}
}
handle(eventName, callback) {
this.handlers[eventName] = callback;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment