import dgram from "node:dgram"; import { createReadStream, existsSync } from "node:fs"; import { createServer, IncomingMessage, ServerResponse } from "node:http"; import { extname, join, normalize } from "node:path"; import { getLeaderboard, updateLeaderboard } from "./leaderboard.js"; import { parsePacket, UDP_PORT, type ParsedPacket } from "./parser.js"; const HTTP_PORT = Number(process.env.HTTP_PORT ?? 3000); const publicDir = join(process.cwd(), "public"); const docsDir = join(process.cwd(), "docs"); const sseClients = new Set(); const latestByType = new Map(); const recentPackets: ParsedPacket[] = []; const contentTypes: Record = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".json": "application/json; charset=utf-8" }; function sendEvent(response: ServerResponse, event: string, payload: unknown) { response.write(`event: ${event}\n`); response.write(`data: ${JSON.stringify(payload)}\n\n`); } function broadcast(packet: ParsedPacket) { const state = { latest: Object.fromEntries(latestByType), recent: recentPackets }; const leaderboard = getLeaderboard(latestByType, recentPackets); for (const client of sseClients) { sendEvent(client, "packet", { packet, state, leaderboard }); sendEvent(client, "leaderboard", leaderboard); } } function processPacket(packet: ParsedPacket) { const mergedPacket = mergeLatestPacket(packet); latestByType.set(String(mergedPacket.base.packetType), mergedPacket); recentPackets.unshift(packet); recentPackets.splice(50); updateLeaderboard(latestByType); broadcast(mergedPacket); } function readRequestBody(request: IncomingMessage): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; request.on("data", (chunk) => chunks.push(Buffer.from(chunk))); request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); request.on("error", reject); }); } function isParsedPacket(value: unknown): value is ParsedPacket { if (!isRecord(value) || !isRecord(value.base) || !isRecord(value.data)) { return false; } return typeof value.base.packetType === "number"; } function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function records(value: unknown): Record[] { return Array.isArray(value) ? value.filter(isRecord) : []; } function mergeArrayByKey( previous: unknown, next: unknown, key: string, fallbackKey = "slot" ): Record[] { const merged = new Map>(); for (const item of [...records(previous), ...records(next)]) { const keyValue = item[key] ?? item[fallbackKey]; if (keyValue === undefined || keyValue === null) continue; merged.set(String(keyValue), item); } return Array.from(merged.values()); } function mergeLatestPacket(packet: ParsedPacket): ParsedPacket { const existing = latestByType.get(String(packet.base.packetType)); if (!existing) { return packet; } if (packet.base.packetType === 2) { return { ...packet, data: { ...existing.data, ...packet.data, participants: mergeArrayByKey( existing.data.participants, packet.data.participants, "slot" ) } }; } if (packet.base.packetType === 8) { return { ...packet, data: { ...existing.data, ...packet.data, vehicles: mergeArrayByKey(existing.data.vehicles, packet.data.vehicles, "index"), classes: mergeArrayByKey(existing.data.classes, packet.data.classes, "classIndex") } }; } return packet; } function serveStatic(pathname: string, response: ServerResponse) { const requestedPath = pathname === "/" ? "/index.html" : pathname; const filePath = normalize(join(publicDir, requestedPath)); if (!filePath.startsWith(publicDir) || !existsSync(filePath)) { response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }); response.end("Not found"); return; } response.writeHead(200, { "content-type": contentTypes[extname(filePath)] ?? "application/octet-stream" }); createReadStream(filePath).pipe(response); } function serveFile(filePath: string, response: ServerResponse, contentType?: string) { if (!existsSync(filePath)) { response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }); response.end("Not found"); return; } response.writeHead(200, { "content-type": contentType ?? contentTypes[extname(filePath)] ?? "application/octet-stream" }); createReadStream(filePath).pipe(response); } const httpServer = createServer((request, response) => { const url = new URL(request.url ?? "/", `http://${request.headers.host}`); if (request.method === "OPTIONS") { response.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "GET,POST,OPTIONS", "access-control-allow-headers": "content-type" }); response.end(); return; } if (url.pathname === "/events") { response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive", "access-control-allow-origin": "*" }); response.write("\n"); sseClients.add(response); sendEvent(response, "state", { latest: Object.fromEntries(latestByType), recent: recentPackets, leaderboard: getLeaderboard(latestByType, recentPackets) }); request.on("close", () => sseClients.delete(response)); return; } if (url.pathname === "/api/state") { response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify({ latest: Object.fromEntries(latestByType), recent: recentPackets })); return; } if (url.pathname === "/api/leaderboard") { response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify(getLeaderboard(latestByType, recentPackets))); return; } if (url.pathname === "/api/ingest/shared-memory" && request.method === "POST") { void readRequestBody(request) .then((body) => { const payload = JSON.parse(body) as unknown; const incomingPackets = isRecord(payload) && Array.isArray(payload.packets) ? payload.packets.filter(isParsedPacket) : []; if (!incomingPackets.length) { response.writeHead(400, { "content-type": "application/json; charset=utf-8", "access-control-allow-origin": "*" }); response.end(JSON.stringify({ ok: false, error: "Expected a packets array." })); return; } for (const packet of incomingPackets) { processPacket({ ...packet, receivedAt: packet.receivedAt ?? new Date().toISOString(), source: packet.source || "shared-memory" }); } response.writeHead(200, { "content-type": "application/json; charset=utf-8", "access-control-allow-origin": "*" }); response.end(JSON.stringify({ ok: true, packets: incomingPackets.length })); }) .catch((error) => { response.writeHead(400, { "content-type": "application/json; charset=utf-8", "access-control-allow-origin": "*" }); response.end(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) })); }); return; } if (url.pathname === "/api/openapi.json") { serveFile(join(publicDir, "openapi.json"), response, "application/json; charset=utf-8"); return; } if (url.pathname === "/schemas/udp-packets.schema.json") { serveFile( join(docsDir, "udp-packets.schema.json"), response, "application/schema+json; charset=utf-8" ); return; } serveStatic(url.pathname, response); }); const udpServer = dgram.createSocket("udp4"); udpServer.on("message", (message, remote) => { try { const packet = parsePacket(message, `${remote.address}:${remote.port}`); processPacket(packet); } catch (error) { console.error("Failed to parse UDP packet:", error); } }); udpServer.on("listening", () => { const address = udpServer.address(); console.log(`UDP listener ready on ${address.address}:${address.port}`); }); udpServer.bind(UDP_PORT, "0.0.0.0"); httpServer.listen(HTTP_PORT, () => { console.log(`Frontend ready at http://localhost:${HTTP_PORT}`); });