cvm-rs: merge guac and jpeg libs together into one

doesn't really need to be two seperate libraries. also preperation for other funnies

the build script has been replaced with a much saner justfile which uses much saner "yarn workspace" invocations instead of blindly cding all over the place
This commit is contained in:
modeco80
2024-06-22 21:14:05 -04:00
parent 87a377a10f
commit b8ed177885
26 changed files with 246 additions and 414 deletions

8
.gitignore vendored
View File

@@ -10,9 +10,5 @@ cvmts/attic
**/dist/ **/dist/
# Guac-rs # Guac-rs
guac-rs/target cvm-rs/target
guac-rs/index.node cvm-rs/index.node
# jpegturbo-rs
jpegturbo-rs/target
jpegturbo-rs/index.node

8
Justfile Normal file
View File

@@ -0,0 +1,8 @@
all:
yarn workspace @cvmts/cvm-rs run build
yarn workspace @cvmts/shared run build
yarn workspace @cvmts/qemu run build
yarn workspace @cvmts/cvmts run build
pkg:
yarn

View File

@@ -7,9 +7,9 @@ This is a drop-in replacement for the dying CollabVM 1.2.11. Currently in beta
**TODO**: These instructions are not finished for the refactor branch. **TODO**: These instructions are not finished for the refactor branch.
1. Copy config.example.toml to config.toml, and fill out fields 1. Copy config.example.toml to config.toml, and fill out fields
2. Install dependencies: `npm i` 2. Install dependencies: `yarn`
3. Build it: `npm run build` 3. Build it: `yarn build`
4. Run it: `npm run serve` 4. Run it: `yarn serve`
## FAQ ## FAQ
### When I try to access the admin panel, the server crashes! ### When I try to access the admin panel, the server crashes!

View File

