BareRTC/src/lib/ChatClient.js

246 lines
7.4 KiB
JavaScript

// WebSocket chat client handler.
class ChatClient {
/**
* Constructor for the client.
*
* @param usePolling: instead of WebSocket use the ajax polling API.
* @param onClientError: function to receive 'ChatClient' messages to
* add to the chat room (this.ChatClient())
*/
constructor({
usePolling=false,
onClientError,
jwt, // JWT token for authorization
prefs, // User preferences for 'me' action (close DMs, etc)
// Chat Protocol handler functions for the caller.
onWho,
onMe,
onMessage,
onTakeback,
onReact,
onPresence,
onRing,
onOpen,
onCandidate,
onSDP,
onWatch,
onUnwatch,
onBlock,
// Misc function registrations for callback.
onNewJWT, // new JWT token from ping response
bulkMuteUsers, // Upload our blocklist on connect.
focusMessageBox, // Tell caller to focus the message entry box.
pushHistory,
}) {
this.usePolling = usePolling;
// Pointer to the 'ChatClient(message)' command from the main app.
this.ChatClient = onClientError;
this.jwt = jwt;
this.prefs = prefs;
// Register the handler functions.
this.onWho = onWho;
this.onMe = onMe;
this.onMessage = onMessage;
this.onTakeback = onTakeback;
this.onReact = onReact;
this.onPresence = onPresence;
this.onRing = onRing;
this.onOpen = onOpen;
this.onCandidate = onCandidate;
this.onSDP = onSDP;
this.onWatch = onWatch;
this.onUnwatch = onUnwatch;
this.onBlock = onBlock;
this.onNewJWT = onNewJWT;
this.bulkMuteUsers = bulkMuteUsers;
this.focusMessageBox = focusMessageBox;
this.pushHistory = pushHistory;
// WebSocket connection.
this.ws = {
conn: null,
connected: false,
};
}
// Connected polls if the client is connected.
connected() {
if (this.usePolling) {
return true;
}
return this.ws.connected;
}
// Disconnect from the server.
disconnect() {
if (this.usePolling) {
throw new Exception("Not implemented");
}
this.ws.conn.close();
}
// Common function to send a message to the server. The message
// is a JSON object before stringify.
send(message) {
if (this.usePolling) {
throw new Exception("Not implemented");
}
if (!this.ws.connected) {
this.ChatClient("Couldn't send WebSocket message: not connected.");
return;
}
console.log("send:", message);
if (typeof(message) !== "string") {
message = JSON.stringify(message);
}
this.ws.conn.send(message);
}
// Common function to handle a message from the server.
handle(msg) {
switch (msg.action) {
case "who":
this.onWho(msg);
break;
case "me":
this.onMe(msg);
break;
case "message":
this.onMessage(msg);
break;
case "takeback":
this.onTakeback(msg);
break;
case "react":
this.onReact(msg);
break;
case "presence":
this.onPresence(msg);
break;
case "ring":
this.onRing(msg);
break;
case "open":
this.onOpen(msg);
break;
case "candidate":
this.onCandidate(msg);
break;
case "sdp":
this.onSDP(msg);
break;
case "watch":
this.onWatch(msg);
break;
case "unwatch":
this.onUnwatch(msg);
break;
case "block":
this.onBlock(msg);
break;
case "error":
this.pushHistory({
channel: msg.channel,
username: msg.username || 'Internal Server Error',
message: msg.message,
isChatServer: true,
});
break;
case "disconnect":
this.onWho({ whoList: [] });
this.disconnect = true;
this.ws.connected = false;
this.ws.conn.close(1000, "server asked to close the connection");
break;
case "ping":
// New JWT token?
if (msg.jwt) {
this.onNewJWT(msg.jwt);
}
// Reset disconnect retry counter: if we were on long enough to get
// a ping, we're well connected and can reconnect no matter how many
// times the chat server is rebooted.
this.disconnectCount = 0;
break;
default:
console.error("Unexpected action: %s", JSON.stringify(msg));
}
}
// Dial the WebSocket.
dial() {
this.ChatClient("Establishing connection to server...");
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
const conn = new WebSocket(`${proto}://${location.host}/ws`);
conn.addEventListener("close", ev => {
// Lost connection to server - scrub who list.
this.onWho({ whoList: [] });
this.ws.connected = false;
this.ChatClient(`WebSocket Disconnected code: ${ev.code}, reason: ${ev.reason}`);
this.disconnectCount++;
if (this.disconnectCount > this.disconnectLimit) {
this.ChatClient(`It seems there's a problem connecting to the server. Please try some other time.`);
return;
}
if (!this.disconnect) {
if (ev.code !== 1001 && ev.code !== 1000) {
this.ChatClient("Reconnecting in 5s");
setTimeout(this.dial, 5000);
}
}
});
conn.addEventListener("open", ev => {
this.ws.connected = true;
this.ChatClient("Websocket connected!");
// Upload our blocklist to the server before login. This resolves a bug where if a block
// was added recently (other user still online in chat), that user would briefly see your
// "has entered the room" message followed by you immediately not being online.
this.bulkMuteUsers();
// Tell the server our username.
this.send({
action: "login",
username: this.username,
jwt: this.jwt.token,
dnd: this.prefs.closeDMs,
});
// Focus the message entry box.
window.requestAnimationFrame(() => {
this.focusMessageBox();
});
});
conn.addEventListener("message", ev => {
if (typeof ev.data !== "string") {
console.error("unexpected message type", typeof ev.data);
return;
}
let msg = JSON.parse(ev.data);
this.handle(msg);
});
this.ws.conn = conn;
}
}
export default ChatClient;