Seperate server classes into new typescript modules

Organization is nice.
This commit is contained in:
Lily Tsuru 2024-04-06 23:46:37 -04:00
parent d7326736ca
commit 9121cc487f
6 changed files with 416 additions and 408 deletions

View file

@ -18,8 +18,8 @@ export class ExtendableTimer extends EventEmitter {
this.timeout = setTimeout(() => {
this.iterationcount--;
if (this.iterationcount == 1) {
this.emit('expiry-near');
} else if (this.iterationcount == 0) {
this.emit('expiry-near');
} else if (this.iterationcount == 0) {
return this.emit('expired');
}
this.Arm();

View file

@ -43,9 +43,7 @@ export function Slot_PCDef(
pushOption(`${netdevOption}`);
pushOption(`-device ${netAdapterModel},id=vm.netadp,netdev=vm.wan,mac=${netMac}`);
pushOption(
`-drive if=none,file=${hdImagePath},cache=writeback,discard=unmap,format=${hdImageFormat},aio=${kQemuAio},id=vm.hda_drive,bps=65000000,bps_max=65000000,iops=1500,iops_max=2000`
);
pushOption(`-drive if=none,file=${hdImagePath},cache=writeback,discard=unmap,format=${hdImageFormat},aio=${kQemuAio},id=vm.hda_drive,bps=65000000,bps_max=65000000,iops=1500,iops_max=2000`);
// prettier-ignore
if (hdaIsSsd)
@ -64,7 +62,7 @@ export function Slot_PCDef(
pushOption('-device usb-tablet');
return {
id: "socketvm1",
id: 'socketvm1',
command: qCommand
};
}

View file

@ -1,418 +1,34 @@
import { QemuVmDefinition, QemuDisplay, QemuVM, VMState, setSnapshot } from '@socketcomputer/qemu';
import { QemuVmDefinition, QemuVM, setSnapshot } from '@socketcomputer/qemu';
import { Slot_PCDef } from './SlotQemuDefs.js';
import { ExtendableTimer } from './ExtendableTimer.js';
import { EventEmitter } from 'node:events';
import * as Shared from '@socketcomputer/shared';
import { Canvas } from 'canvas';
import { FastifyInstance, fastify, FastifyRequest } from 'fastify';
import * as fastifyWebsocket from '@fastify/websocket';
import { WebSocket } from 'ws';
import Queue from 'mnemonist/queue.js';
import { kMaxUserNameLength } from '@socketcomputer/shared';
// for the maximum socket.io experience
const kCanvasJpegQuality = 0.25;
class VMUser {
public connection: WebSocket;
public address: string;
public username: string = "";
private vm: SocketVM;
constructor(connection: WebSocket, slot: SocketVM, address: string) {
this.connection = connection;
this.address = address;
this.vm = slot;
this.vm.AddUser(this);
this.connection.on('message', async (data, isBinary) => {
if (!isBinary) this.connection.close(1000);
await this.vm.OnWSMessage(this, data as Buffer);
});
this.connection.on('close', async () => {
console.log('closed');
await this.vm.RemUser(this);
});
}
async SendMessage(messageGenerator: (encoder: Shared.MessageEncoder) => ArrayBuffer) {
await this.SendBuffer(messageGenerator(new Shared.MessageEncoder()));
}
async SendBuffer(buffer: ArrayBuffer): Promise<void> {
return new Promise((res, rej) => {
if (this.connection.readyState !== WebSocket.CLOSED) {
this.connection.send(buffer);
res();
}
rej(new Error('connection haves closed'));
});
}
static GenerateName() {
return `guest${Math.floor(Math.random() * (99999 - 10000) + 10000)}`;
}
}
const kTurnTimeSeconds = 18;
type userAndTime = {
user: VMUser;
// waiting time if this user is not the front.
time: number;
};
// the turn queue. yes this is mostly stolen from cvmts but I make it cleaner by Seperate!!!!!!!
class TurnQueue extends EventEmitter {
private queue: Queue<VMUser> = new Queue<VMUser>();
private turnTime = kTurnTimeSeconds;
private interval: NodeJS.Timeout|null = null;
constructor() {
super();
}
public CurrentUser(): VMUser|null {
if(this.queue.peek() == undefined)
return null;
// We already check if it'll be undefined
return this.queue.peek()!;
}
public TryEnqueue(user: VMUser) {
// Already the current user
if (this.CurrentUser() == user) return;
// Already in the queue
if (this.queue.toArray().indexOf(user) !== -1) return;
this.queue.enqueue(user);
if (this.queue.size == 1)
this.nextTurn();
else
this.updateQueue();
}
public TryRemove(user: VMUser) {
if (this.queue.toArray().indexOf(user) !== -1) {
let hadTurn = (this.CurrentUser() === user);
this.queue = Queue.from(this.queue.toArray().filter(u => u !== user));
if (hadTurn) this.nextTurn();
}
}
private turnInterval() {
this.turnTime--;
if (this.turnTime < 1) {
this.queue.dequeue();
this.nextTurn();
}
}
private updateQueue() {
// removes the front of the quuee
let arr = this.queue.toArray();
let arr2: Array<userAndTime> = arr.map((u, index) => {
let time = this.turnTime * 1000;
if(index != 0) {
time = this.turnTime * 1000 + ((index - 1) * (kTurnTimeSeconds * 1000))
}
return {
user: u,
time: time
};
}, this);
this.emit('turnQueue', arr2);
}
private nextTurn() {
clearInterval(this.interval!);
if (this.queue.size === 0) {
} else {
this.turnTime = kTurnTimeSeconds;
this.interval = setInterval(() => this.turnInterval(), 1000);
}
this.updateQueue();
}
}
class SocketVM extends EventEmitter {
private vm: QemuVM;
private display: QemuDisplay|null = null;
private timer: ExtendableTimer = new ExtendableTimer(15);
private users: Array<VMUser> = [];
private queue: TurnQueue = new TurnQueue();
constructor(vm: QemuVM) {
super();
this.vm = vm;
this.timer.on('expired', async () => {
// bye bye!
console.log(`[SocketVM] VM timer expired, resetting..`);
await this.vm.Stop();
});
this.timer.on('expiry-near', async () => {
console.log(`[SocketVM] VM timer expires in 1 minute.`);
});
this.queue.on('turnQueue', (arr: Array<userAndTime>) => {
if(arr.length == 0) {
this.BroadcastMessage((encoder: Shared.MessageEncoder) => {
// Empty queue
encoder.Init(8);
encoder.SetTurnSrvMessage(0, []);
return encoder.Finish();
});
return;
}
let front = this.queue.CurrentUser();
for(let user of arr.filter((u) => (u.user !== front))) {
user.user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(16 + (arr.length * (2+kMaxUserNameLength)));
encoder.SetTurnSrvMessage(user.time, arr.map((item) => {
return item.user.username;
}));
return encoder.Finish();
})
}
if(front) {
front.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(16 + (arr.length * (2+kMaxUserNameLength)));
encoder.SetTurnSrvMessage(kTurnTimeSeconds * 1000, arr.map((item) => {
return item.user.username;
}));
return encoder.Finish();
})
}
});
this.vm.on('statechange', async (state: VMState) => {
if (state == VMState.Started) {
this.display = this.vm.GetDisplay();
await this.VMRunning();
} else if (state == VMState.Stopped) {
this.display = null;
await this.VMStopped();
}
});
}
async Start() {
await this.vm.Start();
}
async AddUser(user: VMUser) {
user.username = VMUser.GenerateName();
console.log(`[SocketVM] ${user.username} (IP ${user.address}) joined`);
// send bullshit
await this.sendFullScreen(user);
// send an adduser to the user for themselves
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetAddUserMessage(user.username);
return encoder.Finish();
});
// send an adduser for the other users
for (let userEntry of this.users) {
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetAddUserMessage(userEntry.username);
return encoder.Finish();
});
// also let the other user know about this user joining
await userEntry.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetAddUserMessage(user.username);
return encoder.Finish();
});
}
// officially add the user
this.users.push(user);
}
async RemUser(user: VMUser) {
console.log(`[SocketVM] ${user.username} (IP ${user.address}) left`);
this.users.splice(this.users.indexOf(user), 1);
this.queue.TryRemove(user);
// bye-bye!
await this.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetRemUserMessage(user.username);
return encoder.Finish();
});
}
async OnWSMessage(user: VMUser, message: Buffer) {
try {
let messageBuffer = message.buffer.slice(message.byteOffset);
this.OnDecodedMessage(user, await Shared.MessageDecoder.ReadMessage(messageBuffer, false));
} catch (err) {
// get out
console.log(`FUCK! (user ${user.username}, ip ${user.address})`, err);
user.connection.close();
return;
}
}
private async OnDecodedMessage(user: VMUser, message: Shared.DeserializedMessage) {
switch (message.type) {
case Shared.MessageType.Turn:
this.queue.TryEnqueue(user);
break;
case Shared.MessageType.Mouse:
if(user != this.queue.CurrentUser())
return;
if(this.display == null)
return;
this.display.MouseEvent((message as Shared.MouseMessage).x, (message as Shared.MouseMessage).y, (message as Shared.MouseMessage).buttons);
break;
case Shared.MessageType.Key:
if(user != this.queue.CurrentUser())
return;
if(this.display == null)
return;
this.display.KeyboardEvent((message as Shared.KeyMessage).keysym, (message as Shared.KeyMessage).pressed);
break;
// ignore unhandlable messages (we won't get any invalid ones because they will cause a throw)
default:
break;
}
}
private async BroadcastMessage(messageGenerator: (encoder: Shared.MessageEncoder) => ArrayBuffer) {
let buffer = messageGenerator(new Shared.MessageEncoder());
for (let user of this.users) {
await user.SendBuffer(buffer);
}
}
private async InsertCD(isoPath: string) {
await this.vm.ChangeRemovableMedia('vm.cd', isoPath);
}
private async VMRunning() {
let self = this;
// Hook up the display
this.display?.on('resize', async (width: number, height: number) => {
if(self.display == null)
return;
await self.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4);
encoder.SetDisplaySizeMessage(width, height);
return encoder.Finish();
});
// sexy cream!
let canvas = self.display.GetCanvas();
let buffer = canvas.toBuffer('image/jpeg', { quality: kCanvasJpegQuality });
await self.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(buffer.length + 8);
encoder.SetDisplayRectMessage(0, 0, buffer);
return encoder.Finish();
});
});
this.display?.on('rect', async (x: number, y: number, rect: ImageData) => {
let canvas = new Canvas(rect.width, rect.height);
canvas.getContext('2d').putImageData(rect, 0, 0);
let buffer = canvas.toBuffer('image/jpeg', { quality: kCanvasJpegQuality });
await this.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(buffer.length + 8);
encoder.SetDisplayRectMessage(x, y, buffer);
return encoder.Finish();
});
});
this.timer.Start();
}
private async sendFullScreen(user: VMUser) {
if (this.display == null) return;
let buffer = this.display.GetCanvas().toBuffer('image/jpeg', { quality: kCanvasJpegQuality });
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(8);
encoder.SetDisplaySizeMessage(this.display!.Size().width, this.display!.Size().height);
return encoder.Finish();
});
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(buffer.length + 8);
encoder.SetDisplayRectMessage(0, 0, buffer);
return encoder.Finish();
});
}
private async VMStopped() {
await this.vm.Start();
}
}
import { SocketVM } from './SocketVM.js';
import { VMUser } from './VMUser.js';
// CONFIG types (not used yet)
export type SocketComputerConfig_VM = {
ramsize: string,
hda: string,
netdev: string
ramsize: string;
hda: string;
netdev: string;
};
export type SocketComputerConfig = {
listen: string,
port: number
vm: SocketComputerConfig_VM
listen: string;
port: number;
vm: SocketComputerConfig_VM;
};
export class SocketComputerServer {
private vm: SocketVM|null = null;
private vm: SocketVM | null = null;
private fastify: FastifyInstance = fastify({
exposeHeadRoutes: false
});
Init() {
this.fastify.register(fastifyWebsocket.default);
this.fastify.register(async (app, _) => this.CTRoutes(app), {});
this.fastify.register(async (app, _) => this.Routes(app), {});
}
async Listen() {
@ -432,24 +48,26 @@ export class SocketComputerServer {
}
async InitVM() {
// Create the VM definition
let diskpath = '/srv/collabvm/vms/socket1/winxp.qcow2';
let slotDef: QemuVmDefinition = Slot_PCDef('2G', '-netdev tap,ifname=ktsocket1,script=no,downscript=no,id=vm.wan', 'rtl8139', 'c0:11:ab:69:44:02', true, diskpath, 'qcow2');
setSnapshot(true);
// create the slot for real!
// Create the VM
this.vm = new SocketVM(new QemuVM(slotDef));
await this.vm.Start(); // boot it up
// Boot it up
setSnapshot(true);
await this.vm.Start();
}
CTRoutes(app: FastifyInstance) {
Routes(app: FastifyInstance) {
let self = this;
// @ts-ignore (fastify types are broken...)
app.get('/', { websocket: true }, (connection: fastifyWebsocket.WebSocket, req: FastifyRequest) => {
let address = req.ip;
if(req.headers["cf-connecting-ip"] !== undefined) {
address = req.headers["cf-connecting-ip"] as string;
if (req.headers['cf-connecting-ip'] !== undefined) {
address = req.headers['cf-connecting-ip'] as string;
}
new VMUser(connection, self.vm!, address);
});

259
backend/src/SocketVM.ts Normal file
View file

@ -0,0 +1,259 @@
import { EventEmitter } from "node:events";
import { TurnQueue, UserTimeTuple, kTurnTimeSeconds } from "./TurnQueue.js";
import { VMUser } from "./VMUser.js";
import { QemuDisplay, QemuVM, VMState } from '@socketcomputer/qemu';
import { ExtendableTimer } from './ExtendableTimer.js';
import { kMaxUserNameLength } from '@socketcomputer/shared';
import * as Shared from '@socketcomputer/shared';
import { Canvas } from 'canvas';
// for the maximum socket.io experience
const kCanvasJpegQuality = 0.25;
export class SocketVM extends EventEmitter {
private vm: QemuVM;
private display: QemuDisplay|null = null;
private timer: ExtendableTimer = new ExtendableTimer(15);
private users: Array<VMUser> = [];
private queue: TurnQueue = new TurnQueue();
constructor(vm: QemuVM) {
super();
this.vm = vm;
this.timer.on('expired', async () => {
// bye bye!
console.log(`[SocketVM] VM timer expired, resetting..`);
await this.vm.Stop();
});
this.timer.on('expiry-near', async () => {
console.log(`[SocketVM] VM timer expires in 1 minute.`);
});
this.queue.on('turnQueue', (arr: Array<UserTimeTuple>) => {
if(arr.length == 0) {
this.BroadcastMessage((encoder: Shared.MessageEncoder) => {
// Empty queue
encoder.Init(8);
encoder.SetTurnSrvMessage(0, []);
return encoder.Finish();
});
return;
}
let front = this.queue.CurrentUser();
for(let user of arr.filter((u) => (u.user !== front))) {
user.user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(16 + (arr.length * (2+kMaxUserNameLength)));
encoder.SetTurnSrvMessage(user.time, arr.map((item) => {
return item.user.username;
}));
return encoder.Finish();
})
}
if(front) {
front.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(16 + (arr.length * (2+kMaxUserNameLength)));
encoder.SetTurnSrvMessage(kTurnTimeSeconds * 1000, arr.map((item) => {
return item.user.username;
}));
return encoder.Finish();
})
}
});
this.vm.on('statechange', async (state: VMState) => {
if (state == VMState.Started) {
this.display = this.vm.GetDisplay();
await this.VMRunning();
} else if (state == VMState.Stopped) {
this.display = null;
await this.VMStopped();
}
});
}
async Start() {
await this.vm.Start();
}
async AddUser(user: VMUser) {
user.username = VMUser.GenerateName();
console.log(`[SocketVM] ${user.username} (IP ${user.address}) joined`);
// send bullshit
await this.sendFullScreen(user);
// send an adduser to the user for themselves
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetAddUserMessage(user.username);
return encoder.Finish();
});
// send an adduser for the other users
for (let userEntry of this.users) {
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetAddUserMessage(userEntry.username);
return encoder.Finish();
});
// also let the other user know about this user joining
await userEntry.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetAddUserMessage(user.username);
return encoder.Finish();
});
}
// officially add the user
this.users.push(user);
}
async RemUser(user: VMUser) {
console.log(`[SocketVM] ${user.username} (IP ${user.address}) left`);
this.users.splice(this.users.indexOf(user), 1);
this.queue.TryRemove(user);
// bye-bye!
await this.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4 + Shared.kMaxUserNameLength);
encoder.SetRemUserMessage(user.username);
return encoder.Finish();
});
}
async OnWSMessage(user: VMUser, message: Buffer) {
try {
let messageBuffer = message.buffer.slice(message.byteOffset);
this.OnDecodedMessage(user, await Shared.MessageDecoder.ReadMessage(messageBuffer, false));
} catch (err) {
// Log the error and close the connection
console.log(`Error decoding message, closing connection: (user ${user.username}, ip ${user.address})`, err);
user.connection.close();
return;
}
}
private async OnDecodedMessage(user: VMUser, message: Shared.DeserializedMessage) {
switch (message.type) {
case Shared.MessageType.Turn:
this.queue.TryEnqueue(user);
break;
case Shared.MessageType.Mouse:
if(user != this.queue.CurrentUser())
return;
if(this.display == null)
return;
this.display.MouseEvent((message as Shared.MouseMessage).x, (message as Shared.MouseMessage).y, (message as Shared.MouseMessage).buttons);
break;
case Shared.MessageType.Key:
if(user != this.queue.CurrentUser())
return;
if(this.display == null)
return;
this.display.KeyboardEvent((message as Shared.KeyMessage).keysym, (message as Shared.KeyMessage).pressed);
break;
// ignore messages we don't know about
default:
break;
}
}
private async BroadcastMessage(messageGenerator: (encoder: Shared.MessageEncoder) => ArrayBuffer) {
let buffer = messageGenerator(new Shared.MessageEncoder());
for (let user of this.users) {
await user.SendBuffer(buffer);
}
}
private async InsertCD(isoPath: string) {
await this.vm.ChangeRemovableMedia('vm.cd', isoPath);
}
private async VMRunning() {
let self = this;
// Hook up the display
this.display?.on('resize', async (width: number, height: number) => {
if(self.display == null)
return;
await self.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(4);
encoder.SetDisplaySizeMessage(width, height);
return encoder.Finish();
});
// sexy cream!
let canvas = self.display.GetCanvas();
let buffer = canvas.toBuffer('image/jpeg', { quality: kCanvasJpegQuality });
await self.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(buffer.length + 8);
encoder.SetDisplayRectMessage(0, 0, buffer);
return encoder.Finish();
});
});
this.display?.on('rect', async (x: number, y: number, rect: ImageData) => {
let canvas = new Canvas(rect.width, rect.height);
canvas.getContext('2d').putImageData(rect, 0, 0);
let buffer = canvas.toBuffer('image/jpeg', { quality: kCanvasJpegQuality });
await this.BroadcastMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(buffer.length + 8);
encoder.SetDisplayRectMessage(x, y, buffer);
return encoder.Finish();
});
});
this.timer.Start();
}
private async sendFullScreen(user: VMUser) {
if (this.display == null) return;
let buffer = this.display.GetCanvas().toBuffer('image/jpeg', { quality: kCanvasJpegQuality });
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(8);
encoder.SetDisplaySizeMessage(this.display!.Size().width, this.display!.Size().height);
return encoder.Finish();
});
await user.SendMessage((encoder: Shared.MessageEncoder) => {
encoder.Init(buffer.length + 8);
encoder.SetDisplayRectMessage(0, 0, buffer);
return encoder.Finish();
});
}
private async VMStopped() {
await this.vm.Start();
}
}

