TODO: Add senders for admin responses and flag and rename also verify I didn't fuck boolean conversion up
294 lines
8.4 KiB
TypeScript
294 lines
8.4 KiB
TypeScript
import pino from 'pino';
|
|
import { IProtocol, IProtocolHandlers, ListEntry, ProtocolAddUser, ProtocolChatHistory, ScreenRect } from './Protocol';
|
|
import { User } from './User';
|
|
|
|
import * as cvm from '@cvmts/cvm-rs';
|
|
|
|
// CollabVM protocol implementation for Guacamole.
|
|
export class GuacamoleProtocol implements IProtocol {
|
|
private handlers: IProtocolHandlers | null = null;
|
|
private logger = pino({
|
|
name: 'CVMTS.GuacamoleProtocol'
|
|
});
|
|
|
|
private user: User | null = null;
|
|
|
|
init(u: User): void {
|
|
this.user = u;
|
|
}
|
|
|
|
setHandler(handlers: IProtocolHandlers): void {
|
|
this.handlers = handlers;
|
|
}
|
|
|
|
private __processMessage_admin(decodedElements: string[]): boolean {
|
|
switch (decodedElements[1]) {
|
|
case '2':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminLogin(this.user!, decodedElements[2]);
|
|
break;
|
|
case '5':
|
|
if (decodedElements.length !== 4) return false;
|
|
this.handlers?.onAdminMonitor(this.user!, decodedElements[2], decodedElements[3]);
|
|
break;
|
|
case '8':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminRestore(this.user!, decodedElements[2]);
|
|
break;
|
|
case '10':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminReboot(this.user!, decodedElements[2]);
|
|
break;
|
|
case '12':
|
|
if (decodedElements.length < 3) return false;
|
|
this.handlers?.onAdminBanUser(this.user!, decodedElements[2]);
|
|
case '13':
|
|
{
|
|
if (decodedElements.length !== 3) return false;
|
|
let choice = parseInt(decodedElements[2]);
|
|
if (choice == undefined) return false;
|
|
this.handlers?.onAdminForceVote(this.user!, choice);
|
|
}
|
|
break;
|
|
case '14':
|
|
{
|
|
if (decodedElements.length !== 4) return false;
|
|
let temporary = true;
|
|
if (decodedElements[3] == '0') temporary = true;
|
|
else if (decodedElements[3] == '1') temporary = false;
|
|
else return false;
|
|
this.handlers?.onAdminMuteUser(this.user!, decodedElements[2], temporary);
|
|
}
|
|
break;
|
|
case '15':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminKickUser(this.user!, decodedElements[2]);
|
|
break;
|
|
case '16':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminEndTurn(this.user!, decodedElements[2]);
|
|
break;
|
|
case '17':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminClearQueue(this.user!, decodedElements[2]);
|
|
break;
|
|
case '18':
|
|
if (decodedElements.length !== 4) return false;
|
|
this.handlers?.onAdminRename(this.user!, decodedElements[2], decodedElements[3]);
|
|
break;
|
|
case '19':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminGetIP(this.user!, decodedElements[2]);
|
|
break;
|
|
case '20':
|
|
this.handlers?.onAdminBypassTurn(this.user!);
|
|
break;
|
|
case '21':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminRawMessage(this.user!, decodedElements[2]);
|
|
break;
|
|
case '22':
|
|
{
|
|
// Toggle turns
|
|
if (decodedElements.length !== 3) return false;
|
|
let enabled = true;
|
|
if (decodedElements[2] == '0') enabled = false;
|
|
else if (decodedElements[2] == '1') enabled = true;
|
|
else return false;
|
|
this.handlers?.onAdminToggleTurns(this.user!, enabled);
|
|
}
|
|
break;
|
|
case '23':
|
|
this.handlers?.onAdminIndefiniteTurn(this.user!);
|
|
break;
|
|
case '24':
|
|
{
|
|
if (decodedElements.length !== 3) return false;
|
|
let show = true;
|
|
if (decodedElements[2] == '0') show = false;
|
|
else if (decodedElements[2] == '1') show = true;
|
|
else return false;
|
|
this.handlers?.onAdminHideScreen(this.user!, show);
|
|
}
|
|
break;
|
|
case '25':
|
|
if (decodedElements.length !== 3) return false;
|
|
this.handlers?.onAdminSystemMessage(this.user!, decodedElements[2]);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
processMessage(buffer: Buffer): boolean {
|
|
let decodedElements = cvm.guacDecode(buffer.toString('utf-8'));
|
|
if (decodedElements.length < 1) return false;
|
|
|
|
// The first element is the "opcode".
|
|
switch (decodedElements[0]) {
|
|
case 'nop':
|
|
this.handlers?.onNop(this.user!);
|
|
break;
|
|
case 'cap':
|
|
if (decodedElements.length < 2) return false;
|
|
this.handlers?.onCapabilityUpgrade(this.user!, decodedElements.slice(1));
|
|
break;
|
|
case 'login':
|
|
if (decodedElements.length !== 2) return false;
|
|
this.handlers?.onLogin(this.user!, decodedElements[1]);
|
|
break;
|
|
case 'noflag':
|
|
this.handlers?.onNoFlag(this.user!);
|
|
break;
|
|
case 'list':
|
|
this.handlers?.onList(this.user!);
|
|
break;
|
|
case 'connect':
|
|
if (decodedElements.length !== 2) return false;
|
|
this.handlers?.onConnect(this.user!, decodedElements[1]);
|
|
break;
|
|
case 'view':
|
|
{
|
|
if (decodedElements.length !== 3) return false;
|
|
let viewMode = parseInt(decodedElements[2]);
|
|
if (viewMode == undefined) return false;
|
|
|
|
this.handlers?.onView(this.user!, decodedElements[1], viewMode);
|
|
}
|
|
break;
|
|
case 'rename':
|
|
this.handlers?.onRename(this.user!, decodedElements[1]);
|
|
break;
|
|
case 'chat':
|
|
if (decodedElements.length !== 2) return false;
|
|
this.handlers?.onChat(this.user!, decodedElements[1]);
|
|
break;
|
|
case 'turn':
|
|
let forfeit = false;
|
|
if (decodedElements.length > 2) return false;
|
|
if (decodedElements.length == 1) {
|
|
forfeit = false;
|
|
} else {
|
|
if (decodedElements[1] == '0') forfeit = true;
|
|
else if (decodedElements[1] == '1') forfeit = false;
|
|
}
|
|
|
|
this.handlers?.onTurnRequest(this.user!, forfeit);
|
|
break;
|
|
case 'mouse':
|
|
if (decodedElements.length !== 4) return false;
|
|
|
|
let x = parseInt(decodedElements[1]);
|
|
let y = parseInt(decodedElements[2]);
|
|
let mask = parseInt(decodedElements[3]);
|
|
if (x === undefined || y === undefined || mask === undefined) return false;
|
|
|
|
this.handlers?.onMouse(this.user!, x, y, mask);
|
|
break;
|
|
case 'key':
|
|
if (decodedElements.length !== 3) return false;
|
|
var keysym = parseInt(decodedElements[1]);
|
|
var down = parseInt(decodedElements[2]);
|
|
if (keysym === undefined || (down !== 0 && down !== 1)) return false;
|
|
this.handlers?.onKey(this.user!, keysym, down === 1);
|
|
break;
|
|
case 'vote':
|
|
if (decodedElements.length !== 2) return false;
|
|
let choice = parseInt(decodedElements[1]);
|
|
if (choice == undefined) return false;
|
|
this.handlers?.onVote(this.user!, choice);
|
|
break;
|
|
|
|
case 'admin':
|
|
if (decodedElements.length < 2) return false;
|
|
return this.__processMessage_admin(decodedElements);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Senders
|
|
|
|
sendAuth(authServer: string): void {
|
|
this.user?.sendMsg(cvm.guacEncode('auth', authServer));
|
|
}
|
|
|
|
sendNop(): void {
|
|
this.user?.sendMsg(cvm.guacEncode('nop'));
|
|
}
|
|
|
|
sendSync(now: number): void {
|
|
this.user?.sendMsg(cvm.guacEncode('sync', now.toString()));
|
|
}
|
|
|
|
sendConnectFailResponse(): void {
|
|
this.user?.sendMsg(cvm.guacEncode('connect', '0'));
|
|
}
|
|
|
|
sendConnectOKResponse(votes: boolean): void {
|
|
this.user?.sendMsg(cvm.guacEncode('connect', '1', '1', votes ? '1' : '0', '0'));
|
|
}
|
|
|
|
sendLoginResponse(ok: boolean, message: string | undefined): void {
|
|
if (ok) {
|
|
this.user?.sendMsg(cvm.guacEncode('login', '1'));
|
|
return;
|
|
} else {
|
|
this.user?.sendMsg(cvm.guacEncode('login', '0', message!));
|
|
}
|
|
}
|
|
|
|
sendChatMessage(username: string, message: string): void {
|
|
this.user?.sendMsg(cvm.guacEncode('chat', username, message));
|
|
}
|
|
|
|
sendChatHistoryMessage(history: ProtocolChatHistory[]): void {
|
|
let arr = ['chat'];
|
|
for (let a of history) {
|
|
arr.push(a.user);
|
|
arr.push(a.msg);
|
|
}
|
|
|
|
this.user?.sendMsg(cvm.guacEncode(...arr));
|
|
}
|
|
|
|
sendAddUser(users: ProtocolAddUser[]): void {
|
|
let arr = ['adduser', users.length.toString()];
|
|
for (let user of users) {
|
|
arr.push(user.username);
|
|
arr.push(user.rank.toString());
|
|
}
|
|
|
|
this.user?.sendMsg(cvm.guacEncode(...arr));
|
|
}
|
|
|
|
sendRemUser(users: string[]): void {
|
|
let arr = ['remuser', users.length.toString()];
|
|
|
|
for (let user of users) {
|
|
arr.push(user);
|
|
}
|
|
|
|
this.user?.sendMsg(cvm.guacEncode(...arr));
|
|
}
|
|
|
|
sendListResponse(list: ListEntry[]): void {
|
|
let arr = ['list'];
|
|
for (let node of list) {
|
|
arr.push(node.id);
|
|
arr.push(node.name);
|
|
arr.push(node.thumbnail.toString('base64'));
|
|
}
|
|
|
|
this.user?.sendMsg(cvm.guacEncode(...arr));
|
|
}
|
|
|
|
sendScreenResize(width: number, height: number): void {
|
|
this.user?.sendMsg(cvm.guacEncode('size', '0', width.toString(), height.toString()));
|
|
}
|
|
|
|
sendScreenUpdate(rect: ScreenRect): void {
|
|
this.user?.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), rect.data.toString('base64')));
|
|
this.sendSync(Date.now());
|
|
}
|
|
}
|