From 125e6a769d46a13f8025c157085f0a55d0bbcff1 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Tue, 12 Mar 2024 01:28:23 -0400 Subject: [PATCH] reformat typescript code with prettier Mostly just for nicity. I did manually clean up a few things, but other than that, this basically was just importing configs from cvm3/crusttest, adding prettier as a dev dependency, and just ctrl-shift-I on every typescript file part of the codebase --- .prettierignore | 5 + .prettierrc.json | 20 + package.json | 1 + src/ts/keyboard.ts | 784 ++++++++-------- src/ts/main.ts | 1422 +++++++++++++++-------------- src/ts/protocol/CollabVMClient.ts | 1016 +++++++++++---------- src/ts/protocol/Guacutils.ts | 58 +- src/ts/protocol/MuteState.ts | 8 +- src/ts/protocol/Permissions.ts | 88 +- src/ts/protocol/TurnStatus.ts | 20 +- src/ts/protocol/User.ts | 22 +- src/ts/protocol/VM.ts | 11 +- src/ts/protocol/VoteStatus.ts | 8 +- src/ts/protocol/mouse.ts | 74 +- 14 files changed, 1786 insertions(+), 1751 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..90e561d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +dist +*.md +*.json +*.html +*.css \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..f28fe87 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,20 @@ +{ + "arrowParens": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": true, + "printWidth": 200, + "proseWrap": "preserve", + "quoteProps": "consistent", + "requirePragma": false, + "semi": true, + "singleAttributePerLine": false, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "none", + "useTabs": true, + "vueIndentScriptAndStyle": false + } diff --git a/package.json b/package.json index 884234a..442596c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/bootstrap": "^5.2.10", "parcel": "^2.11.0", "parcel-reporter-static-files-copy": "^1.5.3", + "prettier": "^3.2.5", "run-script-os": "^1.1.6", "typescript": "^5.3.3" } diff --git a/src/ts/keyboard.ts b/src/ts/keyboard.ts index d06cb0f..5a5cec8 100644 --- a/src/ts/keyboard.ts +++ b/src/ts/keyboard.ts @@ -3,412 +3,406 @@ // shitty but it works so /shrug // THIS SUCKS SO BAD AND I HATE IT PLEASE REWRITE ALL OF THIS -export default function GetKeysym( - keyCode: number, - key: string, - location: number - ): number | null { - let keysym = - keysym_from_key_identifier(key, location) || - keysym_from_keycode(keyCode, location); - return keysym; - } - - function keysym_from_key_identifier(identifier: string, location: number): number | null { - if (!identifier) return null; - - let typedCharacter: string | undefined; - - // If identifier is U+xxxx, decode Unicode character - const unicodePrefixLocation = identifier.indexOf("U+"); - if (unicodePrefixLocation >= 0) { - const hex = identifier.substring(unicodePrefixLocation + 2); - typedCharacter = String.fromCharCode(parseInt(hex, 16)); - } else if (identifier.length === 1) typedCharacter = identifier; - else return get_keysym(keyidentifier_keysym[identifier], location); - - if (!typedCharacter) return null; - - const codepoint = typedCharacter.charCodeAt(0); - return keysym_from_charcode(codepoint); - } - - function get_keysym(keysyms: number[] | null, location: number): number | null { - if (!keysyms) return null; - return keysyms[location] || keysyms[0]; - } - - function keysym_from_charcode(codepoint: number): number | null { - if (isControlCharacter(codepoint)) return 0xff00 | codepoint; - if (codepoint >= 0x0000 && codepoint <= 0x00ff) return codepoint; - if (codepoint >= 0x0100 && codepoint <= 0x10ffff) return 0x01000000 | codepoint; - return null; - } - - function isControlCharacter(codepoint: number): boolean { - return codepoint <= 0x1f || (codepoint >= 0x7f && codepoint <= 0x9f); - } - - function keysym_from_keycode(keyCode: number, location: number): number | null { - return get_keysym(keycodeKeysyms[keyCode], location); - } - - function key_identifier_sane(keyCode: number, keyIdentifier: string): boolean { - if (!keyIdentifier) return false; - const unicodePrefixLocation = keyIdentifier.indexOf("U+"); - if (unicodePrefixLocation === -1) return true; - - const codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation + 2), 16); - if (keyCode !== codepoint) return true; - if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57)) return true; - return false; - } - - export function OSK_buttonToKeysym(button: string): number | null { - const keyMapping = OSK_keyMappings.find((mapping) => mapping.includes(button)); - if (keyMapping) { - const [, keyCode, keyIdentifier, key, location] = keyMapping; - return GetKeysym(keyCode, key, location); - } - return null; - } +export default function GetKeysym(keyCode: number, key: string, location: number): number | null { + let keysym = keysym_from_key_identifier(key, location) || keysym_from_keycode(keyCode, location); + return keysym; +} + +function keysym_from_key_identifier(identifier: string, location: number): number | null { + if (!identifier) return null; + + let typedCharacter: string | undefined; + + // If identifier is U+xxxx, decode Unicode character + const unicodePrefixLocation = identifier.indexOf('U+'); + if (unicodePrefixLocation >= 0) { + const hex = identifier.substring(unicodePrefixLocation + 2); + typedCharacter = String.fromCharCode(parseInt(hex, 16)); + } else if (identifier.length === 1) typedCharacter = identifier; + else return get_keysym(keyidentifier_keysym[identifier], location); + + if (!typedCharacter) return null; + + const codepoint = typedCharacter.charCodeAt(0); + return keysym_from_charcode(codepoint); +} + +function get_keysym(keysyms: number[] | null, location: number): number | null { + if (!keysyms) return null; + return keysyms[location] || keysyms[0]; +} + +function keysym_from_charcode(codepoint: number): number | null { + if (isControlCharacter(codepoint)) return 0xff00 | codepoint; + if (codepoint >= 0x0000 && codepoint <= 0x00ff) return codepoint; + if (codepoint >= 0x0100 && codepoint <= 0x10ffff) return 0x01000000 | codepoint; + return null; +} + +function isControlCharacter(codepoint: number): boolean { + return codepoint <= 0x1f || (codepoint >= 0x7f && codepoint <= 0x9f); +} + +function keysym_from_keycode(keyCode: number, location: number): number | null { + return get_keysym(keycodeKeysyms[keyCode], location); +} + +function key_identifier_sane(keyCode: number, keyIdentifier: string): boolean { + if (!keyIdentifier) return false; + const unicodePrefixLocation = keyIdentifier.indexOf('U+'); + if (unicodePrefixLocation === -1) return true; + + const codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation + 2), 16); + if (keyCode !== codepoint) return true; + if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57)) return true; + return false; +} + +export function OSK_buttonToKeysym(button: string): number | null { + const keyMapping = OSK_keyMappings.find((mapping) => mapping.includes(button)); + if (keyMapping) { + const [, keyCode, keyIdentifier, key, location] = keyMapping; + return GetKeysym(keyCode, key, location); + } + return null; +} interface KeyIdentifierKeysym { - [key: string]: number[] | null; + [key: string]: number[] | null; } interface KeyCodeKeysyms { - [key: number]: (number[] | null); + [key: number]: number[] | null; } var keycodeKeysyms: 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) + 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: KeyIdentifierKeysym = { - "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] + 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] }; -const OSK_keyMappings: [string, number, string, string, number][] = [ - ["!", 49, "Digit1", "!", 0], - ["#", 51, "Digit3", "#", 0], - ["$", 52, "Digit4", "$", 0], - ["%", 53, "Digit5", "%", 0], - ["&", 55, "Digit7", "&", 0], - ["'", 222, "Quote", "'", 0], - ["(", 57, "Digit9", "(", 0], - [")", 48, "Digit0", ")", 0], - ["*", 56, "Digit8", "*", 0], - ["+", 187, "Equal", "+", 0], - [",", 188, "Comma", ",", 0], - ["-", 189, "Minus", "-", 0], - [".", 190, "Period", ".", 0], - ["/", 191, "Slash", "/", 0], - ["0", 48, "Digit0", "0", 0], - ["1", 49, "Digit1", "1", 0], - ["2", 50, "Digit2", "2", 0], - ["3", 51, "Digit3", "3", 0], - ["4", 52, "Digit4", "4", 0], - ["5", 53, "Digit5", "5", 0], - ["6", 54, "Digit6", "6", 0], - ["7", 55, "Digit7", "7", 0], - ["8", 56, "Digit8", "8", 0], - ["9", 57, "Digit9", "9", 0], - [":", 186, "Semicolon", ":", 0], - [";", 186, "Semicolon", ";", 0], - ["<", 188, "Comma", "<", 0], - ["=", 187, "Equal", "=", 0], - [">", 190, "Period", ">", 0], - ["?", 191, "Slash", "?", 0], - ["@", 50, "Digit2", "@", 0], - ["A", 65, "KeyA", "A", 0], - ["B", 66, "KeyB", "B", 0], - ["C", 67, "KeyC", "C", 0], - ["D", 68, "KeyD", "D", 0], - ["E", 69, "KeyE", "E", 0], - ["F", 70, "KeyF", "F", 0], - ["G", 71, "KeyG", "G", 0], - ["H", 72, "KeyH", "H", 0], - ["I", 73, "KeyI", "I", 0], - ["J", 74, "KeyJ", "J", 0], - ["K", 75, "KeyK", "K", 0], - ["L", 76, "KeyL", "L", 0], - ["M", 77, "KeyM", "M", 0], - ["N", 78, "KeyN", "N", 0], - ["O", 79, "KeyO", "O", 0], - ["P", 80, "KeyP", "P", 0], - ["Q", 81, "KeyQ", "Q", 0], - ["R", 82, "KeyR", "R", 0], - ["S", 83, "KeyS", "S", 0], - ["T", 84, "KeyT", "T", 0], - ["U", 85, "KeyU", "U", 0], - ["V", 86, "KeyV", "V", 0], - ["W", 87, "KeyW", "W", 0], - ["X", 88, "KeyX", "X", 0], - ["Y", 89, "KeyY", "Y", 0], - ["Z", 90, "KeyZ", "Z", 0], - ["[", 219, "BracketLeft", "[", 0], - ["\\", 220, "Backslash", "\\", 0], - ["]", 221, "BracketRight", "]", 0], - ["^", 54, "Digit6", "^", 0], - ["_", 189, "Minus", "_", 0], - ["`", 192, "Backquote", "`", 0], - ["a", 65, "KeyA", "a", 0], - ["b", 66, "KeyB", "b", 0], - ["c", 67, "KeyC", "c", 0], - ["d", 68, "KeyD", "d", 0], - ["e", 69, "KeyE", "e", 0], - ["f", 70, "KeyF", "f", 0], - ["g", 71, "KeyG", "g", 0], - ["h", 72, "KeyH", "h", 0], - ["i", 73, "KeyI", "i", 0], - ["j", 74, "KeyJ", "j", 0], - ["k", 75, "KeyK", "k", 0], - ["l", 76, "KeyL", "l", 0], - ["m", 77, "KeyM", "m", 0], - ["n", 78, "KeyN", "n", 0], - ["o", 79, "KeyO", "o", 0], - ["p", 80, "KeyP", "p", 0], - ["q", 81, "KeyQ", "q", 0], - ["r", 82, "KeyR", "r", 0], - ["s", 83, "KeyS", "s", 0], - ["t", 84, "KeyT", "t", 0], - ["u", 85, "KeyU", "u", 0], - ["v", 86, "KeyV", "v", 0], - ["w", 87, "KeyW", "w", 0], - ["x", 88, "KeyX", "x", 0], - ["y", 89, "KeyY", "y", 0], - ["z", 90, "KeyZ", "z", 0], - ["{", 219, "BracketLeft", "{", 0], - ["{altleft}", 18, "AltLeft", "AltLeft", 1], - ["{altright}", 18, "AltRight", "AltRight", 2], - ["{arrowdown}", 40, "ArrowDown", "ArrowDown", 0], - ["{arrowleft}", 37, "ArrowLeft", "ArrowLeft", 0], - ["{arrowright}", 39, "ArrowRight", "ArrowRight", 0], - ["{arrowup}", 38, "ArrowUp", "ArrowUp", 0], - ["{backspace}", 8, "Backspace", "Backspace", 0], - ["{capslock}", 20, "CapsLock", "CapsLock", 0], - ["{controlleft}", 17, "ControlLeft", "ControlLeft", 1], - ["{controlright}", 17, "ControlRight", "ControlRight", 2], - ["{delete}", 46, "Delete", "Delete", 0], - ["{end}", 35, "End", "End", 0], - ["{enter}", 13, "Enter", "Enter", 0], - ["{escape}", 27, "Escape", "Escape", 0], - ["{f10}", 121, "F10", "F10", 0], - ["{f11}", 122, "F11", "F11", 0], - ["{f12}", 123, "F12", "F12", 0], - ["{f1}", 112, "F1", "F1", 0], - ["{f2}", 113, "F2", "F2", 0], - ["{f3}", 114, "F3", "F3", 0], - ["{f4}", 115, "F4", "F4", 0], - ["{f5}", 116, "F5", "F5", 0], - ["{f6}", 117, "F6", "F6", 0], - ["{f7}", 118, "F7", "F7", 0], - ["{f8}", 119, "F8", "F8", 0], - ["{f9}", 120, "F9", "F9", 0], - ["{home}", 36, "Home", "Home", 0], - ["{insert}", 45, "Insert", "Insert", 0], - ["{metaleft}", 91, "OSLeft", "OSLeft", 1], - ["{metaright}", 92, "OSRight", "OSRight", 2], - ["{numlock}", 144, "NumLock", "NumLock", 0], - ["{numpad0}", 96, "Numpad0", "Numpad0", 3], - ["{numpad1}", 97, "Numpad1", "Numpad1", 3], - ["{numpad2}", 98, "Numpad2", "Numpad2", 3], - ["{numpad3}", 99, "Numpad3", "Numpad3", 3], - ["{numpad4}", 100, "Numpad4", "Numpad4", 3], - ["{numpad5}", 101, "Numpad5", "Numpad5", 3], - ["{numpad6}", 102, "Numpad6", "Numpad6", 3], - ["{numpad7}", 103, "Numpad7", "Numpad7", 3], - ["{numpad8}", 104, "Numpad8", "Numpad8", 3], - ["{numpad9}", 105, "Numpad9", "Numpad9", 3], - ["{numpadadd}", 107, "NumpadAdd", "NumpadAdd", 3], - ["{numpaddecimal}", 110, "NumpadDecimal", "NumpadDecimal", 3], - ["{numpaddivide}", 111, "NumpadDivide", "NumpadDivide", 3], - ["{numpadenter}", 13, "NumpadEnter", "NumpadEnter", 3], - ["{numpadmultiply}", 106, "NumpadMultiply", "NumpadMultiply", 3], - ["{numpadsubtract}", 109, "NumpadSubtract", "NumpadSubtract", 3], - ["{pagedown}", 34, "PageDown", "PageDown", 0], - ["{pageup}", 33, "PageUp", "PageUp", 0], - ["{pause}", 19, "Pause", "Pause", 0], - ["{prtscr}", 44, "PrintScreen", "PrintScreen", 0], - ["{scrolllock}", 145, "ScrollLock", "ScrollLock", 0], - ["{shiftleft}", 16, "ShiftLeft", "ShiftLeft", 1], - ["{shiftright}", 16, "ShiftRight", "ShiftRight", 2], - ["{space}", 32, "Space", "Space", 0], - ["{tab}", 9, "Tab", "Tab", 0], - ["|", 220, "Backslash", "|", 0], - ["}", 221, "BracketRight", "}", 0], - ["~", 192, "Backquote", "~", 0], - ['"', 222, "Quote", '"', 0] +const OSK_keyMappings: [string, number, string, string, number][] = [ + ['!', 49, 'Digit1', '!', 0], + ['#', 51, 'Digit3', '#', 0], + ['$', 52, 'Digit4', '$', 0], + ['%', 53, 'Digit5', '%', 0], + ['&', 55, 'Digit7', '&', 0], + ["'", 222, 'Quote', "'", 0], + ['(', 57, 'Digit9', '(', 0], + [')', 48, 'Digit0', ')', 0], + ['*', 56, 'Digit8', '*', 0], + ['+', 187, 'Equal', '+', 0], + [',', 188, 'Comma', ',', 0], + ['-', 189, 'Minus', '-', 0], + ['.', 190, 'Period', '.', 0], + ['/', 191, 'Slash', '/', 0], + ['0', 48, 'Digit0', '0', 0], + ['1', 49, 'Digit1', '1', 0], + ['2', 50, 'Digit2', '2', 0], + ['3', 51, 'Digit3', '3', 0], + ['4', 52, 'Digit4', '4', 0], + ['5', 53, 'Digit5', '5', 0], + ['6', 54, 'Digit6', '6', 0], + ['7', 55, 'Digit7', '7', 0], + ['8', 56, 'Digit8', '8', 0], + ['9', 57, 'Digit9', '9', 0], + [':', 186, 'Semicolon', ':', 0], + [';', 186, 'Semicolon', ';', 0], + ['<', 188, 'Comma', '<', 0], + ['=', 187, 'Equal', '=', 0], + ['>', 190, 'Period', '>', 0], + ['?', 191, 'Slash', '?', 0], + ['@', 50, 'Digit2', '@', 0], + ['A', 65, 'KeyA', 'A', 0], + ['B', 66, 'KeyB', 'B', 0], + ['C', 67, 'KeyC', 'C', 0], + ['D', 68, 'KeyD', 'D', 0], + ['E', 69, 'KeyE', 'E', 0], + ['F', 70, 'KeyF', 'F', 0], + ['G', 71, 'KeyG', 'G', 0], + ['H', 72, 'KeyH', 'H', 0], + ['I', 73, 'KeyI', 'I', 0], + ['J', 74, 'KeyJ', 'J', 0], + ['K', 75, 'KeyK', 'K', 0], + ['L', 76, 'KeyL', 'L', 0], + ['M', 77, 'KeyM', 'M', 0], + ['N', 78, 'KeyN', 'N', 0], + ['O', 79, 'KeyO', 'O', 0], + ['P', 80, 'KeyP', 'P', 0], + ['Q', 81, 'KeyQ', 'Q', 0], + ['R', 82, 'KeyR', 'R', 0], + ['S', 83, 'KeyS', 'S', 0], + ['T', 84, 'KeyT', 'T', 0], + ['U', 85, 'KeyU', 'U', 0], + ['V', 86, 'KeyV', 'V', 0], + ['W', 87, 'KeyW', 'W', 0], + ['X', 88, 'KeyX', 'X', 0], + ['Y', 89, 'KeyY', 'Y', 0], + ['Z', 90, 'KeyZ', 'Z', 0], + ['[', 219, 'BracketLeft', '[', 0], + ['\\', 220, 'Backslash', '\\', 0], + [']', 221, 'BracketRight', ']', 0], + ['^', 54, 'Digit6', '^', 0], + ['_', 189, 'Minus', '_', 0], + ['`', 192, 'Backquote', '`', 0], + ['a', 65, 'KeyA', 'a', 0], + ['b', 66, 'KeyB', 'b', 0], + ['c', 67, 'KeyC', 'c', 0], + ['d', 68, 'KeyD', 'd', 0], + ['e', 69, 'KeyE', 'e', 0], + ['f', 70, 'KeyF', 'f', 0], + ['g', 71, 'KeyG', 'g', 0], + ['h', 72, 'KeyH', 'h', 0], + ['i', 73, 'KeyI', 'i', 0], + ['j', 74, 'KeyJ', 'j', 0], + ['k', 75, 'KeyK', 'k', 0], + ['l', 76, 'KeyL', 'l', 0], + ['m', 77, 'KeyM', 'm', 0], + ['n', 78, 'KeyN', 'n', 0], + ['o', 79, 'KeyO', 'o', 0], + ['p', 80, 'KeyP', 'p', 0], + ['q', 81, 'KeyQ', 'q', 0], + ['r', 82, 'KeyR', 'r', 0], + ['s', 83, 'KeyS', 's', 0], + ['t', 84, 'KeyT', 't', 0], + ['u', 85, 'KeyU', 'u', 0], + ['v', 86, 'KeyV', 'v', 0], + ['w', 87, 'KeyW', 'w', 0], + ['x', 88, 'KeyX', 'x', 0], + ['y', 89, 'KeyY', 'y', 0], + ['z', 90, 'KeyZ', 'z', 0], + ['{', 219, 'BracketLeft', '{', 0], + ['{altleft}', 18, 'AltLeft', 'AltLeft', 1], + ['{altright}', 18, 'AltRight', 'AltRight', 2], + ['{arrowdown}', 40, 'ArrowDown', 'ArrowDown', 0], + ['{arrowleft}', 37, 'ArrowLeft', 'ArrowLeft', 0], + ['{arrowright}', 39, 'ArrowRight', 'ArrowRight', 0], + ['{arrowup}', 38, 'ArrowUp', 'ArrowUp', 0], + ['{backspace}', 8, 'Backspace', 'Backspace', 0], + ['{capslock}', 20, 'CapsLock', 'CapsLock', 0], + ['{controlleft}', 17, 'ControlLeft', 'ControlLeft', 1], + ['{controlright}', 17, 'ControlRight', 'ControlRight', 2], + ['{delete}', 46, 'Delete', 'Delete', 0], + ['{end}', 35, 'End', 'End', 0], + ['{enter}', 13, 'Enter', 'Enter', 0], + ['{escape}', 27, 'Escape', 'Escape', 0], + ['{f10}', 121, 'F10', 'F10', 0], + ['{f11}', 122, 'F11', 'F11', 0], + ['{f12}', 123, 'F12', 'F12', 0], + ['{f1}', 112, 'F1', 'F1', 0], + ['{f2}', 113, 'F2', 'F2', 0], + ['{f3}', 114, 'F3', 'F3', 0], + ['{f4}', 115, 'F4', 'F4', 0], + ['{f5}', 116, 'F5', 'F5', 0], + ['{f6}', 117, 'F6', 'F6', 0], + ['{f7}', 118, 'F7', 'F7', 0], + ['{f8}', 119, 'F8', 'F8', 0], + ['{f9}', 120, 'F9', 'F9', 0], + ['{home}', 36, 'Home', 'Home', 0], + ['{insert}', 45, 'Insert', 'Insert', 0], + ['{metaleft}', 91, 'OSLeft', 'OSLeft', 1], + ['{metaright}', 92, 'OSRight', 'OSRight', 2], + ['{numlock}', 144, 'NumLock', 'NumLock', 0], + ['{numpad0}', 96, 'Numpad0', 'Numpad0', 3], + ['{numpad1}', 97, 'Numpad1', 'Numpad1', 3], + ['{numpad2}', 98, 'Numpad2', 'Numpad2', 3], + ['{numpad3}', 99, 'Numpad3', 'Numpad3', 3], + ['{numpad4}', 100, 'Numpad4', 'Numpad4', 3], + ['{numpad5}', 101, 'Numpad5', 'Numpad5', 3], + ['{numpad6}', 102, 'Numpad6', 'Numpad6', 3], + ['{numpad7}', 103, 'Numpad7', 'Numpad7', 3], + ['{numpad8}', 104, 'Numpad8', 'Numpad8', 3], + ['{numpad9}', 105, 'Numpad9', 'Numpad9', 3], + ['{numpadadd}', 107, 'NumpadAdd', 'NumpadAdd', 3], + ['{numpaddecimal}', 110, 'NumpadDecimal', 'NumpadDecimal', 3], + ['{numpaddivide}', 111, 'NumpadDivide', 'NumpadDivide', 3], + ['{numpadenter}', 13, 'NumpadEnter', 'NumpadEnter', 3], + ['{numpadmultiply}', 106, 'NumpadMultiply', 'NumpadMultiply', 3], + ['{numpadsubtract}', 109, 'NumpadSubtract', 'NumpadSubtract', 3], + ['{pagedown}', 34, 'PageDown', 'PageDown', 0], + ['{pageup}', 33, 'PageUp', 'PageUp', 0], + ['{pause}', 19, 'Pause', 'Pause', 0], + ['{prtscr}', 44, 'PrintScreen', 'PrintScreen', 0], + ['{scrolllock}', 145, 'ScrollLock', 'ScrollLock', 0], + ['{shiftleft}', 16, 'ShiftLeft', 'ShiftLeft', 1], + ['{shiftright}', 16, 'ShiftRight', 'ShiftRight', 2], + ['{space}', 32, 'Space', 'Space', 0], + ['{tab}', 9, 'Tab', 'Tab', 0], + ['|', 220, 'Backslash', '|', 0], + ['}', 221, 'BracketRight', '}', 0], + ['~', 192, 'Backquote', '~', 0], + ['"', 222, 'Quote', '"', 0] ]; diff --git a/src/ts/main.ts b/src/ts/main.ts index c5dabd8..2694aae 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -1,618 +1,620 @@ -import CollabVMClient from "./protocol/CollabVMClient.js"; -import VM from "./protocol/VM.js"; -import { Config } from "../../Config.js"; -import { Permissions, Rank } from "./protocol/Permissions.js"; -import { User } from "./protocol/User.js"; -import TurnStatus from "./protocol/TurnStatus.js"; -import Keyboard from "simple-keyboard"; -import { OSK_buttonToKeysym } from "./keyboard"; -import "simple-keyboard/build/css/index.css"; -import VoteStatus from "./protocol/VoteStatus.js"; -import * as bootstrap from "bootstrap"; -import MuteState from "./protocol/MuteState.js"; -import { Unsubscribe } from "nanoevents"; +import CollabVMClient from './protocol/CollabVMClient.js'; +import VM from './protocol/VM.js'; +import { Config } from '../../Config.js'; +import { Permissions, Rank } from './protocol/Permissions.js'; +import { User } from './protocol/User.js'; +import TurnStatus from './protocol/TurnStatus.js'; +import Keyboard from 'simple-keyboard'; +import { OSK_buttonToKeysym } from './keyboard'; +import 'simple-keyboard/build/css/index.css'; +import VoteStatus from './protocol/VoteStatus.js'; +import * as bootstrap from 'bootstrap'; +import MuteState from './protocol/MuteState.js'; +import { Unsubscribe } from 'nanoevents'; // Elements const w = window as any; const elements = { - vmlist: document.getElementById('vmlist') as HTMLDivElement, - vmview: document.getElementById('vmview') as HTMLDivElement, - vmDisplay: document.getElementById('vmDisplay') as HTMLDivElement, - homeBtn: document.getElementById('homeBtn') as HTMLAnchorElement, - chatList: document.getElementById('chatList') as HTMLTableSectionElement, - chatListDiv: document.getElementById('chatListDiv') as HTMLDivElement, - userlist: document.getElementById('userlist') as HTMLTableSectionElement, - onlineusercount: document.getElementById("onlineusercount") as HTMLSpanElement, - username: document.getElementById("username") as HTMLSpanElement, - chatinput: document.getElementById("chat-input") as HTMLInputElement, - sendChatBtn: document.getElementById("sendChatBtn") as HTMLButtonElement, - takeTurnBtn: document.getElementById("takeTurnBtn") as HTMLButtonElement, - changeUsernameBtn: document.getElementById("changeUsernameBtn") as HTMLButtonElement, - turnBtnText: document.getElementById("turnBtnText") as HTMLSpanElement, - turnstatus: document.getElementById("turnstatus") as HTMLParagraphElement, - osk: window.document.getElementById("oskBtn") as HTMLButtonElement, - oskContainer: document.getElementById("osk-container") as HTMLDivElement, - screenshotButton: document.getElementById("screenshotButton") as HTMLButtonElement, - voteResetButton: document.getElementById("voteResetButton") as HTMLButtonElement, - voteResetPanel: document.getElementById("voteResetPanel") as HTMLDivElement, - voteYesBtn: document.getElementById("voteYesBtn") as HTMLButtonElement, - voteNoBtn: document.getElementById("voteNoBtn") as HTMLButtonElement, - voteYesLabel: document.getElementById("voteYesLabel") as HTMLSpanElement, - voteNoLabel: document.getElementById("voteNoLabel") as HTMLSpanElement, - votetime: document.getElementById("votetime") as HTMLSpanElement, - loginModal: document.getElementById("loginModal") as HTMLDivElement, - adminPassword: document.getElementById("adminPassword") as HTMLInputElement, - loginButton: document.getElementById("loginButton") as HTMLButtonElement, - adminInputVMID: document.getElementById("adminInputVMID") as HTMLInputElement, - badPasswordAlert: document.getElementById("badPasswordAlert") as HTMLDivElement, - incorrectPasswordDismissBtn: document.getElementById("incorrectPasswordDismissBtn") as HTMLButtonElement, - ctrlAltDelBtn: document.getElementById("ctrlAltDelBtn") as HTMLButtonElement, - // Admin - staffbtns: document.getElementById("staffbtns") as HTMLDivElement, - restoreBtn: document.getElementById("restoreBtn") as HTMLButtonElement, - rebootBtn: document.getElementById("rebootBtn") as HTMLButtonElement, - clearQueueBtn: document.getElementById("clearQueueBtn") as HTMLButtonElement, - bypassTurnBtn: document.getElementById("bypassTurnBtn") as HTMLButtonElement, - endTurnBtn: document.getElementById("endTurnBtn") as HTMLButtonElement, - qemuMonitorBtn: document.getElementById("qemuMonitorBtn") as HTMLButtonElement, - xssCheckboxContainer: document.getElementById("xssCheckboxContainer") as HTMLDivElement, - xssCheckbox: document.getElementById("xssCheckbox") as HTMLInputElement, - forceVotePanel: document.getElementById("forceVotePanel") as HTMLDivElement, - forceVoteYesBtn: document.getElementById("forceVoteYesBtn") as HTMLButtonElement, - forceVoteNoBtn: document.getElementById("forceVoteNoBtn") as HTMLButtonElement, - indefTurnBtn: document.getElementById("indefTurnBtn") as HTMLButtonElement, - qemuMonitorInput: document.getElementById("qemuMonitorInput") as HTMLInputElement, - qemuMonitorSendBtn: document.getElementById("qemuMonitorSendBtn") as HTMLButtonElement, - qemuMonitorOutput: document.getElementById("qemuMonitorOutput") as HTMLTextAreaElement, -} + vmlist: document.getElementById('vmlist') as HTMLDivElement, + vmview: document.getElementById('vmview') as HTMLDivElement, + vmDisplay: document.getElementById('vmDisplay') as HTMLDivElement, + homeBtn: document.getElementById('homeBtn') as HTMLAnchorElement, + chatList: document.getElementById('chatList') as HTMLTableSectionElement, + chatListDiv: document.getElementById('chatListDiv') as HTMLDivElement, + userlist: document.getElementById('userlist') as HTMLTableSectionElement, + onlineusercount: document.getElementById('onlineusercount') as HTMLSpanElement, + username: document.getElementById('username') as HTMLSpanElement, + chatinput: document.getElementById('chat-input') as HTMLInputElement, + sendChatBtn: document.getElementById('sendChatBtn') as HTMLButtonElement, + takeTurnBtn: document.getElementById('takeTurnBtn') as HTMLButtonElement, + changeUsernameBtn: document.getElementById('changeUsernameBtn') as HTMLButtonElement, + turnBtnText: document.getElementById('turnBtnText') as HTMLSpanElement, + turnstatus: document.getElementById('turnstatus') as HTMLParagraphElement, + osk: window.document.getElementById('oskBtn') as HTMLButtonElement, + oskContainer: document.getElementById('osk-container') as HTMLDivElement, + screenshotButton: document.getElementById('screenshotButton') as HTMLButtonElement, + voteResetButton: document.getElementById('voteResetButton') as HTMLButtonElement, + voteResetPanel: document.getElementById('voteResetPanel') as HTMLDivElement, + voteYesBtn: document.getElementById('voteYesBtn') as HTMLButtonElement, + voteNoBtn: document.getElementById('voteNoBtn') as HTMLButtonElement, + voteYesLabel: document.getElementById('voteYesLabel') as HTMLSpanElement, + voteNoLabel: document.getElementById('voteNoLabel') as HTMLSpanElement, + votetime: document.getElementById('votetime') as HTMLSpanElement, + loginModal: document.getElementById('loginModal') as HTMLDivElement, + adminPassword: document.getElementById('adminPassword') as HTMLInputElement, + loginButton: document.getElementById('loginButton') as HTMLButtonElement, + adminInputVMID: document.getElementById('adminInputVMID') as HTMLInputElement, + badPasswordAlert: document.getElementById('badPasswordAlert') as HTMLDivElement, + incorrectPasswordDismissBtn: document.getElementById('incorrectPasswordDismissBtn') as HTMLButtonElement, + ctrlAltDelBtn: document.getElementById('ctrlAltDelBtn') as HTMLButtonElement, + // Admin + staffbtns: document.getElementById('staffbtns') as HTMLDivElement, + restoreBtn: document.getElementById('restoreBtn') as HTMLButtonElement, + rebootBtn: document.getElementById('rebootBtn') as HTMLButtonElement, + clearQueueBtn: document.getElementById('clearQueueBtn') as HTMLButtonElement, + bypassTurnBtn: document.getElementById('bypassTurnBtn') as HTMLButtonElement, + endTurnBtn: document.getElementById('endTurnBtn') as HTMLButtonElement, + qemuMonitorBtn: document.getElementById('qemuMonitorBtn') as HTMLButtonElement, + xssCheckboxContainer: document.getElementById('xssCheckboxContainer') as HTMLDivElement, + xssCheckbox: document.getElementById('xssCheckbox') as HTMLInputElement, + forceVotePanel: document.getElementById('forceVotePanel') as HTMLDivElement, + forceVoteYesBtn: document.getElementById('forceVoteYesBtn') as HTMLButtonElement, + forceVoteNoBtn: document.getElementById('forceVoteNoBtn') as HTMLButtonElement, + indefTurnBtn: document.getElementById('indefTurnBtn') as HTMLButtonElement, + qemuMonitorInput: document.getElementById('qemuMonitorInput') as HTMLInputElement, + qemuMonitorSendBtn: document.getElementById('qemuMonitorSendBtn') as HTMLButtonElement, + qemuMonitorOutput: document.getElementById('qemuMonitorOutput') as HTMLTextAreaElement +}; /* Start OSK */ let commonKeyboardOptions = { - onKeyPress: (button: string) => onKeyPress(button), - theme: "simple-keyboard hg-theme-default cvmDark cvmDisabled hg-layout-default", - syncInstanceInputs: true, - mergeDisplay: true - }; - - let keyboard = new Keyboard(".osk-main", { - ...commonKeyboardOptions, - layout: { - default: [ - "{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}", - "` 1 2 3 4 5 6 7 8 9 0 - = {backspace}", - "{tab} q w e r t y u i o p [ ] \\", - "{capslock} a s d f g h j k l ; ' {enter}", - "{shiftleft} z x c v b n m , . / {shiftright}", - "{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}" - ], - shift: [ - "{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}", - "~ ! @ # $ % ^ & * ( ) _ + {backspace}", - "{tab} Q W E R T Y U I O P { } |", - '{capslock} A S D F G H J K L : " {enter}', - "{shiftleft} Z X C V B N M < > ? {shiftright}", - "{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}" - ], - capslock: [ - "{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}", - "` 1 2 3 4 5 6 7 8 9 0 - = {backspace}", - "{tab} Q W E R T Y U I O P [ ] \\", - "{capslock} A S D F G H J K L ; ' {enter}", - "{shiftleft} Z X C V B N M , . / {shiftright}", - "{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}" - ], - shiftcaps: [ - "{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}", - "~ ! @ # $ % ^ & * ( ) _ + {backspace}", - "{tab} q w e r t y u i o p { } |", - '{capslock} a s d f g h j k l : " {enter}', - "{shiftleft} z x c v b n m < > ? {shiftright}", - "{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}" - ] - }, - display: { - "{escape}": "Esc", - "{tab}": "Tab", - "{backspace}": "Back", - "{enter}": "Enter", - "{capslock}": "Caps", - "{shiftleft}": "Shift", - "{shiftright}": "Shift", - "{controlleft}": "Ctrl", - "{controlright}": "Ctrl", - "{altleft}": "Alt", - "{altright}": "Alt", - "{metaleft}": "Super", - "{metaright}": "Menu" - } - }); - - let keyboardControlPad = new Keyboard(".osk-control", { - ...commonKeyboardOptions, - layout: { - default: [ - "{prtscr} {scrolllock} {pause}", - "{insert} {home} {pageup}", - "{delete} {end} {pagedown}" - ] - }, - display: { - "{prtscr}": "Print", - "{scrolllock}": "Scroll", - "{pause}": "Pause", - "{insert}": "Ins", - "{home}": "Home", - "{pageup}": "PgUp", - "{delete}": "Del", - "{end}": "End", - "{pagedown}": "PgDn", - } - }); - - let keyboardArrows = new Keyboard(".osk-arrows", { - ...commonKeyboardOptions, - layout: { - default: ["{arrowup}", "{arrowleft} {arrowdown} {arrowright}"] - } - }); - - let keyboardNumPad = new Keyboard(".osk-numpad", { - ...commonKeyboardOptions, - layout: { - default: [ - "{numlock} {numpaddivide} {numpadmultiply}", - "{numpad7} {numpad8} {numpad9}", - "{numpad4} {numpad5} {numpad6}", - "{numpad1} {numpad2} {numpad3}", - "{numpad0} {numpaddecimal}" - ] - } - }); - - let keyboardNumPadEnd = new Keyboard(".osk-numpadEnd", { - ...commonKeyboardOptions, - layout: { - default: ["{numpadsubtract}", "{numpadadd}", "{numpadenter}"] - } - }); - - let shiftHeld = false; - let ctrlHeld = false; - let capsHeld = false; - let altHeld = false; - let metaHeld = false; - - const setButtonBackground = (selectors: string, condition: boolean) => { - for(let button of document.querySelectorAll(selectors) as NodeListOf) { - button.style.backgroundColor = condition ? "#1c4995" : "rgba(0, 0, 0, 0.5)"; - }; - }; - - const enableOSK = (enable: boolean) => { - const theme = `simple-keyboard hg-theme-default cvmDark ${enable ? "" : "cvmDisabled"} hg-layout-default`; - [keyboard, keyboardControlPad, keyboardArrows, keyboardNumPad, keyboardNumPadEnd].forEach(part => { - part.setOptions({ - theme: theme, - }); - }); - - if(enable) updateOSKStyle(); - } - - const updateOSKStyle = () => { - setButtonBackground(".hg-button-shiftleft, .hg-button-shiftright", shiftHeld); - setButtonBackground(".hg-button-controlleft, .hg-button-controlright", ctrlHeld); - setButtonBackground(".hg-button-capslock", capsHeld); - setButtonBackground(".hg-button-altleft, .hg-button-altright", altHeld); - setButtonBackground(".hg-button-metaleft, .hg-button-metaright", metaHeld); - } - - - function onKeyPress(button: string) { - if (VM === null) return; - let keysym = OSK_buttonToKeysym(button); - if (!keysym) { - console.error(`no keysym for ${button}, report this!`); - return; - } - - switch (true) { - case button.startsWith("{shift"): - shiftHeld = !shiftHeld; - VM.key(keysym, shiftHeld); - break; - case button.startsWith("{control"): - ctrlHeld = !ctrlHeld; - VM.key(keysym, ctrlHeld); - break; - case button === "{capslock}": - capsHeld = !capsHeld; - VM.key(keysym, capsHeld); - break; - case button.startsWith("{alt"): - altHeld = !altHeld; - VM.key(keysym, altHeld); - break; - case button.startsWith("{meta"): - metaHeld = !metaHeld; - VM.key(keysym, metaHeld); - break; - default: - VM.key(keysym, true); - VM.key(keysym, false); - } - - keyboard.setOptions({ - layoutName: shiftHeld && capsHeld ? "shiftcaps" : shiftHeld ? "shift" : capsHeld ? "capslock" : "default" - }); - - updateOSKStyle(); - } - - /* End OSK */ + onKeyPress: (button: string) => onKeyPress(button), + theme: 'simple-keyboard hg-theme-default cvmDark cvmDisabled hg-layout-default', + syncInstanceInputs: true, + mergeDisplay: true +}; + +let keyboard = new Keyboard('.osk-main', { + ...commonKeyboardOptions, + layout: { + default: [ + '{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}', + '` 1 2 3 4 5 6 7 8 9 0 - = {backspace}', + '{tab} q w e r t y u i o p [ ] \\', + "{capslock} a s d f g h j k l ; ' {enter}", + '{shiftleft} z x c v b n m , . / {shiftright}', + '{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}' + ], + shift: [ + '{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}', + '~ ! @ # $ % ^ & * ( ) _ + {backspace}', + '{tab} Q W E R T Y U I O P { } |', + '{capslock} A S D F G H J K L : " {enter}', + '{shiftleft} Z X C V B N M < > ? {shiftright}', + '{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}' + ], + capslock: [ + '{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}', + '` 1 2 3 4 5 6 7 8 9 0 - = {backspace}', + '{tab} Q W E R T Y U I O P [ ] \\', + "{capslock} A S D F G H J K L ; ' {enter}", + '{shiftleft} Z X C V B N M , . / {shiftright}', + '{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}' + ], + shiftcaps: [ + '{escape} {f1} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {f10} {f11} {f12}', + '~ ! @ # $ % ^ & * ( ) _ + {backspace}', + '{tab} q w e r t y u i o p { } |', + '{capslock} a s d f g h j k l : " {enter}', + '{shiftleft} z x c v b n m < > ? {shiftright}', + '{controlleft} {metaleft} {altleft} {space} {altright} {metaright} {controlright}' + ] + }, + display: { + '{escape}': 'Esc', + '{tab}': 'Tab', + '{backspace}': 'Back', + '{enter}': 'Enter', + '{capslock}': 'Caps', + '{shiftleft}': 'Shift', + '{shiftright}': 'Shift', + '{controlleft}': 'Ctrl', + '{controlright}': 'Ctrl', + '{altleft}': 'Alt', + '{altright}': 'Alt', + '{metaleft}': 'Super', + '{metaright}': 'Menu' + } +}); + +let keyboardControlPad = new Keyboard('.osk-control', { + ...commonKeyboardOptions, + layout: { + default: ['{prtscr} {scrolllock} {pause}', '{insert} {home} {pageup}', '{delete} {end} {pagedown}'] + }, + display: { + '{prtscr}': 'Print', + '{scrolllock}': 'Scroll', + '{pause}': 'Pause', + '{insert}': 'Ins', + '{home}': 'Home', + '{pageup}': 'PgUp', + '{delete}': 'Del', + '{end}': 'End', + '{pagedown}': 'PgDn' + } +}); + +let keyboardArrows = new Keyboard('.osk-arrows', { + ...commonKeyboardOptions, + layout: { + default: ['{arrowup}', '{arrowleft} {arrowdown} {arrowright}'] + } +}); + +let keyboardNumPad = new Keyboard('.osk-numpad', { + ...commonKeyboardOptions, + layout: { + default: ['{numlock} {numpaddivide} {numpadmultiply}', '{numpad7} {numpad8} {numpad9}', '{numpad4} {numpad5} {numpad6}', '{numpad1} {numpad2} {numpad3}', '{numpad0} {numpaddecimal}'] + } +}); + +let keyboardNumPadEnd = new Keyboard('.osk-numpadEnd', { + ...commonKeyboardOptions, + layout: { + default: ['{numpadsubtract}', '{numpadadd}', '{numpadenter}'] + } +}); + +let shiftHeld = false; +let ctrlHeld = false; +let capsHeld = false; +let altHeld = false; +let metaHeld = false; + +const setButtonBackground = (selectors: string, condition: boolean) => { + for (let button of document.querySelectorAll(selectors) as NodeListOf) { + button.style.backgroundColor = condition ? '#1c4995' : 'rgba(0, 0, 0, 0.5)'; + } +}; + +const enableOSK = (enable: boolean) => { + const theme = `simple-keyboard hg-theme-default cvmDark ${enable ? '' : 'cvmDisabled'} hg-layout-default`; + [keyboard, keyboardControlPad, keyboardArrows, keyboardNumPad, keyboardNumPadEnd].forEach((part) => { + part.setOptions({ + theme: theme + }); + }); + + if (enable) updateOSKStyle(); +}; + +const updateOSKStyle = () => { + setButtonBackground('.hg-button-shiftleft, .hg-button-shiftright', shiftHeld); + setButtonBackground('.hg-button-controlleft, .hg-button-controlright', ctrlHeld); + setButtonBackground('.hg-button-capslock', capsHeld); + setButtonBackground('.hg-button-altleft, .hg-button-altright', altHeld); + setButtonBackground('.hg-button-metaleft, .hg-button-metaright', metaHeld); +}; + +function onKeyPress(button: string) { + if (VM === null) return; + let keysym = OSK_buttonToKeysym(button); + if (!keysym) { + console.error(`no keysym for ${button}, report this!`); + return; + } + + switch (true) { + case button.startsWith('{shift'): + shiftHeld = !shiftHeld; + VM.key(keysym, shiftHeld); + break; + case button.startsWith('{control'): + ctrlHeld = !ctrlHeld; + VM.key(keysym, ctrlHeld); + break; + case button === '{capslock}': + capsHeld = !capsHeld; + VM.key(keysym, capsHeld); + break; + case button.startsWith('{alt'): + altHeld = !altHeld; + VM.key(keysym, altHeld); + break; + case button.startsWith('{meta'): + metaHeld = !metaHeld; + VM.key(keysym, metaHeld); + break; + default: + VM.key(keysym, true); + VM.key(keysym, false); + } + + keyboard.setOptions({ + layoutName: shiftHeld && capsHeld ? 'shiftcaps' : shiftHeld ? 'shift' : capsHeld ? 'capslock' : 'default' + }); + + updateOSKStyle(); +} + +/* End OSK */ var expectedClose = false; var turn = -1; // Listed VMs -const vms : VM[] = []; -const cards : HTMLDivElement[] = []; -const users : { - user : User, - element : HTMLTableRowElement +const vms: VM[] = []; +const cards: HTMLDivElement[] = []; +const users: { + user: User; + element: HTMLTableRowElement; }[] = []; -var turnInterval : number | undefined = undefined; -var voteInterval : number | undefined = undefined; +var turnInterval: number | undefined = undefined; +var voteInterval: number | undefined = undefined; var turnTimer = 0; var voteTimer = 0; -var rank : Rank = Rank.Unregistered; -var perms : Permissions = new Permissions(0); +var rank: Rank = Rank.Unregistered; +var perms: Permissions = new Permissions(0); const chatsound = new Audio(Config.ChatSound); // Active VM -var VM : CollabVMClient | null = null; +var VM: CollabVMClient | null = null; -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(); - // Get the number of online users - var online = client.getUsers().length; - // Close the client - client.close(); - // 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; - var usersOnline = document.createElement("span"); - usersOnline.innerHTML = `( ${online})`; - cardBody.appendChild(cardTitle); - cardBody.appendChild(usersOnline); - card.appendChild(vm.thumbnail); - card.appendChild(cardBody); - div.appendChild(card); - cards.push(div); - sortVMList(); - } - res(); - }); +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(); + // Get the number of online users + var online = client.getUsers().length; + // Close the client + client.close(); + // 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; + var usersOnline = document.createElement('span'); + usersOnline.innerHTML = `( ${online})`; + cardBody.appendChild(cardTitle); + cardBody.appendChild(usersOnline); + card.appendChild(vm.thumbnail); + card.appendChild(cardBody); + div.appendChild(card); + cards.push(div); + sortVMList(); + } + res(); + }); } -function openVM(vm : VM) { - return new Promise(async (res, rej) => { - // If there's an active VM it must be closed before opening another - if (VM !== null) return; - expectedClose = false; - // Set hash - location.hash = vm.id; - // Create the client - VM = new CollabVMClient(vm.url); - // Register event listeners - // An array to keep track of all listeners, and remove them when the VM is closed. Might not be necessary, but it's good practice. - var listeners : Unsubscribe[] = []; - listeners.push(VM!.on('chat', (username, message) => chatMessage(username, message))); - listeners.push(VM!.on('adduser', (user) => addUser(user))); - listeners.push(VM!.on('remuser', (user) => remUser(user))); - listeners.push(VM!.on('rename', (oldname, newname, selfrename) => userRenamed(oldname, newname, selfrename))); - listeners.push(VM!.on('renamestatus', (status) => { - switch (status) { - case 'taken': alert("That username is already taken"); break; - case 'invalid': alert("Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters."); break; - case 'blacklisted': alert("That username has been blacklisted."); break; - } - })); - listeners.push(VM!.on('turn', status => turnUpdate(status))); - listeners.push(VM!.on('vote', (status : VoteStatus) => voteUpdate(status))); - listeners.push(VM!.on('voteend', () => voteEnd())); - listeners.push(VM!.on('votecd', cd => window.alert(`Please wait ${cd} seconds before starting another vote.`))); - listeners.push(VM!.on('login', (rank : Rank, perms : Permissions) => onLogin(rank, perms))); - listeners.push(VM!.on('close', () => { - if (!expectedClose) alert("You have been disconnected from the server"); - for (var l of listeners) l(); - closeVM(); - })); - // Wait for the client to open - await new Promise(res => VM!.on('open', () => res())); - // Connect to node - chatMessage("", `${vm.id}
`); - var username = localStorage.getItem("username"); - var connected = await VM.connect(vm.id, username); - elements.adminInputVMID.value = vm.id; - w.VMName = vm.id; - if (!connected) { - VM.close(); - VM = null; - rej("Failed to connect to node"); - } - // Set the title - document.title = vm.id + " - CollabVM"; - // Append canvas - elements.vmDisplay.appendChild(VM!.canvas); - // Switch to the VM view - elements.vmlist.style.display = "none"; - elements.vmview.style.display = "block"; - }); + +function openVM(vm: VM) { + return new Promise(async (res, rej) => { + // If there's an active VM it must be closed before opening another + if (VM !== null) return; + expectedClose = false; + // Set hash + location.hash = vm.id; + // Create the client + VM = new CollabVMClient(vm.url); + // Register event listeners + + // An array of nanoevent unsubscribe callbacks. These are called when the VM is closed to cleanup nanoevent state. + var unsubscribeCallbacks: Unsubscribe[] = []; + + unsubscribeCallbacks.push(VM!.on('chat', (username, message) => chatMessage(username, message))); + unsubscribeCallbacks.push(VM!.on('adduser', (user) => addUser(user))); + unsubscribeCallbacks.push(VM!.on('remuser', (user) => remUser(user))); + unsubscribeCallbacks.push(VM!.on('rename', (oldname, newname, selfrename) => userRenamed(oldname, newname, selfrename))); + unsubscribeCallbacks.push( + VM!.on('renamestatus', (status) => { + switch (status) { + case 'taken': + alert('That username is already taken'); + break; + case 'invalid': + alert('Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters.'); + break; + case 'blacklisted': + alert('That username has been blacklisted.'); + break; + } + }) + ); + unsubscribeCallbacks.push(VM!.on('turn', (status) => turnUpdate(status))); + unsubscribeCallbacks.push(VM!.on('vote', (status: VoteStatus) => voteUpdate(status))); + unsubscribeCallbacks.push(VM!.on('voteend', () => voteEnd())); + unsubscribeCallbacks.push(VM!.on('votecd', (cd) => window.alert(`Please wait ${cd} seconds before starting another vote.`))); + unsubscribeCallbacks.push(VM!.on('login', (rank: Rank, perms: Permissions) => onLogin(rank, perms))); + unsubscribeCallbacks.push( + VM!.on('close', () => { + if (!expectedClose) alert('You have been disconnected from the server'); + + // Call all the unsubscribe callbacks. + for (var l of unsubscribeCallbacks) l(); + unsubscribeCallbacks = []; + closeVM(); + }) + ); + // Wait for the client to open + await new Promise((res) => VM!.on('open', () => res())); + // Connect to node + chatMessage('', `${vm.id}
`); + var username = localStorage.getItem('username'); + var connected = await VM.connect(vm.id, username); + elements.adminInputVMID.value = vm.id; + w.VMName = vm.id; + if (!connected) { + VM.close(); + VM = null; + rej('Failed to connect to node'); + } + // Set the title + document.title = vm.id + ' - CollabVM'; + // Append canvas + elements.vmDisplay.appendChild(VM!.canvas); + // Switch to the VM view + elements.vmlist.style.display = 'none'; + elements.vmview.style.display = 'block'; + }); } function closeVM() { - if (VM === null) return; - expectedClose = true; - // Close the VM - VM.close(); - VM = null; - document.title = "CollabVM"; - turn = -1; - // Remove the canvas - elements.vmDisplay.innerHTML = ""; - // Switch to the VM list - elements.vmlist.style.display = "block"; - elements.vmview.style.display = "none"; - // Clear users - users.splice(0, users.length); - elements.userlist.innerHTML = ""; - rank = Rank.Unregistered; - perms = new Permissions(0); - w.VMName = null; - // Reset admin and vote panels - elements.staffbtns.style.display = "none"; - elements.restoreBtn.style.display = "none"; - elements.rebootBtn.style.display = "none"; - elements.bypassTurnBtn.style.display = "none"; - elements.endTurnBtn.style.display = "none"; - elements.clearQueueBtn.style.display = "none"; - elements.qemuMonitorBtn.style.display = "none"; - elements.indefTurnBtn.style.display = "none"; - elements.xssCheckboxContainer.style.display = "none"; - elements.forceVotePanel.style.display = "none"; - elements.voteResetPanel.style.display = "none"; - elements.voteYesLabel.innerText = "0"; - elements.voteNoLabel.innerText = "0"; - elements.xssCheckbox.checked = false; - elements.username.classList.remove("username-admin", "username-moderator"); - elements.username.classList.add("text-light"); + if (VM === null) return; + expectedClose = true; + // Close the VM + VM.close(); + VM = null; + document.title = 'CollabVM'; + turn = -1; + // Remove the canvas + elements.vmDisplay.innerHTML = ''; + // Switch to the VM list + elements.vmlist.style.display = 'block'; + elements.vmview.style.display = 'none'; + // Clear users + users.splice(0, users.length); + elements.userlist.innerHTML = ''; + rank = Rank.Unregistered; + perms = new Permissions(0); + w.VMName = null; + // Reset admin and vote panels + elements.staffbtns.style.display = 'none'; + elements.restoreBtn.style.display = 'none'; + elements.rebootBtn.style.display = 'none'; + elements.bypassTurnBtn.style.display = 'none'; + elements.endTurnBtn.style.display = 'none'; + elements.clearQueueBtn.style.display = 'none'; + elements.qemuMonitorBtn.style.display = 'none'; + elements.indefTurnBtn.style.display = 'none'; + elements.xssCheckboxContainer.style.display = 'none'; + elements.forceVotePanel.style.display = 'none'; + elements.voteResetPanel.style.display = 'none'; + elements.voteYesLabel.innerText = '0'; + elements.voteNoLabel.innerText = '0'; + elements.xssCheckbox.checked = false; + elements.username.classList.remove('username-admin', 'username-moderator'); + elements.username.classList.add('text-light'); } function loadList() { - return new Promise(async res => { - var p = []; - for (var url of Config.ServerAddresses) { - p.push(multicollab(url)); - } - await Promise.all(p); - var v = vms.find(v => v.id === window.location.hash.substring(1)); - if (v !== undefined) openVM(v); - res(); - }); + return new Promise(async (res) => { + var p = []; + for (var url of Config.ServerAddresses) { + p.push(multicollab(url)); + } + await Promise.all(p); + var v = vms.find((v) => v.id === window.location.hash.substring(1)); + if (v !== undefined) openVM(v); + res(); + }); } function sortVMList() { - cards.sort(function(a, b) { - return a.children[0].getAttribute("data-cvm-node")! > b.children[0].getAttribute("data-cvm-node")! ? 1 : -1; - }); - elements.vmlist.children[0].innerHTML = ""; - cards.forEach((c) => elements.vmlist.children[0].appendChild(c)); + cards.sort(function (a, b) { + return a.children[0].getAttribute('data-cvm-node')! > b.children[0].getAttribute('data-cvm-node')! ? 1 : -1; + }); + elements.vmlist.children[0].innerHTML = ''; + cards.forEach((c) => elements.vmlist.children[0].appendChild(c)); } function sortUserList() { - users.sort((a, b) => { - if (a.user.username === w.username && (a.user.turn >= b.user.turn) && b.user.turn !== 0) return -1; - if (b.user.username === w.username && (b.user.turn >= a.user.turn) && a.user.turn !== 0) return 1; - if (a.user.turn === b.user.turn) return 0; - if (a.user.turn === -1) return 1; - if (b.user.turn === -1) return -1; - if (a.user.turn < b.user.turn) return -1; - else return 1; - }); - for (const user of users) { - elements.userlist.removeChild(user.element); - elements.userlist.appendChild(user.element); - } + users.sort((a, b) => { + if (a.user.username === w.username && a.user.turn >= b.user.turn && b.user.turn !== 0) return -1; + if (b.user.username === w.username && b.user.turn >= a.user.turn && a.user.turn !== 0) return 1; + if (a.user.turn === b.user.turn) return 0; + if (a.user.turn === -1) return 1; + if (b.user.turn === -1) return -1; + if (a.user.turn < b.user.turn) return -1; + else return 1; + }); + for (const user of users) { + elements.userlist.removeChild(user.element); + elements.userlist.appendChild(user.element); + } } -function chatMessage(username : string, message : string) { - var tr = document.createElement('tr'); - var td = document.createElement('td'); - // System message - if (username === "") td.innerHTML = message; - else { - var user = VM!.getUsers().find(u => u.username === username); - var rank; - if (user !== undefined) rank = user.rank; - else rank = Rank.Unregistered; - var userclass; - var msgclass; - switch (rank) { - case Rank.Unregistered: - userclass = "chat-username-unregistered"; - msgclass = "chat-unregistered"; - break; - case Rank.Admin: - userclass = "chat-username-admin"; - msgclass = "chat-admin"; - break; - case Rank.Moderator: - userclass = "chat-username-moderator"; - msgclass = "chat-moderator"; - break; - } - tr.classList.add(msgclass); - td.innerHTML = `${username}▸ ${message}`; - // hacky way to allow scripts - Array.prototype.slice.call(td.children).forEach((curr) => { - if (curr.nodeName === "SCRIPT") { - eval(curr.text) - } - }); - } - tr.appendChild(td); - elements.chatList.appendChild(tr); - elements.chatListDiv.scrollTop = elements.chatListDiv.scrollHeight; - chatsound.play(); +function chatMessage(username: string, message: string) { + var tr = document.createElement('tr'); + var td = document.createElement('td'); + // System message + if (username === '') td.innerHTML = message; + else { + var user = VM!.getUsers().find((u) => u.username === username); + var rank; + if (user !== undefined) rank = user.rank; + else rank = Rank.Unregistered; + var userclass; + var msgclass; + switch (rank) { + case Rank.Unregistered: + userclass = 'chat-username-unregistered'; + msgclass = 'chat-unregistered'; + break; + case Rank.Admin: + userclass = 'chat-username-admin'; + msgclass = 'chat-admin'; + break; + case Rank.Moderator: + userclass = 'chat-username-moderator'; + msgclass = 'chat-moderator'; + break; + } + tr.classList.add(msgclass); + td.innerHTML = `${username}▸ ${message}`; + // hacky way to allow scripts + Array.prototype.slice.call(td.children).forEach((curr) => { + if (curr.nodeName === 'SCRIPT') { + eval(curr.text); + } + }); + } + tr.appendChild(td); + elements.chatList.appendChild(tr); + elements.chatListDiv.scrollTop = elements.chatListDiv.scrollHeight; + chatsound.play(); } -function addUser(user : User) { - var olduser = users.find(u => u.user === user); - if (olduser !== undefined) elements.userlist.removeChild(olduser.element); - var tr = document.createElement('tr'); - tr.setAttribute("data-cvm-turn", "-1"); - var td = document.createElement('td'); - td.innerHTML = user.username; - switch (user.rank) { - case Rank.Admin: - tr.classList.add("user-admin"); - break; - case Rank.Moderator: - tr.classList.add("user-moderator"); - break; - case Rank.Unregistered: - tr.classList.add("user-unregistered"); - break; - } - if (user.username === w.username) - tr.classList.add("user-current"); - tr.appendChild(td); - var u = {user: user, element: tr}; - if (rank !== Rank.Unregistered) userModOptions(u); - elements.userlist.appendChild(tr); - if (olduser !== undefined) olduser.element = tr; - else users.push(u); - elements.onlineusercount.innerHTML = VM!.getUsers().length.toString(); +function addUser(user: User) { + var olduser = users.find((u) => u.user === user); + if (olduser !== undefined) elements.userlist.removeChild(olduser.element); + var tr = document.createElement('tr'); + tr.setAttribute('data-cvm-turn', '-1'); + var td = document.createElement('td'); + td.innerHTML = user.username; + switch (user.rank) { + case Rank.Admin: + tr.classList.add('user-admin'); + break; + case Rank.Moderator: + tr.classList.add('user-moderator'); + break; + case Rank.Unregistered: + tr.classList.add('user-unregistered'); + break; + } + if (user.username === w.username) tr.classList.add('user-current'); + tr.appendChild(td); + var u = { user: user, element: tr }; + if (rank !== Rank.Unregistered) userModOptions(u); + elements.userlist.appendChild(tr); + if (olduser !== undefined) olduser.element = tr; + else users.push(u); + elements.onlineusercount.innerHTML = VM!.getUsers().length.toString(); } -function remUser(user : User) { - var olduser = users.findIndex(u => u.user === user); - if (olduser !== undefined) elements.userlist.removeChild(users[olduser].element); - elements.onlineusercount.innerHTML = VM!.getUsers().length.toString(); - users.splice(olduser, 1); +function remUser(user: User) { + var olduser = users.findIndex((u) => u.user === user); + if (olduser !== undefined) elements.userlist.removeChild(users[olduser].element); + elements.onlineusercount.innerHTML = VM!.getUsers().length.toString(); + users.splice(olduser, 1); } -function userRenamed(oldname : string, newname : string, selfrename : boolean) { - var user = users.find(u => u.user.username === newname); - if (user) { - user.element.children[0].innerHTML = newname; - } - if (selfrename) { - w.username = newname; - elements.username.innerText = newname; - localStorage.setItem("username", newname); - } +function userRenamed(oldname: string, newname: string, selfrename: boolean) { + var user = users.find((u) => u.user.username === newname); + if (user) { + user.element.children[0].innerHTML = newname; + } + if (selfrename) { + w.username = newname; + elements.username.innerText = newname; + localStorage.setItem('username', newname); + } } -function turnUpdate(status : TurnStatus) { - // Clear all turn data - turn = -1; - VM!.canvas.classList.remove("focused", "waiting"); - clearInterval(turnInterval); - turnTimer = 0; - for (const user of users) { - user.element.classList.remove("user-turn", "user-waiting"); - user.element.setAttribute("data-cvm-turn", "-1"); - } - elements.turnBtnText.innerHTML = "Take Turn"; - enableOSK(false); +function turnUpdate(status: TurnStatus) { + // Clear all turn data + turn = -1; + VM!.canvas.classList.remove('focused', 'waiting'); + clearInterval(turnInterval); + turnTimer = 0; + for (const user of users) { + user.element.classList.remove('user-turn', 'user-waiting'); + user.element.setAttribute('data-cvm-turn', '-1'); + } + elements.turnBtnText.innerHTML = 'Take Turn'; + enableOSK(false); - if (status.user !== null) { - var el = users.find(u => u.user === status.user)!.element; - el!.classList.add("user-turn"); - el!.setAttribute("data-cvm-turn", "0"); - } - for (const user of status.queue) { - var el = users.find(u => u.user === user)!.element; - el!.classList.add("user-waiting"); - el.setAttribute("data-cvm-turn", status.queue.indexOf(user).toString(10)) - } - if (status.user?.username === w.username) { - turn = 0; - turnTimer = status.turnTime! / 1000; - elements.turnBtnText.innerHTML = "End Turn"; - VM!.canvas.classList.add("focused"); - enableOSK(true); - } - if (status.queue.some(u => u.username === w.username)) { - turn = status.queue.findIndex(u => u.username === w.username) + 1; - turnTimer = status.queueTime! / 1000; - elements.turnBtnText.innerHTML = "End Turn"; - VM!.canvas.classList.add("waiting"); - } - if (turn === -1) elements.turnstatus.innerText = ""; - else { - turnInterval = setInterval(() => turnIntervalCb(), 1000); - setTurnStatus(); - } - sortUserList(); + if (status.user !== null) { + var el = users.find((u) => u.user === status.user)!.element; + el!.classList.add('user-turn'); + el!.setAttribute('data-cvm-turn', '0'); + } + for (const user of status.queue) { + var el = users.find((u) => u.user === user)!.element; + el!.classList.add('user-waiting'); + el.setAttribute('data-cvm-turn', status.queue.indexOf(user).toString(10)); + } + if (status.user?.username === w.username) { + turn = 0; + turnTimer = status.turnTime! / 1000; + elements.turnBtnText.innerHTML = 'End Turn'; + VM!.canvas.classList.add('focused'); + enableOSK(true); + } + if (status.queue.some((u) => u.username === w.username)) { + turn = status.queue.findIndex((u) => u.username === w.username) + 1; + turnTimer = status.queueTime! / 1000; + elements.turnBtnText.innerHTML = 'End Turn'; + VM!.canvas.classList.add('waiting'); + } + if (turn === -1) elements.turnstatus.innerText = ''; + else { + turnInterval = setInterval(() => turnIntervalCb(), 1000); + setTurnStatus(); + } + sortUserList(); } -function voteUpdate(status : VoteStatus) { - clearInterval(voteInterval); - elements.voteResetPanel.style.display = "block"; - elements.voteYesLabel.innerText = status.yesVotes.toString(); - elements.voteNoLabel.innerText = status.noVotes.toString(); - voteTimer = Math.floor(status.timeToEnd / 1000); - voteInterval = setInterval(() => updateVoteEndTime(), 1000); - updateVoteEndTime(); +function voteUpdate(status: VoteStatus) { + clearInterval(voteInterval); + elements.voteResetPanel.style.display = 'block'; + elements.voteYesLabel.innerText = status.yesVotes.toString(); + elements.voteNoLabel.innerText = status.noVotes.toString(); + voteTimer = Math.floor(status.timeToEnd / 1000); + voteInterval = setInterval(() => updateVoteEndTime(), 1000); + updateVoteEndTime(); } function updateVoteEndTime() { - voteTimer--; - elements.votetime.innerText = voteTimer.toString(); - if (voteTimer === 0) clearInterval(voteInterval); + voteTimer--; + elements.votetime.innerText = voteTimer.toString(); + if (voteTimer === 0) clearInterval(voteInterval); } function voteEnd() { - clearInterval(voteInterval); - elements.voteResetPanel.style.display = "none"; + clearInterval(voteInterval); + elements.voteResetPanel.style.display = 'none'; } function turnIntervalCb() { - turnTimer--; - setTurnStatus(); + turnTimer--; + setTurnStatus(); } function setTurnStatus() { - if (turn === 0) - elements.turnstatus.innerText = `Turn expires in ${turnTimer} seconds`; - else - elements.turnstatus.innerText = `Waiting for turn in ${turnTimer} seconds`; + if (turn === 0) elements.turnstatus.innerText = `Turn expires in ${turnTimer} seconds`; + else elements.turnstatus.innerText = `Waiting for turn in ${turnTimer} seconds`; } function sendChat() { - if (VM === null) return; - if (elements.xssCheckbox.checked) VM.xss(elements.chatinput.value); - else VM.chat(elements.chatinput.value); - elements.chatinput.value = ""; + if (VM === null) return; + if (elements.xssCheckbox.checked) VM.xss(elements.chatinput.value); + else VM.chat(elements.chatinput.value); + elements.chatinput.value = ''; } // Bind list buttons @@ -621,36 +623,36 @@ elements.homeBtn.addEventListener('click', () => closeVM()); // Bind VM view buttons elements.sendChatBtn.addEventListener('click', sendChat); elements.chatinput.addEventListener('keypress', (e) => { - if (e.key === "Enter") sendChat(); + if (e.key === 'Enter') sendChat(); }); elements.changeUsernameBtn.addEventListener('click', () => { - var newname = prompt("Enter new username, or leave blank to be assigned a guest username", w.username); - if (newname === w.username) return; - VM?.rename(newname); + var newname = prompt('Enter new username, or leave blank to be assigned a guest username', w.username); + if (newname === w.username) return; + VM?.rename(newname); }); elements.takeTurnBtn.addEventListener('click', () => { - VM?.turn(turn === -1); + VM?.turn(turn === -1); }); elements.screenshotButton.addEventListener('click', () => { - if (!VM) return; - VM.canvas.toBlob(blob => { - open(URL.createObjectURL(blob!), '_blank'); - }) + if (!VM) return; + VM.canvas.toBlob((blob) => { + open(URL.createObjectURL(blob!), '_blank'); + }); }); elements.ctrlAltDelBtn.addEventListener('click', () => { - if (!VM) return; - // 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); + if (!VM) return; + // 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); }); elements.voteResetButton.addEventListener('click', () => VM?.vote(true)); elements.voteYesBtn.addEventListener('click', () => VM?.vote(true)); @@ -660,182 +662,184 @@ var usernameClick = false; const loginModal = new bootstrap.Modal(elements.loginModal); elements.loginModal.addEventListener('shown.bs.modal', () => elements.adminPassword.focus()); elements.username.addEventListener('click', () => { - if (!usernameClick) { - usernameClick = true; - setInterval(() => usernameClick = false, 1000); - return; - } - loginModal.show(); + if (!usernameClick) { + usernameClick = true; + setInterval(() => (usernameClick = false), 1000); + return; + } + loginModal.show(); }); elements.loginButton.addEventListener('click', () => doLogin()); -elements.adminPassword.addEventListener('keypress', (e) => e.key === "Enter" && doLogin()); -elements.incorrectPasswordDismissBtn.addEventListener('click', () => elements.badPasswordAlert.style.display = "none"); +elements.adminPassword.addEventListener('keypress', (e) => e.key === 'Enter' && doLogin()); +elements.incorrectPasswordDismissBtn.addEventListener('click', () => (elements.badPasswordAlert.style.display = 'none')); function doLogin() { - var adminPass = elements.adminPassword.value; - if (adminPass === "") return; - VM?.login(adminPass); - elements.adminPassword.value = ""; - var u = VM?.on('login', () => { - u!(); - loginModal.hide(); - elements.badPasswordAlert.style.display = "none"; - }); - var _u = VM?.on('badpw', () => { - _u!(); - elements.badPasswordAlert.style.display = "block"; - }); + var adminPass = elements.adminPassword.value; + if (adminPass === '') return; + VM?.login(adminPass); + elements.adminPassword.value = ''; + var u = VM?.on('login', () => { + u!(); + loginModal.hide(); + elements.badPasswordAlert.style.display = 'none'; + }); + var _u = VM?.on('badpw', () => { + _u!(); + elements.badPasswordAlert.style.display = 'block'; + }); } -function onLogin(_rank : Rank, _perms : Permissions) { - rank = _rank; - perms = _perms; - elements.username.classList.remove("text-dark", "text-light"); - if (rank === Rank.Admin) elements.username.classList.add("username-admin"); - if (rank === Rank.Moderator) elements.username.classList.add("username-moderator"); - elements.staffbtns.style.display = "block"; - if (_perms.restore) elements.restoreBtn.style.display = "inline-block"; - if (_perms.reboot) elements.rebootBtn.style.display = "inline-block"; - if (_perms.bypassturn) { - elements.bypassTurnBtn.style.display = "inline-block"; - elements.endTurnBtn.style.display = "inline-block"; - elements.clearQueueBtn.style.display = "inline-block"; - } - if (_rank === Rank.Admin) { - elements.qemuMonitorBtn.style.display = "inline-block"; - elements.indefTurnBtn.style.display = "inline-block"; - } - if (_perms.xss) elements.xssCheckboxContainer.style.display = "inline-block"; - if (_perms.forcevote) elements.forceVotePanel.style.display = "block"; - for (const user of users) userModOptions(user); +function onLogin(_rank: Rank, _perms: Permissions) { + rank = _rank; + perms = _perms; + elements.username.classList.remove('text-dark', 'text-light'); + if (rank === Rank.Admin) elements.username.classList.add('username-admin'); + if (rank === Rank.Moderator) elements.username.classList.add('username-moderator'); + elements.staffbtns.style.display = 'block'; + if (_perms.restore) elements.restoreBtn.style.display = 'inline-block'; + if (_perms.reboot) elements.rebootBtn.style.display = 'inline-block'; + if (_perms.bypassturn) { + elements.bypassTurnBtn.style.display = 'inline-block'; + elements.endTurnBtn.style.display = 'inline-block'; + elements.clearQueueBtn.style.display = 'inline-block'; + } + if (_rank === Rank.Admin) { + elements.qemuMonitorBtn.style.display = 'inline-block'; + elements.indefTurnBtn.style.display = 'inline-block'; + } + if (_perms.xss) elements.xssCheckboxContainer.style.display = 'inline-block'; + if (_perms.forcevote) elements.forceVotePanel.style.display = 'block'; + for (const user of users) userModOptions(user); } -function userModOptions(user : { - user : User, - element : HTMLTableRowElement -}) { - var tr = user.element; - var td = tr.children[0] as HTMLTableCellElement; - 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.add("dropdown-menu", "dropdown-menu-dark", "table-dark", "text-light"); - if (perms.bypassturn) addUserDropdownItem(ul, "End Turn", () => VM!.endTurn(user.user.username)); - if (perms.ban) addUserDropdownItem(ul, "Ban", () => VM!.ban(user.user.username)); - if (perms.kick) addUserDropdownItem(ul, "Kick", () => VM!.kick(user.user.username)); - if (perms.rename) addUserDropdownItem(ul, "Rename", () => { - var newname = prompt(`Enter new username for ${user.user.username}`); - if (!newname) return; - VM!.renameUser(user.user.username, newname); - }); - if (perms.mute) { - addUserDropdownItem(ul, "Temporary Mute", () => VM!.mute(user.user.username, MuteState.Temp)); - addUserDropdownItem(ul, "Indefinite Mute", () => VM!.mute(user.user.username, MuteState.Perma)); - addUserDropdownItem(ul, "Unmute", () => VM!.mute(user.user.username, MuteState.Unmuted)); - } - if (perms.grabip) addUserDropdownItem(ul, "Get IP", async () => { - var ip = await VM!.getip(user.user.username); - alert(ip); - }); - tr.appendChild(ul); +function userModOptions(user: { user: User; element: HTMLTableRowElement }) { + var tr = user.element; + var td = tr.children[0] as HTMLTableCellElement; + 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.add('dropdown-menu', 'dropdown-menu-dark', 'table-dark', 'text-light'); + if (perms.bypassturn) addUserDropdownItem(ul, 'End Turn', () => VM!.endTurn(user.user.username)); + if (perms.ban) addUserDropdownItem(ul, 'Ban', () => VM!.ban(user.user.username)); + if (perms.kick) addUserDropdownItem(ul, 'Kick', () => VM!.kick(user.user.username)); + if (perms.rename) + addUserDropdownItem(ul, 'Rename', () => { + var newname = prompt(`Enter new username for ${user.user.username}`); + if (!newname) return; + VM!.renameUser(user.user.username, newname); + }); + if (perms.mute) { + addUserDropdownItem(ul, 'Temporary Mute', () => VM!.mute(user.user.username, MuteState.Temp)); + addUserDropdownItem(ul, 'Indefinite Mute', () => VM!.mute(user.user.username, MuteState.Perma)); + addUserDropdownItem(ul, 'Unmute', () => VM!.mute(user.user.username, MuteState.Unmuted)); + } + if (perms.grabip) + addUserDropdownItem(ul, 'Get IP', async () => { + var ip = await VM!.getip(user.user.username); + alert(ip); + }); + tr.appendChild(ul); } -function addUserDropdownItem(ul : HTMLUListElement, text : string, func : () => void) { - 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 addUserDropdownItem(ul: HTMLUListElement, text: string, func: () => void) { + 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); } // Admin buttons -elements.restoreBtn.addEventListener('click', () => window.confirm("Are you sure you want to restore the VM?") && VM?.restore()); +elements.restoreBtn.addEventListener('click', () => window.confirm('Are you sure you want to restore the VM?') && VM?.restore()); elements.rebootBtn.addEventListener('click', () => VM?.reboot()); elements.clearQueueBtn.addEventListener('click', () => VM?.clearQueue()); elements.bypassTurnBtn.addEventListener('click', () => VM?.bypassTurn()); elements.endTurnBtn.addEventListener('click', () => { - var user = VM?.getUsers().find(u => u.turn === 0); - if (user) VM?.endTurn(user.username); + var user = VM?.getUsers().find((u) => u.turn === 0); + if (user) VM?.endTurn(user.username); }); elements.forceVoteNoBtn.addEventListener('click', () => VM?.forceVote(false)); elements.forceVoteYesBtn.addEventListener('click', () => VM?.forceVote(true)); elements.indefTurnBtn.addEventListener('click', () => VM?.indefiniteTurn()); async function sendQEMUCommand() { - if (!elements.qemuMonitorInput.value) return; - var cmd = elements.qemuMonitorInput.value; - elements.qemuMonitorOutput.innerHTML += `> ${cmd}\n`; - elements.qemuMonitorInput.value = ""; - var response = await VM?.qemuMonitor(cmd); - elements.qemuMonitorOutput.innerHTML += `${response}\n`; - elements.qemuMonitorOutput.scrollTop = elements.qemuMonitorOutput.scrollHeight; + if (!elements.qemuMonitorInput.value) return; + var cmd = elements.qemuMonitorInput.value; + elements.qemuMonitorOutput.innerHTML += `> ${cmd}\n`; + elements.qemuMonitorInput.value = ''; + var response = await VM?.qemuMonitor(cmd); + elements.qemuMonitorOutput.innerHTML += `${response}\n`; + elements.qemuMonitorOutput.scrollTop = elements.qemuMonitorOutput.scrollHeight; } elements.qemuMonitorSendBtn.addEventListener('click', () => sendQEMUCommand()); -elements.qemuMonitorInput.addEventListener('keypress', (e) => e.key === "Enter" && sendQEMUCommand()); +elements.qemuMonitorInput.addEventListener('keypress', (e) => e.key === 'Enter' && sendQEMUCommand()); elements.osk.addEventListener('click', () => elements.oskContainer.classList.toggle('d-none')); // Public API w.collabvm = { - openVM: openVM, - closeVM: closeVM, - loadList: loadList, - multicollab: multicollab, - getVM: () => VM, -} + openVM: openVM, + closeVM: closeVM, + loadList: loadList, + multicollab: multicollab, + getVM: () => VM +}; // Multicollab will stay in the global scope for backwards compatibility w.multicollab = multicollab; // Same goes for GetAdmin w.GetAdmin = () => { - if (VM === null) return; - return { - adminInstruction: (...args : string[]) => { - args.unshift("admin"); - VM?.send(...args); - }, - restore: () => VM!.restore(), - reboot: () => VM!.reboot(), - clearQueue: () => VM!.clearQueue(), - bypassTurn: () => VM!.bypassTurn(), - endTurn: (username : string) => VM!.endTurn(username), - ban: (username : string) => VM!.ban(username), - kick: (username : string) => VM!.kick(username), - renameUser: (oldname : string, newname : string) => VM!.renameUser(oldname, newname), - mute: (username : string, state : number) => VM!.mute(username, state), - getip: (username : string) => VM!.getip(username), - qemuMonitor: (cmd : string) => {VM?.qemuMonitor(cmd); return;}, - globalXss: (msg : string) => VM!.xss(msg), - forceVote: (result : boolean) => VM!.forceVote(result), - } + if (VM === null) return; + return { + adminInstruction: (...args: string[]) => { + args.unshift('admin'); + VM?.send(...args); + }, + restore: () => VM!.restore(), + reboot: () => VM!.reboot(), + clearQueue: () => VM!.clearQueue(), + bypassTurn: () => VM!.bypassTurn(), + endTurn: (username: string) => VM!.endTurn(username), + ban: (username: string) => VM!.ban(username), + kick: (username: string) => VM!.kick(username), + renameUser: (oldname: string, newname: string) => VM!.renameUser(oldname, newname), + mute: (username: string, state: number) => VM!.mute(username, state), + getip: (username: string) => VM!.getip(username), + qemuMonitor: (cmd: string) => { + VM?.qemuMonitor(cmd); + return; + }, + globalXss: (msg: string) => VM!.xss(msg), + forceVote: (result: boolean) => VM!.forceVote(result) + }; }; // more backwards compatibility w.cvmEvents = { - on: (event : string | number, cb: (...args: any) => void) => { - if (VM === null) return; - VM.on('message', (...args : any) => cb(...args)); - } -} + on: (event: string | number, cb: (...args: any) => void) => { + if (VM === null) return; + VM.on('message', (...args: any) => cb(...args)); + } +}; w.VMName = null; // Load all VMs loadList(); // Welcome modal -var noWelcomeModal = window.localStorage.getItem("no-welcome-modal"); -if (noWelcomeModal !== "1") { - var welcomeModalDismissBtn = document.getElementById("welcomeModalDismiss") as HTMLButtonElement; - var welcomeModal = new bootstrap.Modal(document.getElementById("welcomeModal") as HTMLDivElement); - welcomeModalDismissBtn.addEventListener("click", () => { - window.localStorage.setItem("no-welcome-modal", "1"); +var noWelcomeModal = window.localStorage.getItem('no-welcome-modal'); +if (noWelcomeModal !== '1') { + var welcomeModalDismissBtn = document.getElementById('welcomeModalDismiss') as HTMLButtonElement; + var welcomeModal = new bootstrap.Modal(document.getElementById('welcomeModal') as HTMLDivElement); + welcomeModalDismissBtn.addEventListener('click', () => { + window.localStorage.setItem('no-welcome-modal', '1'); }); welcomeModalDismissBtn.disabled = true; welcomeModal.show(); setTimeout(() => { welcomeModalDismissBtn.disabled = false; }, 5000); -} \ No newline at end of file +} diff --git a/src/ts/protocol/CollabVMClient.ts b/src/ts/protocol/CollabVMClient.ts index 438a801..be39317 100644 --- a/src/ts/protocol/CollabVMClient.ts +++ b/src/ts/protocol/CollabVMClient.ts @@ -1,570 +1,590 @@ -import {createNanoEvents, Emitter, DefaultEvents } from "nanoevents"; +import { createNanoEvents, Emitter, DefaultEvents } from 'nanoevents'; import * as Guacutils from './Guacutils.js'; -import VM from "./VM.js"; -import { User } from "./User.js"; -import { AdminOpcode, Permissions, Rank } from "./Permissions.js"; -import TurnStatus from "./TurnStatus.js"; -import Mouse from "./mouse.js"; +import VM from './VM.js'; +import { User } from './User.js'; +import { AdminOpcode, Permissions, Rank } from './Permissions.js'; +import TurnStatus from './TurnStatus.js'; +import Mouse from './mouse.js'; import GetKeysym from '../keyboard.js'; -import VoteStatus from "./VoteStatus.js"; -import MuteState from "./MuteState.js"; +import VoteStatus from './VoteStatus.js'; +import MuteState from './MuteState.js'; // TODO: `Object` has a toString(), but we should probably gate that off /// Interface for things that can be turned into strings interface ToStringable { - toString() : string + toString(): string; } /// A type for strings, or things that can (in a valid manner) be turned into strings type StringLike = string | ToStringable; export interface CollabVMClientEvents { - open: () => void; - close: () => void; + open: () => void; + close: () => void; - message: (...args: string[]) => void; + message: (...args: string[]) => void; - // Protocol stuff - chat: (username: string, message: string) => void; + // Protocol stuff + chat: (username: string, message: string) => void; - adduser: (user: User) => void; - remuser: (user: User) => void; + adduser: (user: User) => void; + remuser: (user: User) => void; - renamestatus: (status: 'taken' | 'invalid' | 'blacklisted') => void; - turn: (status: TurnStatus) => void; + renamestatus: (status: 'taken' | 'invalid' | 'blacklisted') => void; + turn: (status: TurnStatus) => void; - rename: (oldUsername: string, newUsername: string, selfRename: boolean) => void; + rename: (oldUsername: string, newUsername: string, selfRename: boolean) => void; - vote: (status: VoteStatus) => void; - voteend: () => void; - votecd: (coolDownTime: number) => void; + vote: (status: VoteStatus) => void; + voteend: () => void; + votecd: (coolDownTime: number) => void; - - badpw: () => void; - login: (rank: Rank, perms: Permissions) => void; + badpw: () => void; + login: (rank: Rank, perms: Permissions) => void; } // types for private emitter interface CollabVMClientPrivateEvents { - list: (listEntries: string[]) => void; - connect: (connectedToVM: boolean) => void; - ip: (username: string, ip: string) => void; - qemu: (qemuResponse: string) => void; + list: (listEntries: string[]) => void; + connect: (connectedToVM: boolean) => void; + ip: (username: string, ip: string) => void; + qemu: (qemuResponse: string) => void; } export default class CollabVMClient { - // Fields - private socket : WebSocket; - canvas : HTMLCanvasElement; - private ctx : CanvasRenderingContext2D; - private url : string; - private connectedToVM : boolean = false; - private users : User[] = []; - private username : string | null = null; - private mouse : Mouse = new Mouse(); - private rank : Rank = Rank.Unregistered; - private perms : Permissions = new Permissions(0); - private voteStatus : VoteStatus | null = null; - private node : string | null = null; - // events that are used internally and not exposed - private internalEmitter : Emitter; - // public events - private publicEmitter : Emitter; + // Fields + private socket: WebSocket; + canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private url: string; + private connectedToVM: boolean = false; + private users: User[] = []; + private username: string | null = null; + private mouse: Mouse = new Mouse(); + private rank: Rank = Rank.Unregistered; + private perms: Permissions = new Permissions(0); + private voteStatus: VoteStatus | null = null; + private node: string | null = null; + // events that are used internally and not exposed + private internalEmitter: Emitter; + // public events + private publicEmitter: Emitter; - constructor(url : string) { - // Save the URL - this.url = url; - // Create the events - this.internalEmitter = 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')!; - // Bind canvas click - this.canvas.addEventListener('click', e => { - if (this.users.find(u => u.username === this.username)?.turn === -1) - this.turn(true); - }); + constructor(url: string) { + // Save the URL + this.url = url; + // Create the events + this.internalEmitter = 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')!; + // Bind canvas click + this.canvas.addEventListener('click', (e) => { + if (this.users.find((u) => u.username === this.username)?.turn === -1) this.turn(true); + }); - // Bind keyboard and mouse - this.canvas.addEventListener('mousedown', (e : MouseEvent) => { - if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; - this.mouse.initFromMouseEvent(e); - this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); - }, { - capture: true - }); + // Bind keyboard and mouse + this.canvas.addEventListener( + 'mousedown', + (e: MouseEvent) => { + if (this.users.find((u) => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.initFromMouseEvent(e); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, + { + capture: true + } + ); - this.canvas.addEventListener('mouseup', (e : MouseEvent) => { - if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; - this.mouse.initFromMouseEvent(e); - this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); - }, { - capture: true - }); + this.canvas.addEventListener( + 'mouseup', + (e: MouseEvent) => { + if (this.users.find((u) => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.initFromMouseEvent(e); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, + { + capture: true + } + ); - this.canvas.addEventListener('mousemove', (e : MouseEvent) => { - if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; - this.mouse.initFromMouseEvent(e); - this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); - }, { - capture: true - }); + this.canvas.addEventListener( + 'mousemove', + (e: MouseEvent) => { + if (this.users.find((u) => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.initFromMouseEvent(e); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, + { + capture: true + } + ); - this.canvas.addEventListener('keydown', (e : KeyboardEvent) => { - e.preventDefault(); - if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; - var keysym = GetKeysym(e.keyCode, e.key, e.location); - if (keysym === null) return; - this.key(keysym, true); - }, { - capture: true - }); + this.canvas.addEventListener( + 'keydown', + (e: KeyboardEvent) => { + e.preventDefault(); + if (this.users.find((u) => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + var keysym = GetKeysym(e.keyCode, e.key, e.location); + if (keysym === null) return; + this.key(keysym, true); + }, + { + capture: true + } + ); - this.canvas.addEventListener('keyup', (e : KeyboardEvent) => { - e.preventDefault(); - if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; - var keysym = GetKeysym(e.keyCode, e.key, e.location); - if (keysym === null) return; - this.key(keysym, false); - }, { - capture: true - }); + this.canvas.addEventListener( + 'keyup', + (e: KeyboardEvent) => { + e.preventDefault(); + if (this.users.find((u) => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + var keysym = GetKeysym(e.keyCode, e.key, e.location); + if (keysym === null) return; + this.key(keysym, false); + }, + { + capture: true + } + ); - this.canvas.addEventListener('wheel', (ev: WheelEvent) => { - ev.preventDefault(); - if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; - this.mouse.initFromWheelEvent(ev); + this.canvas.addEventListener( + 'wheel', + (ev: WheelEvent) => { + ev.preventDefault(); + if (this.users.find((u) => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.initFromWheelEvent(ev); - this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); - // this is a very, very ugly hack but it seems to work so /shrug - if(this.mouse.scrollUp) - this.mouse.scrollUp = false; - else if(this.mouse.scrollDown) - this.mouse.scrollDown = false; + // this is a very, very ugly hack but it seems to work so /shrug + if (this.mouse.scrollUp) this.mouse.scrollUp = false; + else if (this.mouse.scrollDown) this.mouse.scrollDown = false; - this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); - }, { capture: true }); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, + { + capture: true + } + ); - this.canvas.addEventListener('contextmenu', e => e.preventDefault()); - // 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)); - this.socket.addEventListener('close', () => this.publicEmitter.emit('close')); - } + this.canvas.addEventListener('contextmenu', (e) => e.preventDefault()); + // 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)); + this.socket.addEventListener('close', () => this.publicEmitter.emit('close')); + } - // Fires when the WebSocket connection is opened - private onOpen() { - this.publicEmitter.emit('open'); - } + // 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; - } - this.publicEmitter.emit('message', ...msgArr); - switch (msgArr[0]) { - case "nop": { - // Send a NOP back - this.send("nop"); - break; - } - case "list": { - // pass msgarr to the emitter for processing by list() - this.internalEmitter.emit('list', msgArr.slice(1)); - break; - } - case "connect": { - this.connectedToVM = msgArr[1] === "1"; - this.internalEmitter.emit('connect', this.connectedToVM); - break; - } - case "size": { - if (msgArr[1] !== "0") return; - this.canvas.width = parseInt(msgArr[2]); - this.canvas.height = parseInt(msgArr[3]); - break; - } - case "png": { - // Despite the opcode name, this is actually JPEG, because old versions of the server used PNG and yknow backwards compatibility - var img = new Image(); - img.addEventListener('load', () => { - this.ctx.drawImage(img, parseInt(msgArr[3]), parseInt(msgArr[4])); - }); - img.src = "data:image/jpeg;base64," + msgArr[5]; - break; - } - case "chat": { - for (var i = 1; i < msgArr.length; i += 2) { - this.publicEmitter.emit('chat', msgArr[i], msgArr[i + 1]); - } - break; - } - case "adduser": { - for (var i = 2; i < msgArr.length; i += 2) { - var _user = this.users.find(u => u.username === msgArr[i]); - if (_user !== undefined) { - _user.rank = parseInt(msgArr[i + 1]); - } else { - _user = new User(msgArr[i], parseInt(msgArr[i + 1])); - this.users.push(_user); - } - this.publicEmitter.emit('adduser', _user); - } - break; - } - case "remuser": { - for (var i = 2; i < msgArr.length; i++) { - var _user = this.users.find(u => u.username === msgArr[i]); - if (_user === undefined) continue; - this.users.splice(this.users.indexOf(_user), 1); - this.publicEmitter.emit('remuser', _user); - } - } - case "rename": { - var selfrename = false; - var oldusername : string | null = null; - // We've been renamed - if (msgArr[1] === "0") { - selfrename = true; - oldusername = this.username; - // msgArr[2] is the status of the rename - // Anything other than 0 is an error, however the server will still rename us to a guest name - switch (msgArr[2]) { - case "1": - // The username we wanted was taken - this.publicEmitter.emit('renamestatus', 'taken'); - break; - case "2": - // The username we wanted was invalid - this.publicEmitter.emit('renamestatus', 'invalid'); - break; - case "3": - // The username we wanted is blacklisted - this.publicEmitter.emit('renamestatus', 'blacklisted'); - break; - } - this.username = msgArr[3]; - } - else oldusername = msgArr[2]; - var _user = this.users.find(u => u.username === oldusername); - if (_user) { - _user.username = msgArr[3]; - } - this.publicEmitter.emit('rename', oldusername!, msgArr[3], selfrename); - break; - } - case "turn": { - // Reset all turn data - for (var user of this.users) user.turn = -1; - var queuedUsers = parseInt(msgArr[2]); - if (queuedUsers === 0) { - this.publicEmitter.emit('turn', { - user: null, - queue: [], - turnTime: null, - queueTime: null, - }); - return; - } - var currentTurn = this.users.find(u => u.username === msgArr[3])!; - currentTurn.turn = 0; - var queue : User[] = []; - if (queuedUsers > 1) { - for (var i = 1; i < queuedUsers; i++) { - var user = this.users.find(u => u.username === msgArr[i+3])!; - queue.push(user); - user.turn = i; - } - } - this.publicEmitter.emit('turn', { - user: currentTurn, - queue: queue, - turnTime: currentTurn.username === this.username ? parseInt(msgArr[1]) : null, - queueTime: queue.some(u => u.username === this.username) ? parseInt(msgArr[msgArr.length - 1]) : null, - }) - break; - } - case "vote": { - switch (msgArr[1]) { - case "0": - // Vote started - case "1": - // Vote updated - var timeToEnd = parseInt(msgArr[2]); - var yesVotes = parseInt(msgArr[3]); - var noVotes = parseInt(msgArr[4]); - // Some server implementations dont send data for status 0, and some do - if (Number.isNaN(timeToEnd) || Number.isNaN(yesVotes) || Number.isNaN(noVotes)) return; - this.voteStatus = { - timeToEnd: timeToEnd, - yesVotes: yesVotes, - noVotes: noVotes, - }; - this.publicEmitter.emit('vote', this.voteStatus); - break; - case "2": - // Vote ended - this.voteStatus = null; - this.publicEmitter.emit('voteend'); - break; - case "3": - // Cooldown - this.publicEmitter.emit('votecd', parseInt(msgArr[2])); - break; - } - } - case "admin": { - switch (msgArr[1]) { - case "0": { - // Login - switch (msgArr[2]) { - case "0": - this.publicEmitter.emit('badpw'); - return; - case "1": - this.perms = new Permissions(65535); - this.rank = Rank.Admin; - break; - case "3": - this.perms = new Permissions(parseInt(msgArr[3])); - this.rank = Rank.Moderator; - break; - } - this.publicEmitter.emit('login', this.rank, this.perms); - break; - } - case "19": { - // IP - this.internalEmitter.emit('ip', msgArr[2], msgArr[3]); - break; - } - case "2": { - // QEMU - this.internalEmitter.emit('qemu', msgArr[2]); - break; - } - } - } - } - } + // 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; + } + this.publicEmitter.emit('message', ...msgArr); + switch (msgArr[0]) { + case 'nop': { + // Send a NOP back + this.send('nop'); + break; + } + case 'list': { + // pass msgarr to the emitter for processing by list() + this.internalEmitter.emit('list', msgArr.slice(1)); + break; + } + case 'connect': { + this.connectedToVM = msgArr[1] === '1'; + this.internalEmitter.emit('connect', this.connectedToVM); + break; + } + case 'size': { + if (msgArr[1] !== '0') return; + this.canvas.width = parseInt(msgArr[2]); + this.canvas.height = parseInt(msgArr[3]); + break; + } + case 'png': { + // Despite the opcode name, this is actually JPEG, because old versions of the server used PNG and yknow backwards compatibility + var img = new Image(); + img.addEventListener('load', () => { + this.ctx.drawImage(img, parseInt(msgArr[3]), parseInt(msgArr[4])); + }); + img.src = 'data:image/jpeg;base64,' + msgArr[5]; + break; + } + case 'chat': { + for (var i = 1; i < msgArr.length; i += 2) { + this.publicEmitter.emit('chat', msgArr[i], msgArr[i + 1]); + } + break; + } + case 'adduser': { + for (var i = 2; i < msgArr.length; i += 2) { + var _user = this.users.find((u) => u.username === msgArr[i]); + if (_user !== undefined) { + _user.rank = parseInt(msgArr[i + 1]); + } else { + _user = new User(msgArr[i], parseInt(msgArr[i + 1])); + this.users.push(_user); + } + this.publicEmitter.emit('adduser', _user); + } + break; + } + case 'remuser': { + for (var i = 2; i < msgArr.length; i++) { + var _user = this.users.find((u) => u.username === msgArr[i]); + if (_user === undefined) continue; + this.users.splice(this.users.indexOf(_user), 1); + this.publicEmitter.emit('remuser', _user); + } + } + case 'rename': { + var selfrename = false; + var oldusername: string | null = null; + // We've been renamed + if (msgArr[1] === '0') { + selfrename = true; + oldusername = this.username; + // msgArr[2] is the status of the rename + // Anything other than 0 is an error, however the server will still rename us to a guest name + switch (msgArr[2]) { + case '1': + // The username we wanted was taken + this.publicEmitter.emit('renamestatus', 'taken'); + break; + case '2': + // The username we wanted was invalid + this.publicEmitter.emit('renamestatus', 'invalid'); + break; + case '3': + // The username we wanted is blacklisted + this.publicEmitter.emit('renamestatus', 'blacklisted'); + break; + } + this.username = msgArr[3]; + } else oldusername = msgArr[2]; + var _user = this.users.find((u) => u.username === oldusername); + if (_user) { + _user.username = msgArr[3]; + } + this.publicEmitter.emit('rename', oldusername!, msgArr[3], selfrename); + break; + } + case 'turn': { + // Reset all turn data + for (var user of this.users) user.turn = -1; + var queuedUsers = parseInt(msgArr[2]); + if (queuedUsers === 0) { + this.publicEmitter.emit('turn', { + user: null, + queue: [], + turnTime: null, + queueTime: null + }); + return; + } + var currentTurn = this.users.find((u) => u.username === msgArr[3])!; + currentTurn.turn = 0; + var queue: User[] = []; + if (queuedUsers > 1) { + for (var i = 1; i < queuedUsers; i++) { + var user = this.users.find((u) => u.username === msgArr[i + 3])!; + queue.push(user); + user.turn = i; + } + } + this.publicEmitter.emit('turn', { + user: currentTurn, + queue: queue, + turnTime: currentTurn.username === this.username ? parseInt(msgArr[1]) : null, + queueTime: queue.some((u) => u.username === this.username) ? parseInt(msgArr[msgArr.length - 1]) : null + }); + break; + } + case 'vote': { + switch (msgArr[1]) { + case '0': + // Vote started + case '1': + // Vote updated + var timeToEnd = parseInt(msgArr[2]); + var yesVotes = parseInt(msgArr[3]); + var noVotes = parseInt(msgArr[4]); + // Some server implementations dont send data for status 0, and some do + if (Number.isNaN(timeToEnd) || Number.isNaN(yesVotes) || Number.isNaN(noVotes)) return; + this.voteStatus = { + timeToEnd: timeToEnd, + yesVotes: yesVotes, + noVotes: noVotes + }; + this.publicEmitter.emit('vote', this.voteStatus); + break; + case '2': + // Vote ended + this.voteStatus = null; + this.publicEmitter.emit('voteend'); + break; + case '3': + // Cooldown + this.publicEmitter.emit('votecd', parseInt(msgArr[2])); + break; + } + } + case 'admin': { + switch (msgArr[1]) { + case '0': { + // Login + switch (msgArr[2]) { + case '0': + this.publicEmitter.emit('badpw'); + return; + case '1': + this.perms = new Permissions(65535); + this.rank = Rank.Admin; + break; + case '3': + this.perms = new Permissions(parseInt(msgArr[3])); + this.rank = Rank.Moderator; + break; + } + this.publicEmitter.emit('login', this.rank, this.perms); + break; + } + case '19': { + // IP + this.internalEmitter.emit('ip', msgArr[2], msgArr[3]); + break; + } + case '2': { + // QEMU + this.internalEmitter.emit('qemu', msgArr[2]); + break; + } + } + } + } + } - // Sends a message to the server - send(...args : StringLike[]) { - let guacElements = [...args].map((el) => { - // This catches cases where the thing already is a string - if(typeof el == "string") - return (el as string); - return el.toString(); - }); + // Sends a message to the server + send(...args: StringLike[]) { + let guacElements = [...args].map((el) => { + // This catches cases where the thing already is a string + if (typeof el == 'string') return el as string; + return el.toString(); + }); - this.socket.send(Guacutils.encode(...guacElements)); - } + this.socket.send(Guacutils.encode(...guacElements)); + } - // Get a list of all VMs - list() : Promise { - return new Promise((res, rej) => { - var u = this.onInternal('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, - }); - } - res(vms); - }); - this.send("list"); - }); - } + // Get a list of all VMs + list(): Promise { + return new Promise((res, rej) => { + var u = this.onInternal('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 + }); + } + res(vms); + }); + this.send('list'); + }); + } - // Connect to a node - connect(id : string, username : string | null = null) : Promise { - return new Promise(res => { - var u = this.onInternal('connect', (success : boolean) => { - u(); - res(success); - }); - if (username === null) this.send("rename"); - else this.send("rename", username); - this.send("connect", id); - this.node = id; - }) - } + // Connect to a node + connect(id: string, username: string | null = null): Promise { + return new Promise((res) => { + var u = this.onInternal('connect', (success: boolean) => { + u(); + res(success); + }); + if (username === null) this.send('rename'); + else this.send('rename', username); + this.send('connect', id); + this.node = id; + }); + } - // Close the connection - close() { - this.connectedToVM = false; - if (this.socket.readyState === WebSocket.OPEN) this.socket.close(); - } + // Close the connection + close() { + this.connectedToVM = false; + if (this.socket.readyState === WebSocket.OPEN) this.socket.close(); + } - // Get users - getUsers() : User[] { - // Return a copy of the array - return this.users.slice(); - } + // Get users + getUsers(): User[] { + // Return a copy of the array + return this.users.slice(); + } - // Send a chat message - chat(message : string) { - this.send("chat", message); - } + // Send a chat message + chat(message: string) { + this.send('chat', message); + } - // Rename - rename(username : string | null = null) { - if (username) this.send("rename", username); - else this.send("rename"); - } + // Rename + rename(username: string | null = null) { + if (username) this.send('rename', username); + else this.send('rename'); + } - // Take or drop turn - turn(taketurn : boolean) { - this.send("turn", taketurn ? "1" : "0"); - } + // Take or drop turn + turn(taketurn: boolean) { + this.send('turn', taketurn ? '1' : '0'); + } - // Send mouse instruction - sendmouse(x : number, y : number, mask : number) { - this.send("mouse", x, y, mask); - } + // Send mouse instruction + sendmouse(x: number, y: number, mask: number) { + this.send('mouse', x, y, mask); + } - // Send key - key(keysym : number, down : boolean) { - this.send("key", keysym, down ? "1" : "0"); - } + // Send key + key(keysym: number, down: boolean) { + this.send('key', keysym, down ? '1' : '0'); + } - // Get vote status - getVoteStatus() : VoteStatus | null { - return this.voteStatus; - } + // Get vote status + getVoteStatus(): VoteStatus | null { + return this.voteStatus; + } - // Start a vote, or vote - vote(vote : boolean) { - this.send("vote", vote ? "1" : "0"); - } + // Start a vote, or vote + vote(vote: boolean) { + this.send('vote', vote ? '1' : '0'); + } - // Try to login using the specified password - login(password : string) { - this.send("admin", AdminOpcode.Login, password); - } + // Try to login using the specified password + login(password: string) { + this.send('admin', AdminOpcode.Login, password); + } - /* Admin commands */ + /* Admin commands */ - // Restore - restore() { - if (!this.node) return; - this.send("admin", AdminOpcode.Restore, this.node!); - } + // Restore + restore() { + if (!this.node) return; + this.send('admin', AdminOpcode.Restore, this.node!); + } - // Reboot - reboot() { - if (!this.node) return; - this.send("admin", AdminOpcode.Reboot, this.node!); - } + // Reboot + reboot() { + if (!this.node) return; + this.send('admin', AdminOpcode.Reboot, this.node!); + } - // Clear turn queue - clearQueue() { - if (!this.node) return; - this.send("admin", AdminOpcode.ClearTurns, this.node!); - } + // Clear turn queue + clearQueue() { + if (!this.node) return; + this.send('admin', AdminOpcode.ClearTurns, this.node!); + } - // Bypass turn - bypassTurn() { - this.send("admin", AdminOpcode.BypassTurn); - } + // Bypass turn + bypassTurn() { + this.send('admin', AdminOpcode.BypassTurn); + } - // End turn - endTurn(user : string) { - this.send("admin", AdminOpcode.EndTurn, user); - } + // End turn + endTurn(user: string) { + this.send('admin', AdminOpcode.EndTurn, user); + } - // Ban - ban(user : string) { - this.send("admin", AdminOpcode.BanUser, user); - } + // Ban + ban(user: string) { + this.send('admin', AdminOpcode.BanUser, user); + } - // Kick - kick(user : string) { - this.send("admin", AdminOpcode.KickUser, user); - } + // Kick + kick(user: string) { + this.send('admin', AdminOpcode.KickUser, user); + } - // Rename user - renameUser(oldname : string, newname : string) { - this.send("admin", AdminOpcode.RenameUser, oldname, newname); - } + // Rename user + renameUser(oldname: string, newname: string) { + this.send('admin', AdminOpcode.RenameUser, oldname, newname); + } - // Mute user - mute(user : string, state : MuteState) { - this.send("admin", AdminOpcode.MuteUser, user, state); - } + // Mute user + mute(user: string, state: MuteState) { + this.send('admin', AdminOpcode.MuteUser, user, state); + } - // Grab IP - getip(user : string) { - if (this.users.find(u => u.username === user) === undefined) return false; - return new Promise(res => { - var u = this.onInternal('ip', (username : string, ip : string) => { - if (username !== user) return; - u(); - res(ip); - }) - this.send("admin", AdminOpcode.GetIP, user); - }); - } + // Grab IP + getip(user: string) { + if (this.users.find((u) => u.username === user) === undefined) return false; + return new Promise((res) => { + var u = this.onInternal('ip', (username: string, ip: string) => { + if (username !== user) return; + u(); + res(ip); + }); + this.send('admin', AdminOpcode.GetIP, user); + }); + } - // QEMU Monitor - qemuMonitor(cmd : string) { - return new Promise(res => { - var u = this.onInternal('qemu', output => { - u(); - res(output); - }) - this.send("admin", AdminOpcode.MonitorCommand, this.node!, cmd); - }); - } + // QEMU Monitor + qemuMonitor(cmd: string) { + return new Promise((res) => { + var u = this.onInternal('qemu', (output) => { + u(); + res(output); + }); + this.send('admin', AdminOpcode.MonitorCommand, this.node!, cmd); + }); + } - // XSS - xss(msg : string) { - this.send("admin", AdminOpcode.ChatXSS, msg); - } + // XSS + xss(msg: string) { + this.send('admin', AdminOpcode.ChatXSS, msg); + } - // Force vote - forceVote(result : boolean) { - this.send("admin", AdminOpcode.ForceVote, result ? "1" : "0"); - } + // Force vote + forceVote(result: boolean) { + this.send('admin', AdminOpcode.ForceVote, result ? '1' : '0'); + } - // Toggle turns - turns(enabled : boolean) { - this.send("admin", AdminOpcode.ToggleTurns, enabled ? "1" : "0"); - } + // Toggle turns + turns(enabled: boolean) { + this.send('admin', AdminOpcode.ToggleTurns, enabled ? '1' : '0'); + } - // Indefinite turn - indefiniteTurn() { - this.send("admin", AdminOpcode.IndefiniteTurn); - } + // Indefinite turn + indefiniteTurn() { + this.send('admin', AdminOpcode.IndefiniteTurn); + } - // Hide screen - hideScreen(hidden : boolean) { - this.send("admin", AdminOpcode.HideScreen, hidden ? "1" : "0"); - } + // Hide screen + hideScreen(hidden: boolean) { + this.send('admin', AdminOpcode.HideScreen, hidden ? '1' : '0'); + } - private onInternal(event: E, callback: CollabVMClientPrivateEvents[E]) { - return this.internalEmitter.on(event, callback) - } + private onInternal(event: E, callback: CollabVMClientPrivateEvents[E]) { + return this.internalEmitter.on(event, callback); + } - on(event: E, callback: CollabVMClientEvents[E]) { - return this.publicEmitter.on(event, callback) - } -} \ No newline at end of file + on(event: E, callback: CollabVMClientEvents[E]) { + return this.publicEmitter.on(event, callback); + } +} diff --git a/src/ts/protocol/Guacutils.ts b/src/ts/protocol/Guacutils.ts index 0e0d41c..0647f70 100644 --- a/src/ts/protocol/Guacutils.ts +++ b/src/ts/protocol/Guacutils.ts @@ -1,43 +1,37 @@ -export function decode(string : string) : string[] { - let pos = -1; - let sections = []; +export function decode(string: string): string[] { + let pos = -1; + let sections = []; - for(;;) { - let len = string.indexOf('.', pos + 1); + for (;;) { + let len = string.indexOf('.', pos + 1); - if(len === -1) - break; + if (len === -1) break; - pos = parseInt(string.slice(pos + 1, len)) + len + 1; + pos = parseInt(string.slice(pos + 1, len)) + len + 1; - // don't allow funky protocol length - if(pos > string.length) - return []; + // don't allow funky protocol length + if (pos > string.length) return []; - sections.push(string.slice(len + 1, pos)); + sections.push(string.slice(len + 1, pos)); + const sep = string.slice(pos, pos + 1); - const sep = string.slice(pos, pos + 1); + if (sep === ',') continue; + else if (sep === ';') break; + // Invalid data. + else return []; + } - if(sep === ',') - continue; - else if(sep === ';') - break; - else - // Invalid data. - return []; - } - - return sections; + return sections; } -export function encode(...string : string[]) : string { - let command = ''; +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 + for (var i = 0; i < string.length; i++) { + let current = string[i]; + command += current.toString().length + '.' + current; + command += i < string.length - 1 ? ',' : ';'; + } + return command; +} diff --git a/src/ts/protocol/MuteState.ts b/src/ts/protocol/MuteState.ts index de5af70..98d6454 100644 --- a/src/ts/protocol/MuteState.ts +++ b/src/ts/protocol/MuteState.ts @@ -1,7 +1,7 @@ enum MuteState { - Temp = 0, - Perma = 1, - Unmuted = 2 + Temp = 0, + Perma = 1, + Unmuted = 2 } -export default MuteState; \ No newline at end of file +export default MuteState; diff --git a/src/ts/protocol/Permissions.ts b/src/ts/protocol/Permissions.ts index a2a8cc2..8330dfa 100644 --- a/src/ts/protocol/Permissions.ts +++ b/src/ts/protocol/Permissions.ts @@ -1,52 +1,52 @@ export class Permissions { - restore : boolean = false; - reboot : boolean = false; - ban : boolean = false; - forcevote : boolean = false; - mute : boolean = false; - kick : boolean = false; - bypassturn : boolean = false; - rename : boolean = false; - grabip : boolean = false; - xss : boolean = false; - - constructor(mask : number) { - this.restore = (mask & 1) !== 0; - this.reboot = (mask & 2) !== 0; - this.ban = (mask & 4) !== 0; - this.forcevote = (mask & 8) !== 0; - this.mute = (mask & 16) !== 0; - this.kick = (mask & 32) !== 0; - this.bypassturn = (mask & 64) !== 0; - this.rename = (mask & 128) !== 0; - this.grabip = (mask & 256) !== 0; - this.xss = (mask & 512) !== 0; - } + restore: boolean = false; + reboot: boolean = false; + ban: boolean = false; + forcevote: boolean = false; + mute: boolean = false; + kick: boolean = false; + bypassturn: boolean = false; + rename: boolean = false; + grabip: boolean = false; + xss: boolean = false; + + constructor(mask: number) { + this.restore = (mask & 1) !== 0; + this.reboot = (mask & 2) !== 0; + this.ban = (mask & 4) !== 0; + this.forcevote = (mask & 8) !== 0; + this.mute = (mask & 16) !== 0; + this.kick = (mask & 32) !== 0; + this.bypassturn = (mask & 64) !== 0; + this.rename = (mask & 128) !== 0; + this.grabip = (mask & 256) !== 0; + this.xss = (mask & 512) !== 0; + } } export enum Rank { - Unregistered = 0, - Admin = 2, - Moderator = 3, + Unregistered = 0, + Admin = 2, + Moderator = 3 } // All used admin opcodes as a enum export enum AdminOpcode { - Login = '2', - MonitorCommand = '5', - Restore = '8', - Reboot = '10', - BanUser = '12', - ForceVote = '13', - MuteUser = '14', - KickUser = '15', - EndTurn = '16', - ClearTurns = '17', - RenameUser = '18', - GetIP = '19', - BypassTurn = '20', - ChatXSS = '21', - ToggleTurns = '22', - IndefiniteTurn = '23', - HideScreen = '24' -} \ No newline at end of file + Login = '2', + MonitorCommand = '5', + Restore = '8', + Reboot = '10', + BanUser = '12', + ForceVote = '13', + MuteUser = '14', + KickUser = '15', + EndTurn = '16', + ClearTurns = '17', + RenameUser = '18', + GetIP = '19', + BypassTurn = '20', + ChatXSS = '21', + ToggleTurns = '22', + IndefiniteTurn = '23', + HideScreen = '24' +} diff --git a/src/ts/protocol/TurnStatus.ts b/src/ts/protocol/TurnStatus.ts index 8720cba..a39b6ac 100644 --- a/src/ts/protocol/TurnStatus.ts +++ b/src/ts/protocol/TurnStatus.ts @@ -1,12 +1,12 @@ -import { User } from "./User.js"; +import { User } from './User.js'; export default interface TurnStatus { - // The user currently taking their turn - user : User | null; - // The users in the turn queue - queue : User[]; - // Amount of time left in the turn. Null unless the user is taking their turn - turnTime : number | null; - // Amount of time until the user gets their turn. Null unless the user is in the queue - queueTime : number | null; -} \ No newline at end of file + // The user currently taking their turn + user: User | null; + // The users in the turn queue + queue: User[]; + // Amount of time left in the turn. Null unless the user is taking their turn + turnTime: number | null; + // Amount of time until the user gets their turn. Null unless the user is in the queue + queueTime: number | null; +} diff --git a/src/ts/protocol/User.ts b/src/ts/protocol/User.ts index c7942a9..a191276 100644 --- a/src/ts/protocol/User.ts +++ b/src/ts/protocol/User.ts @@ -1,14 +1,14 @@ -import { Rank } from "./Permissions.js"; +import { Rank } from './Permissions.js'; export class User { - username : string; - rank : Rank; - // -1 means not in the turn queue, 0 means the current turn, anything else is the position in the queue - turn : number; + username: string; + rank: Rank; + // -1 means not in the turn queue, 0 means the current turn, anything else is the position in the queue + turn: number; - constructor(username : string, rank : Rank = Rank.Unregistered) { - this.username = username; - this.rank = rank; - this.turn = -1; - } -} \ No newline at end of file + constructor(username: string, rank: Rank = Rank.Unregistered) { + this.username = username; + this.rank = rank; + this.turn = -1; + } +} diff --git a/src/ts/protocol/VM.ts b/src/ts/protocol/VM.ts index e07ee4a..2c6dfed 100644 --- a/src/ts/protocol/VM.ts +++ b/src/ts/protocol/VM.ts @@ -1,10 +1,9 @@ export default interface VM { - url : string; + url: string; - id : string; + id: string; - displayName : string; + displayName: string; - thumbnail : HTMLImageElement; - -} \ No newline at end of file + thumbnail: HTMLImageElement; +} diff --git a/src/ts/protocol/VoteStatus.ts b/src/ts/protocol/VoteStatus.ts index 6996eca..480ce02 100644 --- a/src/ts/protocol/VoteStatus.ts +++ b/src/ts/protocol/VoteStatus.ts @@ -1,5 +1,5 @@ export default interface VoteStatus { - timeToEnd: number; - yesVotes: number; - noVotes: number; -} \ No newline at end of file + timeToEnd: number; + yesVotes: number; + noVotes: number; +} diff --git a/src/ts/protocol/mouse.ts b/src/ts/protocol/mouse.ts index f464e85..cbfb110 100644 --- a/src/ts/protocol/mouse.ts +++ b/src/ts/protocol/mouse.ts @@ -1,47 +1,45 @@ function maskContains(mask: number, bit: number): boolean { - return (mask & bit) == bit; + return (mask & bit) == bit; } export default class Mouse { - left : boolean = false; - middle : boolean = false; - right : boolean = false; - scrollDown : boolean = false; - scrollUp : boolean = false; - x : number = 0; - y : number = 0; - constructor() {} + left: boolean = false; + middle: boolean = false; + right: boolean = false; + scrollDown: boolean = false; + scrollUp: boolean = false; + x: number = 0; + y: number = 0; + constructor() {} - makeMask() { - var mask = 0; - if (this.left) mask |= 1; - if (this.middle) mask |= 2; - if (this.right) mask |= 4; - if (this.scrollUp) mask |= 8; - if (this.scrollDown) mask |= 16; - return mask; - } + makeMask() { + var mask = 0; + if (this.left) mask |= 1; + if (this.middle) mask |= 2; + if (this.right) mask |= 4; + if (this.scrollUp) mask |= 8; + if (this.scrollDown) mask |= 16; + return mask; + } - initFromMouseEvent(e: MouseEvent) { - this.left = maskContains(e.buttons, 1); - this.right = maskContains(e.buttons, 2); - this.middle = maskContains(e.buttons, 4); + initFromMouseEvent(e: MouseEvent) { + this.left = maskContains(e.buttons, 1); + this.right = maskContains(e.buttons, 2); + this.middle = maskContains(e.buttons, 4); - this.x = e.offsetX; - this.y = e.offsetY; - } + this.x = e.offsetX; + this.y = e.offsetY; + } - // don't think there's a good way of shoehorning this in processEvent so .. - // (I guess could union e to be MouseEvent|WheelEvent and put this in there, but it'd be a - // completely unnesscary runtime check for a one-event situation, so having it be seperate - // and even call the MouseEvent implementation is more than good enough) - initFromWheelEvent(ev: WheelEvent) { - this.initFromMouseEvent(ev as MouseEvent); + // don't think there's a good way of shoehorning this in processEvent so .. + // (I guess could union e to be MouseEvent|WheelEvent and put this in there, but it'd be a + // completely unnesscary runtime check for a one-event situation, so having it be seperate + // and even call the MouseEvent implementation is more than good enough) + initFromWheelEvent(ev: WheelEvent) { + this.initFromMouseEvent(ev as MouseEvent); - // Now do the actual wheel handling - if (ev.deltaY < 0) - this.scrollUp = true; - else if (ev.deltaY > 0) - this.scrollDown = true; - } -} \ No newline at end of file + // Now do the actual wheel handling + if (ev.deltaY < 0) this.scrollUp = true; + else if (ev.deltaY > 0) this.scrollDown = true; + } +}