From 225f91f7a4268f10daa51add8a0b8073a5d38563 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Thu, 1 Feb 2024 19:58:19 -0500 Subject: [PATCH] Basic VM list support. --- Config.ts | 13 +++++ src/html/index.html | 6 +- src/ts/main.ts | 66 ++++++++++++++++++++++ src/ts/protocol/CollabVMClient.ts | 91 ++++++++++++++++++++++++++++++- src/ts/protocol/Guacutils.ts | 43 +++++++++++++++ src/ts/protocol/VM.ts | 10 ++++ 6 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 Config.ts create mode 100644 src/ts/protocol/Guacutils.ts create mode 100644 src/ts/protocol/VM.ts diff --git a/Config.ts b/Config.ts new file mode 100644 index 0000000..d29d4d6 --- /dev/null +++ b/Config.ts @@ -0,0 +1,13 @@ +export const Config = { + ServerAddresses: [ + "wss://computernewb.com/collab-vm/vm0", + "wss://computernewb.com/collab-vm/vm1", + "wss://computernewb.com/collab-vm/vm2", + "wss://computernewb.com/collab-vm/vm3", + "wss://computernewb.com/collab-vm/vm4", + "wss://computernewb.com/collab-vm/vm5", + "wss://computernewb.com/collab-vm/vm6", + "wss://computernewb.com/collab-vm/vm7", + "wss://computernewb.com/collab-vm/vm8", + ] +} \ No newline at end of file diff --git a/src/html/index.html b/src/html/index.html index 74963dc..3c309b3 100644 --- a/src/html/index.html +++ b/src/html/index.html @@ -117,7 +117,9 @@
- +
+ +

