not so initial commit

This commit is contained in:
2026-01-11 03:50:03 +01:00
commit a2016e2b0e
41 changed files with 54638 additions and 0 deletions

228
serve.ts Normal file
View File

@@ -0,0 +1,228 @@
import { SHA512_256, type ServerWebSocket } from "bun";
import { SnowflakeGenerator } from "./snowflake.ts";
function getContentType(path: string): string {
if (path.endsWith(".html")) return "text/html";
if (path.endsWith(".css")) return "text/css";
if (path.endsWith(".js")) return "application/javascript";
if (path.endsWith(".json")) return "application/json";
if (path.endsWith(".png")) return "image/png";
if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
if (path.endsWith(".svg")) return "image/svg+xml";
if (path.endsWith(".ico")) return "image/x-icon";
return "application/octet-stream";
}
enum OpcodesClientbound {
authorizeLogin,
initMessages,
updateMessages,
registerSuccess,
error,
users,
keepAlive = 9,
}
enum OpcodesServerbound {
identify,
sendMessage,
register,
getUsers,
keepAlive = 9,
}
interface user {
name: string;
username: string;
pfp: string;
id: number;
token: string;
password: string;
dev: boolean;
}
interface message {
id: string;
author: user;
content: string;
timestamp: number;
editedTimestamp?: number;
replyTo?: string;
hasSent: true; // client sends false but server should always say true
}
const messages: message[] = await Bun.file("./db/messages.json").json();
const users: user[] = await Bun.file("./db/users.json").json();
const generator = new SnowflakeGenerator();
function sendError(ws: ServerWebSocket, reason: string) {
ws.send(
JSON.stringify({
op: OpcodesClientbound.error,
data: {
reason,
},
})
);
ws.close();
}
const server = Bun.serve({
port: 80,
websocket: {
open(ws) {
console.log("connected to ws");
ws.subscribe("messages");
// @ts-expect-error
ws.interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(
JSON.stringify({
op: OpcodesClientbound.keepAlive,
data: null,
})
);
console.log("sending keeaplivve");
} else {
// @ts-expect-error
clearInterval(ws.interval);
}
// @ts-expect-error guhh
clearTimeout(ws.pingTimeout);
// @ts-expect-error guhh
ws.pingTimeout = setTimeout(() => {
ws.close();
}, 30_000);
}, 60_000);
// @ts-expect-error guhh
ws.pingTimeout = null;
},
message(ws, msg) {
const obj = JSON.parse(msg.toString());
let user: string | any[];
switch (obj.op) {
case OpcodesServerbound.identify:
user = users.filter(user => user.token === obj.data.token);
if (!(user && user.length !== 0)) {
user = users.filter(
(user) =>
user.password ===
new SHA512_256().update(obj.data.password ?? "").digest("base64")
);
}
if (user && user.length !== 0) {
ws.send(
JSON.stringify({
op: OpcodesClientbound.authorizeLogin,
data: user[0],
})
);
ws.send(
JSON.stringify({
op: OpcodesClientbound.initMessages,
data: messages,
})
);
} else {
sendError(ws, "Not a real user");
}
break;
case OpcodesServerbound.sendMessage:
user = users.filter((user) => user.token === obj.data.token) as user[];
if (user && user.length !== 0) {
obj.op = OpcodesClientbound.updateMessages;
obj.data.id = generator.generateSnowflake();
obj.data.author = JSON.parse(JSON.stringify(user[0]));
obj.data.author.password = null;
obj.data.author.token = null;
obj.data.hasSent = true;
obj.data.token = null;
obj.data.author.dev = user[0].dev;
ws.publish("messages", JSON.stringify(obj));
ws.send(JSON.stringify(obj));
messages.push(obj.data);
Bun.write("./db/messages.json", JSON.stringify(messages));
} else {
sendError(ws, "Not a real user");
}
break;
case OpcodesServerbound.getUsers:
let mmiow = JSON.parse(JSON.stringify(users)) as user[];
mmiow.forEach(user => {
// @ts-expect-error shush
user.token = null;
// @ts-expect-error shush
user.password = null;
})
ws.send(JSON.stringify({
op: OpcodesClientbound.users,
data: mmiow
}))
break;
case OpcodesServerbound.keepAlive:
// @ts-expect-error guhh
clearTimeout(ws.pingTimeout);
// @ts-expect-error guhh
ws.pingTimeout = setTimeout(() => {
ws.close();
}, 90_000);
break;
default:
break;
}
},
close(ws) {
console.log("client disconnected");
},
},
async fetch(req, server) {
const url = new URL(req.url);
let path = url.pathname;
if (path === "/") {
path = "/index.html";
}
if (url.pathname === "/ws") {
const upgraded = server.upgrade(req, {
// @ts-expect-error guhh whqt
data: {
name: new URL(req.url).searchParams.get("name"),
},
});
if (!upgraded) {
return new Response("Upgrade failed", { status: 400 });
}
return;
}
path = path.replace(/\.\./g, "");
try {
const file = Bun.file(`./dist${path}`);
if (!(await file.exists())) {
return new Response(
"<center><h1>404 Not Found</h1><hr><p>Bun</p></center>",
{ status: 404, headers: { "Content-Type": "text/html" } }
);
}
return new Response(file, {
headers: {
"Content-Type": getContentType(path),
},
});
} catch (err) {
console.error("Error serving file:", err);
return new Response(
"<center><h1>500 Internal Server Error</h1><hr><p>Bun</p></center>",
{ status: 500, headers: { "Content-Type": "text/html" } }
);
}
},
});
console.log(`Server running at http://localhost:${server.port}`);