@@ -59,6 +59,16 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "cvm-rs"
version = "0.1.0"
dependencies = [
"neon",
"once_cell",
"tokio",
"turbojpeg-sys",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@@ -82,16 +92,6 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "jpegturbo-rs"
version = "0.1.0"
dependencies = [
"neon",
"once_cell",
"tokio",
"turbojpeg-sys",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.155" version = "0.2.155"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "jpegturbo-rs" name = "cvm-rs"
description = "Rust powered JPEGTurbo sex" description = "Rust utility library for cvmts. Runs all the high performance code"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
exclude = ["index.node"] exclude = ["index.node"]
@@ -10,6 +10,8 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
neon = "1" neon = "1"
# Required for JPEG
once_cell = "1.19.0" once_cell = "1.19.0"
tokio = { version = "1.38.0", features = [ "rt", "rt-multi-thread" ] } tokio = { version = "1.38.0", features = [ "rt", "rt-multi-thread" ] }
turbojpeg-sys = "1.0.0" turbojpeg-sys = "1.0.0"

84
cvm-rs/index.d.ts vendored Normal file
View File

@@ -0,0 +1,84 @@
//
// Guacamole Codec
export function guacDecode(input: string): string[];
export function guacEncode(...items: string[]): string;
interface JpegInputArgs {
width: number,
height: number,
stride: number, // The width of your input framebuffer OR your image width (if encoding a full image)
buffer: Buffer
// TODO: Allow different formats, or export a boxed ffi object which can store a format
// (i.e: new JpegEncoder(FORMAT_xxx)).
}
/// Performs JPEG encoding.
export function jpegEncode(input: JpegInputArgs) : Promise<Buffer>;
// TODO: Version that can downscale?
/* remoting API?
js side api:
class RemotingClient extends EventEmitter {
constructor(uri: string)
Connect(): Promise<void> - connects to server.
Disconnect(): void - disconnects from a server.
get FullScreen(): Buffer - gets the full screen JPEG at a specific moment. This should only be called once
during some user-specific setup (for example: when a new user connects)
get Thumbnail(): Buffer - gets JPEG thumbnail.
KeyEvent(key: number, pressed: boolean) - sends a key event to the server.
MouseEvent(x: number, y: number, buttons: MouseButtonMask) - sends a mouse event (the button mask is semi-standardized for remoting,
the mask can be converted if not applicable for a given protocol)
// explicit property setter APIs, maybe expose the semi-internal remotingSetProperty API if required?
set JpegQuality(q: number) - sets JPEG quality
// events:
on('open', cb: () => void) - on open
//on('firstupdate', cb: (rect: RectWithJpeg) => void) - the first update of a resize is given here
// doesn't really matter
on('resize', cb: (size: Size) => void) - when the server resizes we do too.
on('update', cb: (rects: Array<RectWithJpeg>) => void) - gives screen frame update as jpeg rects
(pre-batched using existing batcher or a new invention or something)
on('close', cb: () => void) - on close
on('cursor', cb: (b: CursorBitmap) => void) - cursor bitmap changed (always rgba8888)
}
binding side API:
remotingNew("vnc://abc.def:1234") - creates a new remoting client which will use the given protocol in the URI
xxx for callbacks (they will get migrated to eventemitter or something on the JS side so it's more "idiomatic", depending on performance.
In all honesty however, remoting will take care of all the performance sensitive tasks, so it probably won't matter at all)
remotingConnect(client) -> promise<void> (throws rejection) - disconnects
remotingDisconnect(client) - disconnects
remotingGetBuffer(client) -> Buffer - gets the buffer used for the screen
remotingSetProperty(client, propertyId, propertyValue) - sets property (e.g: jpeg quality)
e.g: server uri could be set after client creation
with remotingSetProperty(boxedClient, remoting.propertyServerUri, "vnc://another-server.org::2920")
remotingGetThumbnail(client) - gets thumbnail, this is updated by remoting at about 5 fps
remotingKeyEvent(client, key, pressed) - key event
remotingMouseEvent(client, x, y, buttons) - mouse event
on the rust side a boxed client will contain an inner boxed `dyn RemotingProtocolClient` which will contain protocol specific dispatch,
upon parsing a remoting URI we will create a given client (e.g, for `vnc://` we'd make the VNC one)
*/

View File

@@ -2,5 +2,5 @@
import { createRequire } from 'module'; import { createRequire } from 'module';
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
export let {jpegEncode} = require('./index.node'); export let {guacDecode, guacEncode, jpegEncode} = require('./index.node');

View File

@@ -1,5 +1,5 @@
{ {
"name": "@cvmts/guac-rs", "name": "@cvmts/cvm-rs",
"version": "0.1.0", "version": "0.1.0",
"packageManager": "yarn@4.1.1", "packageManager": "yarn@4.1.1",
"type": "module", "type": "module",

View File

@@ -1,6 +1,5 @@
mod guac;
use neon::prelude::*; use neon::prelude::*;
use crate::guac;
fn guac_decode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsArray> { fn guac_decode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsArray> {
let input = cx.argument::<JsString>(0)?.value(cx); let input = cx.argument::<JsString>(0)?.value(cx);
@@ -40,17 +39,10 @@ fn guac_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsString>
Ok(cx.string(guac::encode_instruction(&elements))) Ok(cx.string(guac::encode_instruction(&elements)))
} }
fn guac_decode(mut cx: FunctionContext) -> JsResult<JsArray> { pub fn guac_decode(mut cx: FunctionContext) -> JsResult<JsArray> {
guac_decode_impl(&mut cx) guac_decode_impl(&mut cx)
} }
fn guac_encode(mut cx: FunctionContext) -> JsResult<JsString> { pub fn guac_encode(mut cx: FunctionContext) -> JsResult<JsString> {
guac_encode_impl(&mut cx) guac_encode_impl(&mut cx)
} }
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("guacDecode", guac_decode)?;
cx.export_function("guacEncode", guac_encode)?;
Ok(())
}

View File

@@ -8,8 +8,10 @@ use tokio::runtime::Runtime;
use std::cell::RefCell; use std::cell::RefCell;
mod jpeg_compressor; use crate::jpeg_compressor::*;
/// Gives a static Tokio runtime. We should replace this with
/// rayon or something, but for now tokio works.
fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> {
static RUNTIME: OnceCell<Runtime> = OnceCell::new(); static RUNTIME: OnceCell<Runtime> = OnceCell::new();
@@ -19,7 +21,7 @@ fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> {
} }
thread_local! { thread_local! {
static COMPRESSOR: RefCell<jpeg_compressor::JpegCompressor> = RefCell::new(jpeg_compressor::JpegCompressor::new()); static COMPRESSOR: RefCell<JpegCompressor> = RefCell::new(JpegCompressor::new());
} }
fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise> { fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise> {
@@ -52,7 +54,7 @@ fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise>
let clone = Arc::clone(&copy); let clone = Arc::clone(&copy);
let locked = clone.lock().unwrap(); let locked = clone.lock().unwrap();
let image: jpeg_compressor::Image = jpeg_compressor::Image { let image: Image = Image {
buffer: locked.as_slice(), buffer: locked.as_slice(),
width: width as u32, width: width as u32,
height: height as u32, height: height as u32,
@@ -80,12 +82,6 @@ fn jpeg_encode_impl<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsPromise>
Ok(promise) Ok(promise)
} }
fn jpeg_encode(mut cx: FunctionContext) -> JsResult<JsPromise> { pub fn jpeg_encode(mut cx: FunctionContext) -> JsResult<JsPromise> {
jpeg_encode_impl(&mut cx) jpeg_encode_impl(&mut cx)
} }
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("jpegEncode", jpeg_encode)?;
Ok(())
}

19
cvm-rs/src/lib.rs Normal file
View File

@@ -0,0 +1,19 @@
mod guac;
mod guac_js;
mod jpeg_compressor;
mod jpeg_js;
use neon::prelude::*;
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
// Mostly transitionary, later on API should change
cx.export_function("jpegEncode", jpeg_js::jpeg_encode)?;
cx.export_function("guacDecode", guac_js::guac_decode)?;
cx.export_function("guacEncode", guac_js::guac_encode)?;
Ok(())
}

View File

@@ -11,8 +11,7 @@
"author": "Elijah R, modeco80", "author": "Elijah R, modeco80",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@cvmts/guac-rs": "*", "@cvmts/cvm-rs": "*",
"@cvmts/jpegturbo-rs": "*",
"@cvmts/qemu": "*", "@cvmts/qemu": "*",
"execa": "^8.0.1", "execa": "^8.0.1",
"mnemonist": "^0.39.5", "mnemonist": "^0.39.5",

View File

@@ -1,7 +1,7 @@
import IConfig from './IConfig.js'; import IConfig from './IConfig.js';
import * as Utilities from './Utilities.js'; import * as Utilities from './Utilities.js';
import { User, Rank } from './User.js'; import { User, Rank } from './User.js';
import * as guac from '@cvmts/guac-rs'; import * as cvm from '@cvmts/cvm-rs';
// I hate that you have to do it like this // I hate that you have to do it like this
import CircularBuffer from 'mnemonist/circular-buffer.js'; import CircularBuffer from 'mnemonist/circular-buffer.js';
import Queue from 'mnemonist/queue.js'; import Queue from 'mnemonist/queue.js';
@@ -142,7 +142,7 @@ export default class CollabVMServer {
user.socket.on('msg', (msg: string) => this.onMessage(user, msg)); user.socket.on('msg', (msg: string) => this.onMessage(user, msg));
user.socket.on('disconnect', () => this.connectionClosed(user)); user.socket.on('disconnect', () => this.connectionClosed(user));
if (this.Config.auth.enabled) { if (this.Config.auth.enabled) {
user.sendMsg(guac.guacEncode('auth', this.Config.auth.apiEndpoint)); user.sendMsg(cvm.guacEncode('auth', this.Config.auth.apiEndpoint));
} }
user.sendMsg(this.getAdduserMsg()); user.sendMsg(this.getAdduserMsg());
} }
@@ -171,25 +171,25 @@ export default class CollabVMServer {
if (hadturn) this.nextTurn(); if (hadturn) this.nextTurn();
} }
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('remuser', '1', user.username!))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('remuser', '1', user.username!)));
} }
private async onMessage(client: User, message: string) { private async onMessage(client: User, message: string) {
try { try {
var msgArr = guac.guacDecode(message); var msgArr = cvm.guacDecode(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(guac.guacEncode('login', '0', 'You must connect to the VM before logging in.')); client.sendMsg(cvm.guacEncode('login', '0', 'You must connect to the VM before logging in.'));
return; return;
} }
try { try {
let res = await this.auth!.Authenticate(msgArr[1], client); let 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(guac.guacEncode('login', '1')); client.sendMsg(cvm.guacEncode('login', '1'));
let old = this.clients.find((c) => c.username === res.username); let 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
@@ -202,13 +202,13 @@ export default class CollabVMServer {
// Set rank // Set rank
client.rank = res.rank; client.rank = res.rank;
if (client.rank === Rank.Admin) { if (client.rank === Rank.Admin) {
client.sendMsg(guac.guacEncode('admin', '0', '1')); client.sendMsg(cvm.guacEncode('admin', '0', '1'));
} else if (client.rank === Rank.Moderator) { } else if (client.rank === Rank.Moderator) {
client.sendMsg(guac.guacEncode('admin', '0', '3', this.ModPerms.toString())); client.sendMsg(cvm.guacEncode('admin', '0', '3', this.ModPerms.toString()));
} }
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('adduser', '1', client.username!, client.rank.toString()))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString())));
} else { } else {
client.sendMsg(guac.guacEncode('login', '0', res.error!)); client.sendMsg(cvm.guacEncode('login', '0', res.error!));
if (res.error === 'You are banned') { if (res.error === 'You are banned') {
client.kick(); client.kick();
} }
@@ -216,28 +216,28 @@ export default class CollabVMServer {
} catch (err) { } catch (err) {
this.logger.Error(`Error authenticating client ${client.IP.address}: ${(err as Error).message}`); this.logger.Error(`Error authenticating client ${client.IP.address}: ${(err as Error).message}`);
// for now? // for now?
client.sendMsg(guac.guacEncode('login', '0', 'There was an internal error while authenticating. Please let a staff member know as soon as possible')); client.sendMsg(cvm.guacEncode('login', '0', 'There was an internal error while authenticating. Please let a staff member know as soon as possible'));
} }
break; break;
case 'list': case 'list':
client.sendMsg(guac.guacEncode('list', this.Config.collabvm.node, this.Config.collabvm.displayname, this.screenHidden ? this.screenHiddenThumb : await this.getThumbnail())); client.sendMsg(cvm.guacEncode('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(guac.guacEncode('connect', '0')); client.sendMsg(cvm.guacEncode('connect', '0'));
return; return;
} }
client.connectedToNode = true; client.connectedToNode = true;
client.sendMsg(guac.guacEncode('connect', '1', '1', this.VM.SnapshotsSupported() ? '1' : '0', '0')); client.sendMsg(cvm.guacEncode('connect', '1', '1', this.VM.SnapshotsSupported() ? '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(guac.guacEncode('chat', '', this.Config.collabvm.motd)); if (this.Config.collabvm.motd) client.sendMsg(cvm.guacEncode('chat', '', this.Config.collabvm.motd));
if (this.screenHidden) { if (this.screenHidden) {
client.sendMsg(guac.guacEncode('size', '0', '1024', '768')); client.sendMsg(cvm.guacEncode('size', '0', '1024', '768'));
client.sendMsg(guac.guacEncode('png', '0', '0', '0', '0', this.screenHiddenImg)); client.sendMsg(cvm.guacEncode('png', '0', '0', '0', '0', this.screenHiddenImg));
} else { } else {
await this.SendFullScreenWithSize(client); await this.SendFullScreenWithSize(client);
} }
client.sendMsg(guac.guacEncode('sync', Date.now().toString())); client.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
if (this.voteInProgress) this.sendVoteUpdate(client); if (this.voteInProgress) this.sendVoteUpdate(client);
this.sendTurnUpdate(client); this.sendTurnUpdate(client);
break; break;
@@ -245,7 +245,7 @@ export default class CollabVMServer {
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(guac.guacEncode('connect', '0')); client.sendMsg(cvm.guacEncode('connect', '0'));
return; return;
} }
@@ -257,22 +257,22 @@ export default class CollabVMServer {
client.viewMode = 1; client.viewMode = 1;
break; break;
default: default:
client.sendMsg(guac.guacEncode('connect', '0')); client.sendMsg(cvm.guacEncode('connect', '0'));
return; return;
} }
client.sendMsg(guac.guacEncode('connect', '1', '1', this.VM.SnapshotsSupported() ? '1' : '0', '0')); client.sendMsg(cvm.guacEncode('connect', '1', '1', this.VM.SnapshotsSupported() ? '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(guac.guacEncode('chat', '', this.Config.collabvm.motd)); if (this.Config.collabvm.motd) client.sendMsg(cvm.guacEncode('chat', '', this.Config.collabvm.motd));
if (client.viewMode == 1) { if (client.viewMode == 1) {
if (this.screenHidden) { if (this.screenHidden) {
client.sendMsg(guac.guacEncode('size', '0', '1024', '768')); client.sendMsg(cvm.guacEncode('size', '0', '1024', '768'));
client.sendMsg(guac.guacEncode('png', '0', '0', '0', '0', this.screenHiddenImg)); client.sendMsg(cvm.guacEncode('png', '0', '0', '0', '0', this.screenHiddenImg));
} else { } else {
await this.SendFullScreenWithSize(client); await this.SendFullScreenWithSize(client);
} }
client.sendMsg(guac.guacEncode('sync', Date.now().toString())); client.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
} }
if (this.voteInProgress) this.sendVoteUpdate(client); if (this.voteInProgress) this.sendVoteUpdate(client);
@@ -282,12 +282,12 @@ export default class CollabVMServer {
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(guac.guacEncode('chat', '', 'Go to your account settings to change your username.')); client.sendMsg(cvm.guacEncode('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(guac.guacEncode('chat', '', 'You need to log in to do that.')); if (client.username) client.sendMsg(cvm.guacEncode('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;
@@ -299,7 +299,7 @@ export default class CollabVMServer {
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(guac.guacEncode('chat', '', 'You need to login to do that.')); client.sendMsg(cvm.guacEncode('chat', '', 'You need to login to do that.'));
return; return;
} }
var msg = Utilities.HTMLSanitize(msgArr[1]); var msg = Utilities.HTMLSanitize(msgArr[1]);
@@ -307,14 +307,14 @@ export default class CollabVMServer {
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(guac.guacEncode('chat', client.username!, msg))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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(guac.guacEncode('chat', '', 'You need to login to do that.')); client.sendMsg(cvm.guacEncode('chat', '', 'You need to login to do that.'));
return; return;
} }
if (!client.TurnRateLimit.request()) return; if (!client.TurnRateLimit.request()) return;
@@ -384,33 +384,33 @@ export default class CollabVMServer {
case '1': case '1':
if (!this.voteInProgress) { if (!this.voteInProgress) {
if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.callForReset) { if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.callForReset) {
client.sendMsg(guac.guacEncode('chat', '', 'You need to login to do that.')); client.sendMsg(cvm.guacEncode('chat', '', 'You need to login to do that.'));
return; return;
} }
if (this.voteCooldown !== 0) { if (this.voteCooldown !== 0) {
client.sendMsg(guac.guacEncode('vote', '3', this.voteCooldown.toString())); client.sendMsg(cvm.guacEncode('vote', '3', this.voteCooldown.toString()));
return; return;
} }
this.startVote(); this.startVote();
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('chat', '', `${client.username} has started a vote to reset the VM.`))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('chat', '', `${client.username} has started a vote to reset the VM.`)));
} }
if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.vote) { if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.vote) {
client.sendMsg(guac.guacEncode('chat', '', 'You need to login to do that.')); client.sendMsg(cvm.guacEncode('chat', '', 'You need to login to do that.'));
return; return;
} else if (client.IP.vote !== true) { } else if (client.IP.vote !== true) {
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('chat', '', `${client.username} has voted yes.`))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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 (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.vote) { if (this.Config.auth.enabled && client.rank === Rank.Unregistered && !this.Config.auth.guestPermissions.vote) {
client.sendMsg(guac.guacEncode('chat', '', 'You need to login to do that.')); client.sendMsg(cvm.guacEncode('chat', '', 'You need to login to do that.'));
return; return;
} }
if (client.IP.vote !== false) { if (client.IP.vote !== false) {
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('chat', '', `${client.username} has voted no.`))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('chat', '', `${client.username} has voted no.`)));
} }
client.IP.vote = false; client.IP.vote = false;
break; break;
@@ -423,7 +423,7 @@ export default class CollabVMServer {
case '2': case '2':
// Login // Login
if (this.Config.auth.enabled) { if (this.Config.auth.enabled) {
client.sendMsg(guac.guacEncode('chat', '', 'This server does not support staff passwords. Please log in to become staff.')); client.sendMsg(cvm.guacEncode('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;
@@ -434,37 +434,37 @@ export default class CollabVMServer {
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(guac.guacEncode('admin', '0', '1')); client.sendMsg(cvm.guacEncode('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(guac.guacEncode('admin', '0', '3', this.ModPerms.toString())); client.sendMsg(cvm.guacEncode('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(guac.guacEncode('chat', '', 'You may now take turns.')); client.sendMsg(cvm.guacEncode('chat', '', 'You may now take turns.'));
} else { } else {
client.sendMsg(guac.guacEncode('admin', '0', '0')); client.sendMsg(cvm.guacEncode('admin', '0', '0'));
return; return;
} }
if (this.screenHidden) { if (this.screenHidden) {
await this.SendFullScreenWithSize(client); await this.SendFullScreenWithSize(client);
client.sendMsg(guac.guacEncode('sync', Date.now().toString())); client.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
} }
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('adduser', '1', client.username!, client.rank.toString()))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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
if (!(this.VM instanceof QEMUVM)) { if (!(this.VM instanceof QEMUVM)) {
client.sendMsg(guac.guacEncode("admin", "2", "This is not a QEMU VM and therefore QEMU monitor commands cannot be run.")); client.sendMsg(cvm.guacEncode("admin", "2", "This is not a QEMU VM and therefore QEMU monitor commands cannot be run."));
return; return;
} }
*/ */
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(guac.guacEncode('admin', '2', String(output))); client.sendMsg(cvm.guacEncode('admin', '2', String(output)));
break; break;
case '8': case '8':
// Restore // Restore
@@ -541,7 +541,7 @@ export default class CollabVMServer {
// 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(guac.guacEncode('chat', '', 'Cannot rename users on a server that uses authentication.')); client.sendMsg(cvm.guacEncode('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]);
@@ -554,7 +554,7 @@ export default class CollabVMServer {
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(guac.guacEncode('admin', '19', msgArr[2], user.IP.address)); client.sendMsg(cvm.guacEncode('admin', '19', msgArr[2], user.IP.address));
break; break;
case '20': case '20':
// Steal turn // Steal turn
@@ -567,14 +567,14 @@ export default class CollabVMServer {
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(guac.guacEncode('chat', client.username!, msgArr[2]))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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(guac.guacEncode('chat', client.username!, msgArr[2]))); this.clients.filter((c) => c.rank !== Rank.Admin).forEach((c) => c.sendMsg(cvm.guacEncode('chat', client.username!, msgArr[2])));
this.clients.filter((c) => c.rank === Rank.Admin).forEach((c) => c.sendMsg(guac.guacEncode('chat', client.username!, Utilities.HTMLSanitize(msgArr[2])))); this.clients.filter((c) => c.rank === Rank.Admin).forEach((c) => c.sendMsg(cvm.guacEncode('chat', client.username!, Utilities.HTMLSanitize(msgArr[2]))));
break; break;
} }
break; break;
@@ -609,9 +609,9 @@ export default class CollabVMServer {
this.clients this.clients
.filter((c) => c.rank == Rank.Unregistered) .filter((c) => c.rank == Rank.Unregistered)
.forEach((client) => { .forEach((client) => {
client.sendMsg(guac.guacEncode('size', '0', '1024', '768')); client.sendMsg(cvm.guacEncode('size', '0', '1024', '768'));
client.sendMsg(guac.guacEncode('png', '0', '0', '0', '0', this.screenHiddenImg)); client.sendMsg(cvm.guacEncode('png', '0', '0', '0', '0', this.screenHiddenImg));
client.sendMsg(guac.guacEncode('sync', Date.now().toString())); client.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
}); });
break; break;
case '1': case '1':
@@ -626,16 +626,16 @@ export default class CollabVMServer {
}); });
this.clients.forEach(async (client) => { this.clients.forEach(async (client) => {
client.sendMsg(guac.guacEncode('size', '0', displaySize.width.toString(), displaySize.height.toString())); client.sendMsg(cvm.guacEncode('size', '0', displaySize.width.toString(), displaySize.height.toString()));
client.sendMsg(guac.guacEncode('png', '0', '0', '0', '0', encoded)); client.sendMsg(cvm.guacEncode('png', '0', '0', '0', '0', encoded));
client.sendMsg(guac.guacEncode('sync', Date.now().toString())); client.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
}); });
break; break;
} }
break; break;
case '25': case '25':
if (client.rank !== Rank.Admin || msgArr.length !== 3) return; if (client.rank !== Rank.Admin || msgArr.length !== 3) return;
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('chat', '', msgArr[2]))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('chat', '', msgArr[2])));
break; break;
} }
break; break;
@@ -665,7 +665,7 @@ export default class CollabVMServer {
} else { } else {
newName = newName.trim(); newName = newName.trim();
if (hadName && newName === oldname) { if (hadName && newName === oldname) {
client.sendMsg(guac.guacEncode('rename', '0', '0', client.username!, client.rank.toString())); client.sendMsg(cvm.guacEncode('rename', '0', '0', client.username!, client.rank.toString()));
return; return;
} }
if (this.getUsernameList().indexOf(newName) !== -1) { if (this.getUsernameList().indexOf(newName) !== -1) {
@@ -682,13 +682,13 @@ export default class CollabVMServer {
} else client.username = newName; } else client.username = newName;
} }
client.sendMsg(guac.guacEncode('rename', '0', status, client.username!, client.rank.toString())); client.sendMsg(cvm.guacEncode('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) => c.sendMsg(guac.guacEncode('rename', '1', oldname, client.username!, client.rank.toString()))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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) => c.sendMsg(guac.guacEncode('adduser', '1', client.username!, client.rank.toString()))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString())));
} }
} }
@@ -696,13 +696,13 @@ export default class CollabVMServer {
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 guac.guacEncode(...arr); return cvm.guacEncode(...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 guac.guacEncode(...arr); return cvm.guacEncode(...arr);
} }
private sendTurnUpdate(client?: User) { private sendTurnUpdate(client?: User) {
@@ -715,7 +715,7 @@ export default class CollabVMServer {
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();
if (client) { if (client) {
client.sendMsg(guac.guacEncode(...arr)); client.sendMsg(cvm.guacEncode(...arr));
return; return;
} }
this.clients this.clients
@@ -725,12 +725,12 @@ export default class CollabVMServer {
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(guac.guacEncode(...arr, time.toString())); c.sendMsg(cvm.guacEncode(...arr, time.toString()));
} else { } else {
c.sendMsg(guac.guacEncode(...arr)); c.sendMsg(cvm.guacEncode(...arr));
} }
}); });
if (currentTurningUser) currentTurningUser.sendMsg(guac.guacEncode(...arr)); if (currentTurningUser) currentTurningUser.sendMsg(cvm.guacEncode(...arr));
} }
private nextTurn() { private nextTurn() {
clearInterval(this.TurnInterval); clearInterval(this.TurnInterval);
@@ -777,8 +777,8 @@ export default class CollabVMServer {
.filter((c) => c.connectedToNode || c.viewMode == 1) .filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => { .forEach((c) => {
if (this.screenHidden && c.rank == Rank.Unregistered) return; if (this.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(guac.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), encodedb64)); c.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), encodedb64));
c.sendMsg(guac.guacEncode('sync', Date.now().toString())); c.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
}); });
} }
@@ -787,7 +787,7 @@ export default class CollabVMServer {
.filter((c) => c.connectedToNode || c.viewMode == 1) .filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => { .forEach((c) => {
if (this.screenHidden && c.rank == Rank.Unregistered) return; if (this.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(guac.guacEncode('size', '0', size.width.toString(), size.height.toString())); c.sendMsg(cvm.guacEncode('size', '0', size.width.toString(), size.height.toString()));
}); });
} }
@@ -802,8 +802,8 @@ export default class CollabVMServer {
height: displaySize.height height: displaySize.height
}); });
client.sendMsg(guac.guacEncode('size', '0', displaySize.width.toString(), displaySize.height.toString())); client.sendMsg(cvm.guacEncode('size', '0', displaySize.width.toString(), displaySize.height.toString()));
client.sendMsg(guac.guacEncode('png', '0', '0', '0', '0', encoded)); client.sendMsg(cvm.guacEncode('png', '0', '0', '0', '0', encoded));
} }
private async MakeRectData(rect: Rect) { private async MakeRectData(rect: Rect) {
@@ -828,7 +828,7 @@ export default class CollabVMServer {
startVote() { startVote() {
if (this.voteInProgress) return; if (this.voteInProgress) return;
this.voteInProgress = true; this.voteInProgress = true;
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('vote', '0'))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('vote', '0')));
this.voteTime = this.Config.collabvm.voteTime; this.voteTime = this.Config.collabvm.voteTime;
this.voteInterval = setInterval(() => { this.voteInterval = setInterval(() => {
this.voteTime--; this.voteTime--;
@@ -843,12 +843,12 @@ export default class CollabVMServer {
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(guac.guacEncode('vote', '2'))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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(guac.guacEncode('chat', '', 'The vote to reset the VM has won.'))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('chat', '', 'The vote to reset the VM has won.')));
this.VM.Reset(); this.VM.Reset();
} else { } else {
this.clients.forEach((c) => c.sendMsg(guac.guacEncode('chat', '', 'The vote to reset the VM has lost.'))); this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('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;
@@ -863,7 +863,7 @@ export default class CollabVMServer {
sendVoteUpdate(client?: User) { sendVoteUpdate(client?: User) {
if (!this.voteInProgress) return; if (!this.voteInProgress) return;
var count = this.getVoteCounts(); var count = this.getVoteCounts();
var msg = guac.guacEncode('vote', '1', (this.voteTime * 1000).toString(), count.yes.toString(), count.no.toString()); var msg = cvm.guacEncode('vote', '1', (this.voteTime * 1000).toString(), count.yes.toString(), count.no.toString());
if (client) client.sendMsg(msg); if (client) client.sendMsg(msg);
else this.clients.forEach((c) => c.sendMsg(msg)); else this.clients.forEach((c) => c.sendMsg(msg));
} }

View File

@@ -1,6 +1,6 @@
import { Size, Rect } from '@cvmts/shared'; import { Size, Rect } from '@cvmts/shared';
import sharp from 'sharp'; import sharp from 'sharp';
import * as jpeg from '@cvmts/jpegturbo-rs'; import * as cvm from '@cvmts/cvm-rs';
// A good balance. TODO: Configurable? // A good balance. TODO: Configurable?
let gJpegQuality = 35; let gJpegQuality = 35;
@@ -28,7 +28,7 @@ export class JPEGEncoder {
static async Encode(canvas: Buffer, displaySize: Size, rect: Rect): Promise<Buffer> { static async Encode(canvas: Buffer, displaySize: Size, rect: Rect): Promise<Buffer> {
let offset = (rect.y * displaySize.width + rect.x) * 4; let offset = (rect.y * displaySize.width + rect.x) * 4;
return jpeg.jpegEncode({ return cvm.jpegEncode({
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
stride: displaySize.width, stride: displaySize.width,
@@ -42,7 +42,7 @@ export class JPEGEncoder {
.raw() .raw()
.toBuffer({ resolveWithObject: true }); .toBuffer({ resolveWithObject: true });
return jpeg.jpegEncode({ return cvm.jpegEncode({
width: kThumbnailSize.width, width: kThumbnailSize.width,
height: kThumbnailSize.height, height: kThumbnailSize.height,
stride: kThumbnailSize.width, stride: kThumbnailSize.width,

View File

@@ -1,5 +1,5 @@
import * as Utilities from './Utilities.js'; import * as Utilities from './Utilities.js';
import * as guac from '@cvmts/guac-rs'; import * as cvm from '@cvmts/cvm-rs';
import { IPData } from './IPData.js'; import { IPData } from './IPData.js';
import IConfig from './IConfig.js'; import IConfig from './IConfig.js';
import RateLimiter from './RateLimiter.js'; import RateLimiter from './RateLimiter.js';
@@ -95,7 +95,7 @@ export class User {
} }
closeConnection() { closeConnection() {
this.socket.send(guac.guacEncode('disconnect')); this.socket.send(cvm.guacEncode('disconnect'));
this.socket.close(); this.socket.close();
} }
@@ -115,7 +115,7 @@ export class User {
mute(permanent: boolean) { mute(permanent: boolean) {
this.IP.muted = true; this.IP.muted = true;
this.sendMsg(guac.guacEncode('chat', '', `You have been muted${permanent ? '' : ` for ${this.Config.collabvm.tempMuteTime} seconds`}.`)); this.sendMsg(cvm.guacEncode('chat', '', `You have been muted${permanent ? '' : ` for ${this.Config.collabvm.tempMuteTime} seconds`}.`));
if (!permanent) { if (!permanent) {
clearTimeout(this.IP.tempMuteExpireTimeout); clearTimeout(this.IP.tempMuteExpireTimeout);
this.IP.tempMuteExpireTimeout = setTimeout(() => this.unmute(), this.Config.collabvm.tempMuteTime * 1000); this.IP.tempMuteExpireTimeout = setTimeout(() => this.unmute(), this.Config.collabvm.tempMuteTime * 1000);
@@ -124,7 +124,7 @@ export class User {
unmute() { unmute() {
clearTimeout(this.IP.tempMuteExpireTimeout); clearTimeout(this.IP.tempMuteExpireTimeout);
this.IP.muted = false; this.IP.muted = false;
this.sendMsg(guac.guacEncode('chat', '', 'You are no longer muted.')); this.sendMsg(cvm.guacEncode('chat', '', 'You are no longer muted.'));
} }
private banCmdArgs(arg: string): string { private banCmdArgs(arg: string): string {

View File

@@ -49,7 +49,14 @@ export default class WSClient extends EventEmitter implements NetworkClient {
} }
close(): void { close(): void {
this.socket.close(); if(this.isOpen()) {
// While this seems counterintutive, do note that the WebSocket protocol
// *sends* a data frame whilist closing a connection. Therefore, if the other end
// has forcibly hung up (closed) their connection, the best way to handle that
// is to just let the inner TCP socket propegate that, which `ws` will do for us.
// Otherwise, we'll try to send data to a closed client then SIGPIPE.
this.socket.close();
}
} }
} }

209
guac-rs/Cargo.lock generated
View File

@@ -1,209 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "guac-rs"
version = "0.1.0"
dependencies = [
"neon",
]
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libloading"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "neon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d75440242411c87dc39847b0e33e961ec1f10326a9d8ecf9c1ea64a3b3c13dc"
dependencies = [
"getrandom",
"libloading",
"neon-macros",
"once_cell",
"semver",
"send_wrapper",
"smallvec",
]
[[package]]
name = "neon-macros"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b"
dependencies = [
"quote",
"syn",
"syn-mid",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "proc-macro2"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "send_wrapper"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn-mid"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View File

@@ -1,12 +0,0 @@
[package]
name = "guac-rs"
description = "Rust guacamole decoding :)"
version = "0.1.0"
edition = "2021"
exclude = ["index.node"]
[lib]
crate-type = ["cdylib"]
[dependencies]
neon = "1"

3
guac-rs/index.d.ts vendored
View File

@@ -1,3 +0,0 @@
export function guacDecode(input: string): string[];
export function guacEncode(...items: string[]): string;

View File

@@ -1,6 +0,0 @@
// *sigh*
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
export let {guacDecode, guacEncode} = require('./index.node');

View File

@@ -1,15 +0,0 @@
interface JpegInputArgs {
width: number,
height: number,
stride: number, // The width of your input framebuffer OR your image width (if encoding a full image)
buffer: Buffer
// TODO: Allow different formats, or export a boxed ffi object which can store a format
// (i.e: new JpegEncoder(FORMAT_xxx)).
}
/// Performs JPEG encoding.
export function jpegEncode(input: JpegInputArgs) : Promise<Buffer>;
// TODO: Version that can downscale?

View File

@@ -1,16 +0,0 @@
{
"name": "@cvmts/jpegturbo-rs",
"version": "0.1.0",
"packageManager": "yarn@4.1.1",
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"build": "cargo-cp-artifact -nc index.node -- cargo build --release --message-format=json-render-diagnostics",
"install": "yarn build",
"test": "cargo test"
},
"devDependencies": {
"cargo-cp-artifact": "^0.1"
}
}

View File

@@ -2,8 +2,7 @@
"name": "cvmts-repo", "name": "cvmts-repo",
"workspaces": [ "workspaces": [
"shared", "shared",
"guac-rs", "cvm-rs",
"jpegturbo-rs",
"nodejs-rfb", "nodejs-rfb",
"qemu", "qemu",
"cvmts" "cvmts"
@@ -19,7 +18,7 @@
}, },
"packageManager": "yarn@4.1.1", "packageManager": "yarn@4.1.1",
"scripts": { "scripts": {
"build": "yarn && cd nodejs-rfb && yarn && yarn build && cd ../shared && yarn build && cd ../qemu && yarn build && cd ../cvmts && yarn build", "build": "just",
"serve": "node cvmts/dist/index.js" "serve": "node cvmts/dist/index.js"
} }
} }

View File

@@ -47,12 +47,19 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@cvmts/cvm-rs@npm:*, @cvmts/cvm-rs@workspace:cvm-rs":
version: 0.0.0-use.local
resolution: "@cvmts/cvm-rs@workspace:cvm-rs"
dependencies:
cargo-cp-artifact: "npm:^0.1"
languageName: unknown
linkType: soft
"@cvmts/cvmts@workspace:cvmts": "@cvmts/cvmts@workspace:cvmts":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@cvmts/cvmts@workspace:cvmts" resolution: "@cvmts/cvmts@workspace:cvmts"
dependencies: dependencies:
"@cvmts/guac-rs": "npm:*" "@cvmts/cvm-rs": "npm:*"
"@cvmts/jpegturbo-rs": "npm:*"
"@cvmts/qemu": "npm:*" "@cvmts/qemu": "npm:*"
"@types/node": "npm:^20.12.5" "@types/node": "npm:^20.12.5"
"@types/ws": "npm:^8.5.5" "@types/ws": "npm:^8.5.5"
@@ -66,22 +73,6 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@cvmts/guac-rs@npm:*, @cvmts/guac-rs@workspace:guac-rs":
version: 0.0.0-use.local
resolution: "@cvmts/guac-rs@workspace:guac-rs"
dependencies:
cargo-cp-artifact: "npm:^0.1"
languageName: unknown
linkType: soft
"@cvmts/jpegturbo-rs@npm:*, @cvmts/jpegturbo-rs@workspace:jpegturbo-rs":
version: 0.0.0-use.local
resolution: "@cvmts/jpegturbo-rs@workspace:jpegturbo-rs"
dependencies:
cargo-cp-artifact: "npm:^0.1"
languageName: unknown
linkType: soft
"@cvmts/qemu@npm:*, @cvmts/qemu@workspace:qemu": "@cvmts/qemu@npm:*, @cvmts/qemu@workspace:qemu":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@cvmts/qemu@workspace:qemu" resolution: "@cvmts/qemu@workspace:qemu"