85
backend/src/TurnQueue.ts Normal file
View file

@ -0,0 +1,85 @@
import { EventEmitter } from 'node:events';
import Queue from 'mnemonist/queue.js';
import { VMUser } from './VMUser';
export const kTurnTimeSeconds = 18;
export type UserTimeTuple = {
user: VMUser;
// waiting time if this user is not the front.
time: number;
};
// the turn queue. yes this is mostly stolen from cvmts but I make it cleaner by Seperate!!!!!!!
export class TurnQueue extends EventEmitter {
private queue: Queue<VMUser> = new Queue<VMUser>();
private turnTime = kTurnTimeSeconds;
private interval: NodeJS.Timeout | null = null;
constructor() {
super();
}
public CurrentUser(): VMUser | null {
if (this.queue.peek() == undefined) return null;
// We already check if it'll be undefined
return this.queue.peek()!;
}
public TryEnqueue(user: VMUser) {
// Already the current user
if (this.CurrentUser() == user) return;
// Already in the queue
if (this.queue.toArray().indexOf(user) !== -1) return;
this.queue.enqueue(user);
if (this.queue.size == 1) this.nextTurn();
else this.updateQueue();
}
public TryRemove(user: VMUser) {
if (this.queue.toArray().indexOf(user) !== -1) {
let hadTurn = this.CurrentUser() === user;
this.queue = Queue.from(this.queue.toArray().filter((u) => u !== user));
if (hadTurn) this.nextTurn();
}
}
private turnInterval() {
this.turnTime--;
if (this.turnTime < 1) {
this.queue.dequeue();
this.nextTurn();
}
}
private updateQueue() {
// removes the front of the quuee
let arr = this.queue.toArray();
let arr2: Array<UserTimeTuple> = arr.map((u, index) => {
let time = this.turnTime * 1000;
if (index != 0) {
time = this.turnTime * 1000 + (index - 1) * (kTurnTimeSeconds * 1000);
}
return {
user: u,
time: time
};
}, this);
this.emit('turnQueue', arr2);
}
private nextTurn() {
clearInterval(this.interval!);
if (this.queue.size === 0) {
} else {
this.turnTime = kTurnTimeSeconds;
this.interval = setInterval(() => this.turnInterval(), 1000);
}
this.updateQueue();
}
}

