server-side that!!

This commit is contained in:
ari melody 2023-09-30 07:58:03 +01:00
parent 32189ecd21
commit 1edc2efdec
Signed by: ari
GPG key ID: CF99829C92678188
10 changed files with 384 additions and 49 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
certs/

6
nodemon.json Normal file
View file

@ -0,0 +1,6 @@
{
"ignore": [
"public/**/*.js",
"*.json"
]
}

44
package-lock.json generated Normal file
View file

@ -0,0 +1,44 @@
{
"name": "openterminal",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "openterminal",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"ws": "^8.14.2"
}
},
"node_modules/ws": {
"version": "8.14.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"ws": {
"version": "8.14.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
"requires": {}
}
}
}

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "openterminal",
"version": "1.0.0",
"description": "",
"main": "./server/main.js",
"scripts": {
"start": "node ./server/main.js",
"dev": "nodemon ./server/main.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mellodoot/openterminal.git"
},
"author": "ari melody",
"license": "ISC",
"bugs": {
"url": "https://github.com/mellodoot/openterminal/issues"
},
"homepage": "https://github.com/mellodoot/openterminal#readme",
"dependencies": {
"ws": "^8.14.2"
}
}

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1,17 +1,18 @@
<!DOCTYPE html>
<html lang="ie">
<head>
<title>Open Terminal</title>
<title>OpenTerminal</title>
<link rel="stylesheet" href="styles/main.css">
<link rel="stylesheet" href="/styles/main.css">
<link rel="icon" type="image/png" href="/favicon.png">
<script src="scripts/main.js"></script>
<script src="/scripts/main.js"></script>
</head>
<body>
<main>
<pre id="content"></pre>
<div id="overlay"></div>
</main>
<div id="overlay"></div>
</body>
</html>

View file

