server-side that!!
This commit is contained in:
parent
32189ecd21
commit
1edc2efdec
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
certs/
|
6
nodemon.json
Normal file
6
nodemon.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"ignore": [
|
||||
"public/**/*.js",
|
||||
"*.json"
|
||||
]
|
||||
}
|
44
package-lock.json
generated
Normal file
44
package-lock.json
generated
Normal 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
23
package.json
Normal 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
BIN
public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
BIN
res/favicon.afdesign
Normal file
Binary file not shown.
122
server/main.js
Normal file
122
server/main.js
Normal 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));
|
||||
}
|
||||
|
Loading…
Reference in a new issue