48
backend/src/VMUser.ts Normal file
View file

@ -0,0 +1,48 @@
import { WebSocket } from "ws";
import { SocketVM } from "./SocketVM";
import * as Shared from "@socketcomputer/shared";
export class VMUser {
public connection: WebSocket;
public address: string;
public username: string = "";
private vm: SocketVM;
constructor(connection: WebSocket, slot: SocketVM, address: string) {
this.connection = connection;
this.address = address;
this.vm = slot;
this.vm.AddUser(this);
this.connection.on('message', async (data, isBinary) => {
if (!isBinary) this.connection.close(1000);
await this.vm.OnWSMessage(this, data as Buffer);
});
this.connection.on('close', async () => {
console.log('closed');
await this.vm.RemUser(this);
});
}
async SendMessage(messageGenerator: (encoder: Shared.MessageEncoder) => ArrayBuffer) {
await this.SendBuffer(messageGenerator(new Shared.MessageEncoder()));
}
async SendBuffer(buffer: ArrayBuffer): Promise<void> {
return new Promise((res, rej) => {
if (this.connection.readyState !== WebSocket.CLOSED) {
this.connection.send(buffer);
res();
}
rej(new Error('connection haves closed'));
});
}
static GenerateName() {
return `guest${Math.floor(Math.random() * (99999 - 10000) + 10000)}`;
}
}