BANNER = "\ /\\\\ I N D E X\n\ \\\\/ Terminal\n\ ===============\n\n" /* Index Virtual Terminal (IVT) Webclient JS Copyright (C) 2026 Morgana This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ function newElement(tag, opt, children) { var elem = document.createElement(tag); if (opt) { if (opt.id) { elem.id = opt.id; } if (opt.cls) { elem.className = opt.cls; } if (opt.clss) { elem.className = opt.clss.join(" "); } if (opt.txt) { elem.append(document.createTextNode(opt.txt)); } if (opt.val) { elem.value = opt.val; } if (opt.name) { elem.name = opt.name; } if (opt.label) { elem.htmlFor = opt.label; } if (opt.href) { elem.href = opt.href; } if (opt.type) { elem.type = opt.type; } if (opt.autocomplete) { elem.autocomplete = opt.autocomplete; } if (opt.term) { elem.title = opt.term; elem.addEventListener("click", (event) => { event.preventDefault(); conn.process(opt.term); return false; }) } } if (children) { for (child of children) { elem.append(opt.list ? newElement("li", {}, [child]) : child); } } return elem; } function newElementFull(opt) { if (opt.raw) { return document.createTextNode(opt.raw); } var tag = opt.tag ?? "span" var children = opt.children ?? [] return newElement(tag, opt, children) } class IVTClient { constructor() { this.stdout = document.getElementById("stdout"); this.stdin = document.getElementById("stdin"); this.stdinBox = document.getElementById("stdin-box"); this.stdinBtn = document.getElementById("stdin-btn"); this.stdinPrompt = document.getElementById("stdin-prompt"); this.socket = null; this.username = null; this.pwdreturn = false; this.oldprompt = null; this.prelude = null; this.printTextCLS(BANNER, "term-bold term-centered"); this.stdinBox.addEventListener("submit", (event) => { event.preventDefault(); if (this.pwdreturn) { this.socket.send(this.stdin.value); this.stdin.type = "text"; this.pwdreturn = false; this.stdin.value = ""; this.stdinPrompt.innerText = this.oldprompt; return false; } this.process(this.stdin.value); this.stdin.value = ""; return false; }); this.socket = new WebSocket("wss://icolotl.com/index"); this.socket.addEventListener("open", (event) => { this.printText("Attempting to connect to INDEX server..."); this.stdinPrompt.innerText = "CONNECTING>"; }); this.socket.addEventListener("error", (event) => { this.printText("*** A network error has occurred. Please try again later. ***"); }) this.socket.addEventListener("close", (event) => { this.printText("Disconnected from INDEX server."); this.stdinPrompt.innerText = "DISCONNECTED>"; this.stdinBox.dataset.offline = ""; this.stdin.disabled = true; this.stdinBtn.disabled = true; }) this.socket.addEventListener("message", (event) => { if (this.prelude) { var fxn = this["on_" + this.prelude + "_prelude"]; if (fxn) { fxn.call(this, event.data) } this.prelude = null; return } var data = JSON.parse(event.data); var fxn = this["on_" + data.act + "_msg"]; if (fxn) { fxn.call(this, data) } }); } on_text_msg(data) { this.printText(data.text); } on_display_msg(data) { this.printTextFULL(data.msg); } on_activate_msg(data) { this.username = data.username; this.stdin.disabled = false; this.stdinBtn.disabled = false; this.stdinPrompt.innerText = `${data.username}@INDEX>`; delete this.stdinBox.dataset.offline; this.stdin.focus(); } on_userchange_msg(data) { this.username = data.username; this.stdinPrompt.innerText = `${data.username}@INDEX>`; this.stdin.focus(); } on_password_msg(data) { this.stdin.type = "password"; this.pwdreturn = true; this.oldprompt = this.stdinPrompt.innerText; this.stdinPrompt.innerText = data.prompt; this.printText(data.text); } on_action_msg(data) { if (data.action == "clear") { this.stdout.innerHTML = ""; } } on_prelude_msg(data) { this.prelude = data.action; } async on_music_prelude(data) { var elem = document.createElement("audio"); elem.src = window.URL.createObjectURL(data); elem.controls = true; elem.autoplay = true; this.stdout.append(elem); } process(text) { if (this.pwdreturn) { return; } this.printTextCLS(`${this.stdinPrompt.innerText} ${text}`, "history"); this.socket.send(text); } printText(text) { this.stdout.append(newElement("div", { txt: text })); this.stdout.scrollTop = this.stdout.scrollHeight; } printTextCLS(text, cls) { this.stdout.append(newElement("div", { txt: text, cls: cls })); this.stdout.scrollTop = this.stdout.scrollHeight; } printTextFULL(txtobj) { this.stdout.append(newElement("div", {}, txtobj.map(newElementFull))); this.stdout.scrollTop = this.stdout.scrollHeight; } } let conn = new IVTClient();