Set up the source tree for the typescript rewrite. Nothing really here just yet, just filler
This commit is contained in:
parent
f99e8bdb2e
commit
a6a28ef518
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/*.js
|
dist/
|
||||||
dist/*.js.LICENSE.txt
|
.parcel-cache/
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"targets": {
|
|
||||||
"edge": "17",
|
|
||||||
"firefox": "60",
|
|
||||||
"chrome": "67",
|
|
||||||
"safari": "11.1"
|
|
||||||
},
|
|
||||||
"useBuiltIns": "usage",
|
|
||||||
"corejs": "3.6.5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
19
package.json
19
package.json
|
|
@ -4,21 +4,22 @@
|
||||||
"description": "kill me",
|
"description": "kill me",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "parcel build --dist-dir dist src/html/index.html",
|
||||||
"serve": "serve dist/"
|
"serve": "parcel src/html/index.html"
|
||||||
},
|
},
|
||||||
"author": "Elijah R",
|
"author": "Elijah R",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcaptcha/types": "^1.0.3",
|
"@hcaptcha/types": "^1.0.3",
|
||||||
"nanoevents": "^7.0.1",
|
"@popperjs/core": "^2.11.8",
|
||||||
"serve": "^14.2.0",
|
"bootstrap": "^5.3.2",
|
||||||
"webpack": "^5.75.0",
|
"nanoevents": "^7.0.1"
|
||||||
"webpack-cli": "^5.0.1"
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@parcel/watcher": "~2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.12",
|
"parcel": "^2.11.0",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"typescript": "^5.3.3"
|
||||||
"babel-loader": "^9.1.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 408 KiB After Width: | Height: | Size: 408 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
|
@ -1,13 +0,0 @@
|
||||||
export default function doCaptcha(sitekey) {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('hcaptchaModal'));
|
|
||||||
modal.show();
|
|
||||||
hcaptcha.render("captcha-box", {
|
|
||||||
sitekey: sitekey,
|
|
||||||
callback: (c) => {
|
|
||||||
modal.hide();
|
|
||||||
res(c);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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",
|
|
||||||
"wss://computernewb.com/collab-vm/vm9",
|
|
||||||
"wss://computernewb.com/collab-vm/eventvm",
|
|
||||||
],
|
|
||||||
chatSound: "https://computernewb.com/collab-vm/notify.ogg",
|
|
||||||
// What XSS implementation the server uses
|
|
||||||
// 0: No XSS (cvm1.2.11)
|
|
||||||
// 1: Internal fork style (cvm1.ts, global opcode 21)
|
|
||||||
// 2: yellows111/collab-vm-server style (per-user opcode 21)
|
|
||||||
xssImplementation: 1,
|
|
||||||
}
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
<title>Control Collaborative Virtual Machines!</title>
|
<title>Control Collaborative Virtual Machines!</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<link href="style.css" rel="stylesheet" type="text/css"/>
|
<link href="../css/style.css" rel="stylesheet" type="text/css"/>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous"/>
|
<link href="../../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
|
||||||
<script src="https://kit.fontawesome.com/7add23c1ae.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/7add23c1ae.js" crossorigin="anonymous"></script>
|
||||||
<link rel="icon" href="favicon.ico">
|
<link rel="icon" href="../assets/favicon.ico">
|
||||||
<meta name="description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
|
<meta name="description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
|
||||||
<!-- Opengraph shit -->
|
<!-- Opengraph shit -->
|
||||||
<meta property="og:type" content="website"/>
|
<meta property="og:type" content="website"/>
|
||||||
|
|
@ -109,25 +109,6 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="https://computernewb.com/collab-vm/user-vm" class="nav-link"><i class="fa-solid fa-user"></i> UserVM</a>
|
<a href="https://computernewb.com/collab-vm/user-vm" class="nav-link"><i class="fa-solid fa-user"></i> UserVM</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a href="#" class="nav-link dropdown-toggle role="button" data-bs-toggle="dropdown" id="navbarDropdown"><i class="fa-solid fa-globe"></i> Languages</a>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<li><a class="dropdown-item" href="lang/ar/"><img src="../vncresolver/flags/sa.png"> اللغة العربية (جزئية)</a></li>
|
|
||||||
<li><a class="dropdown-item" href="lang/de/"><img src="../vncresolver/flags/de.png"> Deutsch</a></li>
|
|
||||||
<li><a class="dropdown-item" href="lang/en"><img src="../vncresolver/flags/gb.png"> English</a></li>
|
|
||||||
<li><a class="dropdown-item" href="https://computernewb.com/collab-vm/lang/es/"><img src="https://computernewb.com/vncresolver/flags/es.png"> Español</a></li>
|
|
||||||
<li><a class="dropdown-item" href="lang/jp/"><img src="../vncresolver/flags/jp.png"> 日本語</a></li>
|
|
||||||
<li><a class="dropdown-item" href="lang/pl/"><img src="../vncresolver/flags/pl.png"> Polski</a></li>
|
|
||||||
<li><a class="dropdown-item" href="lang/ru/"><img src="../vncresolver/flags/ru.png"> Русский</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<!--<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="fa-solid fa-globe"> Languages</a>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<li><a class="dropdown-item" href="ru/">Русский</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>-->
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -194,7 +175,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
|
<script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="main.js" type="application/javascript"></script>
|
<script src="../ts/main.ts" type="application/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
908
src/index.js
908
src/index.js
|
|
@ -1,908 +0,0 @@
|
||||||
import { guacutils } from "./protocol";
|
|
||||||
import { config } from "./common";
|
|
||||||
import { GetKeysym } from "./keyboard";
|
|
||||||
import { createNanoEvents } from "nanoevents";
|
|
||||||
import { makeperms } from "./permissions";
|
|
||||||
import doCaptcha from "./captcha";
|
|
||||||
// None = -1
|
|
||||||
// Has turn = 0
|
|
||||||
// In queue = <queue position>
|
|
||||||
var turn = -1;
|
|
||||||
var perms = makeperms(0, config);
|
|
||||||
var rank = 0;
|
|
||||||
var connected = false;
|
|
||||||
const vms = [];
|
|
||||||
const users = [];
|
|
||||||
const buttons = {
|
|
||||||
home: window.document.getElementById("homeBtn"),
|
|
||||||
takeTurn: window.document.getElementById("takeTurnBtn"),
|
|
||||||
changeUsername: window.document.getElementById("changeUsernameBtn"),
|
|
||||||
voteReset: window.document.getElementById("voteResetButton"),
|
|
||||||
screenshot: window.document.getElementById("screenshotButton"),
|
|
||||||
// Staff
|
|
||||||
restore: window.document.getElementById("restoreBtn"),
|
|
||||||
reboot: window.document.getElementById("rebootBtn"),
|
|
||||||
clearQueue: window.document.getElementById("clearQueueBtn"),
|
|
||||||
bypassTurn: window.document.getElementById("bypassTurnBtn"),
|
|
||||||
endTurn: window.document.getElementById("endTurnBtn"),
|
|
||||||
qemuMonitor: window.document.getElementById("qemuMonitorBtn"),
|
|
||||||
qemuMonitorSend: window.document.getElementById("qemuMonitorSendBtn"),
|
|
||||||
sendChat: window.document.getElementById("sendChatBtn"),
|
|
||||||
ctrlAltDel: window.document.getElementById("ctrlAltDelBtn"),
|
|
||||||
forceVoteYes: window.document.getElementById("forceVoteYesBtn"),
|
|
||||||
forceVoteNo: window.document.getElementById("forceVoteNoBtn"),
|
|
||||||
}
|
|
||||||
var hasTurn = false;
|
|
||||||
var vm;
|
|
||||||
var voteinterval;
|
|
||||||
var turninterval;
|
|
||||||
const chatsound = new Audio(config.chatSound);
|
|
||||||
// Elements
|
|
||||||
const turnstatus = window.document.getElementById("turnstatus");
|
|
||||||
const vmlist = window.document.getElementById("vmlist");
|
|
||||||
const vmview = window.document.getElementById("vmview");
|
|
||||||
const display = window.document.getElementById("display");
|
|
||||||
const displayCtx = display.getContext("2d");
|
|
||||||
const chatList = window.document.getElementById("chatList");
|
|
||||||
const userlist = window.document.getElementById("userlist");
|
|
||||||
const usernameSpan = window.document.getElementById("username");
|
|
||||||
const onlineusercount = window.document.getElementById("onlineusercount");
|
|
||||||
const chatinput = window.document.getElementById("chat-input");
|
|
||||||
const voteresetpanel = document.getElementById("voteResetPanel");
|
|
||||||
const voteyesbtn = document.getElementById("voteYesBtn");
|
|
||||||
const votenobtn = document.getElementById("voteNoBtn");
|
|
||||||
const voteyeslabel = document.getElementById("voteYesLabel");
|
|
||||||
const votenolabel = document.getElementById("voteNoLabel");
|
|
||||||
const votetime = document.getElementById("votetime");
|
|
||||||
const staffbtns = document.getElementById("staffbtns");
|
|
||||||
const qemuMonitorInput = document.getElementById("qemuMonitorInput");
|
|
||||||
const qemuMonitorOutput = document.getElementById("qemuMonitorOutput");
|
|
||||||
const xssCheckbox = document.getElementById("xssCheckbox");
|
|
||||||
const xssCheckboxContainer = document.getElementById("xssCheckboxContainer");
|
|
||||||
const forceVotePanel = document.getElementById("forceVotePanel");
|
|
||||||
// needed to scroll to bottom
|
|
||||||
const chatListDiv = document.querySelector(".chat-table");
|
|
||||||
|
|
||||||
let events = new Map();
|
|
||||||
|
|
||||||
function addListener(element, event, id, callback) {
|
|
||||||
events.set(id, callback);
|
|
||||||
element.addEventListener(event, callback, {capture: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeListener(element, event, id) {
|
|
||||||
element.removeEventListener(event, events.get(id), true);
|
|
||||||
events.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
class CollabVMClient {
|
|
||||||
eventemitter = createNanoEvents();
|
|
||||||
socket;
|
|
||||||
node;
|
|
||||||
#url;
|
|
||||||
#captcha = false;
|
|
||||||
captchaToken;
|
|
||||||
isMainSocket;
|
|
||||||
shouldReconnect = true;
|
|
||||||
constructor(url, isMainSocket) {
|
|
||||||
this.#url = url;
|
|
||||||
this.isMainSocket = isMainSocket;
|
|
||||||
}
|
|
||||||
connect(hcaptchatoken) {
|
|
||||||
this.captchaToken = hcaptchatoken;
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
try {
|
|
||||||
this.socket = new WebSocket(this.#url, "guacamole");
|
|
||||||
} catch (e) {
|
|
||||||
rej(e);
|
|
||||||
}
|
|
||||||
this.socket.addEventListener('message', (e) => this.#onMessage(e));
|
|
||||||
this.socket.addEventListener('open', () => res(true), {once: true});
|
|
||||||
this.socket.addEventListener('close', (e) => { if(!e.wasClean) res(false); }, {once: true});
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
#onClose() {
|
|
||||||
cleanup();
|
|
||||||
if(this.shouldReconnect) {
|
|
||||||
setTimeout(async () => {
|
|
||||||
try {
|
|
||||||
connected = await this.connect(this.captchaToken);
|
|
||||||
} catch {
|
|
||||||
this.#onClose();
|
|
||||||
}
|
|
||||||
this.connectToVM(this.node);
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
disconnect() {
|
|
||||||
this.socket.send(guacutils.encode(["disconnect"]));
|
|
||||||
this.socket.close();
|
|
||||||
}
|
|
||||||
getUrl() {
|
|
||||||
return this.#url;
|
|
||||||
}
|
|
||||||
connectToVM(node) {
|
|
||||||
return new Promise(async (res, rej) => {
|
|
||||||
this.socket.addEventListener('close', () => this.#onClose());
|
|
||||||
this.node = node;
|
|
||||||
if (this.captchaToken !== null) {
|
|
||||||
await new Promise((reso, reje) => {
|
|
||||||
var unbind = this.eventemitter.on('captcha', (result) => {
|
|
||||||
unbind();
|
|
||||||
if (result === true) {
|
|
||||||
reso();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reje();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.socket.send(guacutils.encode(["captcha", this.captchaToken]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var savedUsername = window.localStorage.getItem("username");
|
|
||||||
if (savedUsername === null)
|
|
||||||
this.socket.send(guacutils.encode(["rename"]));
|
|
||||||
else this.socket.send(guacutils.encode(["rename", savedUsername]));
|
|
||||||
var unbind = this.eventemitter.on('connect', () => {
|
|
||||||
unbind();
|
|
||||||
res();
|
|
||||||
});
|
|
||||||
var failunbind = this.eventemitter.on('connectfail', () => {
|
|
||||||
failunbind();
|
|
||||||
rej();
|
|
||||||
});
|
|
||||||
this.socket.send(guacutils.encode(["connect", node]));
|
|
||||||
var pass = window.localStorage.getItem("password_"+this.#url);
|
|
||||||
if (pass)
|
|
||||||
this.admin.login(pass);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async #onMessage(event) {
|
|
||||||
var msgArr = guacutils.decode(event.data);
|
|
||||||
window.cvmEvents.emit(msgArr[0], msgArr.slice(1));
|
|
||||||
switch (msgArr[0]) {
|
|
||||||
case "nop":
|
|
||||||
this.socket.send("3.nop;");
|
|
||||||
break;
|
|
||||||
case "connect":
|
|
||||||
switch (msgArr[1]) {
|
|
||||||
case "0":
|
|
||||||
this.eventemitter.emit('connectfail');
|
|
||||||
break;
|
|
||||||
case "1":
|
|
||||||
this.eventemitter.emit('connect');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "captcha":
|
|
||||||
switch (msgArr[1]) {
|
|
||||||
case "0":
|
|
||||||
this.#captcha = msgArr[2];
|
|
||||||
console.log(this.#captcha);
|
|
||||||
break;
|
|
||||||
case "1":
|
|
||||||
this.eventemitter.emit('captcha', true);
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
this.eventemitter.emit('captcha', false);
|
|
||||||
}
|
|
||||||
case "chat":
|
|
||||||
if (!connected || !this.isMainSocket) return;
|
|
||||||
for (var i = 1; i < msgArr.length; i += 2) {
|
|
||||||
chatMessage(msgArr[i], msgArr[i+1])
|
|
||||||
}
|
|
||||||
chatsound.play();
|
|
||||||
chatListDiv.scrollTop = chatListDiv.scrollHeight;
|
|
||||||
break;
|
|
||||||
case "list":
|
|
||||||
var list = [];
|
|
||||||
for (var i = 1; i < msgArr.length; i+=3) {
|
|
||||||
list.push({
|
|
||||||
url: this.#url,
|
|
||||||
id: msgArr[i],
|
|
||||||
name: msgArr[i+1],
|
|
||||||
thumb: msgArr[i+2],
|
|
||||||
captcha: this.#captcha,
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.eventemitter.emit('list', list);
|
|
||||||
break;
|
|
||||||
case "size":
|
|
||||||
if (!connected || msgArr[1] !== "0") return;
|
|
||||||
display.width = msgArr[2];
|
|
||||||
display.height = msgArr[3];
|
|
||||||
break;
|
|
||||||
case "png":
|
|
||||||
if (!connected || msgArr[2] !== "0") return;
|
|
||||||
var img = new Image(display.width, display.height);
|
|
||||||
img.addEventListener('load', () => {
|
|
||||||
displayCtx.drawImage(img, msgArr[3], msgArr[4]);
|
|
||||||
});
|
|
||||||
img.src = "data:image/png;base64," + msgArr[5];
|
|
||||||
break;
|
|
||||||
case "rename":
|
|
||||||
if (msgArr[1] === "0") {
|
|
||||||
switch (msgArr[2]) {
|
|
||||||
case "1":
|
|
||||||
alert("That username is already taken");
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
alert("Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters.");
|
|
||||||
break;
|
|
||||||
case "3":
|
|
||||||
alert("That username has been blacklisted.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!connected || !this.isMainSocket) return;
|
|
||||||
var u = users.find(u => u.username === window.username);
|
|
||||||
if (u) {
|
|
||||||
u.username = msgArr[3];
|
|
||||||
u.element.children[0].innerHTML = msgArr[3];
|
|
||||||
}
|
|
||||||
window.username = msgArr[3];
|
|
||||||
usernameSpan.innerText = msgArr[3];
|
|
||||||
window.localStorage.setItem("username", msgArr[3]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var user = users.find(u => u.username == msgArr[2]);
|
|
||||||
if (user === undefined) break;
|
|
||||||
user.username = msgArr[3];
|
|
||||||
user.element.children[0].innerHTML = msgArr[3];
|
|
||||||
break;
|
|
||||||
case "adduser":
|
|
||||||
if (!connected || !this.isMainSocket) return;
|
|
||||||
for (var i = 2; i < msgArr.length; i += 2) {
|
|
||||||
this.addUser(msgArr[i], msgArr[i+1]);
|
|
||||||
}
|
|
||||||
onlineusercount.innerText = users.length;
|
|
||||||
break;
|
|
||||||
case "remuser":
|
|
||||||
if (!connected || !this.isMainSocket) return;
|
|
||||||
for (var i = 2; i < msgArr.length; i++) {
|
|
||||||
var user = users.find(u => u.username == msgArr[i]);
|
|
||||||
users.splice(users.indexOf(user), 1);
|
|
||||||
userlist.removeChild(user.element);
|
|
||||||
}
|
|
||||||
onlineusercount.innerText = users.length;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "turn":
|
|
||||||
// Reset all turn data
|
|
||||||
users.forEach((curr) => {
|
|
||||||
curr.turn = -1;
|
|
||||||
curr.element.classList = "";
|
|
||||||
});
|
|
||||||
buttons.takeTurn.innerHTML = "<i class=\"fa-solid fa-computer-mouse\"></i> Take Turn";
|
|
||||||
turn = -1;
|
|
||||||
if (!msgArr.includes(username))
|
|
||||||
turnstatus.innerText = "";
|
|
||||||
display.className = "";
|
|
||||||
clearInterval(turninterval);
|
|
||||||
// Get the number of users queued for a turn
|
|
||||||
var queuedUsers = Number(msgArr[2]);
|
|
||||||
if (queuedUsers === 0) return;
|
|
||||||
var currentTurnUsername = msgArr[3];
|
|
||||||
// Get the user who has the turn and highlight them
|
|
||||||
var currentTurnUser = users.find(u => u.username === currentTurnUsername);
|
|
||||||
currentTurnUser.element.classList = "table-primary";
|
|
||||||
currentTurnUser.turn = 0;
|
|
||||||
if (currentTurnUsername === window.username) {
|
|
||||||
turn = 0;
|
|
||||||
var secs = Math.floor(parseInt(msgArr[1]) / 1000);
|
|
||||||
var turnUpdate = () => {
|
|
||||||
secs--;
|
|
||||||
if (secs === 0)
|
|
||||||
clearInterval(turninterval);
|
|
||||||
turnstatus.innerText = `Turn expires in ${secs} seconds.`;
|
|
||||||
}
|
|
||||||
turnUpdate();
|
|
||||||
turninterval = setInterval(turnUpdate, 1000);
|
|
||||||
display.className = "focused";
|
|
||||||
}
|
|
||||||
// Highlight all waiting users and set their status
|
|
||||||
if (queuedUsers > 1) {
|
|
||||||
for (var i = 1; i < queuedUsers; i++) {
|
|
||||||
if (window.username === msgArr[i+3]) {
|
|
||||||
turn = i;
|
|
||||||
var secs = Math.floor(parseInt(msgArr[msgArr.length-1]) / 1000);
|
|
||||||
var turnUpdate = () => {
|
|
||||||
secs--;
|
|
||||||
if (secs === 0)
|
|
||||||
clearInterval(turninterval);
|
|
||||||
turnstatus.innerText = `Waiting for turn in ${secs} seconds.`;
|
|
||||||
}
|
|
||||||
turninterval = setInterval(turnUpdate, 1000);
|
|
||||||
turnUpdate();
|
|
||||||
display.className = "waiting";
|
|
||||||
};
|
|
||||||
var user = users.find(u => u.username === msgArr[i+3]);
|
|
||||||
user.turn = i;
|
|
||||||
user.element.classList = "table-warning";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (turn === -1) {
|
|
||||||
buttons.takeTurn.innerHTML = "<i class=\"fa-solid fa-computer-mouse\"></i> Take Turn";
|
|
||||||
} else {
|
|
||||||
buttons.takeTurn.innerHTML = "<i class=\"fa-solid fa-computer-mouse\"></i> End Turn";
|
|
||||||
}
|
|
||||||
this.reloadUsers();
|
|
||||||
break;
|
|
||||||
case "vote":
|
|
||||||
switch (msgArr[1]) {
|
|
||||||
case "0":
|
|
||||||
// Vote started
|
|
||||||
case "1":
|
|
||||||
// Vote updated
|
|
||||||
voteresetpanel.style.display = "block";
|
|
||||||
voteyeslabel.innerText = msgArr[3];
|
|
||||||
votenolabel.innerText = msgArr[4];
|
|
||||||
if (voteinterval)
|
|
||||||
clearInterval(voteinterval);
|
|
||||||
var timeToEnd = Math.floor(parseInt(msgArr[2]) / 1000);
|
|
||||||
var updateVote = () => {
|
|
||||||
timeToEnd--;
|
|
||||||
if (timeToEnd === 0)
|
|
||||||
clearInterval(voteinterval);
|
|
||||||
votetime.innerText = timeToEnd;
|
|
||||||
}
|
|
||||||
voteinterval = setInterval(updateVote, 1000);
|
|
||||||
updateVote();
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
// Vote ended
|
|
||||||
voteresetpanel.style.display = "none";
|
|
||||||
break;
|
|
||||||
case "3":
|
|
||||||
// too soon dumbass
|
|
||||||
window.alert(`Please wait ${msgArr[2]} seconds before starting another vote.`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "admin":
|
|
||||||
switch (msgArr[1]) {
|
|
||||||
case "0":
|
|
||||||
// Login
|
|
||||||
switch (msgArr[2]) {
|
|
||||||
case "0":
|
|
||||||
this.eventemitter.emit('login', {error: 'badpassword'});
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
case "1":
|
|
||||||
perms = makeperms(65535, config);
|
|
||||||
rank = 2;
|
|
||||||
break;
|
|
||||||
case "3":
|
|
||||||
rank = 3;
|
|
||||||
perms = makeperms(parseInt(msgArr[3]), config)
|
|
||||||
}
|
|
||||||
this.eventemitter.emit('login', {perms: perms, rank: rank});
|
|
||||||
usernameSpan.classList.remove("text-light");
|
|
||||||
switch (rank) {
|
|
||||||
case 2:
|
|
||||||
usernameSpan.classList.add("text-danger");
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
usernameSpan.classList.add("text-success");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Disabled for now until we figure out the issue of uservm
|
|
||||||
//window.localStorage.setItem("password_"+this.#url, password);
|
|
||||||
staffbtns.style.display = "block";
|
|
||||||
if (perms.restore) buttons.restore.style.display = "inline-block";
|
|
||||||
if (perms.reboot) buttons.reboot.style.display = "inline-block";
|
|
||||||
if (perms.bypassturn) {
|
|
||||||
buttons.bypassTurn.style.display = "inline-block";
|
|
||||||
buttons.clearQueue.style.display = "inline-block";
|
|
||||||
buttons.endTurn.style.display = "inline-block";
|
|
||||||
}
|
|
||||||
if (rank === 2) buttons.qemuMonitor.style.display = "inline-block";
|
|
||||||
if ((config.xssImplementation === 2 && perms.xss) || (rank === 2 && config.xssImplementation === 1)) {
|
|
||||||
xssCheckboxContainer.style.display = "inline-block";
|
|
||||||
}
|
|
||||||
if (perms.forcevote) forceVotePanel.style.display = "block";
|
|
||||||
users.forEach((u) => userModOptions(u.username, u.element, u.element.children[0]));
|
|
||||||
break;
|
|
||||||
case "19":
|
|
||||||
// Got IP
|
|
||||||
this.eventemitter.emit('ip', {username: msgArr[2], ip: msgArr[3]});
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
// QEMU output
|
|
||||||
qemuMonitorOutput.innerHTML += `> ${msgArr[2]}\n`;
|
|
||||||
qemuMonitorOutput.scrollTop = qemuMonitorOutput.scrollHeight;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addUser(name, urank) {
|
|
||||||
var olduser = users.find(u => u.username === name);
|
|
||||||
if (olduser !== undefined) {
|
|
||||||
users.splice(users.indexOf(olduser), 1);
|
|
||||||
userlist.removeChild(olduser.element);
|
|
||||||
}
|
|
||||||
var user = {
|
|
||||||
username: name,
|
|
||||||
rank: Number(urank),
|
|
||||||
turn: -1
|
|
||||||
};
|
|
||||||
users.push(user);
|
|
||||||
var tr = document.createElement("tr");
|
|
||||||
var td = document.createElement("td");
|
|
||||||
td.innerHTML = name;
|
|
||||||
switch (user.rank) {
|
|
||||||
case 2:
|
|
||||||
td.style.color = "#FF0000";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
td.style.color = "#00FF00";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tr.appendChild(td);
|
|
||||||
user.element = tr;
|
|
||||||
if (rank !== 0) userModOptions(user.username, tr, td);
|
|
||||||
userlist.appendChild(tr);
|
|
||||||
}
|
|
||||||
reloadUsers() {
|
|
||||||
// Sort the user list by turn status
|
|
||||||
users.sort((a, b) => {
|
|
||||||
if (a.turn === b.turn) return 0;
|
|
||||||
if (a.turn === -1) return 1;
|
|
||||||
if (b.turn === -1) return -1;
|
|
||||||
if (a.turn < b.turn) return -1;
|
|
||||||
else return 1;
|
|
||||||
});
|
|
||||||
users.forEach((u) => {
|
|
||||||
userlist.removeChild(u.element);
|
|
||||||
userlist.appendChild(u.element);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async list() {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
var unbind = this.eventemitter.on('list', (e) => {
|
|
||||||
unbind();
|
|
||||||
res(e);
|
|
||||||
})
|
|
||||||
this.socket.send("4.list;");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
chat(msg) {
|
|
||||||
this.socket.send(guacutils.encode(["chat", msg]));
|
|
||||||
}
|
|
||||||
rename(username) {
|
|
||||||
this.socket.send(guacutils.encode(["rename", username]));
|
|
||||||
}
|
|
||||||
turn() {
|
|
||||||
if (turn === -1) {
|
|
||||||
this.socket.send(guacutils.encode(["turn", "1"]))
|
|
||||||
} else {
|
|
||||||
this.socket.send(guacutils.encode(["turn", "0"]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mouse(x, y, mask) {
|
|
||||||
this.socket.send(guacutils.encode(["mouse", x, y, mask]));
|
|
||||||
}
|
|
||||||
key(keysym, down) {
|
|
||||||
this.socket.send(guacutils.encode(["key", keysym, down ? "1" : "0"]));
|
|
||||||
}
|
|
||||||
mousewheelhandler(e) {
|
|
||||||
// gutted from guac source code
|
|
||||||
var delta = e.deltaY || -e.wheelDeltaY || -e.wheelDelta;
|
|
||||||
if (!delta) return;
|
|
||||||
if (e.deltaMode === 1)
|
|
||||||
delta = e.deltaY * 40;
|
|
||||||
// Convert to pixels if delta was pages
|
|
||||||
else if (e.deltaMode === 2)
|
|
||||||
delta = e.deltaY * 640;
|
|
||||||
// Up
|
|
||||||
while (delta <= -120) {
|
|
||||||
this.mousestate.scrollup = true;
|
|
||||||
this.sendmouse();
|
|
||||||
this.mousestate.scrollup = false;
|
|
||||||
this.sendmouse();
|
|
||||||
delta += 120;
|
|
||||||
}
|
|
||||||
// Down
|
|
||||||
while (delta >= 120) {
|
|
||||||
this.mousestate.scrolldown = true;
|
|
||||||
this.sendmouse();
|
|
||||||
this.mousestate.scrolldown = false;
|
|
||||||
this.sendmouse();
|
|
||||||
delta -= 120;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mousestate = {
|
|
||||||
left: false,
|
|
||||||
middle: false,
|
|
||||||
right: false,
|
|
||||||
scrolldown: false,
|
|
||||||
scrollup: false,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
}
|
|
||||||
makemousemask() {
|
|
||||||
var mask = 0;
|
|
||||||
if (this.mousestate.left) mask |= 1;
|
|
||||||
if (this.mousestate.middle) mask |= 2;
|
|
||||||
if (this.mousestate.right) mask |= 4;
|
|
||||||
if (this.mousestate.scrollup) mask |= 8;
|
|
||||||
if (this.mousestate.scrolldown) mask |= 16;
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
mouseevent(e, down) {
|
|
||||||
if (down !== undefined) {switch (e.button) {
|
|
||||||
case 0:
|
|
||||||
this.mousestate.left = down;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
this.mousestate.middle = down;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this.mousestate.right = down;
|
|
||||||
break;
|
|
||||||
}}
|
|
||||||
this.mousestate.x = e.offsetX;
|
|
||||||
this.mousestate.y = e.offsetY;
|
|
||||||
this.sendmouse();
|
|
||||||
}
|
|
||||||
sendmouse() {
|
|
||||||
var mask = this.makemousemask();
|
|
||||||
this.mouse(this.mousestate.x, this.mousestate.y, mask);
|
|
||||||
}
|
|
||||||
keyevent(e, down) {
|
|
||||||
e.preventDefault();
|
|
||||||
var keysym = GetKeysym(e.keyCode, e.keyIdentifier, e.key, e.location);
|
|
||||||
if (keysym === undefined) return;
|
|
||||||
this.key(keysym, down);
|
|
||||||
}
|
|
||||||
voteReset(reset) {
|
|
||||||
this.socket.send(guacutils.encode(["vote", reset ? "1" : "0"]));
|
|
||||||
}
|
|
||||||
admin = {
|
|
||||||
login: (password) => {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
var unbind = this.eventemitter.on('login', (args) => {
|
|
||||||
unbind();
|
|
||||||
if (args.error) rej(error);
|
|
||||||
res(args);
|
|
||||||
})
|
|
||||||
this.socket.send(guacutils.encode(["admin", "2", password]));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
adminInstruction: (...args) => { // Compatibility
|
|
||||||
args.unshift("admin");
|
|
||||||
this.socket.send(guacutils.encode(args));
|
|
||||||
},
|
|
||||||
restore: () => this.socket.send(guacutils.encode(["admin", "8", this.node])),
|
|
||||||
reboot: () => this.socket.send(guacutils.encode(["admin", "10", this.node])),
|
|
||||||
clearQueue: () => this.socket.send(guacutils.encode(["admin", "17", this.node])),
|
|
||||||
bypassTurn: () => this.socket.send(guacutils.encode(["admin", "20"])),
|
|
||||||
endTurn: (user) => this.socket.send(guacutils.encode(["admin", "16", user])),
|
|
||||||
ban: (user) => this.socket.send(guacutils.encode(["admin", "12", user])),
|
|
||||||
kick: (user) => this.socket.send(guacutils.encode(["admin", "15", user])),
|
|
||||||
renameUser: (user, newname) => this.socket.send(guacutils.encode(["admin", "18", user, newname])),
|
|
||||||
mute: (user, mutestate) => this.socket.send(guacutils.encode(["admin", "14", user, mutestate])),
|
|
||||||
getip: (user) => {
|
|
||||||
if (users.find(u => u.username === user) === undefined) return;
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
var unbind = this.eventemitter.on('ip', (args) => {
|
|
||||||
if (args.username !== user) return;
|
|
||||||
unbind();
|
|
||||||
res(args.ip);
|
|
||||||
});
|
|
||||||
this.socket.send(guacutils.encode(["admin", "19", user]));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
qemuMonitor: (cmd) => this.socket.send(guacutils.encode(["admin", "5", this.node, cmd])),
|
|
||||||
globalXss: (msg) => {
|
|
||||||
switch (config.xssImplementation) {
|
|
||||||
case 1:
|
|
||||||
this.socket.send(guacutils.encode(["admin", "21", msg]));
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
users.forEach((u) => this.socket.send(guacutils.encode(["admin", "21", u.username, msg])));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
userXss: (user, msg) => {
|
|
||||||
if (config.xssImplementation !== 2 || !users.find(u => u.username === user)) return;
|
|
||||||
this.socket.send(guacutils.encode(["admin", "21", user, msg]));
|
|
||||||
},
|
|
||||||
forceVote: (result) => {
|
|
||||||
this.socket.send(guacutils.encode(["admin", "13", result ? "1" : "0"]));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function multicollab(url) {
|
|
||||||
return new Promise(async (res, rej) => {
|
|
||||||
var vm = new CollabVMClient(url, false);
|
|
||||||
var connected = await vm.connect();
|
|
||||||
if(!connected) return res(false);
|
|
||||||
var list = await vm.list();
|
|
||||||
vm.disconnect();
|
|
||||||
list.forEach((curr) => {
|
|
||||||
var id = curr.id;
|
|
||||||
var name = curr.name;
|
|
||||||
vms.push(curr);
|
|
||||||
var div = document.createElement("div");
|
|
||||||
div.classList = "col-sm-5 col-md-3";
|
|
||||||
var card = document.createElement("div");
|
|
||||||
card.classList = "card bg-dark text-light";
|
|
||||||
card.setAttribute("data-cvm-node", id);
|
|
||||||
card.addEventListener("click", () => openVM(url, id));
|
|
||||||
var img = document.createElement("img");
|
|
||||||
img.src = "data:image/png;base64," + curr.thumb;
|
|
||||||
img.classList = "card-img-top";
|
|
||||||
var bdy = document.createElement("div");
|
|
||||||
bdy.classList = "card-body";
|
|
||||||
var desc = document.createElement("h5");
|
|
||||||
desc.innerHTML = name;
|
|
||||||
bdy.appendChild(desc);
|
|
||||||
card.appendChild(img);
|
|
||||||
card.appendChild(bdy);
|
|
||||||
div.appendChild(card);
|
|
||||||
curr.element = div;
|
|
||||||
reloadVMList();
|
|
||||||
});
|
|
||||||
res(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function reloadVMList() {
|
|
||||||
vms.sort(function(a, b) {
|
|
||||||
return a.id > b.id ? 1 : -1;
|
|
||||||
});
|
|
||||||
vmlist.children[0].innerHTML = "";
|
|
||||||
vms.forEach((v) => vmlist.children[0].appendChild(v.element));
|
|
||||||
}
|
|
||||||
function chatMessage(user, msg) {
|
|
||||||
var tr = document.createElement("tr");
|
|
||||||
var td = document.createElement("td");
|
|
||||||
if (user == "" || user === undefined)
|
|
||||||
td.innerHTML = msg;
|
|
||||||
else {
|
|
||||||
var u = users.find(u => u.username === user);
|
|
||||||
var userclass;
|
|
||||||
if (u) switch (u.rank) {
|
|
||||||
case 2:
|
|
||||||
userclass = "text-danger";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
userclass = "text-success";
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
default:
|
|
||||||
userclass = "text-light";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else userclass = "text-light";
|
|
||||||
td.innerHTML = `<b class="${userclass}">${user}></b> ${msg}`;
|
|
||||||
}
|
|
||||||
// I really hate this but html5 cockblocks me every other way
|
|
||||||
Array.prototype.slice.call(td.children).forEach((curr) => {
|
|
||||||
if (curr.nodeName === "SCRIPT") {
|
|
||||||
eval(curr.text)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tr.appendChild(td);
|
|
||||||
chatList.appendChild(tr);
|
|
||||||
}
|
|
||||||
function userModOptions(user, tr, td) {
|
|
||||||
tr.classList.add("dropdown");
|
|
||||||
td.classList.add("dropdown-toggle");
|
|
||||||
td.setAttribute("data-bs-toggle", "dropdown");
|
|
||||||
td.setAttribute("role", "button");
|
|
||||||
td.setAttribute("aria-expanded", "false");
|
|
||||||
var ul = document.createElement("ul");
|
|
||||||
ul.classList = "dropdown-menu dropdown-menu-dark table-dark text-light";
|
|
||||||
|
|
||||||
if (perms.bypassturn) addUserDropdownItem(ul, "End Turn", () => vm.admin.endTurn(user));
|
|
||||||
if (perms.ban) addUserDropdownItem(ul, "Ban", () => vm.admin.ban(user));
|
|
||||||
if (perms.kick) addUserDropdownItem(ul, "Kick", () => vm.admin.kick(user));
|
|
||||||
if (perms.rename) addUserDropdownItem(ul, "Rename", () => {
|
|
||||||
var newname = window.prompt(`Enter new username for ${user}`);
|
|
||||||
if (newname == null) return;
|
|
||||||
vm.admin.renameUser(user, newname)
|
|
||||||
});
|
|
||||||
if (perms.mute) {
|
|
||||||
addUserDropdownItem(ul, "Temporary Mute", () => vm.admin.mute(user, 0));
|
|
||||||
addUserDropdownItem(ul, "Indefinite Mute", () => vm.admin.mute(user, 1));
|
|
||||||
addUserDropdownItem(ul, "Unmute", () => vm.admin.mute(user, 2));
|
|
||||||
}
|
|
||||||
if (perms.grabip) addUserDropdownItem(ul, "Get IP", async () => {
|
|
||||||
var ip = await vm.admin.getip(user);
|
|
||||||
alert(ip);
|
|
||||||
});
|
|
||||||
if (config.xssImplementation === 2 && perms.xss) addUserDropdownItem(ul, "Direct Message (XSS)", () => {
|
|
||||||
var msg = window.prompt("Enter message to send");
|
|
||||||
if (!msg) return;
|
|
||||||
vm.admin.userXss(user, msg);
|
|
||||||
});
|
|
||||||
tr.appendChild(ul);
|
|
||||||
}
|
|
||||||
function addUserDropdownItem(ul, text, func) {
|
|
||||||
var li = document.createElement("li");
|
|
||||||
var a = document.createElement("a");
|
|
||||||
a.href = "#";
|
|
||||||
a.classList.add("dropdown-item");
|
|
||||||
a.innerHTML = text;
|
|
||||||
a.addEventListener('click', func);
|
|
||||||
li.appendChild(a);
|
|
||||||
ul.appendChild(li);
|
|
||||||
}
|
|
||||||
function returnToVMList() {
|
|
||||||
if(!connected) return;
|
|
||||||
connected = false;
|
|
||||||
vm.disconnect();
|
|
||||||
vm.shouldReconnect = false;
|
|
||||||
voteresetpanel.style.display = "none";
|
|
||||||
vmview.style.display = "none";
|
|
||||||
vmlist.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openVM(url, node) {
|
|
||||||
if (connected) return;
|
|
||||||
connected = true;
|
|
||||||
var _vm = vms.find(v => v.url === url);
|
|
||||||
var token = null;
|
|
||||||
if (_vm.captcha !== false) {
|
|
||||||
token = await doCaptcha(vm.captcha);
|
|
||||||
}
|
|
||||||
window.location.href = "#" + node;
|
|
||||||
window.VMName = node;
|
|
||||||
vm = new CollabVMClient(url, true);
|
|
||||||
await vm.connect(token);
|
|
||||||
await vm.connectToVM(node);
|
|
||||||
vmlist.style.display = "none";
|
|
||||||
vmview.style.display = "block";
|
|
||||||
addListener(display, 'mousemove', 'displayMove', (e) => vm.mouseevent(e, undefined));
|
|
||||||
addListener(display, 'mousedown', 'displayDown', (e) => vm.mouseevent(e, true));
|
|
||||||
addListener(display, 'mouseup', 'displayUp', (e) => vm.mouseevent(e, false));
|
|
||||||
addListener(display, 'wheel', 'displayWheel', (e) => {vm.mousewheelhandler(e);e.preventDefault();return false;}); // BUG: mousewheelhandler seems to be broken!
|
|
||||||
addListener(display, 'contextmenu', 'displayContextMenu', (e) => e.preventDefault());
|
|
||||||
addListener(display, 'click', 'displayClick', () => { if (turn === -1) vm.turn(); });
|
|
||||||
addListener(display, 'keydown', 'displayKeyDown', (e) => vm.keyevent(e, true));
|
|
||||||
addListener(display, 'keyup', 'displayKeyUp', (e) => vm.keyevent(e, false));
|
|
||||||
}
|
|
||||||
function screenshotVM() {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
display.toBlob((b) => {
|
|
||||||
if (b == null) {
|
|
||||||
rej();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res(b);
|
|
||||||
}, "image/png");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Clean everything up after disconnecting
|
|
||||||
function cleanup() {
|
|
||||||
turn = -1;
|
|
||||||
window.username = null;
|
|
||||||
rank = 0;
|
|
||||||
hasTurn = false;
|
|
||||||
if (turninterval) clearInterval(turninterval);
|
|
||||||
if (voteinterval)
|
|
||||||
clearInterval(voteinterval);
|
|
||||||
users.splice(0);
|
|
||||||
userlist.innerHTML = "";
|
|
||||||
Array.prototype.slice.call(staffbtns.children).forEach((curr) => curr.style.display = "none");
|
|
||||||
staffbtns.style.display = "none";
|
|
||||||
usernameSpan.classList = "input-group-text bg-dark text-light";
|
|
||||||
display.height = 0;
|
|
||||||
display.width = 0;
|
|
||||||
removeListener(display, 'mousemove', 'displayMove');
|
|
||||||
removeListener(display, 'mousedown', 'displayDown');
|
|
||||||
removeListener(display, 'mouseup', 'displayUp');
|
|
||||||
removeListener(display, 'wheel', 'displayWheel');
|
|
||||||
removeListener(display, 'contextmenu', 'displayContextMenu');
|
|
||||||
removeListener(display, 'click', 'displayClick');
|
|
||||||
removeListener(display, 'keydown', 'displayKeyDown');
|
|
||||||
removeListener(display, 'keyup', 'displayKeyUp');
|
|
||||||
}
|
|
||||||
buttons.home.addEventListener('click', async () => returnToVMList());
|
|
||||||
buttons.screenshot.addEventListener('click', async () => {
|
|
||||||
var blob = await screenshotVM();
|
|
||||||
var url = URL.createObjectURL(blob);
|
|
||||||
window.open(url, "_blank");
|
|
||||||
});
|
|
||||||
chatinput.addEventListener("keypress", (e) => {
|
|
||||||
if (e.key == "Enter") sendChat();
|
|
||||||
});
|
|
||||||
buttons.sendChat.addEventListener('click', () => sendChat());
|
|
||||||
function sendChat() {
|
|
||||||
if (xssCheckbox.checked)
|
|
||||||
vm.admin.globalXss(chatinput.value);
|
|
||||||
else
|
|
||||||
vm.chat(chatinput.value);
|
|
||||||
chatinput.value = "";
|
|
||||||
}
|
|
||||||
buttons.changeUsername.addEventListener('click', () => {
|
|
||||||
var newuser = window.prompt("Enter new username", window.username);
|
|
||||||
if (newuser == null) return;
|
|
||||||
vm.rename(newuser);
|
|
||||||
});
|
|
||||||
buttons.takeTurn.addEventListener('click', () => vm.turn());
|
|
||||||
buttons.voteReset.addEventListener('click', () => vm.voteReset(true));
|
|
||||||
buttons.ctrlAltDel.addEventListener('click', () => {
|
|
||||||
// Ctrl
|
|
||||||
vm.key(0xffe3, true);
|
|
||||||
// Alt
|
|
||||||
vm.key(0xffe9, true);
|
|
||||||
// Del
|
|
||||||
vm.key(0xffff, true);
|
|
||||||
// Ctrl
|
|
||||||
vm.key(0xffe3, false);
|
|
||||||
// Alt
|
|
||||||
vm.key(0xffe9, false);
|
|
||||||
// Del
|
|
||||||
vm.key(0xffff, false);
|
|
||||||
});
|
|
||||||
voteyesbtn.addEventListener('click', () => vm.voteReset(true));
|
|
||||||
votenobtn.addEventListener('click', () => vm.voteReset(false));
|
|
||||||
// Staff buttons
|
|
||||||
buttons.restore.addEventListener('click', () => {if (window.confirm("Do you really want to restore the VM?")) vm.admin.restore()});
|
|
||||||
buttons.reboot.addEventListener('click', () => vm.admin.reboot());
|
|
||||||
buttons.clearQueue.addEventListener('click', () => vm.admin.clearQueue());
|
|
||||||
buttons.bypassTurn.addEventListener('click', () => vm.admin.bypassTurn());
|
|
||||||
buttons.endTurn.addEventListener('click', () => vm.admin.endTurn(users[0]));
|
|
||||||
buttons.forceVoteYes.addEventListener('click', () => vm.admin.forceVote(true));
|
|
||||||
buttons.forceVoteNo.addEventListener('click', () => vm.admin.forceVote(false));
|
|
||||||
// QEMU Monitor Shit
|
|
||||||
function sendQEMUCommand() {
|
|
||||||
if (!qemuMonitorInput.value) return;
|
|
||||||
vm.admin.qemuMonitor(qemuMonitorInput.value);
|
|
||||||
qemuMonitorInput.value = "";
|
|
||||||
}
|
|
||||||
qemuMonitorInput.addEventListener('keypress', (e) => {
|
|
||||||
if (e.key === "Enter") sendQEMUCommand();
|
|
||||||
});
|
|
||||||
buttons.qemuMonitorSend.addEventListener('click', () => sendQEMUCommand());
|
|
||||||
|
|
||||||
// Login
|
|
||||||
var usernameClick = false;
|
|
||||||
usernameSpan.addEventListener('click', () => {
|
|
||||||
if (!usernameClick) {
|
|
||||||
usernameClick = true;
|
|
||||||
setInterval(() => {usernameClick = false;}, 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var pass = window.prompt("🔑");
|
|
||||||
if (!pass) return;
|
|
||||||
vm.admin.login(pass);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load all vms
|
|
||||||
(async () => {
|
|
||||||
var p = [];
|
|
||||||
config.serverAddresses.forEach(v => p.push(multicollab(v)));
|
|
||||||
await Promise.all(p);
|
|
||||||
var vm = vms.find(v => v.id === window.location.hash.substring(1));
|
|
||||||
if (vm)
|
|
||||||
openVM(vm.url, vm.id);
|
|
||||||
})();
|
|
||||||
// Export some stuff
|
|
||||||
window.screenshotVM = screenshotVM;
|
|
||||||
window.multicollab = multicollab;
|
|
||||||
window.getPerms = () => perms;
|
|
||||||
window.getRank = () => rank;
|
|
||||||
window.GetAdmin = () => vm.admin;
|
|
||||||
window.cvmEvents = createNanoEvents();
|
|
||||||
window.VMName = null;
|
|
||||||
|
|
||||||
// Welcome modal
|
|
||||||
var noWelcomeModal = window.localStorage.getItem("no-welcome-modal");
|
|
||||||
if (noWelcomeModal !== "1") {
|
|
||||||
var welcomeModalDismissBtn = document.getElementById("welcomeModalDismiss");
|
|
||||||
var welcomeModal = new bootstrap.Modal(document.getElementById("welcomeModal"));
|
|
||||||
welcomeModalDismissBtn.addEventListener("click", () => {
|
|
||||||
window.localStorage.setItem("no-welcome-modal", 1);
|
|
||||||
});
|
|
||||||
welcomeModalDismissBtn.disabled = true;
|
|
||||||
welcomeModal.show();
|
|
||||||
setTimeout(() => {
|
|
||||||
welcomeModalDismissBtn.disabled = false;
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
280
src/keyboard.js
280
src/keyboard.js
|
|
@ -1,280 +0,0 @@
|
||||||
// Pulled a bunch of functions out of the guac source code to get a keysym
|
|
||||||
// and then a wrapper
|
|
||||||
// shitty but it works so /shrug
|
|
||||||
export function GetKeysym(keyCode, keyIdentifier, key, location) {
|
|
||||||
var keysym = keysym_from_key_identifier(key, location)
|
|
||||||
|| keysym_from_keycode(keyCode, location);
|
|
||||||
|
|
||||||
if (!keysym && key_identifier_sane(keyCode, keyIdentifier))
|
|
||||||
keysym = keysym_from_key_identifier(keyIdentifier, location);
|
|
||||||
|
|
||||||
return keysym;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function keysym_from_key_identifier(identifier, location) {
|
|
||||||
|
|
||||||
if (!identifier)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var typedCharacter;
|
|
||||||
|
|
||||||
// If identifier is U+xxxx, decode Unicode character
|
|
||||||
var unicodePrefixLocation = identifier.indexOf("U+");
|
|
||||||
if (unicodePrefixLocation >= 0) {
|
|
||||||
var hex = identifier.substring(unicodePrefixLocation+2);
|
|
||||||
typedCharacter = String.fromCharCode(parseInt(hex, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If single character, use that as typed character
|
|
||||||
else if (identifier.length === 1)
|
|
||||||
typedCharacter = identifier;
|
|
||||||
|
|
||||||
// Otherwise, look up corresponding keysym
|
|
||||||
else
|
|
||||||
return get_keysym(keyidentifier_keysym[identifier], location);
|
|
||||||
|
|
||||||
// Get codepoint
|
|
||||||
var codepoint = typedCharacter.charCodeAt(0);
|
|
||||||
return keysym_from_charcode(codepoint);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_keysym(keysyms, location) {
|
|
||||||
|
|
||||||
if (!keysyms)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return keysyms[location] || keysyms[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function keysym_from_charcode(codepoint) {
|
|
||||||
|
|
||||||
// Keysyms for control characters
|
|
||||||
if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;
|
|
||||||
|
|
||||||
// Keysyms for ASCII chars
|
|
||||||
if (codepoint >= 0x0000 && codepoint <= 0x00FF)
|
|
||||||
return codepoint;
|
|
||||||
|
|
||||||
// Keysyms for Unicode
|
|
||||||
if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)
|
|
||||||
return 0x01000000 | codepoint;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function isControlCharacter(codepoint) {
|
|
||||||
return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);
|
|
||||||
}
|
|
||||||
|
|
||||||
function keysym_from_keycode(keyCode, location) {
|
|
||||||
return get_keysym(keycodeKeysyms[keyCode], location);
|
|
||||||
}
|
|
||||||
|
|
||||||
function key_identifier_sane(keyCode, keyIdentifier) {
|
|
||||||
|
|
||||||
// Missing identifier is not sane
|
|
||||||
if (!keyIdentifier)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Assume non-Unicode keyIdentifier values are sane
|
|
||||||
var unicodePrefixLocation = keyIdentifier.indexOf("U+");
|
|
||||||
if (unicodePrefixLocation === -1)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// If the Unicode codepoint isn't identical to the keyCode,
|
|
||||||
// then the identifier is likely correct
|
|
||||||
var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);
|
|
||||||
if (keyCode !== codepoint)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// The keyCodes for A-Z and 0-9 are actually identical to their
|
|
||||||
// Unicode codepoints
|
|
||||||
if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// The keyIdentifier does NOT appear sane
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var keycodeKeysyms = {
|
|
||||||
8: [0xFF08], // backspace
|
|
||||||
9: [0xFF09], // tab
|
|
||||||
12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5
|
|
||||||
13: [0xFF0D], // enter
|
|
||||||
16: [0xFFE1, 0xFFE1, 0xFFE2], // shift
|
|
||||||
17: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl
|
|
||||||
18: [0xFFE9, 0xFFE9, 0xFE03], // alt
|
|
||||||
19: [0xFF13], // pause/break
|
|
||||||
20: [0xFFE5], // caps lock
|
|
||||||
27: [0xFF1B], // escape
|
|
||||||
32: [0x0020], // space
|
|
||||||
33: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 9
|
|
||||||
34: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 3
|
|
||||||
35: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 1
|
|
||||||
36: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 7
|
|
||||||
37: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 4
|
|
||||||
38: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 8
|
|
||||||
39: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6
|
|
||||||
40: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 2
|
|
||||||
45: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 0
|
|
||||||
46: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal
|
|
||||||
91: [0xFFEB], // left window key (hyper_l)
|
|
||||||
92: [0xFF67], // right window key (menu key?)
|
|
||||||
93: null, // select key
|
|
||||||
96: [0xFFB0], // KP 0
|
|
||||||
97: [0xFFB1], // KP 1
|
|
||||||
98: [0xFFB2], // KP 2
|
|
||||||
99: [0xFFB3], // KP 3
|
|
||||||
100: [0xFFB4], // KP 4
|
|
||||||
101: [0xFFB5], // KP 5
|
|
||||||
102: [0xFFB6], // KP 6
|
|
||||||
103: [0xFFB7], // KP 7
|
|
||||||
104: [0xFFB8], // KP 8
|
|
||||||
105: [0xFFB9], // KP 9
|
|
||||||
106: [0xFFAA], // KP multiply
|
|
||||||
107: [0xFFAB], // KP add
|
|
||||||
109: [0xFFAD], // KP subtract
|
|
||||||
110: [0xFFAE], // KP decimal
|
|
||||||
111: [0xFFAF], // KP divide
|
|
||||||
112: [0xFFBE], // f1
|
|
||||||
113: [0xFFBF], // f2
|
|
||||||
114: [0xFFC0], // f3
|
|
||||||
115: [0xFFC1], // f4
|
|
||||||
116: [0xFFC2], // f5
|
|
||||||
117: [0xFFC3], // f6
|
|
||||||
118: [0xFFC4], // f7
|
|
||||||
119: [0xFFC5], // f8
|
|
||||||
120: [0xFFC6], // f9
|
|
||||||
121: [0xFFC7], // f10
|
|
||||||
122: [0xFFC8], // f11
|
|
||||||
123: [0xFFC9], // f12
|
|
||||||
144: [0xFF7F], // num lock
|
|
||||||
145: [0xFF14], // scroll lock
|
|
||||||
225: [0xFE03] // altgraph (iso_level3_shift)
|
|
||||||
};
|
|
||||||
|
|
||||||
var keyidentifier_keysym = {
|
|
||||||
"Again": [0xFF66],
|
|
||||||
"AllCandidates": [0xFF3D],
|
|
||||||
"Alphanumeric": [0xFF30],
|
|
||||||
"Alt": [0xFFE9, 0xFFE9, 0xFE03],
|
|
||||||
"Attn": [0xFD0E],
|
|
||||||
"AltGraph": [0xFE03],
|
|
||||||
"ArrowDown": [0xFF54],
|
|
||||||
"ArrowLeft": [0xFF51],
|
|
||||||
"ArrowRight": [0xFF53],
|
|
||||||
"ArrowUp": [0xFF52],
|
|
||||||
"Backspace": [0xFF08],
|
|
||||||
"CapsLock": [0xFFE5],
|
|
||||||
"Cancel": [0xFF69],
|
|
||||||
"Clear": [0xFF0B],
|
|
||||||
"Convert": [0xFF21],
|
|
||||||
"Copy": [0xFD15],
|
|
||||||
"Crsel": [0xFD1C],
|
|
||||||
"CrSel": [0xFD1C],
|
|
||||||
"CodeInput": [0xFF37],
|
|
||||||
"Compose": [0xFF20],
|
|
||||||
"Control": [0xFFE3, 0xFFE3, 0xFFE4],
|
|
||||||
"ContextMenu": [0xFF67],
|
|
||||||
"DeadGrave": [0xFE50],
|
|
||||||
"DeadAcute": [0xFE51],
|
|
||||||
"DeadCircumflex": [0xFE52],
|
|
||||||
"DeadTilde": [0xFE53],
|
|
||||||
"DeadMacron": [0xFE54],
|
|
||||||
"DeadBreve": [0xFE55],
|
|
||||||
"DeadAboveDot": [0xFE56],
|
|
||||||
"DeadUmlaut": [0xFE57],
|
|
||||||
"DeadAboveRing": [0xFE58],
|
|
||||||
"DeadDoubleacute": [0xFE59],
|
|
||||||
"DeadCaron": [0xFE5A],
|
|
||||||
"DeadCedilla": [0xFE5B],
|
|
||||||
"DeadOgonek": [0xFE5C],
|
|
||||||
"DeadIota": [0xFE5D],
|
|
||||||
"DeadVoicedSound": [0xFE5E],
|
|
||||||
"DeadSemivoicedSound": [0xFE5F],
|
|
||||||
"Delete": [0xFFFF],
|
|
||||||
"Down": [0xFF54],
|
|
||||||
"End": [0xFF57],
|
|
||||||
"Enter": [0xFF0D],
|
|
||||||
"EraseEof": [0xFD06],
|
|
||||||
"Escape": [0xFF1B],
|
|
||||||
"Execute": [0xFF62],
|
|
||||||
"Exsel": [0xFD1D],
|
|
||||||
"ExSel": [0xFD1D],
|
|
||||||
"F1": [0xFFBE],
|
|
||||||
"F2": [0xFFBF],
|
|
||||||
"F3": [0xFFC0],
|
|
||||||
"F4": [0xFFC1],
|
|
||||||
"F5": [0xFFC2],
|
|
||||||
"F6": [0xFFC3],
|
|
||||||
"F7": [0xFFC4],
|
|
||||||
"F8": [0xFFC5],
|
|
||||||
"F9": [0xFFC6],
|
|
||||||
"F10": [0xFFC7],
|
|
||||||
"F11": [0xFFC8],
|
|
||||||
"F12": [0xFFC9],
|
|
||||||
"F13": [0xFFCA],
|
|
||||||
"F14": [0xFFCB],
|
|
||||||
"F15": [0xFFCC],
|
|
||||||
"F16": [0xFFCD],
|
|
||||||
"F17": [0xFFCE],
|
|
||||||
"F18": [0xFFCF],
|
|
||||||
"F19": [0xFFD0],
|
|
||||||
"F20": [0xFFD1],
|
|
||||||
"F21": [0xFFD2],
|
|
||||||
"F22": [0xFFD3],
|
|
||||||
"F23": [0xFFD4],
|
|
||||||
"F24": [0xFFD5],
|
|
||||||
"Find": [0xFF68],
|
|
||||||
"GroupFirst": [0xFE0C],
|
|
||||||
"GroupLast": [0xFE0E],
|
|
||||||
"GroupNext": [0xFE08],
|
|
||||||
"GroupPrevious": [0xFE0A],
|
|
||||||
"FullWidth": null,
|
|
||||||
"HalfWidth": null,
|
|
||||||
"HangulMode": [0xFF31],
|
|
||||||
"Hankaku": [0xFF29],
|
|
||||||
"HanjaMode": [0xFF34],
|
|
||||||
"Help": [0xFF6A],
|
|
||||||
"Hiragana": [0xFF25],
|
|
||||||
"HiraganaKatakana": [0xFF27],
|
|
||||||
"Home": [0xFF50],
|
|
||||||
"Hyper": [0xFFED, 0xFFED, 0xFFEE],
|
|
||||||
"Insert": [0xFF63],
|
|
||||||
"JapaneseHiragana": [0xFF25],
|
|
||||||
"JapaneseKatakana": [0xFF26],
|
|
||||||
"JapaneseRomaji": [0xFF24],
|
|
||||||
"JunjaMode": [0xFF38],
|
|
||||||
"KanaMode": [0xFF2D],
|
|
||||||
"KanjiMode": [0xFF21],
|
|
||||||
"Katakana": [0xFF26],
|
|
||||||
"Left": [0xFF51],
|
|
||||||
"Meta": [0xFFE7, 0xFFE7, 0xFFE8],
|
|
||||||
"ModeChange": [0xFF7E],
|
|
||||||
"NumLock": [0xFF7F],
|
|
||||||
"PageDown": [0xFF56],
|
|
||||||
"PageUp": [0xFF55],
|
|
||||||
"Pause": [0xFF13],
|
|
||||||
"Play": [0xFD16],
|
|
||||||
"PreviousCandidate": [0xFF3E],
|
|
||||||
"PrintScreen": [0xFD1D],
|
|
||||||
"Redo": [0xFF66],
|
|
||||||
"Right": [0xFF53],
|
|
||||||
"RomanCharacters": null,
|
|
||||||
"Scroll": [0xFF14],
|
|
||||||
"Select": [0xFF60],
|
|
||||||
"Separator": [0xFFAC],
|
|
||||||
"Shift": [0xFFE1, 0xFFE1, 0xFFE2],
|
|
||||||
"SingleCandidate": [0xFF3C],
|
|
||||||
"Super": [0xFFEB, 0xFFEB, 0xFFEC],
|
|
||||||
"Tab": [0xFF09],
|
|
||||||
"Up": [0xFF52],
|
|
||||||
"Undo": [0xFF65],
|
|
||||||
"Win": [0xFFEB],
|
|
||||||
"Zenkaku": [0xFF28],
|
|
||||||
"ZenkakuHankaku": [0xFF2A]
|
|
||||||
};
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
export function makeperms(mask, config) {
|
|
||||||
const perms = {
|
|
||||||
restore: false,
|
|
||||||
reboot: false,
|
|
||||||
ban: false,
|
|
||||||
forcevote: false,
|
|
||||||
mute: false,
|
|
||||||
kick: false,
|
|
||||||
bypassturn: false,
|
|
||||||
rename: false,
|
|
||||||
grabip: false,
|
|
||||||
xss: false
|
|
||||||
};
|
|
||||||
if ((mask & 1) !== 0) perms.restore = true;
|
|
||||||
if ((mask & 2) !== 0) perms.reboot = true;
|
|
||||||
if ((mask & 4) !== 0) perms.ban = true;
|
|
||||||
if ((mask & 8) !== 0) perms.forcevote = true;
|
|
||||||
if ((mask & 16) !== 0) perms.mute = true;
|
|
||||||
if ((mask & 32) !== 0) perms.kick = true;
|
|
||||||
if ((mask & 64) !== 0) perms.bypassturn = true;
|
|
||||||
if ((mask & 128) !== 0) perms.rename = true;
|
|
||||||
if ((mask & 256) !== 0) perms.grabip = true;
|
|
||||||
if (config.xssImplementation === 2 && (mask & 512) !== 0) perms.xss = true;
|
|
||||||
return perms;
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
export const guacutils = {
|
|
||||||
decode: (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;
|
|
||||||
},
|
|
||||||
|
|
||||||
encode: (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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
0
src/ts/main.ts
Normal file
0
src/ts/main.ts
Normal file
3
src/ts/protocol/CollabVMClient.ts
Normal file
3
src/ts/protocol/CollabVMClient.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default class CollabVMClient {
|
||||||
|
|
||||||
|
}
|
||||||
109
tsconfig.json
Normal file
109
tsconfig.json
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "ES2022", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: "./src/index.js",
|
|
||||||
|
|
||||||
output: {
|
|
||||||
filename: 'main.js',
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
},
|
|
||||||
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.m?js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/preset-env'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
mode: "production"
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user