Compare commits
10 Commits
d9f9f0d07f
...
proxmox
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef75f135c6 | ||
|
|
3bbdf1c1b6 | ||
|
|
698fb19014 | ||
|
|
fb3c91221c | ||
|
|
4211941560 | ||
|
|
bce2a0172a | ||
|
|
c4c08ae830 | ||
|
|
d3db220c1f | ||
|
|
857eb46d2a | ||
|
|
23c57dbb3b |
27
cvm-rs/Cargo.lock
generated
27
cvm-rs/Cargo.lock
generated
@@ -53,6 +53,12 @@ version = "2.9.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.23"
|
version = "1.2.23"
|
||||||
@@ -132,6 +138,8 @@ dependencies = [
|
|||||||
"napi-derive",
|
"napi-derive",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"resize",
|
||||||
|
"rgb",
|
||||||
"turbojpeg-sys",
|
"turbojpeg-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -331,6 +339,25 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "resize"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87a103d0b47e783f4579149402f7499397ab25540c7a57b2f70487a5d2d20ef0"
|
||||||
|
dependencies = [
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ rayon = "1.10.0"
|
|||||||
napi = { version = "2.16.9", features = [ "async", "napi8", "error_anyhow" ] }
|
napi = { version = "2.16.9", features = [ "async", "napi8", "error_anyhow" ] }
|
||||||
napi-derive = "2.16.11"
|
napi-derive = "2.16.11"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
resize = "0.8.8"
|
||||||
|
rgb = "0.8.50"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = "2.1.3"
|
napi-build = "2.1.3"
|
||||||
|
|||||||
14
cvm-rs/index.d.ts
vendored
14
cvm-rs/index.d.ts
vendored
@@ -9,15 +9,27 @@ interface JpegInputArgs {
|
|||||||
height: number;
|
height: number;
|
||||||
stride: number; // The width of your input framebuffer OR your image width (if encoding a full image)
|
stride: number; // The width of your input framebuffer OR your image width (if encoding a full image)
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
|
quality: number;
|
||||||
|
|
||||||
// TODO: Allow different formats, or export a boxed ffi object which can store a format
|
// TODO: Allow different formats, or export a boxed ffi object which can store a format
|
||||||
// (i.e: new JpegEncoder(FORMAT_xxx)).
|
// (i.e: new JpegEncoder(FORMAT_xxx)).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface JpegResizeInputArgs {
|
||||||
|
width: number; // source width
|
||||||
|
height: number; // source height
|
||||||
|
desiredWidth: number; // dest width
|
||||||
|
desiredHeight: number; // dest height
|
||||||
|
buffer: Buffer; // source raw pixel buffer
|
||||||
|
quality: number;
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs JPEG encoding.
|
/// Performs JPEG encoding.
|
||||||
export function jpegEncode(input: JpegInputArgs): Promise<Buffer>;
|
export function jpegEncode(input: JpegInputArgs): Promise<Buffer>;
|
||||||
|
|
||||||
// TODO: Version that can downscale?
|
/// Performs JPEG encoding with resizing.
|
||||||
|
export function jpegResizeEncode(input: JpegResizeInputArgs): Promise<Buffer>;
|
||||||
|
|
||||||
|
|
||||||
/* remoting API?
|
/* remoting API?
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { createRequire } from 'module';
|
import { createRequire } from 'module';
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
let {guacDecode, guacEncodeImpl, jpegEncode} = require('./index.node');
|
let {guacDecode, guacEncodeImpl, jpegEncode, jpegResizeEncode} = require('./index.node');
|
||||||
|
|
||||||
export { guacDecode, jpegEncode };
|
export { guacDecode, jpegEncode, jpegResizeEncode };
|
||||||
|
|
||||||
// shim for js->rust interop, because napi-rs kind of blows in this regard
|
// shim for js->rust interop, because napi-rs kind of blows in this regard
|
||||||
export function guacEncode(...args) {
|
export function guacEncode(...args) {
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ use rayon::{ThreadPool, ThreadPoolBuilder};
|
|||||||
|
|
||||||
use crate::jpeg_compressor::*;
|
use crate::jpeg_compressor::*;
|
||||||
|
|
||||||
|
|
||||||
|
use resize::Pixel::RGBA8;
|
||||||
|
use resize::Type::Triangle;
|
||||||
|
use rgb::FromSlice;
|
||||||
|
|
||||||
|
|
||||||
/// Gives a Rayon thread pool we use for parallelism
|
/// Gives a Rayon thread pool we use for parallelism
|
||||||
fn rayon_pool() -> &'static ThreadPool {
|
fn rayon_pool() -> &'static ThreadPool {
|
||||||
static RUNTIME: OnceCell<ThreadPool> = OnceCell::new();
|
static RUNTIME: OnceCell<ThreadPool> = OnceCell::new();
|
||||||
@@ -41,6 +47,7 @@ pub struct JpegInputArgs {
|
|||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub stride: u32,
|
pub stride: u32,
|
||||||
pub buffer: napi::JsBuffer,
|
pub buffer: napi::JsBuffer,
|
||||||
|
pub quality: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(js_name = "jpegEncode")]
|
#[napi(js_name = "jpegEncode")]
|
||||||
@@ -63,15 +70,83 @@ pub fn jpeg_encode(env: Env, input: JpegInputArgs) -> napi::Result<napi::JsObjec
|
|||||||
|
|
||||||
let vec = COMPRESSOR.with(|lazy| {
|
let vec = COMPRESSOR.with(|lazy| {
|
||||||
let mut b = lazy.borrow_mut();
|
let mut b = lazy.borrow_mut();
|
||||||
b.set_quality(35);
|
b.set_quality(input.quality);
|
||||||
b.set_subsamp(turbojpeg_sys::TJSAMP_TJSAMP_420);
|
b.set_subsamp(turbojpeg_sys::TJSAMP_TJSAMP_420);
|
||||||
b.compress_buffer(&image)
|
b.compress_buffer(&image)
|
||||||
});
|
});
|
||||||
|
|
||||||
deferred_resolver.resolve(move |env| {
|
deferred_resolver.resolve(move |env| {
|
||||||
let buffer = env
|
let buffer = env.create_buffer_with_data(vec).expect(
|
||||||
.create_buffer_with_data(vec)
|
"Couldn't create node Buffer, things are probably very broken by this point",
|
||||||
.expect("Couldn't create node Buffer, things are probably very broken by this point");
|
);
|
||||||
|
// no longer need the input buffer
|
||||||
|
buf.unref(env)?;
|
||||||
|
Ok(buffer.into_raw())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct JpegResizeInputArgs {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub desired_width: u32,
|
||||||
|
pub desired_height: u32,
|
||||||
|
pub buffer: napi::JsBuffer,
|
||||||
|
pub quality: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(js_name = "jpegResizeEncode")]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn jpeg_resize_and_encode(
|
||||||
|
env: Env,
|
||||||
|
input: JpegResizeInputArgs,
|
||||||
|
) -> napi::Result<napi::JsObject> {
|
||||||
|
let (deferred_resolver, promise) = env.create_deferred::<napi::JsBuffer, _>()?;
|
||||||
|
let mut buf = input.buffer.into_ref()?;
|
||||||
|
|
||||||
|
// Spawn a task on the rayon pool that encodes the JPEG and fufills the promise
|
||||||
|
// once it is done encoding.
|
||||||
|
rayon_pool().spawn_fifo(move || {
|
||||||
|
let mut new_data: Vec<u8> =
|
||||||
|
vec![0; (input.desired_width * input.desired_height) as usize * 4];
|
||||||
|
|
||||||
|
let mut resizer = resize::new(
|
||||||
|
input.width as usize,
|
||||||
|
input.height as usize,
|
||||||
|
input.desired_width as usize,
|
||||||
|
input.desired_height as usize,
|
||||||
|
RGBA8,
|
||||||
|
Triangle,
|
||||||
|
)
|
||||||
|
.expect("Could not create resizer");
|
||||||
|
|
||||||
|
resizer
|
||||||
|
.resize(&buf.as_rgba(), new_data.as_rgba_mut())
|
||||||
|
.expect("Resize operation failed");
|
||||||
|
|
||||||
|
// then just jpeg encode. Ideally this would be shared :(
|
||||||
|
let image = Image {
|
||||||
|
buffer: &new_data,
|
||||||
|
width: input.desired_width as u32,
|
||||||
|
height: input.desired_height as u32,
|
||||||
|
stride: (input.desired_width as u64 * 4u64) as u32,
|
||||||
|
format: turbojpeg_sys::TJPF_TJPF_RGBA,
|
||||||
|
};
|
||||||
|
|
||||||
|
let vec = COMPRESSOR.with(|lazy| {
|
||||||
|
let mut b = lazy.borrow_mut();
|
||||||
|
b.set_quality(input.quality);
|
||||||
|
b.set_subsamp(turbojpeg_sys::TJSAMP_TJSAMP_420);
|
||||||
|
b.compress_buffer(&image)
|
||||||
|
});
|
||||||
|
|
||||||
|
deferred_resolver.resolve(move |env| {
|
||||||
|
let buffer = env.create_buffer_with_data(vec).expect(
|
||||||
|
"Couldn't create node Buffer, things are probably very broken by this point",
|
||||||
|
);
|
||||||
// no longer need the input buffer
|
// no longer need the input buffer
|
||||||
buf.unref(env)?;
|
buf.unref(env)?;
|
||||||
Ok(buffer.into_raw())
|
Ok(buffer.into_raw())
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
"mnemonist": "^0.39.5",
|
"mnemonist": "^0.39.5",
|
||||||
"msgpackr": "^1.10.2",
|
"msgpackr": "^1.10.2",
|
||||||
"pino": "^9.3.1",
|
"pino": "^9.3.1",
|
||||||
"sharp": "^0.33.3",
|
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
"ws": "^8.17.1"
|
"ws": "^8.17.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -170,18 +170,18 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (this.Config.geoip.enabled) {
|
if (this.Config.geoip.enabled) {
|
||||||
try {
|
try {
|
||||||
user.countryCode = this.geoipReader!.country(user.IP.address).country!.isoCode;
|
user.countryCode = this.geoipReader!.country(user.IP.address).country!.isoCode;
|
||||||
|
user.logger.info({event: "geoip/resolved", geoip: user.countryCode});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(`Failed to get country code for ${user.IP.address}: ${(error as Error).message}`);
|
user.logger.warn({event: "geoip/unresolved", msg: `${(error as Error)}`});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.socket.on('msg', (buf: Buffer, binary: boolean) => {
|
user.socket.on('msg', (buf: Buffer, binary: boolean) => {
|
||||||
try {
|
try {
|
||||||
user.protocol.processMessage(buf);
|
user.processMessage(this, buf);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error({
|
user.logger.error({
|
||||||
ip: user.IP.address,
|
event: "msg/general error",
|
||||||
username: user.username,
|
|
||||||
error_message: (err as Error).message
|
error_message: (err as Error).message
|
||||||
}, 'Error in %s#processMessage.', Object.getPrototypeOf(user.protocol).constructor?.name);
|
}, 'Error in %s#processMessage.', Object.getPrototypeOf(user.protocol).constructor?.name);
|
||||||
user.kick();
|
user.kick();
|
||||||
@@ -190,17 +190,14 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
|
|
||||||
user.socket.on('disconnect', () => this.connectionClosed(user));
|
user.socket.on('disconnect', () => this.connectionClosed(user));
|
||||||
|
|
||||||
// Set ourselves as the handler
|
|
||||||
user.protocol.setHandler(this as IProtocolMessageHandler);
|
|
||||||
|
|
||||||
if (this.Config.auth.enabled) {
|
if (this.Config.auth.enabled) {
|
||||||
user.protocol.sendAuth(this.Config.auth.apiEndpoint);
|
user.sendAuth(this.Config.auth.apiEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
user.protocol.sendAddUser(this.getAddUser());
|
user.sendAddUser(this.getAddUser());
|
||||||
if (this.Config.geoip.enabled) {
|
if (this.Config.geoip.enabled) {
|
||||||
let flags = this.getFlags();
|
let flags = this.getFlags();
|
||||||
user.protocol.sendFlag(flags);
|
user.sendFlag(flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +214,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
|
|
||||||
this.clients.splice(clientIndex, 1);
|
this.clients.splice(clientIndex, 1);
|
||||||
|
|
||||||
user.protocol.dispose();
|
user.logger.info({event: "user/disconnect"});
|
||||||
|
|
||||||
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;
|
||||||
@@ -227,7 +222,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (hadturn) this.nextTurn();
|
if (hadturn) this.nextTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clients.forEach((c) => c.protocol.sendRemUser([user.username!]));
|
this.clients.forEach((c) => c.sendRemUser([user.username!]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol message handlers
|
// Protocol message handlers
|
||||||
@@ -237,7 +232,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (!this.Config.auth.enabled) return true;
|
if (!this.Config.auth.enabled) return true;
|
||||||
|
|
||||||
if (user.rank === Rank.Unregistered && !guestPermission) {
|
if (user.rank === Rank.Unregistered && !guestPermission) {
|
||||||
user.protocol.sendChatMessage('', 'You need to login to do that.');
|
user.sendChatMessage('', 'You need to login to do that.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +247,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (!this.Config.auth.enabled) return;
|
if (!this.Config.auth.enabled) return;
|
||||||
|
|
||||||
if (!user.connectedToNode) {
|
if (!user.connectedToNode) {
|
||||||
user.protocol.sendLoginResponse(false, 'You must connect to the VM before logging in.');
|
user.sendLoginResponse(false, 'You must connect to the VM before logging in.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,8 +255,8 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
let res = await this.auth!.Authenticate(token, user);
|
let res = await this.auth!.Authenticate(token, user);
|
||||||
|
|
||||||
if (res.clientSuccess) {
|
if (res.clientSuccess) {
|
||||||
this.logger.info(`${user.IP.address} logged in as ${res.username}`);
|
user.logger.info({ event: "user/auth/login", username: res.username });
|
||||||
user.protocol.sendLoginResponse(true, '');
|
user.sendLoginResponse(true, '');
|
||||||
|
|
||||||
let old = this.clients.find((c) => c.username === res.username);
|
let old = this.clients.find((c) => c.username === res.username);
|
||||||
if (old) {
|
if (old) {
|
||||||
@@ -274,19 +269,19 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (user.countryCode !== null && user.noFlag) {
|
if (user.countryCode !== null && user.noFlag) {
|
||||||
// privacy
|
// privacy
|
||||||
for (let cl of this.clients.filter((c) => c !== user)) {
|
for (let cl of this.clients.filter((c) => c !== user)) {
|
||||||
cl.protocol.sendRemUser([user.username!]);
|
cl.sendRemUser([user.username!]);
|
||||||
}
|
}
|
||||||
this.renameUser(user, res.username, false);
|
this.renameUser(user, res.username, false);
|
||||||
} else this.renameUser(user, res.username, true);
|
} else this.renameUser(user, res.username, true);
|
||||||
// Set rank
|
// Set rank
|
||||||
user.rank = res.rank;
|
user.rank = res.rank;
|
||||||
if (user.rank === Rank.Admin) {
|
if (user.rank === Rank.Admin) {
|
||||||
user.protocol.sendAdminLoginResponse(true, undefined);
|
user.sendAdminLoginResponse(true, undefined);
|
||||||
} else if (user.rank === Rank.Moderator) {
|
} else if (user.rank === Rank.Moderator) {
|
||||||
user.protocol.sendAdminLoginResponse(true, this.ModPerms);
|
user.sendAdminLoginResponse(true, this.ModPerms);
|
||||||
}
|
}
|
||||||
this.clients.forEach((c) =>
|
this.clients.forEach((c) =>
|
||||||
c.protocol.sendAddUser([
|
c.sendAddUser([
|
||||||
{
|
{
|
||||||
username: user.username!,
|
username: user.username!,
|
||||||
rank: user.rank
|
rank: user.rank
|
||||||
@@ -294,15 +289,15 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
user.protocol.sendLoginResponse(false, res.error!);
|
user.sendLoginResponse(false, res.error!);
|
||||||
if (res.error === 'You are banned') {
|
if (res.error === 'You are banned') {
|
||||||
user.kick();
|
user.kick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(`Error authenticating client ${user.IP.address}: ${(err as Error).message}`);
|
this.logger.error({event: "user/auth/internal error", msg: `${(err as Error).message}`});
|
||||||
|
|
||||||
user.protocol.sendLoginResponse(false, 'There was an internal error while authenticating. Please let a staff member know as soon as possible');
|
user.sendLoginResponse(false, 'There was an internal error while authenticating. Please let a staff member know as soon as possible');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,26 +318,31 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
case ProtocolUpgradeCapability.BinRects:
|
case ProtocolUpgradeCapability.BinRects:
|
||||||
enabledCaps.push(cap as ProtocolUpgradeCapability);
|
enabledCaps.push(cap as ProtocolUpgradeCapability);
|
||||||
user.Capabilities.bin = true;
|
user.Capabilities.bin = true;
|
||||||
user.protocol.dispose();
|
user.protocol = TheProtocolManager.getProtocol('binary1');
|
||||||
user.protocol = TheProtocolManager.createProtocol('binary1', user);
|
|
||||||
user.protocol.setHandler(this as IProtocolMessageHandler);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.protocol.sendCapabilities(enabledCaps);
|
user.sendCapabilities(enabledCaps);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTurnRequest(user: User, forfeit: boolean): void {
|
onTurnRequest(user: User, forfeit: boolean): void {
|
||||||
|
user.logger.trace({event: "turn/requested"});
|
||||||
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && user.rank !== Rank.Admin && user.rank !== Rank.Moderator && !user.turnWhitelist) return;
|
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && user.rank !== Rank.Admin && user.rank !== Rank.Moderator && !user.turnWhitelist) return;
|
||||||
|
|
||||||
if (!this.authCheck(user, this.Config.auth.guestPermissions.turn)) return;
|
if (!this.authCheck(user, this.Config.auth.guestPermissions.turn)) return;
|
||||||
|
|
||||||
if (!user.TurnRateLimit.request()) return;
|
if (!user.TurnRateLimit.request()) {
|
||||||
if (!user.connectedToNode) return;
|
user.logger.warn({event: "turn/ratelimited"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.connectedToNode) {
|
||||||
|
user.logger.warn({event: "turn/requested when not in queue"})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (forfeit == false) {
|
if (forfeit == false) {
|
||||||
var currentQueue = this.TurnQueue.toArray();
|
var currentQueue = this.TurnQueue.toArray();
|
||||||
@@ -355,8 +355,12 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
// 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((otheruser) => otheruser.IP.address == user.IP.address);
|
let turns = currentQueue.filter((otheruser) => otheruser.IP.address == user.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) {
|
||||||
|
user.logger.warn({event: "turn/ignoring request due to turn limit"});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
user.logger.info({event: "turn/entering queue"});
|
||||||
this.TurnQueue.enqueue(user);
|
this.TurnQueue.enqueue(user);
|
||||||
if (this.TurnQueue.size === 1) this.nextTurn();
|
if (this.TurnQueue.size === 1) this.nextTurn();
|
||||||
} else {
|
} else {
|
||||||
@@ -367,29 +371,41 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onVote(user: User, choice: number): void {
|
onVote(user: User, choice: number): void {
|
||||||
if (!this.VM.SnapshotsSupported()) return;
|
if (!this.VM.SnapshotsSupported()) {
|
||||||
|
user.logger.warn({event: "vote/voted without snapshots enabled"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && user.rank !== Rank.Admin && user.rank !== Rank.Moderator && !user.turnWhitelist) return;
|
if ((!this.turnsAllowed || this.Config.collabvm.turnwhitelist) && user.rank !== Rank.Admin && user.rank !== Rank.Moderator && !user.turnWhitelist) return;
|
||||||
if (!user.connectedToNode) return;
|
if (!user.connectedToNode) {
|
||||||
if (!user.VoteRateLimit.request()) return;
|
user.logger.warn({event: "vote/not connected to node"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.VoteRateLimit.request()) {
|
||||||
|
user.logger.warn({event: "vote/voted but was ratelimited"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case 1:
|
case 1:
|
||||||
if (!this.voteInProgress) {
|
if (!this.voteInProgress) {
|
||||||
if (!this.authCheck(user, this.Config.auth.guestPermissions.callForReset)) return;
|
if (!this.authCheck(user, this.Config.auth.guestPermissions.callForReset)) return;
|
||||||
|
|
||||||
if (this.voteCooldown !== 0) {
|
if (this.voteCooldown !== 0) {
|
||||||
user.protocol.sendVoteCooldown(this.voteCooldown);
|
user.sendVoteCooldown(this.voteCooldown);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.logger.info({event: "vote/user initiated a vote"});
|
||||||
this.startVote();
|
this.startVote();
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage('', `${user.username} has started a vote to reset the VM.`));
|
this.clients.forEach((c) => c.sendChatMessage('', `${user.username} has started a vote to reset the VM.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.authCheck(user, this.Config.auth.guestPermissions.vote)) return;
|
if (!this.authCheck(user, this.Config.auth.guestPermissions.vote)) return;
|
||||||
|
|
||||||
if (user.IP.vote !== true) {
|
if (user.IP.vote !== true) {
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage('', `${user.username} has voted yes.`));
|
this.clients.forEach((c) => c.sendChatMessage('', `${user.username} has voted yes.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.logger.info({event: "vote/yes"});
|
||||||
user.IP.vote = true;
|
user.IP.vote = true;
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
@@ -398,8 +414,10 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (!this.authCheck(user, this.Config.auth.guestPermissions.vote)) return;
|
if (!this.authCheck(user, this.Config.auth.guestPermissions.vote)) return;
|
||||||
|
|
||||||
if (user.IP.vote !== false) {
|
if (user.IP.vote !== false) {
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage('', `${user.username} has voted no.`));
|
this.clients.forEach((c) => c.sendChatMessage('', `${user.username} has voted no.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.logger.info({event: "vote/no"});
|
||||||
user.IP.vote = false;
|
user.IP.vote = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -416,13 +434,13 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.VM.GetState() == VMState.Started) {
|
if (this.VM.GetState() == VMState.Started) {
|
||||||
user.protocol.sendListResponse([listEntry]);
|
user.sendListResponse([listEntry]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async connectViewShared(user: User, node: string, viewMode: number | undefined) {
|
private async connectViewShared(user: User, node: string, viewMode: number | undefined) {
|
||||||
if (!user.username || node !== this.Config.collabvm.node) {
|
if (!user.username || node !== this.Config.collabvm.node) {
|
||||||
user.protocol.sendConnectFailResponse();
|
user.sendConnectFailResponse();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,23 +448,23 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
|
|
||||||
if (viewMode !== undefined) {
|
if (viewMode !== undefined) {
|
||||||
if (viewMode !== 0 && viewMode !== 1) {
|
if (viewMode !== 0 && viewMode !== 1) {
|
||||||
user.protocol.sendConnectFailResponse();
|
user.sendConnectFailResponse();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.viewMode = viewMode;
|
user.viewMode = viewMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.protocol.sendConnectOKResponse(this.VM.SnapshotsSupported());
|
user.sendConnectOKResponse(this.VM.SnapshotsSupported());
|
||||||
|
|
||||||
if (this.ChatHistory.size !== 0) {
|
if (this.ChatHistory.size !== 0) {
|
||||||
let history = this.ChatHistory.toArray() as ChatHistory[];
|
let history = this.ChatHistory.toArray() as ChatHistory[];
|
||||||
user.protocol.sendChatHistoryMessage(history);
|
user.sendChatHistoryMessage(history);
|
||||||
}
|
}
|
||||||
if (this.Config.collabvm.motd) user.protocol.sendChatMessage('', this.Config.collabvm.motd);
|
if (this.Config.collabvm.motd) user.sendChatMessage('', this.Config.collabvm.motd);
|
||||||
if (this.screenHidden) {
|
if (this.screenHidden) {
|
||||||
user?.protocol.sendScreenResize(1024, 768);
|
user?.sendScreenResize(1024, 768);
|
||||||
user?.protocol.sendScreenUpdate({
|
user?.sendScreenUpdate({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
data: this.screenHiddenImg
|
data: this.screenHiddenImg
|
||||||
@@ -455,30 +473,38 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
await this.SendFullScreenWithSize(user);
|
await this.SendFullScreenWithSize(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
user.protocol.sendSync(Date.now());
|
user.sendSync(Date.now());
|
||||||
|
|
||||||
if (this.voteInProgress) this.sendVoteUpdate(user);
|
if (this.voteInProgress) this.sendVoteUpdate(user);
|
||||||
this.sendTurnUpdate(user);
|
this.sendTurnUpdate(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onConnect(user: User, node: string) {
|
async onConnect(user: User, node: string) {
|
||||||
|
user.logger.info({event: "user/joined node", node});
|
||||||
return this.connectViewShared(user, node, undefined);
|
return this.connectViewShared(user, node, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onView(user: User, node: string, viewMode: number) {
|
async onView(user: User, node: string, viewMode: number) {
|
||||||
|
user.logger.info({event: "user/entering view", node, viewMode});
|
||||||
return this.connectViewShared(user, node, viewMode);
|
return this.connectViewShared(user, node, viewMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRename(user: User, newName: string | undefined): void {
|
onRename(user: User, newName: string | undefined): void {
|
||||||
if (!user.RenameRateLimit.request()) return;
|
if (!user.RenameRateLimit.request()) {
|
||||||
if (user.connectedToNode && user.IP.muted) return;
|
user.logger.warn({event: "rename/ratelimit"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.connectedToNode && user.IP.muted) {
|
||||||
|
user.logger.warn({event: "rename/attempted to rename while muted"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.Config.auth.enabled && user.rank !== Rank.Unregistered) {
|
if (this.Config.auth.enabled && user.rank !== Rank.Unregistered) {
|
||||||
user.protocol.sendChatMessage('', 'Go to your account settings to change your username.');
|
user.sendChatMessage('', 'Go to your account settings to change your username.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.Config.auth.enabled && newName !== undefined) {
|
if (this.Config.auth.enabled && newName !== 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 (user.username) user.protocol.sendChatMessage('', 'You need to log in to do that.');
|
if (user.username) user.sendChatMessage('', 'You need to log in to do that.');
|
||||||
if (user.rank !== Rank.Unregistered) return;
|
if (user.rank !== Rank.Unregistered) return;
|
||||||
this.renameUser(user, undefined);
|
this.renameUser(user, undefined);
|
||||||
return;
|
return;
|
||||||
@@ -487,7 +513,10 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChat(user: User, message: string): void {
|
onChat(user: User, message: string): void {
|
||||||
if (!user.username) return;
|
if (!user.username) {
|
||||||
|
user.logger.warn({event: "chat/dropped message without username", message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (user.IP.muted) return;
|
if (user.IP.muted) return;
|
||||||
if (!this.authCheck(user, this.Config.auth.guestPermissions.chat)) return;
|
if (!this.authCheck(user, this.Config.auth.guestPermissions.chat)) return;
|
||||||
|
|
||||||
@@ -496,13 +525,15 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
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.protocol.sendChatMessage(user.username!, msg));
|
user.logger.info({event: "chat/message", msg});
|
||||||
|
this.clients.forEach((c) => c.sendChatMessage(user.username!, msg));
|
||||||
this.ChatHistory.push({ user: user.username, msg: msg });
|
this.ChatHistory.push({ user: user.username, msg: msg });
|
||||||
user.onChatMsgSent();
|
user.onChatMsgSent();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKey(user: User, keysym: number, pressed: boolean): void {
|
onKey(user: User, keysym: number, pressed: boolean): void {
|
||||||
if (this.TurnQueue.peek() !== user && user.rank !== Rank.Admin) return;
|
if (this.TurnQueue.peek() !== user && user.rank !== Rank.Admin) return;
|
||||||
|
user.logger.info({event: "key", keysym, pressed});
|
||||||
this.VM.GetDisplay()?.KeyboardEvent(keysym, pressed);
|
this.VM.GetDisplay()?.KeyboardEvent(keysym, pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,24 +550,28 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
sha256.destroy();
|
sha256.destroy();
|
||||||
|
|
||||||
if (this.Config.collabvm.turnwhitelist && pwdHash === this.Config.collabvm.turnpass) {
|
if (this.Config.collabvm.turnwhitelist && pwdHash === this.Config.collabvm.turnpass) {
|
||||||
|
user.logger.info({event: "admin/granted turnpass"})
|
||||||
user.turnWhitelist = true;
|
user.turnWhitelist = true;
|
||||||
user.protocol.sendChatMessage('', 'You may now take turns.');
|
user.sendChatMessage('', 'You may now take turns.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.Config.auth.enabled) {
|
if (this.Config.auth.enabled) {
|
||||||
user.protocol.sendChatMessage('', 'This server does not support staff passwords. Please log in to become staff.');
|
user.sendChatMessage('', 'This server does not support staff passwords. Please log in to become staff.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pwdHash === this.Config.collabvm.adminpass) {
|
if (pwdHash === this.Config.collabvm.adminpass) {
|
||||||
|
user.logger.info({event: "admin/granted adminpass"})
|
||||||
user.rank = Rank.Admin;
|
user.rank = Rank.Admin;
|
||||||
user.protocol.sendAdminLoginResponse(true, undefined);
|
user.sendAdminLoginResponse(true, undefined);
|
||||||
} else if (this.Config.collabvm.moderatorEnabled && pwdHash === this.Config.collabvm.modpass) {
|
} else if (this.Config.collabvm.moderatorEnabled && pwdHash === this.Config.collabvm.modpass) {
|
||||||
|
user.logger.info({event: "admin/granted modpass"})
|
||||||
user.rank = Rank.Moderator;
|
user.rank = Rank.Moderator;
|
||||||
user.protocol.sendAdminLoginResponse(true, this.ModPerms);
|
user.sendAdminLoginResponse(true, this.ModPerms);
|
||||||
} else {
|
} else {
|
||||||
user.protocol.sendAdminLoginResponse(false, undefined);
|
user.logger.warn({event: "admin/failed login attempt"})
|
||||||
|
user.sendAdminLoginResponse(false, undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +581,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
|
|
||||||
// Update rank
|
// Update rank
|
||||||
this.clients.forEach((c) =>
|
this.clients.forEach((c) =>
|
||||||
c.protocol.sendAddUser([
|
c.sendAddUser([
|
||||||
{
|
{
|
||||||
username: user.username!,
|
username: user.username!,
|
||||||
rank: user.rank
|
rank: user.rank
|
||||||
@@ -560,7 +595,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (node !== this.Config.collabvm.node) return;
|
if (node !== this.Config.collabvm.node) return;
|
||||||
TheAuditLog.onMonitorCommand(user, command);
|
TheAuditLog.onMonitorCommand(user, command);
|
||||||
let output = await this.VM.MonitorCommand(command);
|
let output = await this.VM.MonitorCommand(command);
|
||||||
user.protocol.sendAdminMonitorResponse(String(output));
|
user.sendAdminMonitorResponse(String(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdminRestore(user: User, node: string): void {
|
onAdminRestore(user: User, node: string): void {
|
||||||
@@ -624,7 +659,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
onAdminRename(user: User, target: string, newName: string): void {
|
onAdminRename(user: User, target: string, newName: string): void {
|
||||||
if (user.rank !== Rank.Admin && (user.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.rename)) return;
|
if (user.rank !== Rank.Admin && (user.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.rename)) return;
|
||||||
if (this.Config.auth.enabled) {
|
if (this.Config.auth.enabled) {
|
||||||
user.protocol.sendChatMessage('', 'Cannot rename users on a server that uses authentication.');
|
user.sendChatMessage('', 'Cannot rename users on a server that uses authentication.');
|
||||||
}
|
}
|
||||||
var targetUser = this.clients.find((c) => c.username === target);
|
var targetUser = this.clients.find((c) => c.username === target);
|
||||||
if (!targetUser) return;
|
if (!targetUser) return;
|
||||||
@@ -635,7 +670,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (user.rank !== Rank.Admin && (user.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.grabip)) return;
|
if (user.rank !== Rank.Admin && (user.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.grabip)) return;
|
||||||
let target = this.clients.find((c) => c.username === username);
|
let target = this.clients.find((c) => c.username === username);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
user.protocol.sendAdminIPResponse(username, target.IP.address);
|
user.sendAdminIPResponse(username, target.IP.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdminBypassTurn(user: User): void {
|
onAdminBypassTurn(user: User): void {
|
||||||
@@ -647,14 +682,14 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (user.rank !== Rank.Admin && (user.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.xss)) return;
|
if (user.rank !== Rank.Admin && (user.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.xss)) return;
|
||||||
switch (user.rank) {
|
switch (user.rank) {
|
||||||
case Rank.Admin:
|
case Rank.Admin:
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage(user.username!, message));
|
this.clients.forEach((c) => c.sendChatMessage(user.username!, message));
|
||||||
|
|
||||||
this.ChatHistory.push({ user: user.username!, msg: message });
|
this.ChatHistory.push({ user: user.username!, msg: message });
|
||||||
break;
|
break;
|
||||||
case Rank.Moderator:
|
case Rank.Moderator:
|
||||||
this.clients.filter((c) => c.rank !== Rank.Admin).forEach((c) => c.protocol.sendChatMessage(user.username!, message));
|
this.clients.filter((c) => c.rank !== Rank.Admin).forEach((c) => c.sendChatMessage(user.username!, message));
|
||||||
|
|
||||||
this.clients.filter((c) => c.rank === Rank.Admin).forEach((c) => c.protocol.sendChatMessage(user.username!, Utilities.HTMLSanitize(message)));
|
this.clients.filter((c) => c.rank === Rank.Admin).forEach((c) => c.sendChatMessage(user.username!, Utilities.HTMLSanitize(message)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,8 +735,8 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
this.clients
|
this.clients
|
||||||
.filter((c) => c.rank == Rank.Unregistered)
|
.filter((c) => c.rank == Rank.Unregistered)
|
||||||
.forEach((client) => {
|
.forEach((client) => {
|
||||||
client.protocol.sendScreenResize(1024, 768);
|
client.sendScreenResize(1024, 768);
|
||||||
client.protocol.sendScreenUpdate({
|
client.sendScreenUpdate({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
data: this.screenHiddenImg
|
data: this.screenHiddenImg
|
||||||
@@ -712,7 +747,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
|
|
||||||
onAdminSystemMessage(user: User, message: string): void {
|
onAdminSystemMessage(user: User, message: string): void {
|
||||||
if (user.rank !== Rank.Admin) return;
|
if (user.rank !== Rank.Admin) return;
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage('', message));
|
this.clients.forEach((c) => c.sendChatMessage('', message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// end protocol message handlers
|
// end protocol message handlers
|
||||||
@@ -736,7 +771,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
} else {
|
} else {
|
||||||
newName = newName.trim();
|
newName = newName.trim();
|
||||||
if (hadName && newName === oldname) {
|
if (hadName && newName === oldname) {
|
||||||
client.protocol.sendSelfRename(ProtocolRenameStatus.Ok, client.username!, client.rank);
|
client.sendSelfRename(ProtocolRenameStatus.Ok, client.username!, client.rank);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,16 +789,16 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
} else client.username = newName;
|
} else client.username = newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.protocol.sendSelfRename(status, client.username!, client.rank);
|
client.sendSelfRename(status, client.username!, client.rank);
|
||||||
|
|
||||||
if (hadName) {
|
if (hadName) {
|
||||||
this.logger.info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
|
client.logger.info({event: "rename", from: oldname, to: client.username});
|
||||||
if (announce) this.clients.forEach((c) => c.protocol.sendRename(oldname, client.username!, client.rank));
|
if (announce) this.clients.forEach((c) => c.sendRename(oldname, client.username!, client.rank));
|
||||||
} else {
|
} else {
|
||||||
this.logger.info(`Rename ${client.IP.address} to ${client.username}`);
|
client.logger.info({event: "rename", to: client.username});
|
||||||
if (announce)
|
if (announce)
|
||||||
this.clients.forEach((c) => {
|
this.clients.forEach((c) => {
|
||||||
c.protocol.sendAddUser([
|
c.sendAddUser([
|
||||||
{
|
{
|
||||||
username: client.username!,
|
username: client.username!,
|
||||||
rank: client.rank
|
rank: client.rank
|
||||||
@@ -771,7 +806,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (client.countryCode !== null) {
|
if (client.countryCode !== null) {
|
||||||
c.protocol.sendFlag([
|
c.sendFlag([
|
||||||
{
|
{
|
||||||
username: client.username!,
|
username: client.username!,
|
||||||
countryCode: client.countryCode
|
countryCode: client.countryCode
|
||||||
@@ -816,7 +851,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
var currentTurningUser = this.TurnQueue.peek();
|
var currentTurningUser = this.TurnQueue.peek();
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
client.protocol.sendTurnQueue(turntime, users);
|
client.sendTurnQueue(turntime, users);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,13 +862,17 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
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.protocol.sendTurnQueueWaiting(turntime, users, time);
|
c.sendTurnQueueWaiting(turntime, users, time);
|
||||||
} else {
|
} else {
|
||||||
c.protocol.sendTurnQueue(turntime, users);
|
c.sendTurnQueue(turntime, users);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (currentTurningUser) currentTurningUser.protocol.sendTurnQueue(turntime, users);
|
if (currentTurningUser) {
|
||||||
|
currentTurningUser.logger.info({event: "turn/held"});
|
||||||
|
currentTurningUser.sendTurnQueue(turntime, users);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private nextTurn() {
|
private nextTurn() {
|
||||||
clearInterval(this.TurnInterval);
|
clearInterval(this.TurnInterval);
|
||||||
if (this.TurnQueue.size === 0) {
|
if (this.TurnQueue.size === 0) {
|
||||||
@@ -845,18 +884,21 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearTurns() {
|
clearTurns() {
|
||||||
|
this.logger.info({event: "turn/clearing turn queue"});
|
||||||
clearInterval(this.TurnInterval);
|
clearInterval(this.TurnInterval);
|
||||||
this.TurnQueue.clear();
|
this.TurnQueue.clear();
|
||||||
this.sendTurnUpdate();
|
this.sendTurnUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bypassTurn(client: User) {
|
bypassTurn(client: User) {
|
||||||
|
client.logger.info({event: "turn/bypassing"});
|
||||||
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) {
|
||||||
|
client.logger.info({event: "turn/ending"});
|
||||||
// I must have somehow accidentally removed this while scalpaling everything out
|
// I must have somehow accidentally removed this while scalpaling everything out
|
||||||
if (this.indefiniteTurn === client) this.indefiniteTurn = null;
|
if (this.indefiniteTurn === client) this.indefiniteTurn = null;
|
||||||
var hasTurn = this.TurnQueue.peek() === client;
|
var hasTurn = this.TurnQueue.peek() === client;
|
||||||
@@ -883,7 +925,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
.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.protocol.sendScreenResize(size.width, size.height);
|
c.sendScreenResize(size.width, size.height);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,7 +940,7 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
.forEach((c) => {
|
.forEach((c) => {
|
||||||
if (self.screenHidden && c.rank == Rank.Unregistered) return;
|
if (self.screenHidden && c.rank == Rank.Unregistered) return;
|
||||||
|
|
||||||
c.protocol.sendScreenUpdate({
|
c.sendScreenUpdate({
|
||||||
x: rect.x,
|
x: rect.x,
|
||||||
y: rect.y,
|
y: rect.y,
|
||||||
data: encoded
|
data: encoded
|
||||||
@@ -930,9 +972,9 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
height: displaySize.height
|
height: displaySize.height
|
||||||
});
|
});
|
||||||
|
|
||||||
client.protocol.sendScreenResize(displaySize.width, displaySize.height);
|
client.sendScreenResize(displaySize.width, displaySize.height);
|
||||||
|
|
||||||
client.protocol.sendScreenUpdate({
|
client.sendScreenUpdate({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
data: encoded
|
data: encoded
|
||||||
@@ -963,7 +1005,8 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
startVote() {
|
startVote() {
|
||||||
if (this.voteInProgress) return;
|
if (this.voteInProgress) return;
|
||||||
this.voteInProgress = true;
|
this.voteInProgress = true;
|
||||||
this.clients.forEach((c) => c.protocol.sendVoteStarted());
|
this.logger.info({event: "vote/start"});
|
||||||
|
this.clients.forEach((c) => c.sendVoteStarted());
|
||||||
this.voteTime = this.Config.collabvm.voteTime;
|
this.voteTime = this.Config.collabvm.voteTime;
|
||||||
this.voteInterval = setInterval(() => {
|
this.voteInterval = setInterval(() => {
|
||||||
this.voteTime--;
|
this.voteTime--;
|
||||||
@@ -978,12 +1021,12 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
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.protocol.sendVoteEnded());
|
this.clients.forEach((c) => c.sendVoteEnded());
|
||||||
if (result === true || (result === undefined && count.yes >= count.no)) {
|
if (result === true || (result === undefined && count.yes >= count.no)) {
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage('', 'The vote to reset the VM has won.'));
|
this.clients.forEach((c) => c.sendChatMessage('', 'The vote to reset the VM has won.'));
|
||||||
this.VM.Reset();
|
this.VM.Reset();
|
||||||
} else {
|
} else {
|
||||||
this.clients.forEach((c) => c.protocol.sendChatMessage('', 'The vote to reset the VM has lost.'));
|
this.clients.forEach((c) => c.sendChatMessage('', 'The vote to reset the VM has lost.'));
|
||||||
}
|
}
|
||||||
this.clients.forEach((c) => {
|
this.clients.forEach((c) => {
|
||||||
c.IP.vote = null;
|
c.IP.vote = null;
|
||||||
@@ -999,8 +1042,8 @@ export default class CollabVMServer implements IProtocolMessageHandler {
|
|||||||
if (!this.voteInProgress) return;
|
if (!this.voteInProgress) return;
|
||||||
var count = this.getVoteCounts();
|
var count = this.getVoteCounts();
|
||||||
|
|
||||||
if (client) client.protocol.sendVoteStats(this.voteTime * 1000, count.yes, count.no);
|
if (client) client.sendVoteStats(this.voteTime * 1000, count.yes, count.no);
|
||||||
else this.clients.forEach((c) => c.protocol.sendVoteStats(this.voteTime * 1000, count.yes, count.no));
|
else this.clients.forEach((c) => c.sendVoteStats(this.voteTime * 1000, count.yes, count.no));
|
||||||
}
|
}
|
||||||
|
|
||||||
getVoteCounts(): VoteTally {
|
getVoteCounts(): VoteTally {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Size, Rect } from './Utilities';
|
import { Size, Rect } from './Utilities';
|
||||||
import sharp from 'sharp';
|
|
||||||
import * as cvm from '@cvmts/cvm-rs';
|
import * as cvm from '@cvmts/cvm-rs';
|
||||||
|
|
||||||
// A good balance. TODO: Configurable?
|
// A good balance. TODO: Configurable?
|
||||||
@@ -10,17 +9,6 @@ const kThumbnailSize: Size = {
|
|||||||
height: 300
|
height: 300
|
||||||
};
|
};
|
||||||
|
|
||||||
// this returns appropiate Sharp options to deal with CVMTS raw framebuffers
|
|
||||||
// (which are RGBA bitmaps, essentially. We probably should abstract that out but
|
|
||||||
// that'd mean having to introduce that to rfb and oihwekjtgferklds;./tghnredsltg;erhds)
|
|
||||||
function GetRawSharpOptions(size: Size): sharp.CreateRaw {
|
|
||||||
return {
|
|
||||||
width: size.width,
|
|
||||||
height: size.height,
|
|
||||||
channels: 4
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JPEGEncoder {
|
export class JPEGEncoder {
|
||||||
static SetQuality(quality: number) {
|
static SetQuality(quality: number) {
|
||||||
gJpegQuality = quality;
|
gJpegQuality = quality;
|
||||||
@@ -32,21 +20,19 @@ export class JPEGEncoder {
|
|||||||
width: rect.width,
|
width: rect.width,
|
||||||
height: rect.height,
|
height: rect.height,
|
||||||
stride: displaySize.width,
|
stride: displaySize.width,
|
||||||
buffer: canvas.subarray(offset)
|
buffer: canvas.subarray(offset),
|
||||||
|
quality: gJpegQuality
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async EncodeThumbnail(buffer: Buffer, size: Size): Promise<Buffer> {
|
static async EncodeThumbnail(buffer: Buffer, size: Size): Promise<Buffer> {
|
||||||
let { data, info } = await sharp(buffer, { raw: GetRawSharpOptions(size) })
|
return cvm.jpegResizeEncode({
|
||||||
.resize(kThumbnailSize.width, kThumbnailSize.height, { fit: 'fill' })
|
width: size.width,
|
||||||
.raw()
|
height: size.height,
|
||||||
.toBuffer({ resolveWithObject: true });
|
desiredWidth: kThumbnailSize.width,
|
||||||
|
desiredHeight: kThumbnailSize.height,
|
||||||
return cvm.jpegEncode({
|
buffer: buffer,
|
||||||
width: kThumbnailSize.width,
|
quality: gJpegQuality
|
||||||
height: kThumbnailSize.height,
|
|
||||||
stride: kThumbnailSize.width,
|
|
||||||
buffer: data
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import IConfig from './IConfig.js';
|
|||||||
import RateLimiter from './RateLimiter.js';
|
import RateLimiter from './RateLimiter.js';
|
||||||
import { NetworkClient } from './net/NetworkClient.js';
|
import { NetworkClient } from './net/NetworkClient.js';
|
||||||
import { CollabVMCapabilities } from '@cvmts/collab-vm-1.2-binary-protocol';
|
import { CollabVMCapabilities } from '@cvmts/collab-vm-1.2-binary-protocol';
|
||||||
import pino from 'pino';
|
import { pino, type Logger } from 'pino';
|
||||||
|
import { v4 as uuid4 } from 'uuid';
|
||||||
import { BanManager } from './BanManager.js';
|
import { BanManager } from './BanManager.js';
|
||||||
import { IProtocol } from './protocol/Protocol.js';
|
import { IProtocol, IProtocolMessageHandler, ListEntry, ProtocolAddUser, ProtocolChatHistory, ProtocolFlag, ProtocolRenameStatus, ProtocolUpgradeCapability, ScreenRect } from './protocol/Protocol.js';
|
||||||
import { TheProtocolManager } from './protocol/Manager.js';
|
import { TheProtocolManager } from './protocol/Manager.js';
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
@@ -15,7 +16,7 @@ export class User {
|
|||||||
nopSendInterval: NodeJS.Timeout;
|
nopSendInterval: NodeJS.Timeout;
|
||||||
msgRecieveInterval: NodeJS.Timeout;
|
msgRecieveInterval: NodeJS.Timeout;
|
||||||
nopRecieveTimeout?: NodeJS.Timeout;
|
nopRecieveTimeout?: NodeJS.Timeout;
|
||||||
username?: string;
|
private _username?: string;
|
||||||
connectedToNode: boolean;
|
connectedToNode: boolean;
|
||||||
viewMode: number;
|
viewMode: number;
|
||||||
rank: Rank;
|
rank: Rank;
|
||||||
@@ -34,8 +35,9 @@ export class User {
|
|||||||
RenameRateLimit: RateLimiter;
|
RenameRateLimit: RateLimiter;
|
||||||
TurnRateLimit: RateLimiter;
|
TurnRateLimit: RateLimiter;
|
||||||
VoteRateLimit: RateLimiter;
|
VoteRateLimit: RateLimiter;
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
private logger = pino({ name: 'CVMTS.User' });
|
logger: Logger;
|
||||||
|
|
||||||
constructor(socket: NetworkClient, protocol: string, ip: IPData, config: IConfig, username?: string, node?: string) {
|
constructor(socket: NetworkClient, protocol: string, ip: IPData, config: IConfig, username?: string, node?: string) {
|
||||||
this.IP = ip;
|
this.IP = ip;
|
||||||
@@ -44,12 +46,19 @@ export class User {
|
|||||||
this.Config = config;
|
this.Config = config;
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.msgsSent = 0;
|
this.msgsSent = 0;
|
||||||
|
this.uuid = uuid4();
|
||||||
|
this.logger = pino().child({
|
||||||
|
name: "CVMTS.User",
|
||||||
|
"uuid/user": this.uuid,
|
||||||
|
ip: ip.address,
|
||||||
|
});
|
||||||
this.Capabilities = new CollabVMCapabilities();
|
this.Capabilities = new CollabVMCapabilities();
|
||||||
|
|
||||||
// All clients default to the Guacamole protocol.
|
// All clients default to the Guacamole protocol.
|
||||||
this.protocol = TheProtocolManager.createProtocol(protocol, this);
|
this.protocol = TheProtocolManager.getProtocol(protocol);
|
||||||
|
|
||||||
this.socket.on('disconnect', () => {
|
this.socket.on('disconnect', () => {
|
||||||
|
this.logger.info({event: "user disconnected", username});
|
||||||
// Unref the ip data for this connection
|
// Unref the ip data for this connection
|
||||||
this.IP.Unref();
|
this.IP.Unref();
|
||||||
|
|
||||||
@@ -80,6 +89,7 @@ export class User {
|
|||||||
do {
|
do {
|
||||||
username = 'guest' + Utilities.Randint(10000, 99999);
|
username = 'guest' + Utilities.Randint(10000, 99999);
|
||||||
} while (existingUsers.indexOf(username) !== -1);
|
} while (existingUsers.indexOf(username) !== -1);
|
||||||
|
this.logger.info({event: "assign guest username"});
|
||||||
this.username = username;
|
this.username = username;
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
@@ -90,10 +100,6 @@ export class User {
|
|||||||
this.msgRecieveInterval = setInterval(() => this.onNoMsg(), 10000);
|
this.msgRecieveInterval = setInterval(() => this.onNoMsg(), 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNop() {
|
|
||||||
this.protocol.sendNop();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMsg(msg: string) {
|
sendMsg(msg: string) {
|
||||||
if (!this.socket.isOpen()) return;
|
if (!this.socket.isOpen()) return;
|
||||||
clearInterval(this.nopSendInterval);
|
clearInterval(this.nopSendInterval);
|
||||||
@@ -104,11 +110,13 @@ export class User {
|
|||||||
private onNoMsg() {
|
private onNoMsg() {
|
||||||
this.sendNop();
|
this.sendNop();
|
||||||
this.nopRecieveTimeout = setTimeout(() => {
|
this.nopRecieveTimeout = setTimeout(() => {
|
||||||
|
this.logger.info({event: "nop timeout"});
|
||||||
this.closeConnection();
|
this.closeConnection();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeConnection() {
|
closeConnection() {
|
||||||
|
this.logger.info({event: "closing connection"});
|
||||||
this.socket.send(cvm.guacEncode('disconnect'));
|
this.socket.send(cvm.guacEncode('disconnect'));
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
}
|
}
|
||||||
@@ -128,6 +136,7 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mute(permanent: boolean) {
|
mute(permanent: boolean) {
|
||||||
|
this.logger.info({event: "mute", time_seconds: this.Config.collabvm.tempMuteTime, permanent});
|
||||||
this.IP.muted = true;
|
this.IP.muted = true;
|
||||||
this.sendMsg(cvm.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) {
|
||||||
@@ -135,13 +144,16 @@ export class User {
|
|||||||
this.IP.tempMuteExpireTimeout = setTimeout(() => this.unmute(), this.Config.collabvm.tempMuteTime * 1000);
|
this.IP.tempMuteExpireTimeout = setTimeout(() => this.unmute(), this.Config.collabvm.tempMuteTime * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unmute() {
|
unmute() {
|
||||||
|
this.logger.info({event: "unmute"});
|
||||||
clearTimeout(this.IP.tempMuteExpireTimeout);
|
clearTimeout(this.IP.tempMuteExpireTimeout);
|
||||||
this.IP.muted = false;
|
this.IP.muted = false;
|
||||||
this.sendMsg(cvm.guacEncode('chat', '', 'You are no longer muted.'));
|
this.sendMsg(cvm.guacEncode('chat', '', 'You are no longer muted.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async ban(banmgr: BanManager) {
|
async ban(banmgr: BanManager) {
|
||||||
|
this.logger.info({event: "ban"});
|
||||||
// Prevent the user from taking turns or chatting, in case the ban command takes a while
|
// Prevent the user from taking turns or chatting, in case the ban command takes a while
|
||||||
this.IP.muted = true;
|
this.IP.muted = true;
|
||||||
await banmgr.BanUser(this.IP.address, this.username || '');
|
await banmgr.BanUser(this.IP.address, this.username || '');
|
||||||
@@ -149,9 +161,134 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async kick() {
|
async kick() {
|
||||||
|
this.logger.info({event: "kick"});
|
||||||
this.sendMsg('10.disconnect;');
|
this.sendMsg('10.disconnect;');
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These wrap the currently set IProtocol instance to feed state to them.
|
||||||
|
// This is probably grody, but /shrug. It works, and feels less awful than
|
||||||
|
// manually wrapping state (and probably prevents mixup bugs too.)
|
||||||
|
|
||||||
|
processMessage(handler: IProtocolMessageHandler, buffer: Buffer) {
|
||||||
|
this.protocol.processMessage(this, handler, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNop(): void {
|
||||||
|
this.protocol.sendNop(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSync(now: number): void {
|
||||||
|
this.protocol.sendSync(this, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAuth(authServer: string): void {
|
||||||
|
this.protocol.sendAuth(this, authServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCapabilities(caps: ProtocolUpgradeCapability[]): void {
|
||||||
|
this.protocol.sendCapabilities(this, caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendConnectFailResponse(): void {
|
||||||
|
this.protocol.sendConnectFailResponse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendConnectOKResponse(votes: boolean): void {
|
||||||
|
this.protocol.sendConnectOKResponse(this, votes);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLoginResponse(ok: boolean, message: string | undefined): void {
|
||||||
|
this.protocol.sendLoginResponse(this, ok, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAdminLoginResponse(ok: boolean, modPerms: number | undefined): void {
|
||||||
|
this.protocol.sendAdminLoginResponse(this, ok, modPerms);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAdminMonitorResponse(output: string): void {
|
||||||
|
this.protocol.sendAdminMonitorResponse(this, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAdminIPResponse(username: string, ip: string): void {
|
||||||
|
this.protocol.sendAdminIPResponse(this, username, ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendChatMessage(username: '' | string, message: string): void {
|
||||||
|
this.protocol.sendChatMessage(this, username, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendChatHistoryMessage(history: ProtocolChatHistory[]): void {
|
||||||
|
this.protocol.sendChatHistoryMessage(this, history);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAddUser(users: ProtocolAddUser[]): void {
|
||||||
|
this.protocol.sendAddUser(this, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRemUser(users: string[]): void {
|
||||||
|
this.protocol.sendRemUser(this, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFlag(flag: ProtocolFlag[]): void {
|
||||||
|
this.protocol.sendFlag(this, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSelfRename(status: ProtocolRenameStatus, newUsername: string, rank: Rank): void {
|
||||||
|
this.protocol.sendSelfRename(this, status, newUsername, rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRename(oldUsername: string, newUsername: string, rank: Rank): void {
|
||||||
|
this.protocol.sendRename(this, oldUsername, newUsername, rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendListResponse(list: ListEntry[]): void {
|
||||||
|
this.protocol.sendListResponse(this, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTurnQueue(turnTime: number, users: string[]): void {
|
||||||
|
this.protocol.sendTurnQueue(this, turnTime, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTurnQueueWaiting(turnTime: number, users: string[], waitTime: number): void {
|
||||||
|
this.protocol.sendTurnQueueWaiting(this, turnTime, users, waitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendVoteStarted(): void {
|
||||||
|
this.protocol.sendVoteStarted(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendVoteStats(msLeft: number, nrYes: number, nrNo: number): void {
|
||||||
|
this.protocol.sendVoteStats(this, msLeft, nrYes, nrNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendVoteEnded(): void {
|
||||||
|
this.protocol.sendVoteEnded(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendVoteCooldown(ms: number): void {
|
||||||
|
this.protocol.sendVoteCooldown(this, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendScreenResize(width: number, height: number): void {
|
||||||
|
this.protocol.sendScreenResize(this, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendScreenUpdate(rect: ScreenRect): void {
|
||||||
|
this.protocol.sendScreenUpdate(this, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
get username(): string {
|
||||||
|
return this._username!;
|
||||||
|
}
|
||||||
|
|
||||||
|
set username(updated: string) {
|
||||||
|
this.logger = this.logger.child({
|
||||||
|
username: updated,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._username = updated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Rank {
|
export enum Rank {
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
import { WebSocket } from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import { NetworkClient } from '../NetworkClient.js';
|
import { NetworkClient } from '../NetworkClient.js';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import pino from 'pino';
|
import { pino, type Logger } from 'pino';
|
||||||
|
|
||||||
export default class WSClient extends EventEmitter implements NetworkClient {
|
export default class WSClient extends EventEmitter implements NetworkClient {
|
||||||
socket: WebSocket;
|
socket: WebSocket;
|
||||||
ip: string;
|
ip: string;
|
||||||
|
uuid: string;
|
||||||
enforceTextOnly = true
|
enforceTextOnly = true
|
||||||
private logger = pino({ name: "CVMTS.WebsocketClient" });
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(ws: WebSocket, ip: string) {
|
constructor(ws: WebSocket, ip: string, uuid: string) {
|
||||||
super();
|
super();
|
||||||
this.socket = ws;
|
this.socket = ws;
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.logger = pino().child({
|
||||||
|
name: "CVMTS.WebsocketClient",
|
||||||
|
"uuid/websocket/client": uuid,
|
||||||
|
src_ip: ip,
|
||||||
|
});
|
||||||
this.socket.on('message', (buf: Buffer, isBinary: boolean) => {
|
this.socket.on('message', (buf: Buffer, isBinary: boolean) => {
|
||||||
// Close the user's connection if they send a binary message
|
// Close the user's connection if they send a binary message
|
||||||
// when we are not expecting them yet.
|
// when we are not expecting them yet.
|
||||||
if (isBinary && this.enforceTextOnly) {
|
if (isBinary && this.enforceTextOnly) {
|
||||||
|
this.logger.info({event: "received unexpected binary message"});
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -25,10 +33,11 @@ export default class WSClient extends EventEmitter implements NetworkClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('error', (err: Error) => {
|
this.socket.on('error', (err: Error) => {
|
||||||
this.logger.error(err, 'WebSocket recv error');
|
this.logger.error({event: "websocket recv error", msg: err});
|
||||||
})
|
})
|
||||||
|
|
||||||
this.socket.on('close', () => {
|
this.socket.on('close', () => {
|
||||||
|
this.logger.info({event: "disconnecting client"});
|
||||||
this.emit('disconnect');
|
this.emit('disconnect');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -42,12 +51,13 @@ export default class WSClient extends EventEmitter implements NetworkClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send(msg: string): Promise<void> {
|
send(msg: string): Promise<void> {
|
||||||
|
this.logger.trace({event: "outgoing message", msg});
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
if (!this.isOpen()) return res();
|
if (!this.isOpen()) return res();
|
||||||
|
|
||||||
this.socket.send(msg, (err) => {
|
this.socket.send(msg, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.logger.error(err, 'WebSocket send error');
|
this.logger.error({event: "websocket send error", msg: err});
|
||||||
this.close();
|
this.close();
|
||||||
res();
|
res();
|
||||||
return;
|
return;
|
||||||
@@ -58,12 +68,13 @@ export default class WSClient extends EventEmitter implements NetworkClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendBinary(msg: Uint8Array): Promise<void> {
|
sendBinary(msg: Uint8Array): Promise<void> {
|
||||||
|
this.logger.trace({event: "outgoing message", msg});
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
if (!this.isOpen()) return res();
|
if (!this.isOpen()) return res();
|
||||||
|
|
||||||
this.socket.send(msg, (err) => {
|
this.socket.send(msg, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.logger.error(err, 'WebSocket send error');
|
this.logger.error({event: "websocket send error", msg: err});
|
||||||
this.close();
|
this.close();
|
||||||
res();
|
res();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import { isIP } from 'net';
|
|||||||
import { IPDataManager } from '../../IPData.js';
|
import { IPDataManager } from '../../IPData.js';
|
||||||
import WSClient from './WSClient.js';
|
import WSClient from './WSClient.js';
|
||||||
import { User } from '../../User.js';
|
import { User } from '../../User.js';
|
||||||
import pino from 'pino';
|
import { pino, type Logger } from 'pino';
|
||||||
import { BanManager } from '../../BanManager.js';
|
import { BanManager } from '../../BanManager.js';
|
||||||
|
import { v4 as uuid4 } from 'uuid';
|
||||||
|
|
||||||
const kAllowedProtocols = [
|
const kAllowedProtocols = [
|
||||||
"guacamole" // Regular ol' collabvm1 protocol
|
"guacamole" // Regular ol' collabvm1 protocol
|
||||||
@@ -20,17 +21,25 @@ export default class WSServer extends EventEmitter implements NetworkServer {
|
|||||||
private wsServer: WebSocketServer;
|
private wsServer: WebSocketServer;
|
||||||
private clients: WSClient[];
|
private clients: WSClient[];
|
||||||
private Config: IConfig;
|
private Config: IConfig;
|
||||||
private logger = pino({ name: 'CVMTS.WSServer' });
|
private logger: Logger;
|
||||||
private banmgr: BanManager;
|
private banmgr: BanManager;
|
||||||
|
private uuid: string;
|
||||||
|
|
||||||
constructor(config: IConfig, banmgr: BanManager) {
|
constructor(config: IConfig, banmgr: BanManager) {
|
||||||
super();
|
super();
|
||||||
this.Config = config;
|
this.Config = config;
|
||||||
this.clients = [];
|
this.clients = [];
|
||||||
|
this.uuid = uuid4();
|
||||||
|
this.logger = pino().child({
|
||||||
|
stream: 'CVMTS.WSServer',
|
||||||
|
"uuid/websocket/server": this.uuid,
|
||||||
|
node: config.collabvm.node,
|
||||||
|
});
|
||||||
this.httpServer = http.createServer();
|
this.httpServer = http.createServer();
|
||||||
this.wsServer = new WebSocketServer({ noServer: true, perMessageDeflate: false, clientTracking: false });
|
this.wsServer = new WebSocketServer({ noServer: true, perMessageDeflate: false, clientTracking: false });
|
||||||
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) => {
|
||||||
|
this.logger.debug({ event: "request", path: req.url });
|
||||||
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();
|
||||||
@@ -39,13 +48,33 @@ export default class WSServer extends EventEmitter implements NetworkServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
|
this.logger.info({
|
||||||
|
event: "websocket server starting",
|
||||||
|
host: this.Config.http.host,
|
||||||
|
port: this.Config.http.port,
|
||||||
|
});
|
||||||
this.httpServer.listen(this.Config.http.port, this.Config.http.host, () => {
|
this.httpServer.listen(this.Config.http.port, this.Config.http.host, () => {
|
||||||
this.logger.info(`WebSocket server listening on ${this.Config.http.host}:${this.Config.http.port}`);
|
this.logger.info({
|
||||||
|
event: "websocket server started",
|
||||||
|
host: this.Config.http.host,
|
||||||
|
port: this.Config.http.port,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(): void {
|
stop(): void {
|
||||||
this.httpServer.close();
|
this.logger.info({
|
||||||
|
event: "websocket server stopping",
|
||||||
|
host: this.Config.http.host,
|
||||||
|
port: this.Config.http.port,
|
||||||
|
});
|
||||||
|
this.httpServer.close(() => {
|
||||||
|
this.logger.info({
|
||||||
|
event: "websocket server stopped",
|
||||||
|
host: this.Config.http.host,
|
||||||
|
port: this.Config.http.port,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async httpOnUpgrade(req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) {
|
private async httpOnUpgrade(req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) {
|
||||||
@@ -142,17 +171,34 @@ export default class WSServer extends EventEmitter implements NetworkServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onConnection(ws: WebSocket, req: http.IncomingMessage, ip: string, protocol: string) {
|
private onConnection(ws: WebSocket, req: http.IncomingMessage, ip: string, protocol: string) {
|
||||||
let client = new WSClient(ws, ip);
|
const uuid = uuid4();
|
||||||
|
const connectionId = {
|
||||||
|
"uuid/websocket/client": uuid,
|
||||||
|
src_ip: ip
|
||||||
|
};
|
||||||
|
this.logger.info({ ...connectionId, event: "websocket client connecting" });
|
||||||
|
|
||||||
|
let client = new WSClient(ws, ip, uuid);
|
||||||
this.clients.push(client);
|
this.clients.push(client);
|
||||||
|
|
||||||
let user = new User(client, protocol, IPDataManager.GetIPData(ip), this.Config);
|
let user = new User(client, protocol, IPDataManager.GetIPData(ip), this.Config);
|
||||||
|
this.logger.info({
|
||||||
|
...connectionId,
|
||||||
|
event: "websocket client connection bound to user",
|
||||||
|
"uuid/user": user.uuid
|
||||||
|
});
|
||||||
|
|
||||||
this.emit('connect', user);
|
this.emit('connect', user);
|
||||||
|
|
||||||
ws.on('error', (e) => {
|
ws.on('error', (e) => {
|
||||||
this.logger.error(`${e} (caused by connection ${ip})`);
|
this.logger.error({ ...connectionId, event: "websocket connection error" });
|
||||||
ws.close();
|
ws.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info(`New WebSocket connection from ${user.IP.address}`);
|
ws.on('close', () => {
|
||||||
|
this.logger.error({ ...connectionId, event: "websocket connection closed" });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.info({ ...connectionId, event: "websocket client connected" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import { CollabVMProtocolMessage, CollabVMProtocolMessageType } from '@cvmts/col
|
|||||||
import { GuacamoleProtocol } from './GuacamoleProtocol.js';
|
import { GuacamoleProtocol } from './GuacamoleProtocol.js';
|
||||||
|
|
||||||
import { ScreenRect } from './Protocol';
|
import { ScreenRect } from './Protocol';
|
||||||
|
import { User } from '../User.js';
|
||||||
|
|
||||||
export class BinRectsProtocol extends GuacamoleProtocol {
|
export class BinRectsProtocol extends GuacamoleProtocol {
|
||||||
sendScreenUpdate(rect: ScreenRect): void {
|
sendScreenUpdate(user: User, rect: ScreenRect): void {
|
||||||
let bmsg: CollabVMProtocolMessage = {
|
let bmsg: CollabVMProtocolMessage = {
|
||||||
type: CollabVMProtocolMessageType.rect,
|
type: CollabVMProtocolMessageType.rect,
|
||||||
rect: rect
|
rect: rect
|
||||||
};
|
};
|
||||||
|
|
||||||
this.user?.socket.sendBinary(msgpack.encode(bmsg));
|
user.socket.sendBinary(msgpack.encode(bmsg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,37 @@
|
|||||||
import pino from 'pino';
|
import { IProtocol, IProtocolMessageHandler, ListEntry, ProtocolAddUser, ProtocolChatHistory, ProtocolFlag, ProtocolRenameStatus, ProtocolUpgradeCapability, ScreenRect } from './Protocol.js';
|
||||||
import { IProtocol, IProtocolMessageHandler, ListEntry, ProtocolAddUser, ProtocolBase, ProtocolChatHistory, ProtocolFlag, ProtocolRenameStatus, ProtocolUpgradeCapability, ScreenRect } from './Protocol.js';
|
|
||||||
import { Rank, User } from '../User.js';
|
import { Rank, User } from '../User.js';
|
||||||
|
|
||||||
import * as cvm from '@cvmts/cvm-rs';
|
import * as cvm from '@cvmts/cvm-rs';
|
||||||
|
|
||||||
// CollabVM protocol implementation for Guacamole.
|
// CollabVM protocol implementation for Guacamole.
|
||||||
export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
export class GuacamoleProtocol implements IProtocol {
|
||||||
private logger = pino({
|
private __processMessage_admin(user: User, handler: IProtocolMessageHandler, decodedElements: string[]): boolean {
|
||||||
name: 'CVMTS.GuacamoleProtocol'
|
|
||||||
});
|
|
||||||
|
|
||||||
private __processMessage_admin(decodedElements: string[]): boolean {
|
|
||||||
switch (decodedElements[1]) {
|
switch (decodedElements[1]) {
|
||||||
case '2':
|
case '2':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminLogin(this.user!, decodedElements[2]);
|
handler.onAdminLogin(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '5':
|
case '5':
|
||||||
if (decodedElements.length !== 4) return false;
|
if (decodedElements.length !== 4) return false;
|
||||||
this.handlers?.onAdminMonitor(this.user!, decodedElements[2], decodedElements[3]);
|
handler.onAdminMonitor(user, decodedElements[2], decodedElements[3]);
|
||||||
break;
|
break;
|
||||||
case '8':
|
case '8':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminRestore(this.user!, decodedElements[2]);
|
handler.onAdminRestore(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '10':
|
case '10':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminReboot(this.user!, decodedElements[2]);
|
handler.onAdminReboot(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '12':
|
case '12':
|
||||||
if (decodedElements.length < 3) return false;
|
if (decodedElements.length < 3) return false;
|
||||||
this.handlers?.onAdminBanUser(this.user!, decodedElements[2]);
|
handler.onAdminBanUser(user, decodedElements[2]);
|
||||||
case '13':
|
case '13':
|
||||||
{
|
{
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
let choice = parseInt(decodedElements[2]);
|
let choice = parseInt(decodedElements[2]);
|
||||||
if (choice == undefined) return false;
|
if (choice == undefined) return false;
|
||||||
this.handlers?.onAdminForceVote(this.user!, choice);
|
handler.onAdminForceVote(user, choice);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '14':
|
case '14':
|
||||||
@@ -46,35 +41,35 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
if (decodedElements[3] == '0') temporary = true;
|
if (decodedElements[3] == '0') temporary = true;
|
||||||
else if (decodedElements[3] == '1') temporary = false;
|
else if (decodedElements[3] == '1') temporary = false;
|
||||||
else return false;
|
else return false;
|
||||||
this.handlers?.onAdminMuteUser(this.user!, decodedElements[2], temporary);
|
handler.onAdminMuteUser(user, decodedElements[2], temporary);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '15':
|
case '15':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminKickUser(this.user!, decodedElements[2]);
|
handler.onAdminKickUser(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '16':
|
case '16':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminEndTurn(this.user!, decodedElements[2]);
|
handler.onAdminEndTurn(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '17':
|
case '17':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminClearQueue(this.user!, decodedElements[2]);
|
handler.onAdminClearQueue(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '18':
|
case '18':
|
||||||
if (decodedElements.length !== 4) return false;
|
if (decodedElements.length !== 4) return false;
|
||||||
this.handlers?.onAdminRename(this.user!, decodedElements[2], decodedElements[3]);
|
handler.onAdminRename(user, decodedElements[2], decodedElements[3]);
|
||||||
break;
|
break;
|
||||||
case '19':
|
case '19':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminGetIP(this.user!, decodedElements[2]);
|
handler.onAdminGetIP(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '20':
|
case '20':
|
||||||
this.handlers?.onAdminBypassTurn(this.user!);
|
handler.onAdminBypassTurn(user);
|
||||||
break;
|
break;
|
||||||
case '21':
|
case '21':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminRawMessage(this.user!, decodedElements[2]);
|
handler.onAdminRawMessage(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
case '22':
|
case '22':
|
||||||
{
|
{
|
||||||
@@ -84,11 +79,11 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
if (decodedElements[2] == '0') enabled = false;
|
if (decodedElements[2] == '0') enabled = false;
|
||||||
else if (decodedElements[2] == '1') enabled = true;
|
else if (decodedElements[2] == '1') enabled = true;
|
||||||
else return false;
|
else return false;
|
||||||
this.handlers?.onAdminToggleTurns(this.user!, enabled);
|
handler.onAdminToggleTurns(user, enabled);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '23':
|
case '23':
|
||||||
this.handlers?.onAdminIndefiniteTurn(this.user!);
|
handler.onAdminIndefiniteTurn(user);
|
||||||
break;
|
break;
|
||||||
case '24':
|
case '24':
|
||||||
{
|
{
|
||||||
@@ -97,43 +92,43 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
if (decodedElements[2] == '0') show = false;
|
if (decodedElements[2] == '0') show = false;
|
||||||
else if (decodedElements[2] == '1') show = true;
|
else if (decodedElements[2] == '1') show = true;
|
||||||
else return false;
|
else return false;
|
||||||
this.handlers?.onAdminHideScreen(this.user!, show);
|
handler.onAdminHideScreen(user, show);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '25':
|
case '25':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
this.handlers?.onAdminSystemMessage(this.user!, decodedElements[2]);
|
handler.onAdminSystemMessage(user, decodedElements[2]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
processMessage(buffer: Buffer): boolean {
|
processMessage(user: User, handler: IProtocolMessageHandler, buffer: Buffer): boolean {
|
||||||
let decodedElements = cvm.guacDecode(buffer.toString('utf-8'));
|
let decodedElements = cvm.guacDecode(buffer.toString('utf-8'));
|
||||||
if (decodedElements.length < 1) return false;
|
if (decodedElements.length < 1) return false;
|
||||||
|
|
||||||
// The first element is the "opcode".
|
// The first element is the "opcode".
|
||||||
switch (decodedElements[0]) {
|
switch (decodedElements[0]) {
|
||||||
case 'nop':
|
case 'nop':
|
||||||
this.handlers?.onNop(this.user!);
|
handler.onNop(user);
|
||||||
break;
|
break;
|
||||||
case 'cap':
|
case 'cap':
|
||||||
if (decodedElements.length < 2) return false;
|
if (decodedElements.length < 2) return false;
|
||||||
this.handlers?.onCapabilityUpgrade(this.user!, decodedElements.slice(1));
|
handler.onCapabilityUpgrade(user, decodedElements.slice(1));
|
||||||
break;
|
break;
|
||||||
case 'login':
|
case 'login':
|
||||||
if (decodedElements.length !== 2) return false;
|
if (decodedElements.length !== 2) return false;
|
||||||
this.handlers?.onLogin(this.user!, decodedElements[1]);
|
handler.onLogin(user, decodedElements[1]);
|
||||||
break;
|
break;
|
||||||
case 'noflag':
|
case 'noflag':
|
||||||
this.handlers?.onNoFlag(this.user!);
|
handler.onNoFlag(user);
|
||||||
break;
|
break;
|
||||||
case 'list':
|
case 'list':
|
||||||
this.handlers?.onList(this.user!);
|
handler.onList(user);
|
||||||
break;
|
break;
|
||||||
case 'connect':
|
case 'connect':
|
||||||
if (decodedElements.length !== 2) return false;
|
if (decodedElements.length !== 2) return false;
|
||||||
this.handlers?.onConnect(this.user!, decodedElements[1]);
|
handler.onConnect(user, decodedElements[1]);
|
||||||
break;
|
break;
|
||||||
case 'view':
|
case 'view':
|
||||||
{
|
{
|
||||||
@@ -141,15 +136,15 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
let viewMode = parseInt(decodedElements[2]);
|
let viewMode = parseInt(decodedElements[2]);
|
||||||
if (viewMode == undefined) return false;
|
if (viewMode == undefined) return false;
|
||||||
|
|
||||||
this.handlers?.onView(this.user!, decodedElements[1], viewMode);
|
handler.onView(user, decodedElements[1], viewMode);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'rename':
|
case 'rename':
|
||||||
this.handlers?.onRename(this.user!, decodedElements[1]);
|
handler.onRename(user, decodedElements[1]);
|
||||||
break;
|
break;
|
||||||
case 'chat':
|
case 'chat':
|
||||||
if (decodedElements.length !== 2) return false;
|
if (decodedElements.length !== 2) return false;
|
||||||
this.handlers?.onChat(this.user!, decodedElements[1]);
|
handler.onChat(user, decodedElements[1]);
|
||||||
break;
|
break;
|
||||||
case 'turn':
|
case 'turn':
|
||||||
let forfeit = false;
|
let forfeit = false;
|
||||||
@@ -161,7 +156,7 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
else if (decodedElements[1] == '1') forfeit = false;
|
else if (decodedElements[1] == '1') forfeit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handlers?.onTurnRequest(this.user!, forfeit);
|
handler.onTurnRequest(user, forfeit);
|
||||||
break;
|
break;
|
||||||
case 'mouse':
|
case 'mouse':
|
||||||
if (decodedElements.length !== 4) return false;
|
if (decodedElements.length !== 4) return false;
|
||||||
@@ -171,25 +166,25 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
let mask = parseInt(decodedElements[3]);
|
let mask = parseInt(decodedElements[3]);
|
||||||
if (x === undefined || y === undefined || mask === undefined) return false;
|
if (x === undefined || y === undefined || mask === undefined) return false;
|
||||||
|
|
||||||
this.handlers?.onMouse(this.user!, x, y, mask);
|
handler.onMouse(user, x, y, mask);
|
||||||
break;
|
break;
|
||||||
case 'key':
|
case 'key':
|
||||||
if (decodedElements.length !== 3) return false;
|
if (decodedElements.length !== 3) return false;
|
||||||
var keysym = parseInt(decodedElements[1]);
|
var keysym = parseInt(decodedElements[1]);
|
||||||
var down = parseInt(decodedElements[2]);
|
var down = parseInt(decodedElements[2]);
|
||||||
if (keysym === undefined || (down !== 0 && down !== 1)) return false;
|
if (keysym === undefined || (down !== 0 && down !== 1)) return false;
|
||||||
this.handlers?.onKey(this.user!, keysym, down === 1);
|
handler.onKey(user, keysym, down === 1);
|
||||||
break;
|
break;
|
||||||
case 'vote':
|
case 'vote':
|
||||||
if (decodedElements.length !== 2) return false;
|
if (decodedElements.length !== 2) return false;
|
||||||
let choice = parseInt(decodedElements[1]);
|
let choice = parseInt(decodedElements[1]);
|
||||||
if (choice == undefined) return false;
|
if (choice == undefined) return false;
|
||||||
this.handlers?.onVote(this.user!, choice);
|
handler.onVote(user, choice);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'admin':
|
case 'admin':
|
||||||
if (decodedElements.length < 2) return false;
|
if (decodedElements.length < 2) return false;
|
||||||
return this.__processMessage_admin(decodedElements);
|
return this.__processMessage_admin(user, handler, decodedElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -197,109 +192,109 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
|
|
||||||
// Senders
|
// Senders
|
||||||
|
|
||||||
sendAuth(authServer: string): void {
|
sendAuth(user: User, authServer: string): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('auth', authServer));
|
user.sendMsg(cvm.guacEncode('auth', authServer));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNop(): void {
|
sendNop(user: User): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('nop'));
|
user.sendMsg(cvm.guacEncode('nop'));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSync(now: number): void {
|
sendSync(user: User, now: number): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('sync', now.toString()));
|
user.sendMsg(cvm.guacEncode('sync', now.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCapabilities(caps: ProtocolUpgradeCapability[]): void {
|
sendCapabilities(user: User, caps: ProtocolUpgradeCapability[]): void {
|
||||||
let arr = ['cap', ...caps];
|
let arr = ['cap', ...caps];
|
||||||
this?.user?.sendMsg(cvm.guacEncode(...arr));
|
user.sendMsg(cvm.guacEncode(...arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendConnectFailResponse(): void {
|
sendConnectFailResponse(user: User): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('connect', '0'));
|
user.sendMsg(cvm.guacEncode('connect', '0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendConnectOKResponse(votes: boolean): void {
|
sendConnectOKResponse(user: User, votes: boolean): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('connect', '1', '1', votes ? '1' : '0', '0'));
|
user.sendMsg(cvm.guacEncode('connect', '1', '1', votes ? '1' : '0', '0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendLoginResponse(ok: boolean, message: string | undefined): void {
|
sendLoginResponse(user: User, ok: boolean, message: string | undefined): void {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.user?.sendMsg(cvm.guacEncode('login', '1'));
|
user.sendMsg(cvm.guacEncode('login', '1'));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this.user?.sendMsg(cvm.guacEncode('login', '0', message!));
|
user.sendMsg(cvm.guacEncode('login', '0', message!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAdminLoginResponse(ok: boolean, modPerms: number | undefined): void {
|
sendAdminLoginResponse(user: User, ok: boolean, modPerms: number | undefined): void {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (modPerms == undefined) {
|
if (modPerms == undefined) {
|
||||||
this.user?.sendMsg(cvm.guacEncode('admin', '0', '1'));
|
user.sendMsg(cvm.guacEncode('admin', '0', '1'));
|
||||||
} else {
|
} else {
|
||||||
this.user?.sendMsg(cvm.guacEncode('admin', '0', '3', modPerms.toString()));
|
user.sendMsg(cvm.guacEncode('admin', '0', '3', modPerms.toString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.user?.sendMsg(cvm.guacEncode('admin', '0', '0'));
|
user.sendMsg(cvm.guacEncode('admin', '0', '0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAdminMonitorResponse(output: string): void {
|
sendAdminMonitorResponse(user: User, output: string): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('admin', '2', output));
|
user.sendMsg(cvm.guacEncode('admin', '2', output));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAdminIPResponse(username: string, ip: string): void {
|
sendAdminIPResponse(user: User, username: string, ip: string): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('admin', '19', username, ip));
|
user.sendMsg(cvm.guacEncode('admin', '19', username, ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendChatMessage(username: string, message: string): void {
|
sendChatMessage(user: User, username: string, message: string): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('chat', username, message));
|
user.sendMsg(cvm.guacEncode('chat', username, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendChatHistoryMessage(history: ProtocolChatHistory[]): void {
|
sendChatHistoryMessage(user: User, history: ProtocolChatHistory[]): void {
|
||||||
let arr = ['chat'];
|
let arr = ['chat'];
|
||||||
for (let a of history) {
|
for (let a of history) {
|
||||||
arr.push(a.user, a.msg);
|
arr.push(a.user, a.msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.user?.sendMsg(cvm.guacEncode(...arr));
|
user.sendMsg(cvm.guacEncode(...arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAddUser(users: ProtocolAddUser[]): void {
|
sendAddUser(user: User, users: ProtocolAddUser[]): void {
|
||||||
let arr = ['adduser', users.length.toString()];
|
let arr = ['adduser', users.length.toString()];
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
arr.push(user.username);
|
arr.push(user.username);
|
||||||
arr.push(user.rank.toString());
|
arr.push(user.rank.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.user?.sendMsg(cvm.guacEncode(...arr));
|
user.sendMsg(cvm.guacEncode(...arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRemUser(users: string[]): void {
|
sendRemUser(user: User, users: string[]): void {
|
||||||
let arr = ['remuser', users.length.toString()];
|
let arr = ['remuser', users.length.toString()];
|
||||||
|
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
arr.push(user);
|
arr.push(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.user?.sendMsg(cvm.guacEncode(...arr));
|
user.sendMsg(cvm.guacEncode(...arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFlag(flag: ProtocolFlag[]): void {
|
sendFlag(user: User, flag: ProtocolFlag[]): void {
|
||||||
// Basically this does the same as the above manual for of things
|
// Basically this does the same as the above manual for of things
|
||||||
// but in one line of code
|
// but in one line of code
|
||||||
let arr = ['flag', ...flag.flatMap((flag) => [flag.username, flag.countryCode])];
|
let arr = ['flag', ...flag.flatMap((flag) => [flag.username, flag.countryCode])];
|
||||||
this.user?.sendMsg(cvm.guacEncode(...arr));
|
user.sendMsg(cvm.guacEncode(...arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSelfRename(status: ProtocolRenameStatus, newUsername: string, rank: Rank): void {
|
sendSelfRename(user: User, status: ProtocolRenameStatus, newUsername: string, rank: Rank): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('rename', '0', status.toString(), newUsername));
|
user.sendMsg(cvm.guacEncode('rename', '0', status.toString(), newUsername));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRename(oldUsername: string, newUsername: string, rank: Rank): void {
|
sendRename(user: User, oldUsername: string, newUsername: string, rank: Rank): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('rename', '1', oldUsername, newUsername));
|
user.sendMsg(cvm.guacEncode('rename', '1', oldUsername, newUsername));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendListResponse(list: ListEntry[]): void {
|
sendListResponse(user: User, list: ListEntry[]): void {
|
||||||
let arr = ['list'];
|
let arr = ['list'];
|
||||||
for (let node of list) {
|
for (let node of list) {
|
||||||
arr.push(node.id);
|
arr.push(node.id);
|
||||||
@@ -307,45 +302,45 @@ export class GuacamoleProtocol extends ProtocolBase implements IProtocol {
|
|||||||
arr.push(node.thumbnail.toString('base64'));
|
arr.push(node.thumbnail.toString('base64'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.user?.sendMsg(cvm.guacEncode(...arr));
|
user.sendMsg(cvm.guacEncode(...arr));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendVoteStarted(): void {
|
sendVoteStarted(user: User): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('vote', '0'));
|
user.sendMsg(cvm.guacEncode('vote', '0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendVoteStats(msLeft: number, nrYes: number, nrNo: number): void {
|
sendVoteStats(user: User, msLeft: number, nrYes: number, nrNo: number): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('vote', '1', msLeft.toString(), nrYes.toString(), nrNo.toString()));
|
user.sendMsg(cvm.guacEncode('vote', '1', msLeft.toString(), nrYes.toString(), nrNo.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendVoteEnded(): void {
|
sendVoteEnded(user: User): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('vote', '2'));
|
user.sendMsg(cvm.guacEncode('vote', '2'));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendVoteCooldown(ms: number): void {
|
sendVoteCooldown(user: User, ms: number): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('vote', '3', ms.toString()));
|
user.sendMsg(cvm.guacEncode('vote', '3', ms.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTurnQueueBase(turnTime: number, users: string[]): string[] {
|
private getTurnQueueBase(turnTime: number, users: string[]): string[] {
|
||||||
return ['turn', turnTime.toString(), users.length.toString(), ...users];
|
return ['turn', turnTime.toString(), users.length.toString(), ...users];
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTurnQueue(turnTime: number, users: string[]): void {
|
sendTurnQueue(user: User, turnTime: number, users: string[]): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode(...this.getTurnQueueBase(turnTime, users)));
|
user.sendMsg(cvm.guacEncode(...this.getTurnQueueBase(turnTime, users)));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTurnQueueWaiting(turnTime: number, users: string[], waitTime: number): void {
|
sendTurnQueueWaiting(user: User, turnTime: number, users: string[], waitTime: number): void {
|
||||||
let queue = this.getTurnQueueBase(turnTime, users);
|
let queue = this.getTurnQueueBase(turnTime, users);
|
||||||
queue.push(waitTime.toString());
|
queue.push(waitTime.toString());
|
||||||
this.user?.sendMsg(cvm.guacEncode(...queue));
|
user.sendMsg(cvm.guacEncode(...queue));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendScreenResize(width: number, height: number): void {
|
sendScreenResize(user: User, width: number, height: number): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('size', '0', width.toString(), height.toString()));
|
user.sendMsg(cvm.guacEncode('size', '0', width.toString(), height.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendScreenUpdate(rect: ScreenRect): void {
|
sendScreenUpdate(user: User, rect: ScreenRect): void {
|
||||||
this.user?.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), rect.data.toString('base64')));
|
user.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), rect.data.toString('base64')));
|
||||||
this.sendSync(Date.now());
|
this.sendSync(user, Date.now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import { IProtocol } from "./Protocol";
|
import { IProtocol } from './Protocol';
|
||||||
import { User } from "../User";
|
import { User } from '../User';
|
||||||
|
|
||||||
// The protocol manager. Holds protocol factories, and provides the ability
|
// The protocol manager.
|
||||||
// to create a protocol by name. Avoids direct dependency on a given list of protocols,
|
// Holds protocols, and provides the ability to obtain them by name.
|
||||||
// and allows (relatively simple) expansion.
|
//
|
||||||
|
// Avoids direct dependency on a given list of protocols,
|
||||||
|
// and allows (relatively simple) expansion of the supported protocols.
|
||||||
export class ProtocolManager {
|
export class ProtocolManager {
|
||||||
private protocols = new Map<String, () => IProtocol>();
|
private protocols = new Map<String, IProtocol>();
|
||||||
|
|
||||||
// Registers a protocol with the given name.
|
// Registers a protocol with the given name, creates it, and stores it for later use.
|
||||||
registerProtocol(name: string, protocolFactory: () => IProtocol) {
|
registerProtocol(name: string, protocolFactory: () => IProtocol) {
|
||||||
if (!this.protocols.has(name)) this.protocols.set(name, protocolFactory);
|
if (!this.protocols.has(name)) this.protocols.set(name, protocolFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an instance of a given protocol for a user.
|
// Gets an instance of a protocol.
|
||||||
createProtocol(name: string, user: User): IProtocol {
|
getProtocol(name: string): IProtocol {
|
||||||
if (!this.protocols.has(name)) throw new Error(`ProtocolManager does not have protocol \"${name}\"`);
|
let proto = this.protocols.get(name);
|
||||||
|
if (proto == undefined) throw new Error(`ProtocolManager does not have protocol \"${name}\"`);
|
||||||
let factory = this.protocols.get(name)!;
|
|
||||||
let proto = factory();
|
|
||||||
proto.init(user);
|
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,13 +90,6 @@ export interface IProtocolMessageHandler {
|
|||||||
// allowing it to be protocol-independent (as long as the client and server
|
// allowing it to be protocol-independent (as long as the client and server
|
||||||
// are able to speak the same protocol.)
|
// are able to speak the same protocol.)
|
||||||
export interface IProtocol {
|
export interface IProtocol {
|
||||||
// don't implement this yourself, extend from ProtocolBase
|
|
||||||
init(u: User): void;
|
|
||||||
dispose(): void;
|
|
||||||
|
|
||||||
// Sets handler object.
|
|
||||||
setHandler(handlers: IProtocolMessageHandler): void;
|
|
||||||
|
|
||||||
// Protocol implementation stuff
|
// Protocol implementation stuff
|
||||||
|
|
||||||
// Parses a single message and fires the given handler with deserialized arguments.
|
// Parses a single message and fires the given handler with deserialized arguments.
|
||||||
@@ -104,67 +97,48 @@ export interface IProtocol {
|
|||||||
// to handle errors. It should, however, catch invalid parameters without failing.
|
// to handle errors. It should, however, catch invalid parameters without failing.
|
||||||
//
|
//
|
||||||
// This function will perform conversion to text if it is required.
|
// This function will perform conversion to text if it is required.
|
||||||
processMessage(buffer: Buffer): boolean;
|
processMessage(user: User, handler: IProtocolMessageHandler, buffer: Buffer): boolean;
|
||||||
|
|
||||||
// Senders
|
// Senders
|
||||||
|
|
||||||
sendNop(): void;
|
sendNop(user: User): void;
|
||||||
sendSync(now: number): void;
|
sendSync(user: User, now: number): void;
|
||||||
|
|
||||||
sendAuth(authServer: string): void;
|
sendAuth(user: User, authServer: string): void;
|
||||||
|
|
||||||
sendCapabilities(caps: ProtocolUpgradeCapability[]): void;
|
sendCapabilities(user: User, caps: ProtocolUpgradeCapability[]): void;
|
||||||
|
|
||||||
sendConnectFailResponse(): void;
|
sendConnectFailResponse(user: User): void;
|
||||||
sendConnectOKResponse(votes: boolean): void;
|
sendConnectOKResponse(user: User, votes: boolean): void;
|
||||||
|
|
||||||
sendLoginResponse(ok: boolean, message: string | undefined): void;
|
sendLoginResponse(user: User, ok: boolean, message: string | undefined): void;
|
||||||
|
|
||||||
sendAdminLoginResponse(ok: boolean, modPerms: number | undefined): void;
|
sendAdminLoginResponse(user: User, ok: boolean, modPerms: number | undefined): void;
|
||||||
sendAdminMonitorResponse(output: string): void;
|
sendAdminMonitorResponse(user: User, output: string): void;
|
||||||
sendAdminIPResponse(username: string, ip: string): void;
|
sendAdminIPResponse(user: User, username: string, ip: string): void;
|
||||||
|
|
||||||
sendChatMessage(username: '' | string, message: string): void;
|
sendChatMessage(user: User, username: '' | string, message: string): void;
|
||||||
sendChatHistoryMessage(history: ProtocolChatHistory[]): void;
|
sendChatHistoryMessage(user: User, history: ProtocolChatHistory[]): void;
|
||||||
|
|
||||||
sendAddUser(users: ProtocolAddUser[]): void;
|
sendAddUser(user: User, users: ProtocolAddUser[]): void;
|
||||||
sendRemUser(users: string[]): void;
|
sendRemUser(user: User, users: string[]): void;
|
||||||
sendFlag(flag: ProtocolFlag[]): void;
|
sendFlag(user: User, flag: ProtocolFlag[]): void;
|
||||||
|
|
||||||
sendSelfRename(status: ProtocolRenameStatus, newUsername: string, rank: Rank): void;
|
sendSelfRename(user: User, status: ProtocolRenameStatus, newUsername: string, rank: Rank): void;
|
||||||
sendRename(oldUsername: string, newUsername: string, rank: Rank): void;
|
sendRename(user: User, oldUsername: string, newUsername: string, rank: Rank): void;
|
||||||
|
|
||||||
sendListResponse(list: ListEntry[]): void;
|
sendListResponse(user: User, list: ListEntry[]): void;
|
||||||
|
|
||||||
sendTurnQueue(turnTime: number, users: string[]): void;
|
sendTurnQueue(user: User, turnTime: number, users: string[]): void;
|
||||||
sendTurnQueueWaiting(turnTime: number, users: string[], waitTime: number): void;
|
sendTurnQueueWaiting(user: User, turnTime: number, users: string[], waitTime: number): void;
|
||||||
|
|
||||||
sendVoteStarted(): void;
|
sendVoteStarted(user: User): void;
|
||||||
sendVoteStats(msLeft: number, nrYes: number, nrNo: number): void;
|
sendVoteStats(user: User, msLeft: number, nrYes: number, nrNo: number): void;
|
||||||
sendVoteEnded(): void;
|
sendVoteEnded(user: User): void;
|
||||||
sendVoteCooldown(ms: number): void;
|
sendVoteCooldown(user: User, ms: number): void;
|
||||||
|
|
||||||
sendScreenResize(width: number, height: number): void;
|
sendScreenResize(user: User, width: number, height: number): void;
|
||||||
|
|
||||||
// Sends a rectangle update to the user.
|
// Sends a rectangle update to the user.
|
||||||
sendScreenUpdate(rect: ScreenRect): void;
|
sendScreenUpdate(user: User, rect: ScreenRect): void;
|
||||||
}
|
|
||||||
|
|
||||||
// Base mixin for all concrete protocols to use. Inherit from this!
|
|
||||||
export class ProtocolBase {
|
|
||||||
protected handlers: IProtocolMessageHandler | null = null;
|
|
||||||
protected user: User | null = null;
|
|
||||||
|
|
||||||
init(u: User): void {
|
|
||||||
this.user = u;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.user = null;
|
|
||||||
this.handlers = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setHandler(handlers: IProtocolMessageHandler): void {
|
|
||||||
this.handlers = handlers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,13 +104,27 @@ export class CGroup {
|
|||||||
if (!existsSync(kCgroupSelfPath)) throw new Error('This process is not in a CGroup.');
|
if (!existsSync(kCgroupSelfPath)) throw new Error('This process is not in a CGroup.');
|
||||||
let res = readFileSync(kCgroupSelfPath, { encoding: 'utf-8' });
|
let res = readFileSync(kCgroupSelfPath, { encoding: 'utf-8' });
|
||||||
|
|
||||||
// Make sure the first/only line is a cgroups2 0::/path/to/cgroup entry.
|
for (let item of res.split('\n')) {
|
||||||
// Legacy cgroups1 is not supported.
|
switch (item[0]) {
|
||||||
if (res[0] != '0') throw new Error('CGroup.Self() does not work with cgroups 1 systems. Please do not the cgroups 1.');
|
// This is techinically cgroupsv1. However, on a unified system
|
||||||
let cg_path = res.substring(3, res.indexOf('\n'));
|
// `systemd-nspawn` by default will mount /sys/fs/cgroup/systemd in the container,
|
||||||
|
// and this leaks out slightly to the host with `1:name=systemd:/`.. for some reason.
|
||||||
|
// Therefore we can allow this. Other controllers not so much.
|
||||||
|
case '1':
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
case '0': // cgroups2
|
||||||
|
let cg_path = item.substring(3);
|
||||||
let cg = new CGroup(path.join('/sys/fs/cgroup', cg_path));
|
let cg = new CGroup(path.join('/sys/fs/cgroup', cg_path));
|
||||||
|
|
||||||
return cg;
|
return cg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Legacy CGroups 1 is not supported.
|
||||||
|
default:
|
||||||
|
throw new Error('CGroup.Self() does not work with cgroups 1 systems. Please do not the cgroups 1.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('1 2 3 5 4 6');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"@types/jsbn": "^1.2.33",
|
"@types/jsbn": "^1.2.33",
|
||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"parcel": "^2.12.0",
|
"parcel": "^2.12.0",
|
||||||
|
"pino-pretty": "^11.2.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-toml": "^2.0.1",
|
"prettier-plugin-toml": "^2.0.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
|
|||||||
307
yarn.lock
307
yarn.lock
@@ -90,197 +90,13 @@ __metadata:
|
|||||||
msgpackr: "npm:^1.10.2"
|
msgpackr: "npm:^1.10.2"
|
||||||
pino: "npm:^9.3.1"
|
pino: "npm:^9.3.1"
|
||||||
pino-pretty: "npm:^11.2.1"
|
pino-pretty: "npm:^11.2.1"
|
||||||
sharp: "npm:^0.33.3"
|
|
||||||
toml: "npm:^3.0.0"
|
toml: "npm:^3.0.0"
|
||||||
typescript: "npm:^5.4.4"
|
typescript: "npm:^5.4.4"
|
||||||
|
uuid: "npm:^13.0.0"
|
||||||
ws: "npm:^8.17.1"
|
ws: "npm:^8.17.1"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@emnapi/runtime@npm:^1.1.1":
|
|
||||||
version: 1.2.0
|
|
||||||
resolution: "@emnapi/runtime@npm:1.2.0"
|
|
||||||
dependencies:
|
|
||||||
tslib: "npm:^2.4.0"
|
|
||||||
checksum: 10c0/7005ff8b67724c9e61b6cd79a3decbdb2ce25d24abd4d3d187472f200ee6e573329c30264335125fb136bd813aa9cf9f4f7c9391d04b07dd1e63ce0a3427be57
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-darwin-arm64@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-darwin-arm64":
|
|
||||||
optional: true
|
|
||||||
conditions: os=darwin & cpu=arm64
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-darwin-x64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-darwin-x64@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-darwin-x64": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-darwin-x64":
|
|
||||||
optional: true
|
|
||||||
conditions: os=darwin & cpu=x64
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-arm64@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.2"
|
|
||||||
conditions: os=darwin & cpu=arm64
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-x64@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.2"
|
|
||||||
conditions: os=darwin & cpu=x64
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm64@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.2"
|
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-linux-arm@npm:1.0.2"
|
|
||||||
conditions: os=linux & cpu=arm & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-s390x@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.2"
|
|
||||||
conditions: os=linux & cpu=s390x & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-x64@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-linux-x64@npm:1.0.2"
|
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2"
|
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64@npm:1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2"
|
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-linux-arm64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-linux-arm64@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-linux-arm64": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-linux-arm64":
|
|
||||||
optional: true
|
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-linux-arm@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-linux-arm@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-linux-arm": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-linux-arm":
|
|
||||||
optional: true
|
|
||||||
conditions: os=linux & cpu=arm & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-linux-s390x@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-linux-s390x@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-linux-s390x": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-linux-s390x":
|
|
||||||
optional: true
|
|
||||||
conditions: os=linux & cpu=s390x & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-linux-x64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-linux-x64@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-linux-x64": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-linux-x64":
|
|
||||||
optional: true
|
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-arm64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64":
|
|
||||||
optional: true
|
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-x64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64":
|
|
||||||
optional: true
|
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-wasm32@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-wasm32@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@emnapi/runtime": "npm:^1.1.1"
|
|
||||||
conditions: cpu=wasm32
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-win32-ia32@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-win32-ia32@npm:0.33.4"
|
|
||||||
conditions: os=win32 & cpu=ia32
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@img/sharp-win32-x64@npm:0.33.4":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "@img/sharp-win32-x64@npm:0.33.4"
|
|
||||||
conditions: os=win32 & cpu=x64
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@isaacs/cliui@npm:^8.0.2":
|
"@isaacs/cliui@npm:^8.0.2":
|
||||||
version: 8.0.2
|
version: 8.0.2
|
||||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||||
@@ -1858,33 +1674,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"color-name@npm:^1.0.0, color-name@npm:~1.1.4":
|
"color-name@npm:~1.1.4":
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
resolution: "color-name@npm:1.1.4"
|
resolution: "color-name@npm:1.1.4"
|
||||||
checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95
|
checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"color-string@npm:^1.9.0":
|
|
||||||
version: 1.9.1
|
|
||||||
resolution: "color-string@npm:1.9.1"
|
|
||||||
dependencies:
|
|
||||||
color-name: "npm:^1.0.0"
|
|
||||||
simple-swizzle: "npm:^0.2.2"
|
|
||||||
checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"color@npm:^4.2.3":
|
|
||||||
version: 4.2.3
|
|
||||||
resolution: "color@npm:4.2.3"
|
|
||||||
dependencies:
|
|
||||||
color-convert: "npm:^2.0.1"
|
|
||||||
color-string: "npm:^1.9.0"
|
|
||||||
checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"colorette@npm:^2.0.7":
|
"colorette@npm:^2.0.7":
|
||||||
version: 2.0.20
|
version: 2.0.20
|
||||||
resolution: "colorette@npm:2.0.20"
|
resolution: "colorette@npm:2.0.20"
|
||||||
@@ -1983,6 +1779,7 @@ __metadata:
|
|||||||
"@types/jsbn": "npm:^1.2.33"
|
"@types/jsbn": "npm:^1.2.33"
|
||||||
"@types/node": "npm:^20.14.10"
|
"@types/node": "npm:^20.14.10"
|
||||||
parcel: "npm:^2.12.0"
|
parcel: "npm:^2.12.0"
|
||||||
|
pino-pretty: "npm:^11.2.1"
|
||||||
prettier: "npm:^3.3.3"
|
prettier: "npm:^3.3.3"
|
||||||
prettier-plugin-toml: "npm:^2.0.1"
|
prettier-plugin-toml: "npm:^2.0.1"
|
||||||
rimraf: "npm:^6.0.1"
|
rimraf: "npm:^6.0.1"
|
||||||
@@ -2035,7 +1832,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3":
|
"detect-libc@npm:^2.0.1":
|
||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
resolution: "detect-libc@npm:2.0.3"
|
resolution: "detect-libc@npm:2.0.3"
|
||||||
checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7
|
checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7
|
||||||
@@ -2576,13 +2373,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"is-arrayish@npm:^0.3.1":
|
|
||||||
version: 0.3.2
|
|
||||||
resolution: "is-arrayish@npm:0.3.2"
|
|
||||||
checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"is-binary-path@npm:~2.1.0":
|
"is-binary-path@npm:~2.1.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "is-binary-path@npm:2.1.0"
|
resolution: "is-binary-path@npm:2.1.0"
|
||||||
@@ -3738,7 +3528,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.6.0":
|
"semver@npm:^7.3.5, semver@npm:^7.5.2":
|
||||||
version: 7.6.3
|
version: 7.6.3
|
||||||
resolution: "semver@npm:7.6.3"
|
resolution: "semver@npm:7.6.3"
|
||||||
bin:
|
bin:
|
||||||
@@ -3747,75 +3537,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"sharp@npm:^0.33.3":
|
|
||||||
version: 0.33.4
|
|
||||||
resolution: "sharp@npm:0.33.4"
|
|
||||||
dependencies:
|
|
||||||
"@img/sharp-darwin-arm64": "npm:0.33.4"
|
|
||||||
"@img/sharp-darwin-x64": "npm:0.33.4"
|
|
||||||
"@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-darwin-x64": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-linux-arm": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-linux-arm64": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-linux-s390x": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-linux-x64": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
|
|
||||||
"@img/sharp-linux-arm": "npm:0.33.4"
|
|
||||||
"@img/sharp-linux-arm64": "npm:0.33.4"
|
|
||||||
"@img/sharp-linux-s390x": "npm:0.33.4"
|
|
||||||
"@img/sharp-linux-x64": "npm:0.33.4"
|
|
||||||
"@img/sharp-linuxmusl-arm64": "npm:0.33.4"
|
|
||||||
"@img/sharp-linuxmusl-x64": "npm:0.33.4"
|
|
||||||
"@img/sharp-wasm32": "npm:0.33.4"
|
|
||||||
"@img/sharp-win32-ia32": "npm:0.33.4"
|
|
||||||
"@img/sharp-win32-x64": "npm:0.33.4"
|
|
||||||
color: "npm:^4.2.3"
|
|
||||||
detect-libc: "npm:^2.0.3"
|
|
||||||
semver: "npm:^7.6.0"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@img/sharp-darwin-arm64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-darwin-x64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-darwin-arm64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-darwin-x64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-linux-arm":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-linux-arm64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-linux-s390x":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-linux-x64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-linux-arm":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-linux-arm64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-linux-s390x":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-linux-x64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-linuxmusl-arm64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-linuxmusl-x64":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-wasm32":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-win32-ia32":
|
|
||||||
optional: true
|
|
||||||
"@img/sharp-win32-x64":
|
|
||||||
optional: true
|
|
||||||
checksum: 10c0/428c5c6a84ff8968effe50c2de931002f5f30b9f263e1c026d0384e581673c13088a49322f7748114d3d9be4ae9476a74bf003a3af34743e97ef2f880d1cfe45
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"shebang-command@npm:^2.0.0":
|
"shebang-command@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "shebang-command@npm:2.0.0"
|
resolution: "shebang-command@npm:2.0.0"
|
||||||
@@ -3839,15 +3560,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"simple-swizzle@npm:^0.2.2":
|
|
||||||
version: 0.2.2
|
|
||||||
resolution: "simple-swizzle@npm:0.2.2"
|
|
||||||
dependencies:
|
|
||||||
is-arrayish: "npm:^0.3.1"
|
|
||||||
checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"smart-buffer@npm:^4.2.0":
|
"smart-buffer@npm:^4.2.0":
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
resolution: "smart-buffer@npm:4.2.0"
|
resolution: "smart-buffer@npm:4.2.0"
|
||||||
@@ -4181,6 +3893,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"uuid@npm:^13.0.0":
|
||||||
|
version: 13.0.0
|
||||||
|
resolution: "uuid@npm:13.0.0"
|
||||||
|
bin:
|
||||||
|
uuid: dist-node/bin/uuid
|
||||||
|
checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"verror@npm:1.10.0":
|
"verror@npm:1.10.0":
|
||||||
version: 1.10.0
|
version: 1.10.0
|
||||||
resolution: "verror@npm:1.10.0"
|
resolution: "verror@npm:1.10.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user