Hammer1279 8b31201ad5
Some checks failed
Node.js CI / build (arm64, linux) (push) Failing after 47s
Node.js CI / build (arm64, win) (push) Failing after 32s
Node.js CI / build (x64, linux) (push) Successful in 32s
Node.js CI / build (x64, win) (push) Successful in 30s
Refactor code to use CommonJS syntax, update README and package.json for clarity and add binary packaging. Also added initial custom client init stuff.
Signed-off-by: Hammer1279 <hammer@ht-dev.de>
2025-01-19 00:41:58 +01:00

169 lines
6.1 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const { createConnection } = require('net');
const port = 23;
const hostname = process.argv[2] || (process.pkg ? "dom.ht-dev.de" : "localhost");
const delayMs = 100; // Command delay in milliseconds
// Telnet commands
const IAC = Buffer.from([0xff]); // Interpret As Command
const DONT = Buffer.from([0xfe]); // Don't
const DO = Buffer.from([0xfd]); // Do
const WONT = Buffer.from([0xfc]); // Won't
const WILL = Buffer.from([0xfb]); // Will
const SB = Buffer.from([0xfa]); // Subnegotiation Begin
const GA = Buffer.from([0xf9]); // Go Ahead
const EL = Buffer.from([0xf8]); // Erase Line
const EC = Buffer.from([0xf7]); // Erase Character
const AYT = Buffer.from([0xf6]); // Are You There
const AO = Buffer.from([0xf5]); // Abort Output
const IP = Buffer.from([0xf4]); // Interrupt Process
const BRK = Buffer.from([0xf3]); // Break
const DM = Buffer.from([0xf2]); // Data Mark
const NOP = Buffer.from([0xf1]); // No Operation
const SE = Buffer.from([0xf0]); // Subnegotiation End
// Common Telnet options or features
const ECHO = Buffer.from([0x01]); // Echo
const SUPPRESS_GO_AHEAD = Buffer.from([0x03]); // Suppress Go Ahead
const STATUS = Buffer.from([0x05]); // Status
const TIMING_MARK = Buffer.from([0x06]); // Timing Mark
const TERMINAL_TYPE = Buffer.from([0x18]); // Terminal Type
const NAWS = Buffer.from([0x1f]); // Negotiate About Window Size
const TERMINAL_SPEED = Buffer.from([0x20]); // Terminal Speed
const REMOTE_FLOW_CONTROL = Buffer.from([0x21]); // Remote Flow Control
const LINEMODE = Buffer.from([0x22]); // Line Mode
const ENVIRONMENT_VARIABLES = Buffer.from([0x24]); // Environment Variables
// custom codes for own client
const CUSTOM_CLIENT_INIT = Buffer.from([0x80]); // Custom Client Initialization
const KEY_EXCHANGE = Buffer.from([0x81]); // Key Exchange
const ENCRYPTION = Buffer.from([0x82]); // Encryption
const AUTHENTICATION = Buffer.from([0x83]); // Authentication
// 0x84 reserved for future use
const FILE_TRANSFER = Buffer.from([0x85]); // File Transfer
const PAUSE = Buffer.from([0x86]); // Pause
const RESUME = Buffer.from([0x87]); // Resume
// Set the path of the project folder base on whether it is run with nodejs or as an executable
let project_folder;
if (process.pkg) {
// It is run as an executable
project_folder = path.dirname(process.execPath)
} else {
// It is run with nodejs
project_folder = __dirname
}
if (process.pkg) {
console.debug = () => {}; // Disable debug logging when run as an executable
fs.writeFileSync(path.join(project_folder, "README.md"), fs.readFileSync(path.join(__dirname, "README.md"))); // copy newest readme to folder
fs.writeFileSync(path.join(project_folder, "LICENSE"), fs.readFileSync(path.join(__dirname, "LICENSE"))); // copy newest license to folder
}
const socket = createConnection(port, hostname);
let hold = false; // Hold input
process.stdin.setEncoding("ascii");
process.stdin.setRawMode(true);
process.stdin.resume();
// format keys before sending to server
process.stdin.on('data', async (key) => {
if (key === '\u0004') { // Ctrl+D
process.exit();
} else if (key === '\u0003') { // Ctrl+C
send("\u0003");
} else if (key === '\u0018') { // Ctrl+X
console.log("Entered client command mode");
hold = true; // Hold input
process.stdin.setRawMode(false);
// Add any additional functionality for Ctrl+X here
}
// Log Unicode values of input for debugging
console.debug("Key pressed:", Array.from(key).map(b => '\\u' + b.toString(16).padStart(4, '0')));
if (key == "\r") {
// Enter key
socket.write("\r\n");
} else if (!hold) {
socket.write(key);
} else {
console.debug("Input on hold");
const command = key.replace(/\r?\n|\r/g, ''); // Remove new line characters
if (["exit", "quit"].includes(command)) {
hold = false; // Release input
process.stdin.setRawMode(true);
console.log("Exited client command mode");
process.stdout.write("> ");
} else if (command == "disconnect") {
send("\u0003");
} else if (command == "help") {
// Add any additional functionality for client side commands
} else {
process.stdout.write("$ ");
}
}
});
socket.on("data", (data) => {
if (data.equals(PAUSE)) {
process.stdin.pause();
} else if (data.equals(RESUME)) {
process.stdin.resume();
} else if (data.equals(IP)) {
process.exit();
} else if (data.equals(Buffer.concat([IAC, DO, CUSTOM_CLIENT_INIT]))) {
socket.write(Buffer.concat([IAC, WILL, KEY_EXCHANGE])); // Start Key Exchange
} else if (data.equals(Buffer.concat([IAC, DO, KEY_EXCHANGE]))) {
// Key Exchange to be implemented here
} else {
process.stdout.write(data);
}
});
socket.on("connect", async () => {
console.log("Connected to server");
// initialization
socket.write(Buffer.concat([IAC, WILL, NAWS, SB, NAWS, Buffer.from([/* 24x80 */ 0x00, 0x18, 0x00, 0x50]), SE])); // Negotiate About Window Size
await delay(delayMs); // Wait for server to process
socket.write(Buffer.concat([IAC, WILL, TERMINAL_TYPE, IAC, SB, TERMINAL_TYPE, Buffer.from("xterm-256color"), IAC, SE])); // Terminal Type
await delay(delayMs);
socket.write(Buffer.concat([IAC, WILL, ECHO])); // Echo
await delay(delayMs);
socket.write(Buffer.concat([IAC, WILL, CUSTOM_CLIENT_INIT])); // Custom Client Initialization
await delay(delayMs);
socket.write(Buffer.from([0x0d, 0x0a])); // Line Feed
await delay(delayMs);
// initialization complete
await send("help");
await delay(500);
process.stdout.write("\rCtrl+X for client side commands\r\nCtrl+C to exit, Ctrl+D to force close\r\n> ");
});
socket.on("end", () => {
console.log("\nDisconnected from server");
process.exit();
});
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const send = async (command) => {
await delay(delayMs);
socket.write(command); // Command
await delay(delayMs);
socket.write("\r\n"); // Line Feed
};
module.exports = {
delay,
send,
project_folder
};