chore: reformat all code with prettier
This commit is contained in:
1 parent
bcbf7db8d9
commit
ddae307874
13 files changed
+257
-278
No files matched your search
@@ -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}`);
|
||||||
|
|||||||
+5
-9
@@ -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);
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
+8
-1
@@ -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`}.`));
|
||||||
|
|||||||
+18
-18
@@ -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 += "<"
|
output += '<';
|
||||||
break;
|
break;
|
||||||
case ">":
|
case '>':
|
||||||
output += ">"
|
output += '>';
|
||||||
break;
|
break;
|
||||||
case "&":
|
case '&':
|
||||||
output += "&"
|
output += '&';
|
||||||
break;
|
break;
|
||||||
case "\"":
|
case '"':
|
||||||
output += """
|
output += '"';
|
||||||
break;
|
break;
|
||||||
case "'":
|
case "'":
|
||||||
output += "'";
|
output += ''';
|
||||||
break;
|
break;
|
||||||
case "/":
|
case '/':
|
||||||
output += "/";
|
output += '/';
|
||||||
break;
|
break;
|
||||||
case "\n":
|
case '\n':
|
||||||
output += " ";
|
output += ' ';
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+194
-203
@@ -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++;
|
||||||
|
|||||||
+6
-12
@@ -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;
|
||||||
}
|
}
|
||||||
+13
-14
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+3
-9
@@ -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) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+4
-4
@@ -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;
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user