waffle/frontend/src/gateway.js

182 lines
4.8 KiB
JavaScript

import logger 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,
TypingStart: 130,
PresenceUpdate: 140
}
export const GatewayEventType = {
...GatewayPayloadType,
Open: -5,
Close: -4,
BadAuth: -3,
}
export const GatewayPresenceStatus = {
Offline: 0,
Online: 1
}
const log = logger("Gateway");
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.Close, GatewayErrors.BAD_AUTH);
this.dispatch(GatewayEventType.BadAuth, 0);
return false;
}
log(`connecting to gateway - gatewayBase: ${getItem("server:gatewayBase")}`);
this.ws = new WebSocket(getItem("server: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();
}
};