not so initial commit
This commit is contained in:
228
serve.ts
Normal file
228
serve.ts
Normal 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}`);
|
||||
Reference in New Issue
Block a user