@ -1,45 +1,106 @@
var TERM_INTERVAL = 0;
var buffer = "";
var send_buffer = "";
var content;
const banner =
`OpenTerminal v0.1.0
made with <3 by ari melody
`;
var client;
var pre_buffer_chars = 0;
var term_interval = 10;
var ready = false;
function start() {
console.log("%chello, world!", "color: #b7fd49; font-size: 3rem; font-weight: bold");
console.log(
`welcome to OpenTerminal!
home to a little shared text buffer.
i hope you enjoy your stay here!
to help you feel a little more comfortable, i've prepared some commands for you:
- set_colours(foreground, background)
changes the foreground and background colours of your terminal!
\`foreground\` and \`background\` must be hex colour codes, such as \`#ff00ff\`.
- set_palette(palette)
changes the foreground and background colours of your terminal to one of our many options of premade themes! including but not limited to the entire collection of catppuccin mocha colours! (i really like their palette ;p)
try it out! type \`PALETTE.\` into your console and browse the list of themes we have!`);
const foreground = localStorage.getItem("foreground");
const background = localStorage.getItem("background");
if (foreground && background) {
set_colours(foreground, background);
}
content = document.getElementById("content");
send_text(banner);
buffer += "Connecting to the server...";
setTimeout(connect, 500);
loop();
}
function loop() {
if (buffer.length > 0) {
const carat = content.querySelector("#carat");
if (carat) carat.remove();
const char = buffer.slice(0, 1);
if (char == "\b") {
content.innerText = content.innerText.slice(0, content.innerText.length - 1);
} else {
content.innerText += char;
}
buffer = buffer.slice(1);
const new_carat = document.createElement("div");
new_carat.id = "carat";
content.appendChild(new_carat);
const char = buffer.substring(0, 1);
insert_text(char);
buffer = buffer.substring(1);
}
setTimeout(loop, TERM_INTERVAL);
if (send_buffer.length > 0) {
const char = send_buffer.substring(0, 1);
client.send(char);
send_buffer = send_buffer.substring(1);
}
setTimeout(loop, term_interval);
}
function connect() {
client = new WebSocket("wss://localhost:8080");
client.addEventListener('open', () => {
// insert_text('\x00');
buffer += "\nConnection successful.\n\n";
buffer += "=== BEGIN SESSION ===\n\n";
});
client.addEventListener('message', event => {
buffer += event.data;
if (pre_buffer_chars == 0) {
pre_buffer_chars = content.innerText.length + buffer.length;
}
});
client.addEventListener('close', () => {
insert_text("\n\n[CONNECTION LOST]");
});
}
function insert_text(text) {
const carat = content.querySelector("#carat");
if (carat) carat.remove();
if (text == "\x00") {
content.innerText = "";
pre_buffer_chars = 0;
} else if (text == "\b" && content.innerText.length > pre_buffer_chars) {
content.innerText = content.innerText.slice(0, content.innerText.length - 1);
} else {
content.innerText += text;
}
const new_carat = document.createElement("div");
new_carat.id = "carat";
content.appendChild(new_carat);
}
function handle_input(event) {
// console.debug(event.key);
if (event.key == "'") {
event.preventDefault();
}
if (event.key == "Backspace") {
if (event.ctrlKey) {
if (event.ctrlKey && send_buffer.length == 0) {
const last_space = content.innerText.lastIndexOf(" ");
const last_newline = content.innerText.lastIndexOf("\n");
@ -49,34 +110,108 @@ function handle_input(event) {
}
const word_length = content.innerText.length - break_at;
send_text("\b".repeat(word_length));
for (let i = 0; i < word_length; i++) {
send_buffer += '\b';
}
return;
}
send_text("\b");
send_buffer += '\b';
return;
}
if (event.key.startsWith("Arrow")) {
if (event.key == "Enter") {
send_buffer += '\n';
return;
}
switch (event.key) {
case 'Shift':
case 'Control':
case 'Alt':
return;
case 'Enter':
send_text('\n');
break;
}
if (event.key.length > 1) {
return;
}
if (event.ctrlKey) {
return;
}
send_text(event.key);
send_buffer += event.key;
content.scrollTop = content.scrollHeight;
}
function send_text(char) {
function handle_paste(event) {
event.preventDefault();
if (send_buffer.length > 0) {
return;
}
const paste = (event.clipboardData || window.clipboardData).getData("text");
send_buffer += paste;
content.scrollTop = content.scrollHeight;
buffer += char;
}
const PALETTE = {
ari:
["#b7fd49", "#111111"],
green:
["#00ff00", "#111111"],
gold:
["#f9cb16", "#111111"],
bsod:
["#ffffff", "#0000ff"],
starlight:
["#d2b660", "#110717"],
catppuccin: {
frappe: {
green: ["#a6d189", "#232634"],
},
macchiato: {
green: ["#a6da95", "#24273a"],
},
mocha: {
rosewater: ["#f9e2af", "#1e1e2e"],
flamingo: ["#f2cdcd", "#1e1e2e"],
pink: ["#f5c2e7", "#1e1e2e"],
mauve: ["#cba6f7", "#1e1e2e"],
red: ["#f38ba8", "#1e1e2e"],
maroon: ["#eba0ac", "#1e1e2e"],
peach: ["#fab387", "#1e1e2e"],
yellow: ["#f9e2af", "#1e1e2e"],
green: ["#a6e3a1", "#1e1e2e"],
teal: ["#94e2d5", "#1e1e2e"],
sky: ["#89dceb", "#1e1e2e"],
sapphire: ["#74c7ec", "#1e1e2e"],
blue: ["#89b4fa", "#1e1e2e"],
lavendar: ["#b4befe", "#1e1e2e"],
},
},
community: {
jorun: /* @jorun@meta.jorun.dev */
["#0080ff", "#0d1020"],
meowca: /* @meowcatheorange@moth.zone */
["#ff4000", "#130805"],
halloween:
["#ff8000", "#1a120a"],
alcea: {
peach:
["#cf4a7299", "#fff"],
purple:
["#7f00ff", "#fff"],
},
},
};
function set_palette(palette) {
set_colours(palette[0], palette[1]);
}
function set_colours(foreground, background) {
localStorage.setItem("foreground", foreground);
localStorage.setItem("background", background);
document.documentElement.style.setProperty('--colour', foreground);
document.documentElement.style.setProperty('--bgcolour', background);
}
function clear_colours() {
localStorage.removeItem("foreground");
localStorage.removeItem("background");
document.documentElement.style.removeProperty('--colour');
document.documentElement.style.removeProperty('--bgcolour');
}
document.addEventListener("DOMContentLoaded", () => {
@ -84,4 +219,5 @@ document.addEventListener("DOMContentLoaded", () => {
});
document.addEventListener("keydown", handle_input);
document.addEventListener("paste", handle_paste);

View file

@ -1,19 +1,20 @@
:root {
--term-colour: #00ff00;
--colour: #a6e3a1;
--bgcolour: #1e1e2e;
}
body {
margin: 0;
padding: 0;
color: var(--term-colour);
background: #111;
color: var(--colour);
background-color: var(--bgcolour);
font-family: monospace;
font-size: 12px;
}
main {
margin: 1rem;
border: 1px solid var(--term-colour);
border: 1px solid var(--colour);
}
pre#content {
@ -31,7 +32,7 @@ div#carat {
width: .5em;
height: .9em;
display: inline-block;
background: var(--term-colour);
background: var(--colour);
transform: translateY(1px);
animation: linear .5s infinite forwards carat-blink;
}

BIN
res/favicon.afdesign Normal file

Binary file not shown.

122
server/main.js Normal file
View file

@ -0,0 +1,122 @@
const fs = require('fs');
const https = require('https');
const path = require('path');
const Websocket = require('ws');
const config = {
cert: fs.readFileSync('./certs/cert.crt'),
key: fs.readFileSync('./certs/cert.key'),
}
const MIME_TYPES = {
default: "application/octet-stream",
html: "text/html; charset=UTF-8",
js: "application/javascript",
css: "text/css",
png: "image/png",
jpg: "image/jpg",
gif: "image/gif",
ico: "image/x-icon",
svg: "image/svg+xml",
};
const motds = [
"hello, world!",
"all your TTY are belong to us.",
"TIP: got a linux system low on storage? try running `sudo rm -rf /`!",
"none of this is real! don't believe what they tell you.",
"it's awfully cosy in here!",
"how's the weather?",
"with each web request, my server room grows hotter.",
"mobile support coming later probably!",
];
const STATIC_PATH = path.join(process.cwd(), "public");
const banner =
`OpenTerminal v0.1.0
made with <3 by ari melody
`;
let sockets = [];
let buffer = "";
let MAX_BUFFER_SIZE = 10240;
async function get_file(url) {
const paths = [STATIC_PATH, url];
if (url.endsWith("/")) paths.push("index.html");
const file_path = path.join(...paths);
const path_traversal = !file_path.startsWith(STATIC_PATH);
const exists = await fs.promises.access(file_path).then(...[() => true, () => false]);
if (path_traversal || !exists) return false;
const ext = path.extname(file_path).substring(1).toLowerCase();
const stream = fs.createReadStream(file_path);
return { stream, ext };
}
const server = https.createServer(config, async (req, res) => {
const file = await get_file(req.url);
if (!file) {
res.writeHead(404);
res.end();
return;
}
const mime_type = MIME_TYPES[file.ext] || MIME_TYPES.default;
res.writeHead(200, { "Content-Type": mime_type });
file.stream.pipe(res);
// console.log(`${req.method} - ${req.url}`);
});
const wss = new Websocket.Server({ server });
wss.on('connection', socket => {
socket.send(banner + motds[Math.floor(Math.random() * motds.length)] + "\n\n");
socket.send(buffer);
sockets.push(socket);
console.log(`new connection.\n\tcurrent connections: ${sockets.length}`);
socket.on('message', handle_message);
socket.on('close', () => {
sockets = sockets.filter(s => s !== socket);
console.log(`connection closed.\n\tcurrent connections: ${sockets.length}`);
});
});
function handle_message(msg) {
if (msg == '\b') {
buffer = buffer.slice(0, buffer.length - 1);
send_text('\b');
return;
} else if (buffer.length >= MAX_BUFFER_SIZE) {
return;
}
if (msg == '\n') {
buffer += '\n';
send_text('\n');
return;
}
if (msg.length > 1) {
return;
}
buffer += msg.toString();
send_text(msg.toString());
/*
if (buffer.length > MAX_BUFFER_SIZE) {
buffer = buffer.slice(buffer.length - MAX_BUFFER_SIZE, buffer.length);
}
*/
}
server.listen(8080);
function send_text(text) {
sockets.forEach(s => s.send(text));
}