Refactor code to use CommonJS syntax, update README and package.json for clarity and add binary packaging. Also added initial custom client init stuff.
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

Signed-off-by: Hammer1279 <hammer@ht-dev.de>
This commit is contained in:
Hammer1279 2025-01-19 00:41:58 +01:00
parent 4b3c6e861d
commit 8b31201ad5
Signed by: Hammer1279
GPG Key ID: B6091167F4862B8F
5 changed files with 1373 additions and 51 deletions

41
.github/workflows/pkg.yml vendored Normal file
View File

@ -0,0 +1,41 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x64
- arm64
os:
- linux
- win
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v3
- name: Install dependencies
run: npm install
- name: Package into node binary
uses: lando/pkg-action@v2
with:
entrypoint: index.js
arch: ${{ matrix.arch }}
os: ${{ matrix.os }}
env:
PKG_ARCH: ${{ matrix.arch }}
PKG_OS: ${{ matrix.os }}

View File

@ -1,3 +1,7 @@
# node-telnet-client
To be used with HTDev BBS Server for encrypted communication and extended functionality
To be used with HTDev BBS Server for encrypted communication and extended functionality.
It has zero dependencies itself so to run a copy locally, just run it with `node index.js`
To build the application, run `npm install` and then `npm run build`.

157
index.js
View File

@ -1,68 +1,112 @@
import { createConnection } from 'net'
const fs = require("fs");
const path = require("path");
const { createConnection } = require('net');
const port = 23;
const hostname = "localhost";
const hostname = process.argv[2] || (process.pkg ? "dom.ht-dev.de" : "localhost");
const delayMs = 100; // Command delay in milliseconds
export const IAC = Buffer.from([0xff]); // Interpret As Command
export const DONT = Buffer.from([0xfe]); // Don't
export const DO = Buffer.from([0xfd]); // Do
export const WONT = Buffer.from([0xfc]); // Won't
export const WILL = Buffer.from([0xfb]); // Will
export const SB = Buffer.from([0xfa]); // Subnegotiation Begin
export const GA = Buffer.from([0xf9]); // Go Ahead
export const EL = Buffer.from([0xf8]); // Erase Line
export const EC = Buffer.from([0xf7]); // Erase Character
export const AYT = Buffer.from([0xf6]); // Are You There
export const AO = Buffer.from([0xf5]); // Abort Output
export const IP = Buffer.from([0xf4]); // Interrupt Process
export const BRK = Buffer.from([0xf3]); // Break
export const DM = Buffer.from([0xf2]); // Data Mark
export const NOP = Buffer.from([0xf1]); // No Operation
export const SE = Buffer.from([0xf0]); // Subnegotiation End
// 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
export const ECHO = Buffer.from([0x01]); // Echo
export const SUPPRESS_GO_AHEAD = Buffer.from([0x03]); // Suppress Go Ahead
export const STATUS = Buffer.from([0x05]); // Status
export const TIMING_MARK = Buffer.from([0x06]); // Timing Mark
export const TERMINAL_TYPE = Buffer.from([0x18]); // Terminal Type
export const NAWS = Buffer.from([0x1f]); // Negotiate About Window Size
export const TERMINAL_SPEED = Buffer.from([0x20]); // Terminal Speed
export const REMOTE_FLOW_CONTROL = Buffer.from([0x21]); // Remote Flow Control
export const LINEMODE = Buffer.from([0x22]); // Line Mode
export const ENVIRONMENT_VARIABLES = Buffer.from([0x24]); // Environment Variables
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
export const CUSTOM_CLIENT_INIT = Buffer.from([0x80]); // Custom Client Initialization
export const KEY_EXCHANGE = Buffer.from([0x81]); // Key Exchange
export const ENCRYPTION = Buffer.from([0x82]); // Encryption
export const AUTHENTICATION = Buffer.from([0x83]); // Authentication
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
export const FILE_TRANSFER = Buffer.from([0x85]); // File Transfer
export const PAUSE = Buffer.from([0x86]); // Pause
export const RESUME = Buffer.from([0x87]); // Resume
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', (key) => {
if (key === '\u0003') { // Ctrl+C
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 hex values of input for debugging
// console.log("Key pressed:", Array.from(key).map(b => '0x' + b.toString(16)));
// 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 {
} 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("$ ");
}
}
});
@ -71,6 +115,12 @@ socket.on("data", (data) => {
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);
}
@ -78,24 +128,41 @@ socket.on("data", (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> ");
});
export const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
socket.on("end", () => {
console.log("\nDisconnected from server");
process.exit();
});
export const send = async (command) => {
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
};

1197
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
{
"name": "telnet-client",
"version": "1.0.0",
"version": "0.0.1",
"description": "To be used with HTDev BBS Server for encrypted communication and extended functionality.",
"main": "index.js",
"bin": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npx pkg package.json -C GZip"
},
"repository": {
"type": "git",
@ -12,8 +14,23 @@
},
"author": "Hammer1279",
"license": "MIT",
"type": "module",
"dependencies": {
"telnet-client": "file:"
},
"devDependencies": {
"@yao-pkg/pkg": "^5.12.1"
},
"type": "commonjs",
"pkg": {
"assets": [
"README.md",
"LICENSE",
"package.json"
],
"targets": [
"latest-win-x64",
"latest-linux-x64"
],
"outputPath": "dist"
}
}