chore: reformat all code with prettier

This commit is contained in:
modeco80
2024-04-24 03:50:17 -04:00
parent bcbf7db8d9
commit ddae307874
13 changed files with 1053 additions and 1074 deletions

View File

@@ -1,12 +1,11 @@
import { Logger } from '@cvmts/shared'; import { Logger } from '@cvmts/shared';
import { Rank, User } from './User.js'; import { Rank, User } from './User.js';
export default class AuthManager { export default class AuthManager {
apiEndpoint: string; apiEndpoint: string;
secretKey: string; secretKey: string;
private logger = new Logger("CVMTS.AuthMan"); private logger = new Logger('CVMTS.AuthMan');
constructor(apiEndpoint: string, secretKey: string) { constructor(apiEndpoint: string, secretKey: string) {
this.apiEndpoint = apiEndpoint; this.apiEndpoint = apiEndpoint;
@@ -14,7 +13,7 @@ export default class AuthManager {
} }
async Authenticate(token: string, user: User): Promise<JoinResponse> { async Authenticate(token: string, user: User): Promise<JoinResponse> {
var response = await fetch(this.apiEndpoint + '/api/v1/join', { let response = await fetch(this.apiEndpoint + '/api/v1/join', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -26,7 +25,7 @@ export default class AuthManager {
}) })
}); });
var json = (await response.json()) as JoinResponse; let json = (await response.json()) as JoinResponse;
if (!json.success) { if (!json.success) {
this.logger.Error(`Failed to query auth server: ${json.error}`); this.logger.Error(`Failed to query auth server: ${json.error}`);

View File

@@ -1,4 +1,4 @@
import { Logger } from "@cvmts/shared"; import { Logger } from '@cvmts/shared';
export class IPData { export class IPData {
tempMuteExpireTimeout?: NodeJS.Timeout; tempMuteExpireTimeout?: NodeJS.Timeout;
@@ -15,16 +15,14 @@ export class IPData {
// Call when a connection is closed to "release" the ip data // Call when a connection is closed to "release" the ip data
Unref() { Unref() {
if(this.refCount - 1 < 0) if (this.refCount - 1 < 0) this.refCount = 0;
this.refCount = 0;
this.refCount--; this.refCount--;
} }
} }
export class IPDataManager { export class IPDataManager {
static ipDatas = new Map<string, IPData>(); static ipDatas = new Map<string, IPData>();
static logger = new Logger("CVMTS.IPDataManager"); static logger = new Logger('CVMTS.IPDataManager');
static GetIPData(address: string) { static GetIPData(address: string) {
if (IPDataManager.ipDatas.has(address)) { if (IPDataManager.ipDatas.has(address)) {
@@ -43,8 +41,7 @@ export class IPDataManager {
} }
static ForEachIPData(callback: (d: IPData) => void) { static ForEachIPData(callback: (d: IPData) => void) {
for(let tuple of IPDataManager.ipDatas) for (let tuple of IPDataManager.ipDatas) callback(tuple[1]);
callback(tuple[1]);
} }
} }
@@ -54,9 +51,8 @@ export class IPDataManager {
setInterval(() => { setInterval(() => {
for (let tuple of IPDataManager.ipDatas) { for (let tuple of IPDataManager.ipDatas) {
if (tuple[1].refCount == 0) { if (tuple[1].refCount == 0) {
IPDataManager.logger.Info("Deleted ipdata for IP {0}", tuple[0]); IPDataManager.logger.Info('Deleted ipdata for IP {0}', tuple[0]);
IPDataManager.ipDatas.delete(tuple[0]); IPDataManager.ipDatas.delete(tuple[0]);
} }
} }
}, 15000); }, 15000);

View File

@@ -1,4 +1,4 @@
import { EventEmitter } from "events"; import { EventEmitter } from 'events';
// Class to ratelimit a resource (chatting, logging in, etc) // Class to ratelimit a resource (chatting, logging in, etc)
export default class RateLimiter extends EventEmitter { export default class RateLimiter extends EventEmitter {

View File

@@ -26,7 +26,7 @@ export class User {
TurnRateLimit: RateLimiter; TurnRateLimit: RateLimiter;
VoteRateLimit: RateLimiter; VoteRateLimit: RateLimiter;
private logger = new Logger("CVMTS.User"); private logger = new Logger('CVMTS.User');
constructor(ws: WebSocket, ip: IPData, config: IConfig, username?: string, node?: string) { constructor(ws: WebSocket, ip: IPData, config: IConfig, username?: string, node?: string) {
this.IP = ip; this.IP = ip;
@@ -59,6 +59,7 @@ export class User {
this.VoteRateLimit = new RateLimiter(3, 3); this.VoteRateLimit = new RateLimiter(3, 3);
this.VoteRateLimit.on('limit', () => this.closeConnection()); this.VoteRateLimit.on('limit', () => this.closeConnection());
} }
assignGuestName(existingUsers: string[]): string { assignGuestName(existingUsers: string[]): string {
var username; var username;
do { do {
@@ -67,25 +68,30 @@ export class User {
this.username = username; this.username = username;
return username; return username;
} }
sendNop() { sendNop() {
this.socket.send('3.nop;'); this.socket.send('3.nop;');
} }
sendMsg(msg: string | Buffer) { sendMsg(msg: string | Buffer) {
if (this.socket.readyState !== this.socket.OPEN) return; if (this.socket.readyState !== this.socket.OPEN) return;
clearInterval(this.nopSendInterval); clearInterval(this.nopSendInterval);
this.nopSendInterval = setInterval(() => this.sendNop(), 5000); this.nopSendInterval = setInterval(() => this.sendNop(), 5000);
this.socket.send(msg); this.socket.send(msg);
} }
private onNoMsg() { private onNoMsg() {
this.sendNop(); this.sendNop();
this.nopRecieveTimeout = setTimeout(() => { this.nopRecieveTimeout = setTimeout(() => {
this.closeConnection(); this.closeConnection();
}, 3000); }, 3000);
} }
closeConnection() { closeConnection() {
this.socket.send(guacutils.encode('disconnect')); this.socket.send(guacutils.encode('disconnect'));
this.socket.close(); this.socket.close();
} }
onMsgSent() { onMsgSent() {
if (!this.Config.collabvm.automute.enabled) return; if (!this.Config.collabvm.automute.enabled) return;
// rate limit guest and unregistered chat messages, but not staff ones // rate limit guest and unregistered chat messages, but not staff ones
@@ -99,6 +105,7 @@ export class User {
break; break;
} }
} }
mute(permanent: boolean) { mute(permanent: boolean) {
this.IP.muted = true; this.IP.muted = true;
this.sendMsg(guacutils.encode('chat', '', `You have been muted${permanent ? '' : ` for ${this.Config.collabvm.tempMuteTime} seconds`}.`)); this.sendMsg(guacutils.encode('chat', '', `You have been muted${permanent ? '' : ` for ${this.Config.collabvm.tempMuteTime} seconds`}.`));

View File

@@ -1,37 +1,37 @@
import { Permissions } from "./IConfig"; import { Permissions } from './IConfig';
export function Randint(min: number, max: number) { export function Randint(min: number, max: number) {
return Math.floor((Math.random() * (max - min)) + min); return Math.floor(Math.random() * (max - min) + min);
} }
export function HTMLSanitize(input: string): string { export function HTMLSanitize(input: string): string {
var output = ""; var output = '';
for (var i = 0; i < input.length; i++) { for (var i = 0; i < input.length; i++) {
switch (input[i]) { switch (input[i]) {
case "<": case '<':
output += "&lt;" output += '&lt;';
break; break;
case ">": case '>':
output += "&gt;" output += '&gt;';
break; break;
case "&": case '&':
output += "&amp;" output += '&amp;';
break; break;
case "\"": case '"':
output += "&quot;" output += '&quot;';
break; break;
case "'": case "'":
output += "&#x27;"; output += '&#x27;';
break; break;
case "/": case '/':
output += "&#x2F;"; output += '&#x2F;';
break; break;
case "\n": case '\n':
output += "&#13;&#10;"; output += '&#13;&#10;';
break; break;
default: default:
var charcode: number = input.charCodeAt(i); var charcode: number = input.charCodeAt(i);
if (charcode >= 32 && charcode <= 126) if (charcode >= 32 && charcode <= 126) output += input[i];
output += input[i];
break; break;
} }
} }

View File

@@ -28,8 +28,13 @@ const __dirname = import.meta.dirname;
const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets'); const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets');
type ChatHistory = { type ChatHistory = {
user: string, user: string;
msg: string msg: string;
};
type VoteTally = {
yes: number;
no: number;
}; };
// A good balance. TODO: Configurable? // A good balance. TODO: Configurable?
@@ -41,7 +46,7 @@ function GetRawSharpOptions(size: Size): sharp.CreateRaw {
width: size.width, width: size.width,
height: size.height, height: size.height,
channels: 4 channels: 4
} };
} }
// Thread pool for doing JPEG encoding for rects. // Thread pool for doing JPEG encoding for rects.
@@ -63,9 +68,7 @@ async function EncodeJpeg(canvas: Buffer, displaySize: Size, rect: Rect): Promis
}); });
// TODO: There's probably (definitely) a better way to fix this // TODO: There's probably (definitely) a better way to fix this
if(res == undefined) if (res == undefined) return Buffer.from([]);
return Buffer.from([]);
// have to manually turn it back into a buffer because // have to manually turn it back into a buffer because
// Piscina for some reason turns it into a Uint8Array // Piscina for some reason turns it into a Uint8Array
@@ -80,7 +83,7 @@ export default class WSServer {
private clients: User[]; private clients: User[];
private ChatHistory : CircularBuffer<ChatHistory> private ChatHistory: CircularBuffer<ChatHistory>;
private TurnQueue: Queue<User>; private TurnQueue: Queue<User>;
@@ -123,7 +126,7 @@ export default class WSServer {
// Authentication manager // Authentication manager
private auth: AuthManager | null; private auth: AuthManager | null;
private logger = new Logger("CVMTS.Server"); private logger = new Logger('CVMTS.Server');
constructor(config: IConfig, vm: QemuVM, auth: AuthManager | null) { constructor(config: IConfig, vm: QemuVM, auth: AuthManager | null) {
this.Config = config; this.Config = config;
@@ -136,8 +139,8 @@ export default class WSServer {
this.voteCooldown = 0; this.voteCooldown = 0;
this.turnsAllowed = true; this.turnsAllowed = true;
this.screenHidden = false; this.screenHidden = false;
this.screenHiddenImg = readFileSync(path.join(kCVMTSAssetsRoot, "screenhidden.jpeg")).toString("base64"); this.screenHiddenImg = readFileSync(path.join(kCVMTSAssetsRoot, 'screenhidden.jpeg')).toString('base64');
this.screenHiddenThumb = readFileSync(path.join(kCVMTSAssetsRoot, "screenhiddenthumb.jpeg")).toString("base64"); this.screenHiddenThumb = readFileSync(path.join(kCVMTSAssetsRoot, 'screenhiddenthumb.jpeg')).toString('base64');
this.indefiniteTurn = null; this.indefiniteTurn = null;
this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions); this.ModPerms = Utilities.MakeModPerms(this.Config.collabvm.moderatorPermissions);
@@ -146,7 +149,7 @@ export default class WSServer {
this.httpServer.on('upgrade', (req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) => this.httpOnUpgrade(req, socket, head)); this.httpServer.on('upgrade', (req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) => this.httpOnUpgrade(req, socket, head));
this.httpServer.on('request', (req, res) => { this.httpServer.on('request', (req, res) => {
res.writeHead(426); res.writeHead(426);
res.write("This server only accepts WebSocket connections."); res.write('This server only accepts WebSocket connections.');
res.end(); res.end();
}); });
@@ -172,11 +175,11 @@ export default class WSServer {
private httpOnUpgrade(req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) { private httpOnUpgrade(req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) {
var killConnection = () => { var killConnection = () => {
socket.write("HTTP/1.1 400 Bad Request\n\n400 Bad Request"); socket.write('HTTP/1.1 400 Bad Request\n\n400 Bad Request');
socket.destroy(); socket.destroy();
} };
if (req.headers['sec-websocket-protocol'] !== "guacamole") { if (req.headers['sec-websocket-protocol'] !== 'guacamole') {
killConnection(); killConnection();
return; return;
} }
@@ -200,7 +203,7 @@ export default class WSServer {
} }
// detect fake origin headers // detect fake origin headers
if (_uri.pathname !== "/" || _uri.search !== "") { if (_uri.pathname !== '/' || _uri.search !== '') {
killConnection(); killConnection();
return; return;
} }
@@ -220,13 +223,13 @@ export default class WSServer {
return; return;
} }
// Make sure x-forwarded-for is set // Make sure x-forwarded-for is set
if (req.headers["x-forwarded-for"] === undefined) { if (req.headers['x-forwarded-for'] === undefined) {
killConnection(); killConnection();
return; return;
} }
try { try {
// Get the first IP from the X-Forwarded-For variable // Get the first IP from the X-Forwarded-For variable
ip = req.headers["x-forwarded-for"]?.toString().replace(/\ /g, "").split(",")[0]; ip = req.headers['x-forwarded-for']?.toString().replace(/\ /g, '').split(',')[0];
} catch { } catch {
// If we can't get the IP, kill the connection // If we can't get the IP, kill the connection
killConnection(); killConnection();
@@ -248,10 +251,10 @@ export default class WSServer {
} }
// Get the amount of active connections coming from the requesting IP. // Get the amount of active connections coming from the requesting IP.
let connections = this.clients.filter(client => client.IP.address == ip); let connections = this.clients.filter((client) => client.IP.address == ip);
// If it exceeds the limit set in the config, reject the connection with a 429. // If it exceeds the limit set in the config, reject the connection with a 429.
if (connections.length + 1 > this.Config.http.maxConnections) { if (connections.length + 1 > this.Config.http.maxConnections) {
socket.write("HTTP/1.1 429 Too Many Requests\n\n429 Too Many Requests"); socket.write('HTTP/1.1 429 Too Many Requests\n\n429 Too Many Requests');
socket.destroy(); socket.destroy();
} }
@@ -283,19 +286,18 @@ export default class WSServer {
try { try {
this.onMessage(user, buf.toString()); this.onMessage(user, buf.toString());
} catch { } catch {}
}
}); });
if (this.Config.auth.enabled) { if (this.Config.auth.enabled) {
user.sendMsg(guacutils.encode("auth", this.Config.auth.apiEndpoint)); user.sendMsg(guacutils.encode('auth', this.Config.auth.apiEndpoint));
} }
user.sendMsg(this.getAdduserMsg()); user.sendMsg(this.getAdduserMsg());
this.logger.Info(`Connect from ${user.IP.address}`); this.logger.Info(`Connect from ${user.IP.address}`);
}; }
private connectionClosed(user: User) { private connectionClosed(user: User) {
let clientIndex = this.clients.indexOf(user) let clientIndex = this.clients.indexOf(user);
if (clientIndex === -1) return; if (clientIndex === -1) return;
if (user.IP.vote != null) { if (user.IP.vote != null) {
@@ -310,33 +312,32 @@ export default class WSServer {
this.clients.splice(clientIndex, 1); this.clients.splice(clientIndex, 1);
this.logger.Info(`Disconnect From ${user.IP.address}${user.username ? ` with username ${user.username}` : ""}`); this.logger.Info(`Disconnect From ${user.IP.address}${user.username ? ` with username ${user.username}` : ''}`);
if (!user.username) return; if (!user.username) return;
if (this.TurnQueue.toArray().indexOf(user) !== -1) { if (this.TurnQueue.toArray().indexOf(user) !== -1) {
var hadturn = (this.TurnQueue.peek() === user); var hadturn = this.TurnQueue.peek() === user;
this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter(u => u !== user)); this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter((u) => u !== user));
if (hadturn) this.nextTurn(); if (hadturn) this.nextTurn();
} }
this.clients.forEach((c) => c.sendMsg(guacutils.encode("remuser", "1", user.username!))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('remuser', '1', user.username!)));
} }
private async onMessage(client: User, message: string) { private async onMessage(client: User, message: string) {
var msgArr = guacutils.decode(message); var msgArr = guacutils.decode(message);
if (msgArr.length < 1) return; if (msgArr.length < 1) return;
switch (msgArr[0]) { switch (msgArr[0]) {
case "login": case 'login':
if (msgArr.length !== 2 || !this.Config.auth.enabled) return; if (msgArr.length !== 2 || !this.Config.auth.enabled) return;
if (!client.connectedToNode) { if (!client.connectedToNode) {
client.sendMsg(guacutils.encode("login", "0", "You must connect to the VM before logging in.")); client.sendMsg(guacutils.encode('login', '0', 'You must connect to the VM before logging in.'));
return; return;
} }
var res = await this.auth!.Authenticate(msgArr[1], client); var res = await this.auth!.Authenticate(msgArr[1], client);
if (res.clientSuccess) { if (res.clientSuccess) {
this.logger.Info(`${client.IP.address} logged in as ${res.username}`); this.logger.Info(`${client.IP.address} logged in as ${res.username}`);
client.sendMsg(guacutils.encode("login", "1")); client.sendMsg(guacutils.encode('login', '1'));
var old = this.clients.find(c=>c.username === res.username); var old = this.clients.find((c) => c.username === res.username);
if (old) { if (old) {
// kick() doesnt wait until the user is actually removed from the list and itd be anal to make it do that // kick() doesnt wait until the user is actually removed from the list and itd be anal to make it do that
// so we call connectionClosed manually here. When it gets called on kick(), it will return because the user isn't in the list // so we call connectionClosed manually here. When it gets called on kick(), it will return because the user isn't in the list
@@ -348,99 +349,99 @@ export default class WSServer {
// Set rank // Set rank
client.rank = res.rank; client.rank = res.rank;
if (client.rank === Rank.Admin) { if (client.rank === Rank.Admin) {
client.sendMsg(guacutils.encode("admin", "0", "1")); client.sendMsg(guacutils.encode('admin', '0', '1'));
} else if (client.rank === Rank.Moderator) { } else if (client.rank === Rank.Moderator) {
client.sendMsg(guacutils.encode("admin", "0", "3", this.ModPerms.toString())); client.sendMsg(guacutils.encode('admin', '0', '3', this.ModPerms.toString()));
} }
this.clients.forEach((c) => c.sendMsg(guacutils.encode("adduser", "1", client.username!, client.rank.toString()))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('adduser', '1', client.username!, client.rank.toString())));
} else { } else {
client.sendMsg(guacutils.encode("login", "0", res.error!)); client.sendMsg(guacutils.encode('login', '0', res.error!));
if (res.error === "You are banned") { if (res.error === 'You are banned') {
client.kick(); client.kick();
} }
} }
break; break;
case "list": case 'list':
client.sendMsg(guacutils.encode("list", this.Config.collabvm.node, this.Config.collabvm.displayname, this.screenHidden ? this.screenHiddenThumb : await this.getThumbnail())); client.sendMsg(guacutils.encode('list', this.Config.collabvm.node, this.Config.collabvm.displayname, this.screenHidden ? this.screenHiddenThumb : await this.getThumbnail()));
break; break;
case "connect": case 'connect':
if (!client.username || msgArr.length !== 2 || msgArr[1] !== this.Config.collabvm.node) { if (!client.username || msgArr.length !== 2 || msgArr[1] !== this.Config.collabvm.node) {
client.sendMsg(guacutils.encode("connect", "0")); client.sendMsg(guacutils.encode('connect', '0'));
return; return;
} }
client.connectedToNode = true; client.connectedToNode = true;
client.sendMsg(guacutils.encode("connect", "1", "1", this.Config.vm.snapshots ? "1" : "0", "0")); client.sendMsg(guacutils.encode('connect', '1', '1', this.Config.vm.snapshots ? '1' : '0', '0'));
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg()); if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode("chat", "", this.Config.collabvm.motd)); if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode('chat', '', this.Config.collabvm.motd));
if (this.screenHidden) { if (this.screenHidden) {
client.sendMsg(guacutils.encode("size", "0", "1024", "768")); client.sendMsg(guacutils.encode('size', '0', '1024', '768'));
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", this.screenHiddenImg)); client.sendMsg(guacutils.encode('png', '0', '0', '0', '0', this.screenHiddenImg));
} else { } else {
await this.SendFullScreenWithSize(client); await this.SendFullScreenWithSize(client);
} }
client.sendMsg(guacutils.encode("sync", Date.now().toString())); client.sendMsg(guacutils.encode('sync', Date.now().toString()));
if (this.voteInProgress) this.sendVoteUpdate(client); if (this.voteInProgress) this.sendVoteUpdate(client);
this.sendTurnUpdate(client); this.sendTurnUpdate(client);
break; break;
case "view": case 'view':
if (client.connectedToNode) return; if (client.connectedToNode) return;
if (client.username || msgArr.length !== 3 || msgArr[1] !== this.Config.collabvm.node) { if (client.username || msgArr.length !== 3 || msgArr[1] !== this.Config.collabvm.node) {
// The use of connect here is intentional. // The use of connect here is intentional.
client.sendMsg(guacutils.encode("connect", "0")); client.sendMsg(guacutils.encode('connect', '0'));
return; return;
} }
switch (msgArr[2]) { switch (msgArr[2]) {
case "0": case '0':
client.viewMode = 0; client.viewMode = 0;
break; break;
case "1": case '1':
client.viewMode = 1; client.viewMode = 1;
break; break;
default: default:
client.sendMsg(guacutils.encode("connect", "0")); client.sendMsg(guacutils.encode('connect', '0'));
return; return;
} }
client.sendMsg(guacutils.encode("connect", "1", "1", this.Config.vm.snapshots ? "1" : "0", "0")); client.sendMsg(guacutils.encode('connect', '1', '1', this.Config.vm.snapshots ? '1' : '0', '0'));
if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg()); if (this.ChatHistory.size !== 0) client.sendMsg(this.getChatHistoryMsg());
if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode("chat", "", this.Config.collabvm.motd)); if (this.Config.collabvm.motd) client.sendMsg(guacutils.encode('chat', '', this.Config.collabvm.motd));
if (client.viewMode == 1) { if (client.viewMode == 1) {
if (this.screenHidden) { if (this.screenHidden) {
client.sendMsg(guacutils.encode("size", "0", "1024", "768")); client.sendMsg(guacutils.encode('size', '0', '1024', '768'));
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", this.screenHiddenImg)); client.sendMsg(guacutils.encode('png', '0', '0', '0', '0', this.screenHiddenImg));
} else { } else {
await this.SendFullScreenWithSize(client); await this.SendFullScreenWithSize(client);
} }
client.sendMsg(guacutils.encode("sync", Date.now().toString())); client.sendMsg(guacutils.encode('sync', Date.now().toString()));
} }
if (this.voteInProgress) this.sendVoteUpdate(client); if (this.voteInProgress) this.sendVoteUpdate(client);
this.sendTurnUpdate(client); this.sendTurnUpdate(client);
break; break;
case "rename": case 'rename':
if (!client.RenameRateLimit.request()) return; if (!client.RenameRateLimit.request()) return;
if (client.connectedToNode && client.IP.muted) return; if (client.connectedToNode && client.IP.muted) return;
if (this.Config.auth.enabled && client.rank !== Rank.Unregistered) { if (this.Config.auth.enabled && client.rank !== Rank.Unregistered) {
client.sendMsg(guacutils.encode("chat", "", "Go to your account settings to change your username.")); client.sendMsg(guacutils.encode('chat', '', 'Go to your account settings to change your username.'));
return; return;
} }
if (this.Config.auth.enabled && msgArr[1] !== undefined) { if (this.Config.auth.enabled && msgArr[1] !== undefined) {
// Don't send system message to a user without a username since it was likely an automated attempt by the webapp // Don't send system message to a user without a username since it was likely an automated attempt by the webapp
if (client.username) client.sendMsg(guacutils.encode("chat", "", "You need to log in to do that.")); if (client.username) client.sendMsg(guacutils.encode('chat', '', 'You need to log in to do that.'));
if (client.rank !== Rank.Unregistered) return; if (client.rank !== Rank.Unregistered) return;
this.renameUser(client, undefined); this.renameUser(client, undefined);
return; return;
} }
this.renameUser(client, msgArr[1]); this.renameUser(client, msgArr[1]);
break; break;
case "chat": case 'chat':
if (!client.username) return; if (!client.username) return;
if (client.IP.muted) return; if (client.IP.muted) return;
if (msgArr.length !== 2) return; if (msgArr.length !== 2) return;
if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.chat) { if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.chat) {
client.sendMsg(guacutils.encode("chat", "", "You need to login to do that.")); client.sendMsg(guacutils.encode('chat', '', 'You need to login to do that.'));
return; return;
} }
var msg = Utilities.HTMLSanitize(msgArr[1]); var msg = Utilities.HTMLSanitize(msgArr[1]);
@@ -448,14 +449,14 @@ export default class WSServer {
if (msg.length > this.Config.collabvm.maxChatLength) msg = msg.substring(0, this.Config.collabvm.maxChatLength); if (msg.length > this.Config.collabvm.maxChatLength) msg = msg.substring(0, this.Config.collabvm.maxChatLength);
if (msg.trim().length < 1) return; if (msg.trim().length < 1) return;
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", client.username!, msg))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', client.username!, msg)));
this.ChatHistory.push({ user: client.username, msg: msg }); this.ChatHistory.push({ user: client.username, msg: msg });
client.onMsgSent(); client.onMsgSent();
break; break;
case "turn": case 'turn':
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && client.rank !== Rank.Admin && client.rank !== Rank.Moderator && client.rank !== Rank.Turn) return; if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && client.rank !== Rank.Admin && client.rank !== Rank.Moderator && client.rank !== Rank.Turn) return;
if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.turn) { if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.turn) {
client.sendMsg(guacutils.encode("chat", "", "You need to login to do that.")); client.sendMsg(guacutils.encode('chat', '', 'You need to login to do that.'));
return; return;
} }
if (!client.TurnRateLimit.request()) return; if (!client.TurnRateLimit.request()) return;
@@ -463,14 +464,15 @@ export default class WSServer {
if (msgArr.length > 2) return; if (msgArr.length > 2) return;
var takingTurn: boolean; var takingTurn: boolean;
if (msgArr.length === 1) takingTurn = true; if (msgArr.length === 1) takingTurn = true;
else switch (msgArr[1]) { else
case "0": switch (msgArr[1]) {
case '0':
if (this.indefiniteTurn === client) { if (this.indefiniteTurn === client) {
this.indefiniteTurn = null; this.indefiniteTurn = null;
} }
takingTurn = false; takingTurn = false;
break; break;
case "1": case '1':
takingTurn = true; takingTurn = true;
break; break;
default: default:
@@ -486,20 +488,20 @@ export default class WSServer {
if (client.IP.muted) return; if (client.IP.muted) return;
if (this.Config.collabvm.turnlimit.enabled) { if (this.Config.collabvm.turnlimit.enabled) {
// Get the amount of users in the turn queue with the same IP as the user requesting a turn. // Get the amount of users in the turn queue with the same IP as the user requesting a turn.
let turns = currentQueue.filter(user => user.IP.address == client.IP.address); let turns = currentQueue.filter((user) => user.IP.address == client.IP.address);
// If it exceeds the limit set in the config, ignore the turn request. // If it exceeds the limit set in the config, ignore the turn request.
if (turns.length + 1 > this.Config.collabvm.turnlimit.maximum) return; if (turns.length + 1 > this.Config.collabvm.turnlimit.maximum) return;
} }
this.TurnQueue.enqueue(client); this.TurnQueue.enqueue(client);
if (this.TurnQueue.size === 1) this.nextTurn(); if (this.TurnQueue.size === 1) this.nextTurn();
} else { } else {
var hadturn = (this.TurnQueue.peek() === client); var hadturn = this.TurnQueue.peek() === client;
this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter(u => u !== client)); this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter((u) => u !== client));
if (hadturn) this.nextTurn(); if (hadturn) this.nextTurn();
} }
this.sendTurnUpdate(); this.sendTurnUpdate();
break; break;
case "mouse": case 'mouse':
if (this.TurnQueue.peek() !== client && client.rank !== Rank.Admin) return; if (this.TurnQueue.peek() !== client && client.rank !== Rank.Admin) return;
var x = parseInt(msgArr[1]); var x = parseInt(msgArr[1]);
var y = parseInt(msgArr[2]); var y = parseInt(msgArr[2]);
@@ -507,79 +509,76 @@ export default class WSServer {
if (x === undefined || y === undefined || mask === undefined) return; if (x === undefined || y === undefined || mask === undefined) return;
this.VM.GetDisplay()!.MouseEvent(x, y, mask); this.VM.GetDisplay()!.MouseEvent(x, y, mask);
break; break;
case "key": case 'key':
if (this.TurnQueue.peek() !== client && client.rank !== Rank.Admin) return; if (this.TurnQueue.peek() !== client && client.rank !== Rank.Admin) return;
var keysym = parseInt(msgArr[1]); var keysym = parseInt(msgArr[1]);
var down = parseInt(msgArr[2]); var down = parseInt(msgArr[2]);
if (keysym === undefined || (down !== 0 && down !== 1)) return; if (keysym === undefined || (down !== 0 && down !== 1)) return;
this.VM.GetDisplay()!.KeyboardEvent(keysym, down === 1 ? true : false); this.VM.GetDisplay()!.KeyboardEvent(keysym, down === 1 ? true : false);
break; break;
case "vote": case 'vote':
if (!this.Config.vm.snapshots) return; if (!this.Config.vm.snapshots) return;
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && client.rank !== Rank.Admin && client.rank !== Rank.Moderator && client.rank !== Rank.Turn) return; if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && client.rank !== Rank.Admin && client.rank !== Rank.Moderator && client.rank !== Rank.Turn) return;
if (!client.connectedToNode) return; if (!client.connectedToNode) return;
if (msgArr.length !== 2) return; if (msgArr.length !== 2) return;
if (!client.VoteRateLimit.request()) return; if (!client.VoteRateLimit.request()) return;
switch (msgArr[1]) { switch (msgArr[1]) {
case "1": case '1':
if (!this.voteInProgress) { if (!this.voteInProgress) {
if (this.voteCooldown !== 0) { if (this.voteCooldown !== 0) {
client.sendMsg(guacutils.encode("vote", "3", this.voteCooldown.toString())); client.sendMsg(guacutils.encode('vote', '3', this.voteCooldown.toString()));
return; return;
} }
this.startVote(); this.startVote();
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", `${client.username} has started a vote to reset the VM.`))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', '', `${client.username} has started a vote to reset the VM.`)));
} } else if (client.IP.vote !== true) this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', '', `${client.username} has voted yes.`)));
else if (client.IP.vote !== true)
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", `${client.username} has voted yes.`)));
client.IP.vote = true; client.IP.vote = true;
break; break;
case "0": case '0':
if (!this.voteInProgress) return; if (!this.voteInProgress) return;
if (client.IP.vote !== false) if (client.IP.vote !== false) this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', '', `${client.username} has voted no.`)));
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", `${client.username} has voted no.`)));
client.IP.vote = false; client.IP.vote = false;
break; break;
} }
this.sendVoteUpdate(); this.sendVoteUpdate();
break; break;
case "admin": case 'admin':
if (msgArr.length < 2) return; if (msgArr.length < 2) return;
switch (msgArr[1]) { switch (msgArr[1]) {
case "2": case '2':
// Login // Login
if (this.Config.auth.enabled) { if (this.Config.auth.enabled) {
client.sendMsg(guacutils.encode("chat", "", "This server does not support staff passwords. Please log in to become staff.")); client.sendMsg(guacutils.encode('chat', '', 'This server does not support staff passwords. Please log in to become staff.'));
return; return;
} }
if (!client.LoginRateLimit.request() || !client.username) return; if (!client.LoginRateLimit.request() || !client.username) return;
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
var sha256 = createHash("sha256"); var sha256 = createHash('sha256');
sha256.update(msgArr[2]); sha256.update(msgArr[2]);
var pwdHash = sha256.digest('hex'); var pwdHash = sha256.digest('hex');
sha256.destroy(); sha256.destroy();
if (pwdHash === this.Config.collabvm.adminpass) { if (pwdHash === this.Config.collabvm.adminpass) {
client.rank = Rank.Admin; client.rank = Rank.Admin;
client.sendMsg(guacutils.encode("admin", "0", "1")); client.sendMsg(guacutils.encode('admin', '0', '1'));
} else if (this.Config.collabvm.moderatorEnabled && pwdHash === this.Config.collabvm.modpass) { } else if (this.Config.collabvm.moderatorEnabled && pwdHash === this.Config.collabvm.modpass) {
client.rank = Rank.Moderator; client.rank = Rank.Moderator;
client.sendMsg(guacutils.encode("admin", "0", "3", this.ModPerms.toString())); client.sendMsg(guacutils.encode('admin', '0', '3', this.ModPerms.toString()));
} else if (this.Config.collabvm.turnwhitelist && pwdHash === this.Config.collabvm.turnpass) { } else if (this.Config.collabvm.turnwhitelist && pwdHash === this.Config.collabvm.turnpass) {
client.rank = Rank.Turn; client.rank = Rank.Turn;
client.sendMsg(guacutils.encode("chat", "", "You may now take turns.")); client.sendMsg(guacutils.encode('chat', '', 'You may now take turns.'));
} else { } else {
client.sendMsg(guacutils.encode("admin", "0", "0")); client.sendMsg(guacutils.encode('admin', '0', '0'));
return; return;
} }
if (this.screenHidden) { if (this.screenHidden) {
await this.SendFullScreenWithSize(client); await this.SendFullScreenWithSize(client);
client.sendMsg(guacutils.encode("sync", Date.now().toString())); client.sendMsg(guacutils.encode('sync', Date.now().toString()));
} }
this.clients.forEach((c) => c.sendMsg(guacutils.encode("adduser", "1", client.username!, client.rank.toString()))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('adduser', '1', client.username!, client.rank.toString())));
break; break;
case "5": case '5':
// QEMU Monitor // QEMU Monitor
if (client.rank !== Rank.Admin) return; if (client.rank !== Rank.Admin) return;
/* Surely there could be rudimentary processing to convert some qemu monitor syntax to [XYZ hypervisor] if possible /* Surely there could be rudimentary processing to convert some qemu monitor syntax to [XYZ hypervisor] if possible
@@ -590,51 +589,51 @@ export default class WSServer {
*/ */
if (msgArr.length !== 4 || msgArr[2] !== this.Config.collabvm.node) return; if (msgArr.length !== 4 || msgArr[2] !== this.Config.collabvm.node) return;
var output = await this.VM.MonitorCommand(msgArr[3]); var output = await this.VM.MonitorCommand(msgArr[3]);
client.sendMsg(guacutils.encode("admin", "2", String(output))); client.sendMsg(guacutils.encode('admin', '2', String(output)));
break; break;
case "8": case '8':
// Restore // Restore
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.restore)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.restore)) return;
this.VM.Reset(); this.VM.Reset();
break; break;
case "10": case '10':
// Reboot // Reboot
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return;
if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return; if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
this.VM.MonitorCommand("system_reset"); this.VM.MonitorCommand('system_reset');
break; break;
case "12": case '12':
// Ban // Ban
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.ban)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.ban)) return;
var user = this.clients.find(c => c.username === msgArr[2]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
user.ban(); user.ban();
case "13": case '13':
// Force Vote // Force Vote
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.forcevote)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.forcevote)) return;
if (!this.voteInProgress) return; if (!this.voteInProgress) return;
switch (msgArr[2]) { switch (msgArr[2]) {
case "1": case '1':
this.endVote(true); this.endVote(true);
break; break;
case "0": case '0':
this.endVote(false); this.endVote(false);
break; break;
} }
break; break;
case "14": case '14':
// Mute // Mute
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.mute)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.mute)) return;
if (msgArr.length !== 4) return; if (msgArr.length !== 4) return;
var user = this.clients.find(c => c.username === msgArr[2]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
var permamute; var permamute;
switch (msgArr[3]) { switch (msgArr[3]) {
case "0": case '0':
permamute = false; permamute = false;
break; break;
case "1": case '1':
permamute = true; permamute = true;
break; break;
default: default:
@@ -642,105 +641,105 @@ export default class WSServer {
} }
user.mute(permamute); user.mute(permamute);
break; break;
case "15": case '15':
// Kick // Kick
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.kick)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.kick)) return;
var user = this.clients.find(c => c.username === msgArr[2]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
user.kick(); user.kick();
break; break;
case "16": case '16':
// End turn // End turn
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return;
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
var user = this.clients.find(c => c.username === msgArr[2]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
this.endTurn(user); this.endTurn(user);
break; break;
case "17": case '17':
// Clear turn queue // Clear turn queue
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return;
if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return; if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
this.clearTurns(); this.clearTurns();
break; break;
case "18": case '18':
// Rename user // Rename user
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.rename)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.rename)) return;
if (this.Config.auth.enabled) { if (this.Config.auth.enabled) {
client.sendMsg(guacutils.encode("chat", "", "Cannot rename users on a server that uses authentication.")); client.sendMsg(guacutils.encode('chat', '', 'Cannot rename users on a server that uses authentication.'));
} }
if (msgArr.length !== 4) return; if (msgArr.length !== 4) return;
var user = this.clients.find(c => c.username === msgArr[2]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
this.renameUser(user, msgArr[3]); this.renameUser(user, msgArr[3]);
break; break;
case "19": case '19':
// Get IP // Get IP
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.grabip)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.grabip)) return;
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
var user = this.clients.find(c => c.username === msgArr[2]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
client.sendMsg(guacutils.encode("admin", "19", msgArr[2], user.IP.address)); client.sendMsg(guacutils.encode('admin', '19', msgArr[2], user.IP.address));
break; break;
case "20": case '20':
// Steal turn // Steal turn
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.bypassturn)) return;
this.bypassTurn(client); this.bypassTurn(client);
break; break;
case "21": case '21':
// XSS // XSS
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.xss)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.xss)) return;
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
switch (client.rank) { switch (client.rank) {
case Rank.Admin: case Rank.Admin:
this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', client.username!, msgArr[2])));
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", client.username!, msgArr[2])));
this.ChatHistory.push({ user: client.username!, msg: msgArr[2] }); this.ChatHistory.push({ user: client.username!, msg: msgArr[2] });
break; break;
case Rank.Moderator: case Rank.Moderator:
this.clients.filter((c) => c.rank !== Rank.Admin).forEach((c) => c.sendMsg(guacutils.encode('chat', client.username!, msgArr[2])));
this.clients.filter(c => c.rank !== Rank.Admin).forEach(c => c.sendMsg(guacutils.encode("chat", client.username!, msgArr[2]))); this.clients.filter((c) => c.rank === Rank.Admin).forEach((c) => c.sendMsg(guacutils.encode('chat', client.username!, Utilities.HTMLSanitize(msgArr[2]))));
this.clients.filter(c => c.rank === Rank.Admin).forEach(c => c.sendMsg(guacutils.encode("chat", client.username!, Utilities.HTMLSanitize(msgArr[2]))));
break; break;
} }
break; break;
case "22": case '22':
// Toggle turns // Toggle turns
if (client.rank !== Rank.Admin) return; if (client.rank !== Rank.Admin) return;
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
switch (msgArr[2]) { switch (msgArr[2]) {
case "0": case '0':
this.clearTurns(); this.clearTurns();
this.turnsAllowed = false; this.turnsAllowed = false;
break; break;
case "1": case '1':
this.turnsAllowed = true; this.turnsAllowed = true;
break; break;
} }
break; break;
case "23": case '23':
// Indefinite turn // Indefinite turn
if (client.rank !== Rank.Admin) return; if (client.rank !== Rank.Admin) return;
this.indefiniteTurn = client; this.indefiniteTurn = client;
this.TurnQueue = Queue.from([client, ...this.TurnQueue.toArray().filter(c=>c!==client)]); this.TurnQueue = Queue.from([client, ...this.TurnQueue.toArray().filter((c) => c !== client)]);
this.sendTurnUpdate(); this.sendTurnUpdate();
break; break;
case "24": case '24':
// Hide screen // Hide screen
if (client.rank !== Rank.Admin) return; if (client.rank !== Rank.Admin) return;
if (msgArr.length !== 3) return; if (msgArr.length !== 3) return;
switch (msgArr[2]) { switch (msgArr[2]) {
case "0": case '0':
this.screenHidden = true; this.screenHidden = true;
this.clients.filter(c => c.rank == Rank.Unregistered).forEach(client => { this.clients
client.sendMsg(guacutils.encode("size", "0", "1024", "768")); .filter((c) => c.rank == Rank.Unregistered)
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", this.screenHiddenImg)); .forEach((client) => {
client.sendMsg(guacutils.encode("sync", Date.now().toString())); client.sendMsg(guacutils.encode('size', '0', '1024', '768'));
client.sendMsg(guacutils.encode('png', '0', '0', '0', '0', this.screenHiddenImg));
client.sendMsg(guacutils.encode('sync', Date.now().toString()));
}); });
break; break;
case "1": case '1':
this.screenHidden = false; this.screenHidden = false;
let displaySize = this.VM.GetDisplay().Size(); let displaySize = this.VM.GetDisplay().Size();
@@ -751,31 +750,27 @@ export default class WSServer {
height: displaySize.height height: displaySize.height
}); });
this.clients.forEach(async (client) => {
this.clients.forEach(async client => { client.sendMsg(guacutils.encode('size', '0', displaySize.width.toString(), displaySize.height.toString()));
client.sendMsg(guacutils.encode("size", "0", displaySize.width.toString(), displaySize.height.toString())); client.sendMsg(guacutils.encode('png', '0', '0', '0', '0', encoded));
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", encoded)); client.sendMsg(guacutils.encode('sync', Date.now().toString()));
client.sendMsg(guacutils.encode("sync", Date.now().toString()));
}); });
break; break;
} }
break; break;
case "25": case '25':
if (client.rank !== Rank.Admin || msgArr.length !== 3) if (client.rank !== Rank.Admin || msgArr.length !== 3) return;
return; this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', '', msgArr[2])));
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", msgArr[2])));
break; break;
} }
break; break;
} }
} }
getUsernameList(): string[] { getUsernameList(): string[] {
var arr: string[] = []; var arr: string[] = [];
this.clients.filter(c => c.username).forEach((c) => arr.push(c.username!)); this.clients.filter((c) => c.username).forEach((c) => arr.push(c.username!));
return arr; return arr;
} }
@@ -784,63 +779,58 @@ export default class WSServer {
var hadName: boolean = client.username ? true : false; var hadName: boolean = client.username ? true : false;
var oldname: any; var oldname: any;
if (hadName) oldname = client.username; if (hadName) oldname = client.username;
var status = "0"; var status = '0';
if (!newName) { if (!newName) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
} else { } else {
newName = newName.trim(); newName = newName.trim();
if (hadName && newName === oldname) { if (hadName && newName === oldname) {
client.sendMsg(guacutils.encode('rename', '0', '0', client.username!, client.rank.toString()));
client.sendMsg(guacutils.encode("rename", "0", "0", client.username!, client.rank.toString()));
return; return;
} }
if (this.getUsernameList().indexOf(newName) !== -1) { if (this.getUsernameList().indexOf(newName) !== -1) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
if (client.connectedToNode) { if (client.connectedToNode) {
status = "1"; status = '1';
} }
} else } else if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName) || newName.length > 20 || newName.length < 3) {
if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(newName) || newName.length > 20 || newName.length < 3) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
status = "2"; status = '2';
} else } else if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) {
if (this.Config.collabvm.usernameblacklist.indexOf(newName) !== -1) {
client.assignGuestName(this.getUsernameList()); client.assignGuestName(this.getUsernameList());
status = "3"; status = '3';
} else client.username = newName; } else client.username = newName;
} }
client.sendMsg(guacutils.encode("rename", "0", status, client.username!, client.rank.toString())); client.sendMsg(guacutils.encode('rename', '0', status, client.username!, client.rank.toString()));
if (hadName) { if (hadName) {
this.logger.Info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`); this.logger.Info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
this.clients.forEach((c) => this.clients.forEach((c) => c.sendMsg(guacutils.encode('rename', '1', oldname, client.username!, client.rank.toString())));
c.sendMsg(guacutils.encode("rename", "1", oldname, client.username!, client.rank.toString())));
} else { } else {
this.logger.Info(`Rename ${client.IP.address} to ${client.username}`); this.logger.Info(`Rename ${client.IP.address} to ${client.username}`);
this.clients.forEach((c) => this.clients.forEach((c) => c.sendMsg(guacutils.encode('adduser', '1', client.username!, client.rank.toString())));
c.sendMsg(guacutils.encode("adduser", "1", client.username!, client.rank.toString())));
} }
} }
getAdduserMsg(): string { getAdduserMsg(): string {
var arr : string[] = ["adduser", this.clients.filter(c=>c.username).length.toString()]; var arr: string[] = ['adduser', this.clients.filter((c) => c.username).length.toString()];
this.clients.filter(c=>c.username).forEach((c) => arr.push(c.username!, c.rank.toString())); this.clients.filter((c) => c.username).forEach((c) => arr.push(c.username!, c.rank.toString()));
return guacutils.encode(...arr); return guacutils.encode(...arr);
} }
getChatHistoryMsg(): string { getChatHistoryMsg(): string {
var arr : string[] = ["chat"]; var arr: string[] = ['chat'];
this.ChatHistory.forEach(c => arr.push(c.user, c.msg)); this.ChatHistory.forEach((c) => arr.push(c.user, c.msg));
return guacutils.encode(...arr); return guacutils.encode(...arr);
} }
private sendTurnUpdate(client?: User) { private sendTurnUpdate(client?: User) {
var turnQueueArr = this.TurnQueue.toArray(); var turnQueueArr = this.TurnQueue.toArray();
var turntime; var turntime;
if (this.indefiniteTurn === null) turntime = (this.TurnTime * 1000); if (this.indefiniteTurn === null) turntime = this.TurnTime * 1000;
else turntime = 9999999999; else turntime = 9999999999;
var arr = ["turn", turntime.toString(), this.TurnQueue.size.toString()]; var arr = ['turn', turntime.toString(), this.TurnQueue.size.toString()];
// @ts-ignore // @ts-ignore
this.TurnQueue.forEach((c) => arr.push(c.username)); this.TurnQueue.forEach((c) => arr.push(c.username));
var currentTurningUser = this.TurnQueue.peek(); var currentTurningUser = this.TurnQueue.peek();
@@ -848,18 +838,19 @@ export default class WSServer {
client.sendMsg(guacutils.encode(...arr)); client.sendMsg(guacutils.encode(...arr));
return; return;
} }
this.clients.filter(c => (c !== currentTurningUser && c.connectedToNode)).forEach((c) => { this.clients
.filter((c) => c !== currentTurningUser && c.connectedToNode)
.forEach((c) => {
if (turnQueueArr.indexOf(c) !== -1) { if (turnQueueArr.indexOf(c) !== -1) {
var time; var time;
if (this.indefiniteTurn === null) time = ((this.TurnTime * 1000) + ((turnQueueArr.indexOf(c) - 1) * this.Config.collabvm.turnTime * 1000)); if (this.indefiniteTurn === null) time = this.TurnTime * 1000 + (turnQueueArr.indexOf(c) - 1) * this.Config.collabvm.turnTime * 1000;
else time = 9999999999; else time = 9999999999;
c.sendMsg(guacutils.encode(...arr, time.toString())); c.sendMsg(guacutils.encode(...arr, time.toString()));
} else { } else {
c.sendMsg(guacutils.encode(...arr)); c.sendMsg(guacutils.encode(...arr));
} }
}); });
if (currentTurningUser) if (currentTurningUser) currentTurningUser.sendMsg(guacutils.encode(...arr));
currentTurningUser.sendMsg(guacutils.encode(...arr));
} }
private nextTurn() { private nextTurn() {
clearInterval(this.TurnInterval); clearInterval(this.TurnInterval);
@@ -878,14 +869,14 @@ export default class WSServer {
} }
bypassTurn(client: User) { bypassTurn(client: User) {
var a = this.TurnQueue.toArray().filter(c => c !== client); var a = this.TurnQueue.toArray().filter((c) => c !== client);
this.TurnQueue = Queue.from([client, ...a]); this.TurnQueue = Queue.from([client, ...a]);
this.nextTurn(); this.nextTurn();
} }
endTurn(client: User) { endTurn(client: User) {
var hasTurn = (this.TurnQueue.peek() === client); var hasTurn = this.TurnQueue.peek() === client;
this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter(c => c !== client)); this.TurnQueue = Queue.from(this.TurnQueue.toArray().filter((c) => c !== client));
if (hasTurn) this.nextTurn(); if (hasTurn) this.nextTurn();
else this.sendTurnUpdate(); else this.sendTurnUpdate();
} }
@@ -902,17 +893,21 @@ export default class WSServer {
private async OnDisplayRectangle(rect: Rect) { private async OnDisplayRectangle(rect: Rect) {
let encodedb64 = await this.MakeRectData(rect); let encodedb64 = await this.MakeRectData(rect);
this.clients.filter(c => c.connectedToNode || c.viewMode == 1).forEach(c => { this.clients
.filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => {
if (this.screenHidden && c.rank == Rank.Unregistered) return; if (this.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(guacutils.encode("png", "0", "0", rect.x.toString(), rect.y.toString(), encodedb64)); c.sendMsg(guacutils.encode('png', '0', '0', rect.x.toString(), rect.y.toString(), encodedb64));
c.sendMsg(guacutils.encode("sync", Date.now().toString())); c.sendMsg(guacutils.encode('sync', Date.now().toString()));
}); });
} }
private OnDisplayResized(size: Size) { private OnDisplayResized(size: Size) {
this.clients.filter(c => c.connectedToNode || c.viewMode == 1).forEach(c => { this.clients
.filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => {
if (this.screenHidden && c.rank == Rank.Unregistered) return; if (this.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(guacutils.encode("size", "0", size.width.toString(), size.height.toString())) c.sendMsg(guacutils.encode('size', '0', size.width.toString(), size.height.toString()));
}); });
} }
@@ -927,8 +922,8 @@ export default class WSServer {
height: displaySize.height height: displaySize.height
}); });
client.sendMsg(guacutils.encode("size", "0", displaySize.width.toString(), displaySize.height.toString())); client.sendMsg(guacutils.encode('size', '0', displaySize.width.toString(), displaySize.height.toString()));
client.sendMsg(guacutils.encode("png", "0", "0", "0", "0", encoded)); client.sendMsg(guacutils.encode('png', '0', '0', '0', '0', encoded));
} }
private async MakeRectData(rect: Rect) { private async MakeRectData(rect: Rect) {
@@ -943,8 +938,7 @@ export default class WSServer {
getThumbnail(): Promise<string> { getThumbnail(): Promise<string> {
return new Promise(async (res, rej) => { return new Promise(async (res, rej) => {
let display = this.VM.GetDisplay(); let display = this.VM.GetDisplay();
if(display == null) if (display == null) return;
return;
// TODO: pass custom options to Sharp.resize() probably // TODO: pass custom options to Sharp.resize() probably
let out = await sharp(display.Buffer(), { raw: GetRawSharpOptions(display.Size()) }) let out = await sharp(display.Buffer(), { raw: GetRawSharpOptions(display.Size()) })
@@ -959,7 +953,7 @@ export default class WSServer {
startVote() { startVote() {
if (this.voteInProgress) return; if (this.voteInProgress) return;
this.voteInProgress = true; this.voteInProgress = true;
this.clients.forEach(c => c.sendMsg(guacutils.encode("vote", "0"))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('vote', '0')));
this.voteTime = this.Config.collabvm.voteTime; this.voteTime = this.Config.collabvm.voteTime;
this.voteInterval = setInterval(() => { this.voteInterval = setInterval(() => {
this.voteTime--; this.voteTime--;
@@ -974,37 +968,34 @@ export default class WSServer {
this.voteInProgress = false; this.voteInProgress = false;
clearInterval(this.voteInterval); clearInterval(this.voteInterval);
var count = this.getVoteCounts(); var count = this.getVoteCounts();
this.clients.forEach((c) => c.sendMsg(guacutils.encode("vote", "2"))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('vote', '2')));
if (result === true || (result === undefined && count.yes >= count.no)) { if (result === true || (result === undefined && count.yes >= count.no)) {
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", "The vote to reset the VM has won."))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', '', 'The vote to reset the VM has won.')));
this.VM.Reset(); this.VM.Reset();
} else { } else {
this.clients.forEach(c => c.sendMsg(guacutils.encode("chat", "", "The vote to reset the VM has lost."))); this.clients.forEach((c) => c.sendMsg(guacutils.encode('chat', '', 'The vote to reset the VM has lost.')));
} }
this.clients.forEach(c => { this.clients.forEach((c) => {
c.IP.vote = null; c.IP.vote = null;
}); });
this.voteCooldown = this.Config.collabvm.voteCooldown; this.voteCooldown = this.Config.collabvm.voteCooldown;
this.voteCooldownInterval = setInterval(() => { this.voteCooldownInterval = setInterval(() => {
this.voteCooldown--; this.voteCooldown--;
if (this.voteCooldown < 1) if (this.voteCooldown < 1) clearInterval(this.voteCooldownInterval);
clearInterval(this.voteCooldownInterval);
}, 1000); }, 1000);
} }
sendVoteUpdate(client?: User) { sendVoteUpdate(client?: User) {
if (!this.voteInProgress) return; if (!this.voteInProgress) return;
var count = this.getVoteCounts(); var count = this.getVoteCounts();
var msg = guacutils.encode("vote", "1", (this.voteTime * 1000).toString(), count.yes.toString(), count.no.toString()); var msg = guacutils.encode('vote', '1', (this.voteTime * 1000).toString(), count.yes.toString(), count.no.toString());
if (client) if (client) client.sendMsg(msg);
client.sendMsg(msg); else this.clients.forEach((c) => c.sendMsg(msg));
else
this.clients.forEach((c) => c.sendMsg(msg));
} }
getVoteCounts() : {yes:number,no:number} { getVoteCounts(): VoteTally {
var yes = 0; let yes = 0;
var no = 0; let no = 0;
IPDataManager.ForEachIPData((c) => { IPDataManager.ForEachIPData((c) => {
if (c.vote === true) yes++; if (c.vote === true) yes++;
if (c.vote === false) no++; if (c.vote === false) no++;

View File

@@ -5,27 +5,21 @@ export function decode(string : string) : string[] {
for (;;) { for (;;) {
let len = string.indexOf('.', pos + 1); let len = string.indexOf('.', pos + 1);
if(len === -1) if (len === -1) break;
break;
pos = parseInt(string.slice(pos + 1, len)) + len + 1; pos = parseInt(string.slice(pos + 1, len)) + len + 1;
// don't allow funky protocol length // don't allow funky protocol length
if(pos > string.length) if (pos > string.length) return [];
return [];
sections.push(string.slice(len + 1, pos)); sections.push(string.slice(len + 1, pos));
const sep = string.slice(pos, pos + 1); const sep = string.slice(pos, pos + 1);
if(sep === ',') if (sep === ',') continue;
continue; else if (sep === ';') break;
else if(sep === ';')
break;
else
// Invalid data. // Invalid data.
return []; else return [];
} }
return sections; return sections;
@@ -37,7 +31,7 @@ export function encode(...string : string[]) : string {
for (var i = 0; i < string.length; i++) { for (var i = 0; i < string.length; i++) {
let current = string[i]; let current = string[i];
command += current.toString().length + '.' + current; command += current.toString().length + '.' + current;
command += ( i < string.length - 1 ? ',' : ';'); command += i < string.length - 1 ? ',' : ';';
} }
return command; return command;
} }

View File

@@ -1,6 +1,6 @@
import * as toml from 'toml'; import * as toml from 'toml';
import IConfig from './IConfig.js'; import IConfig from './IConfig.js';
import * as fs from "fs"; import * as fs from 'fs';
import WSServer from './WSServer.js'; import WSServer from './WSServer.js';
import { QemuVM, QemuVmDefinition } from '@cvmts/qemu'; import { QemuVM, QemuVmDefinition } from '@cvmts/qemu';
@@ -8,35 +8,34 @@ import { QemuVM, QemuVmDefinition } from '@cvmts/qemu';
import * as Shared from '@cvmts/shared'; import * as Shared from '@cvmts/shared';
import AuthManager from './AuthManager.js'; import AuthManager from './AuthManager.js';
let logger = new Shared.Logger("CVMTS.Init"); let logger = new Shared.Logger('CVMTS.Init');
logger.Info("CollabVM Server starting up"); logger.Info('CollabVM Server starting up');
// Parse the config file // Parse the config file
var Config : IConfig; let Config: IConfig;
if (!fs.existsSync("config.toml")) { if (!fs.existsSync('config.toml')) {
logger.Error("Fatal error: Config.toml not found. Please copy config.example.toml and fill out fields") logger.Error('Fatal error: Config.toml not found. Please copy config.example.toml and fill out fields');
process.exit(1); process.exit(1);
} }
try { try {
var configRaw = fs.readFileSync("config.toml").toString(); var configRaw = fs.readFileSync('config.toml').toString();
Config = toml.parse(configRaw); Config = toml.parse(configRaw);
} catch (e) { } catch (e) {
logger.Error("Fatal error: Failed to read or parse the config file: {0}", (e as Error).message); logger.Error('Fatal error: Failed to read or parse the config file: {0}', (e as Error).message);
process.exit(1); process.exit(1);
} }
async function start() { async function start() {
// Print a warning if qmpSockDir is set // Print a warning if qmpSockDir is set
// and the host OS is Windows, as this // and the host OS is Windows, as this
// configuration will very likely not work. // configuration will very likely not work.
if(process.platform === "win32" && Config.vm.qmpSockDir) { if (process.platform === 'win32' && Config.vm.qmpSockDir) {
logger.Warning("You appear to have the option 'qmpSockDir' enabled in the config.") logger.Warning("You appear to have the option 'qmpSockDir' enabled in the config.");
logger.Warning("This is not supported on Windows, and you will likely run into issues."); logger.Warning('This is not supported on Windows, and you will likely run into issues.');
logger.Warning("To remove this warning, use the qmpHost and qmpPort options instead."); logger.Warning('To remove this warning, use the qmpHost and qmpPort options instead.');
} }
// Init the auth manager if enabled // Init the auth manager if enabled
@@ -46,7 +45,7 @@ async function start() {
let def: QemuVmDefinition = { let def: QemuVmDefinition = {
id: Config.collabvm.node, id: Config.collabvm.node,
command: Config.vm.qemuArgs command: Config.vm.qemuArgs
} };
var VM = new QemuVM(def); var VM = new QemuVM(def);
await VM.Start(); await VM.Start();

View File

@@ -1,4 +1,4 @@
import { Size, Rect } from "@cvmts/shared"; import { Size, Rect } from '@cvmts/shared';
export function BatchRects(size: Size, rects: Array<Rect>): Rect { export function BatchRects(size: Size, rects: Array<Rect>): Rect {
var mergedX = size.width; var mergedX = size.width;

View File

@@ -30,8 +30,6 @@ const kMaxFailCount = 5;
// TODO: This should be added to QemuVmDefinition and the below export removed // TODO: This should be added to QemuVmDefinition and the below export removed
let gVMShouldSnapshot = true; let gVMShouldSnapshot = true;
export function setSnapshot(val: boolean) { export function setSnapshot(val: boolean) {
gVMShouldSnapshot = val; gVMShouldSnapshot = val;
} }
@@ -70,8 +68,7 @@ export class QemuVM extends EventEmitter {
// FIXME: Still use TCP if on Windows. // FIXME: Still use TCP if on Windows.
if (!this.addedAdditionalArguments) { if (!this.addedAdditionalArguments) {
cmd += ' -no-shutdown'; cmd += ' -no-shutdown';
if(gVMShouldSnapshot) if (gVMShouldSnapshot) cmd += ' -snapshot';
cmd += ' -snapshot';
cmd += ` -qmp unix:${this.GetQmpPath()},server,wait -vnc unix:${this.GetVncPath()}`; cmd += ` -qmp unix:${this.GetQmpPath()},server,wait -vnc unix:${this.GetVncPath()}`;
this.definition.command = cmd; this.definition.command = cmd;
this.addedAdditionalArguments = true; this.addedAdditionalArguments = true;
@@ -275,16 +272,13 @@ export class QemuVM extends EventEmitter {
private async DisconnectQmp() { private async DisconnectQmp() {
if (this.qmpConnected) return; if (this.qmpConnected) return;
if(this.qmpInstance == null) if (this.qmpInstance == null) return;
return;
this.qmpConnected = false; this.qmpConnected = false;
this.qmpInstance.end(); this.qmpInstance.end();
this.qmpInstance = null; this.qmpInstance = null;
try { try {
await unlink(this.GetQmpPath()); await unlink(this.GetQmpPath());
} catch(err) { } catch (err) {}
}
} }
} }

View File

@@ -76,7 +76,7 @@ export default class QmpClient extends Socket {
// just rethrow lol // just rethrow lol
//throw err; //throw err;
console.log("you have pants: rules,", err); console.log('you have pants: rules,', err);
}); });
this.once('data', (data) => { this.once('data', (data) => {

View File

@@ -1,4 +1,3 @@
// TODO: `Object` has a toString(), but we should probably gate that off // TODO: `Object` has a toString(), but we should probably gate that off
/// Interface for things that can be turned into strings /// Interface for things that can be turned into strings
export interface ToStringable { export interface ToStringable {

View File

@@ -17,8 +17,8 @@ export type Size = {
}; };
export type Rect = { export type Rect = {
x: number, x: number;
y: number, y: number;
width: number, width: number;
height: number height: number;
}; };