- + diff --git a/src/ts/main.ts b/src/ts/main.ts index e69de29..59f21dc 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -0,0 +1,66 @@ +import CollabVMClient from "./protocol/CollabVMClient.js"; +import VM from "./protocol/VM.js"; +import { Config } from "../../Config.js"; + +// Elements +const elements = { + vmlist: document.getElementById('vmlist') as HTMLDivElement, +} +// Listed VMs +const vms : VM[] = []; + +function multicollab(url : string) { + return new Promise(async (res, rej) => { + // Create the client + var client = new CollabVMClient(url); + // Wait for the client to open + await new Promise(res => client.on('open', () => res())); + // Get the list of VMs + var list = await client.list(); + // Add to the list + vms.push(...list); + // Add to the DOM + for (var vm of list) { + var div = document.createElement('div'); + div.classList.add("col-sm-5", "col-md-3"); + var card = document.createElement('div'); + card.classList.add("card", "bg-dark", "text-light"); + card.setAttribute("data-cvm-node", vm.id); + card.addEventListener('click', () => openVM(vm)); + vm.thumbnail.classList.add("card-img-top"); + var cardBody = document.createElement('div'); + cardBody.classList.add("card-body"); + var cardTitle = document.createElement('h5'); + cardTitle.innerHTML = vm.displayName; + cardBody.appendChild(cardTitle); + card.appendChild(vm.thumbnail); + card.appendChild(cardBody); + div.appendChild(card); + elements.vmlist.children[0].appendChild(div); + reloadVMList(); + } + res(); + }); +} +function openVM(vm : VM) { + +} + +function reloadVMList() { + var cards = Array.prototype.slice.call(elements.vmlist.children[0].children); + cards.sort(function(a, b) { + return a.id > b.id ? 1 : -1; + }); + elements.vmlist.children[0].innerHTML = ""; + cards.forEach((c) => elements.vmlist.children[0].appendChild(c)); +} + +// Public API +var w = window as any; +w.multicollab = multicollab; +w.openVM = openVM; + +// Load all VMs +for (var url of Config.ServerAddresses) { + multicollab(url); +} \ No newline at end of file diff --git a/src/ts/protocol/CollabVMClient.ts b/src/ts/protocol/CollabVMClient.ts index e84bd29..a683cab 100644 --- a/src/ts/protocol/CollabVMClient.ts +++ b/src/ts/protocol/CollabVMClient.ts @@ -1,3 +1,92 @@ +import {createNanoEvents } from "nanoevents"; +import * as Guacutils from './Guacutils.js'; +import VM from "./VM.js"; + export default class CollabVMClient { - + // Fields + private socket : WebSocket; + private canvas : HTMLCanvasElement; + private ctx : CanvasRenderingContext2D; + private url : string; + // events that are used internally and not exposed + private emitter; + // public events + private publicEmitter; + + constructor(url : string) { + // Save the URL + this.url = url; + // Create the events + this.emitter = createNanoEvents(); + this.publicEmitter = createNanoEvents(); + // Create the canvas + this.canvas = document.createElement('canvas'); + // Set tab index so it can be focused + this.canvas.tabIndex = -1; + // Get the 2D context + this.ctx = this.canvas.getContext('2d')!; + // Create the WebSocket + this.socket = new WebSocket(url, "guacamole"); + // Add the event listeners + this.socket.addEventListener('open', () => this.onOpen()); + this.socket.addEventListener('message', (event) => this.onMessage(event)); + } + + // Fires when the WebSocket connection is opened + private onOpen() { + this.publicEmitter.emit('open'); + } + + // Fires on WebSocket message + private onMessage(event : MessageEvent) { + var msgArr : string[]; + try { + msgArr = Guacutils.decode(event.data); + } catch (e) { + console.error(`Server sent invalid message (${e})`); + return; + } + switch (msgArr[0]) { + case "nop": { + // Send a NOP back + this.send("nop"); + break; + } + case "list": { + // pass msgarr to the emitter for processing by list() + console.log("got list") + this.emitter.emit('list', msgArr.slice(1)); + } + } + } + + // Sends a message to the server + send(...args : string[]) { + this.socket.send(Guacutils.encode(...args)); + } + + // Get a list of all VMs + list() : Promise { + return new Promise((res, rej) => { + var u = this.emitter.on('list', (list : string[]) => { + u(); + var vms : VM[] = []; + for (var i = 0; i < list.length; i += 3) { + var th = new Image(); + th.src = "data:image/jpeg;base64," + list[i + 2]; + vms.push({ + url: this.url, + id: list[i], + displayName: list[i + 1], + thumbnail: th, + }); + console.log("pushed", list[i]); + } + res(vms); + }); + this.send("list"); + }); + } + + on = (event : string | number, cb: (...args: any) => void) => this.publicEmitter.on(event, cb); } \ No newline at end of file diff --git a/src/ts/protocol/Guacutils.ts b/src/ts/protocol/Guacutils.ts new file mode 100644 index 0000000..0e0d41c --- /dev/null +++ b/src/ts/protocol/Guacutils.ts @@ -0,0 +1,43 @@ +export function decode(string : string) : string[] { + let pos = -1; + let sections = []; + + for(;;) { + let len = string.indexOf('.', pos + 1); + + if(len === -1) + break; + + pos = parseInt(string.slice(pos + 1, len)) + len + 1; + + // don't allow funky protocol length + if(pos > string.length) + return []; + + sections.push(string.slice(len + 1, pos)); + + + const sep = string.slice(pos, pos + 1); + + if(sep === ',') + continue; + else if(sep === ';') + break; + else + // Invalid data. + return []; + } + + return sections; +} + +export function encode(...string : string[]) : string { + let command = ''; + + for(var i = 0; i < string.length; i++) { + let current = string[i]; + command += current.toString().length + '.' + current; + command += ( i < string.length - 1 ? ',' : ';'); + } + return command; +} \ No newline at end of file diff --git a/src/ts/protocol/VM.ts b/src/ts/protocol/VM.ts new file mode 100644 index 0000000..e07ee4a --- /dev/null +++ b/src/ts/protocol/VM.ts @@ -0,0 +1,10 @@ +export default interface VM { + url : string; + + id : string; + + displayName : string; + + thumbnail : HTMLImageElement; + +} \ No newline at end of file