gommand/static/start-interactive.js

170 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

document.addEventListener("DOMContentLoaded", function () {
const input = document.getElementById("command-input");
const terminal = document.getElementById("terminal");
let interactiveWS = null;
let interactiveMode = false;
let lastKeySentTime = 0;
const throttleInterval = 50; // milliseconds
// Holds the users input on the current line.
let currentLineBuffer = "";
let currentLineElem = null;
const ansi_up = new AnsiUp;
input.addEventListener("keydown", function (event) {
if (!interactiveMode && event.key === "Enter") {
const command = input.value.trim();
if (command === "start-interactive") {
startInteractiveSession();
input.value = "";
event.preventDefault();
return;
}
// Otherwise, normal HTTP submission…
}
});
function startInteractiveSession() {
interactiveWS = new WebSocket("ws://" + location.host + "/terminal");
interactiveMode = true;
terminal.insertAdjacentHTML('beforeend', "\n--- Interactive session started ---\n");
input.style.display = "none"; // Hide normal input.
currentLineBuffer = "";
createCurrentLineElem();
interactiveWS.binaryType = "arraybuffer";
interactiveWS.onmessage = function (event) {
// When the shell returns output (for example, after Enter), process it.
let text = new TextDecoder("utf-8").decode(event.data);
text = removeOSCTitle(text);
processPTYOutput(text);
};
interactiveWS.onclose = function () {
interactiveMode = false;
terminal.insertAdjacentHTML('beforeend', "\n--- Interactive session ended ---\n");
input.style.display = "block";
input.focus();
document.removeEventListener("keydown", handleInteractiveKey, true);
};
interactiveWS.onerror = function (err) {
terminal.insertAdjacentHTML('beforeend', "\n--- Error in interactive session ---\n");
console.error("Interactive WS error:", err);
interactiveMode = false;
input.style.display = "block";
document.removeEventListener("keydown", handleInteractiveKey, true);
};
// Listen for all key events while in interactive mode.
document.addEventListener("keydown", handleInteractiveKey, true);
}
// Process output received from the PTY.
// We expect output to include completed lines (with newline) as command results.
function processPTYOutput(text) {
// Split output into lines.
const parts = text.split("\n");
for (let i = 0; i < parts.length; i++) {
const processedPart = processBackspaces(parts[i]);
if (i < parts.length - 1) {
// Completed line.
currentLineBuffer += processedPart;
const finishedLine = document.createElement("div");
finishedLine.innerHTML = ansi_up.ansi_to_html(currentLineBuffer);
terminal.appendChild(finishedLine);
currentLineBuffer = "";
createCurrentLineElem();
} else {
// Incomplete line.
currentLineBuffer += processedPart;
}
}
updateCurrentLine();
}
// Update the current line element with the current buffer and a blinking cursor.
function updateCurrentLine() {
if (currentLineElem) {
currentLineElem.innerHTML = ansi_up.ansi_to_html(currentLineBuffer);
updateCursor();
}
terminal.scrollTop = terminal.scrollHeight;
}
// Create (or recreate) the current line element.
function createCurrentLineElem() {
if (currentLineElem && currentLineElem.parentNode) {
currentLineElem.parentNode.removeChild(currentLineElem);
}
currentLineElem = document.createElement("span");
currentLineElem.id = "current-line";
// Ensure the current line element is inline so the cursor stays on the same line.
currentLineElem.style.display = "inline";
terminal.appendChild(currentLineElem);
}
// Append a blinking cursor to the current line.
function updateCursor() {
const existingCursor = currentLineElem.querySelector(".cursor");
if (existingCursor) {
existingCursor.parentNode.removeChild(existingCursor);
}
currentLineElem.insertAdjacentHTML('beforeend', '<span class="cursor">█</span>');
}
// Throttled key handler that updates the local current line buffer and sends keys to the PTY.
function handleInteractiveKey(event) {
if (!interactiveMode || !interactiveWS || interactiveWS.readyState !== WebSocket.OPEN)
return;
const now = Date.now();
if (now - lastKeySentTime < throttleInterval) {
event.preventDefault();
return;
}
lastKeySentTime = now;
let data = "";
if (event.key === "Enter") {
data = "\n";
// Append newline to the local buffer and clear it
currentLineBuffer += "\n";
updateCurrentLine();
currentLineBuffer = "";
} else if (event.key === "Backspace") {
data = "\x7f"; // DEL
// Update the local buffer: remove the last character.
currentLineBuffer = currentLineBuffer.slice(0, -1);
updateCurrentLine();
} else if (event.key.length === 1) {
data = event.key;
currentLineBuffer += event.key;
updateCurrentLine();
} else {
return;
}
interactiveWS.send(data);
event.preventDefault();
}
// Remove OSC sequences (for terminal title updates).
function removeOSCTitle(text) {
const oscRegex = /\x1b\]0;.*?(?:\x07|\x1b\\)/g;
return text.replace(oscRegex, "");
}
// Process backspace characters in a string (for PTY output).
function processBackspaces(text) {
let result = "";
for (let i = 0; i < text.length; i++) {
const ch = text[i];
if (ch === "\x7f" || ch === "\b") {
result = result.slice(0, -1);
} else {
result += ch;
}
}
return result;
}
});