Compare commits

...

2 commits

Author SHA1 Message Date
f69ae1b965 Merge pull request 'move index to src, add .env add types, fix > to actual bash like input and history' (#1) from creations/ipv4.army:main into main
Some checks failed
Code quality checks / biome (push) Failing after 12s
Reviewed-on: #1
2025-05-06 00:59:43 +02:00
17467258fc
move index to src, add .env add types, fix > to actual bash like input and history
Some checks failed
Code quality checks / biome (pull_request) Failing after 6s
2025-05-05 18:55:57 -04:00
11 changed files with 339 additions and 180 deletions

2
.env.example Normal file
View file

@ -0,0 +1,2 @@
HOSTNAME=localhost
PORT=2056

119
index.ts
View file

@ -1,119 +0,0 @@
import { file, gc, serve } from "bun";
import Backend from "./src/back";
import pkg from "./package.json";
let heartrate = 0;
let lanyard = {};
require("node:fs/promises")
.rm("./dist", { recursive: true, force: true })
.catch(() => {
// ignore
});
if (!Backend.development) {
await Backend.build();
}
const server = serve({
routes: {
"/": async (req: Bun.BunRequest, server: Bun.Server) => {
await Backend.postAnalytics(req, server);
if (Backend.development) {
await Backend.build();
}
return await Backend.Responses.file(file("./dist/index.html"));
},
"/assets/:file": async (req: Bun.BunRequest<"/assets/:file">) => {
return await Backend.Responses.file(file(`./dist/${req.params.file}`));
},
"/public/:file": async (req: Bun.BunRequest<"/public/:file">) => {
return await Backend.Responses.file(file(`./public/${req.params.file}`));
},
"/api/server": () => {
const string = JSON.stringify(process);
const data = JSON.parse(string);
// clear possibly data that could be sensitive
data.env = {};
data.availableMemory = process.availableMemory();
data.constrainedMemory = process.constrainedMemory();
data.cpuUsage = process.cpuUsage();
data.memoryUsage = process.memoryUsage();
data.uptime = process.uptime();
data.package = pkg;
return Backend.Responses.json({ data });
},
"/api/health": () => {
return Backend.Responses.ok();
},
"/api/ws": async (req, server) => {
if (server.upgrade(req)) {
return;
}
await Backend.postAnalytics(req, server);
return Response.redirect("/");
},
"/api/gc": async () => {
gc(true);
return Backend.Responses.ok();
},
"/api/headers": async (req) => {
return Backend.Responses.json({ ...req.headers.toJSON() });
},
},
fetch: async (request, server) => {
await Backend.postAnalytics(request, server);
return Response.redirect("/");
},
websocket: {
idleTimeout: 1,
open: async (ws) => {
ws.subscribe("lanyard");
ws.send(JSON.stringify({ type: "lanyard", data: lanyard }), true);
ws.subscribe("hyperate");
ws.send(
JSON.stringify({ type: "hyperate", data: { hr: heartrate } }),
true,
);
},
message: async (ws, message) => {
ws.send(JSON.stringify({ type: "echo", data: message }), true);
},
},
development: Backend.development,
port: 2056,
});
new Backend.Sockets.Hyperate((data) => {
heartrate = data;
server.publish(
"hyperate",
JSON.stringify({ type: "hyperate", data: { hr: heartrate } }),
true,
);
});
new Backend.Sockets.Lanyard((data) => {
lanyard = data;
server.publish(
"lanyard",
JSON.stringify({ type: "lanyard", data: lanyard }),
true,
);
});

View file

@ -2,8 +2,8 @@
"name": "ipv4.army", "name": "ipv4.army",
"module": "index.ts", "module": "index.ts",
"scripts": { "scripts": {
"dev": "NODE_ENV=development bun run --hot . --watch", "dev": "NODE_ENV=development bun run --hot src/index.ts",
"start": "bun run .", "start": "bun run src/index.ts",
"lint": "bunx biome ci . --verbose", "lint": "bunx biome ci . --verbose",
"lint:fix": "bunx biome check --fix" "lint:fix": "bunx biome check --fix"
}, },

View file

@ -3,9 +3,9 @@ import ReconnectingWebSocket from "reconnecting-websocket";
export default class { export default class {
private _socket: ReconnectingWebSocket; private _socket: ReconnectingWebSocket;
private _keepAlive: NodeJS.Timeout | null; private _keepAlive: NodeJS.Timeout | null;
private _callback: (data: { [key: string]: string }) => void; private _callback: (data: LanyardData) => void;
constructor(callback: (data: { [key: string]: string }) => void) { constructor(callback: (data: LanyardData) => void) {
this._socket = new ReconnectingWebSocket( this._socket = new ReconnectingWebSocket(
"wss://lanyard.creations.works/socket", "wss://lanyard.creations.works/socket",
); );

View file

@ -1,27 +1,145 @@
import Hyperate from "./components/Hyperate"; import Hyperate from "./components/Hyperate";
import Lanyard from "./components/Lanyard"; import Lanyard from "./components/Lanyard";
let latestLanyard: LanyardData | null = null;
window.addEventListener("lanyard-update", (e) => {
latestLanyard = (e as CustomEvent<LanyardData>).detail;
});
export default () => { export default () => {
return ( const container = document.createElement("div");
<div class="app"> container.className = "app terminal";
<p>seth&gt; cat ./about.txt</p>
<p>
A Dedicated Backend Developer,
<br />
with a passion for high-fidelity audio,
<br />
gaming, and web development.
</p>
<p>seth&gt; curl /tmp/discord-ipc</p> const renderElement = (content: string | Node) => {
<p> const p = document.createElement("p");
<Lanyard /> if (typeof content === "string") {
</p> p.textContent = content;
} else {
p.appendChild(content);
}
return p;
};
<p>seth&gt; cat /tmp/heartrate</p> const prompt = "[seth@ipv4 ~]$";
<p>
<Hyperate /> const staticLines: (string | (() => Node))[] = [
</p> `${prompt} cat ./about.txt`,
</div> () =>
); document
.createRange()
.createContextualFragment(
"A Dedicated Backend Developer,<br />with a passion for high-fidelity audio,<br />gaming, and web development.",
),
`${prompt} cat /tmp/discord-ipc`,
() => Lanyard(),
`${prompt} cat /tmp/heartrate`,
() => Hyperate(),
];
const renderStatic = () => {
for (const line of staticLines) {
const content = typeof line === "function" ? line() : line;
container.appendChild(renderElement(content));
}
};
renderStatic();
const lanyardInstance = Lanyard();
const files: Record<string, () => Node> = {
"./about.txt": () =>
document
.createRange()
.createContextualFragment(
"A Dedicated Backend Developer,<br />with a passion for high-fidelity audio,<br />gaming, and web development.",
),
"/tmp/discord-ipc": () => lanyardInstance,
"/tmp/heartrate": () => Hyperate(),
};
const history: string[] = [];
let historyIndex = -1;
const inputBox = document.createElement("input");
inputBox.className = "terminal-input";
inputBox.autofocus = true;
const inputLine = document.createElement("div");
inputLine.className = "terminal-line";
const promptSpan = document.createElement("span");
promptSpan.textContent = `${prompt} `;
inputLine.appendChild(promptSpan);
inputLine.appendChild(inputBox);
container.appendChild(inputLine);
const appendLine = (line: string | Node) => {
container.insertBefore(renderElement(line), inputLine);
};
inputBox.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const cmd = inputBox.value.trim();
if (!cmd) return;
history.push(cmd);
historyIndex = history.length;
appendLine(`${prompt} ${cmd}`);
let out: string | Node;
if (cmd.startsWith("cat ")) {
const file = cmd.slice(4).trim();
out = files[file]?.() ?? `cat: ${file}: No such file`;
} else if (cmd === "ls") {
out = Object.keys(files)
.filter((f) => f.startsWith("./"))
.map((f) => f.slice(2))
.join("\n");
} else if (cmd.startsWith("ls ")) {
const dir = cmd.slice(3).trim();
if (dir === "/tmp") {
out = Object.keys(files)
.filter((f) => f.startsWith("/tmp/"))
.map((f) => f.slice("/tmp/".length))
.join("\n");
} else {
out = `ls: cannot access '${dir}': No such file or directory`;
}
} else if (cmd === "help") {
out = [
"Available commands:",
" cat [file] View contents of a file",
" ls List files in current directory",
" ls /tmp List files in /tmp directory",
" help Show this message",
].join("\n");
} else {
out = `bash: ${cmd}: command not found`;
}
appendLine(out);
inputBox.value = "";
} else if (e.key === "ArrowUp") {
if (historyIndex > 0) {
historyIndex--;
inputBox.value = history[historyIndex] || "";
}
e.preventDefault();
} else if (e.key === "ArrowDown") {
if (historyIndex < history.length - 1) {
historyIndex++;
inputBox.value = history[historyIndex] || "";
} else {
historyIndex = history.length;
inputBox.value = "";
}
e.preventDefault();
}
});
return container;
}; };

View file

@ -39,8 +39,11 @@ class Socket extends EventTarget {
}, 30 * 1000); }, 30 * 1000);
} }
emitLanyard(lanyard: object) { emitLanyard(lanyard: LanyardData) {
this.dispatchEvent(new CustomEvent("lanyard", { detail: lanyard })); this.dispatchEvent(new CustomEvent("lanyard", { detail: lanyard }));
window.dispatchEvent(
new CustomEvent("lanyard-update", { detail: lanyard }),
);
} }
emitHyperate(heartRate: number) { emitHyperate(heartRate: number) {
this.dispatchEvent(new CustomEvent("hyperate", { detail: heartRate })); this.dispatchEvent(new CustomEvent("hyperate", { detail: heartRate }));

View file

@ -1,22 +1,22 @@
import { highlightAll } from "@speed-highlight/core"; import { highlightElement } from "@speed-highlight/core";
import { createRef } from "tsx-dom"; import { createRef } from "tsx-dom";
import socket from "../../Socket"; import socket from "../../Socket";
const statusTypes: { [key: string]: string } = { const statusTypes = {
online: "rgb(0, 150, 0)", online: "rgb(0, 150, 0)",
idle: "rgb(150, 150, 0)", idle: "rgb(150, 150, 0)",
dnd: "rgb(150, 0, 0)", dnd: "rgb(150, 0, 0)",
offline: "rgb(150, 150, 150)", offline: "rgb(150, 150, 150)",
}; };
const gradientTypes: { [key: string]: string } = { const gradientTypes = {
online: "rgba(0, 150, 0, 0.1)", online: "rgba(0, 150, 0, 0.1)",
idle: "rgba(150, 150, 0, 0.1)", idle: "rgba(150, 150, 0, 0.1)",
dnd: "rgba(150, 0, 0, 0.1)", dnd: "rgba(150, 0, 0, 0.1)",
offline: "rgba(150, 150, 150, 0.1)", offline: "rgba(150, 150, 150, 0.1)",
}; };
const activityTypes: { [key: number]: string } = {
const activityTypes: Record<number, string> = {
0: "Playing", 0: "Playing",
1: "Streaming", 1: "Streaming",
2: "Listening to", 2: "Listening to",
@ -25,43 +25,37 @@ const activityTypes: { [key: number]: string } = {
5: "Competing in", 5: "Competing in",
}; };
const stringify = (data: { [key: string]: string }) => {
return JSON.stringify(data, null, 2);
};
export default () => { export default () => {
const code = createRef<HTMLDivElement>(); const container = createRef<HTMLDivElement>();
socket.addEventListener("lanyard", (event: Event) => { socket.addEventListener("lanyard", (event: Event) => {
const lanyard = (event as CustomEvent).detail; const lanyard = (event as CustomEvent<LanyardData>).detail;
document.body.style = `--status-color: ${statusTypes[lanyard.discord_status]}; --gradient-color: ${gradientTypes[lanyard.discord_status]};`; document.body.style = `--status-color: ${statusTypes[lanyard.discord_status]}; --gradient-color: ${gradientTypes[lanyard.discord_status]};`;
if (code.current) {
code.current.innerHTML = stringify({ if (container.current) {
status: lanyard.discord_status, container.current.className = "shj-lang-json";
activities: lanyard.activities.map( container.current.textContent = JSON.stringify(
(act: { {
type: number; status: lanyard.discord_status,
name: string; activities: lanyard.activities.map((act) => {
details: string; const type = activityTypes[act.type];
state: string; const parts = [type];
}) => { if (act.name !== type) parts.push(act.name);
return [ if (act.details) parts.push(act.details);
...new Set([ if (act.state) parts.push(act.state);
activityTypes[act.type], return parts;
act.name, }),
act.details, },
act.state, null,
]), 2
].filter((n) => n); );
}, highlightElement(container.current);
),
});
} }
highlightAll();
}); });
return ( return (
<div class="shj-lang-json" ref={code}> <div class="shj-lang-json" ref={container}>
{"{}"} {"{}"}
</div> </div>
); );

View file

@ -1,5 +1,4 @@
@import "../../node_modules/@speed-highlight/core/dist/themes/dark.css"; @import "../../node_modules/@speed-highlight/core/dist/themes/dark.css";
@import "./App.css"; @import "./App.css";
html, html,
@ -21,4 +20,47 @@ body {
rgba(0, 0, 0, 1) 100% rgba(0, 0, 0, 1) 100%
); );
display: flex; display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
}
p {
margin: 0;
padding: 0;
line-height: 1.4em;
}
.terminal {
white-space: pre-wrap;
font-family: monospace;
width: 100vw;
height: 100vh;
overflow-y: auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
gap: 0.4em;
}
.terminal-input {
background: transparent;
border: none;
color: inherit;
font: inherit;
outline: none;
display: inline-block;
width: 100%;
}
.terminal-line {
display: flex;
align-items: baseline;
flex-direction: row;
width: 100%;
}
.terminal-line > span {
white-space: pre;
} }

105
src/index.ts Normal file
View file

@ -0,0 +1,105 @@
import fs from "node:fs/promises";
import { file, gc, serve } from "bun";
import pkg from "../package.json";
import Backend from "./back";
let heartrate = 0;
let lanyard: LanyardData = {
discord_status: "online",
activities: [],
};
await fs.rm("./dist", { recursive: true, force: true }).catch(() => {});
if (!Backend.development) {
await Backend.build();
}
const server = serve({
port: process.env.PORT || 3000,
hostname: process.env.HOSTNAME || "localhost",
development: Backend.development,
routes: {
"/": async (req, server) => {
await Backend.postAnalytics(req, server);
if (Backend.development) await Backend.build();
return Backend.Responses.file(file("./dist/index.html"));
},
"/assets/:file": async (req) =>
Backend.Responses.file(file(`./dist/${req.params.file}`)),
"/public/:file": async (req) =>
Backend.Responses.file(file(`./public/${req.params.file}`)),
"/api/server": () => {
const safeProcess = JSON.parse(JSON.stringify(process));
safeProcess.env = {};
safeProcess.availableMemory = process.availableMemory();
safeProcess.constrainedMemory = process.constrainedMemory();
safeProcess.cpuUsage = process.cpuUsage();
safeProcess.memoryUsage = process.memoryUsage();
safeProcess.uptime = process.uptime();
safeProcess.package = pkg;
return Backend.Responses.json({ data: safeProcess });
},
"/api/health": () => Backend.Responses.ok(),
"/api/ws": async (req, server) => {
if (!server.upgrade(req)) {
await Backend.postAnalytics(req, server);
return Response.redirect("/");
}
},
"/api/gc": async () => {
gc(true);
return Backend.Responses.ok();
},
"/api/headers": (req) => Backend.Responses.json(req.headers.toJSON()),
},
fetch: async (req, server) => {
await Backend.postAnalytics(req, server);
return Response.redirect("/");
},
websocket: {
idleTimeout: 1,
open: (ws) => {
ws.subscribe("lanyard");
ws.send(JSON.stringify({ type: "lanyard", data: lanyard }), true);
ws.subscribe("hyperate");
ws.send(
JSON.stringify({ type: "hyperate", data: { hr: heartrate } }),
true,
);
},
message: (ws, msg) => {
ws.send(JSON.stringify({ type: "echo", data: msg }), true);
},
},
});
new Backend.Sockets.Hyperate((data) => {
heartrate = data;
server.publish(
"hyperate",
JSON.stringify({ type: "hyperate", data: { hr: heartrate } }),
true,
);
});
new Backend.Sockets.Lanyard((data) => {
lanyard = data;
server.publish(
"lanyard",
JSON.stringify({ type: "lanyard", data: lanyard }),
true,
);
});

View file

@ -21,6 +21,7 @@
// Some stricter flags (disabled by default) // Some stricter flags (disabled by default)
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false "noPropertyAccessFromIndexSignature": false,
"typeRoots": ["./types"]
} }
} }

13
types/lanyard.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
type LanyardActivity = {
type: number;
name: string;
details?: string;
state?: string;
[key: string]: unknown;
};
type LanyardData = {
discord_status: "online" | "idle" | "dnd" | "offline";
activities: LanyardActivity[];
[key: string]: unknown;
};