Deno HTTP + WebSocket server serving three pages: - / desktop with YOU DIED overlay and keyboard controls - /mobile touch-optimized control page - /obs transparent browser source for OBS Count persisted to counter.json, synced in real time across all connected clients. Compiles to a self-contained Windows .exe via deno compile.
85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
import { join } from "jsr:@std/path";
|
|
|
|
const dir = import.meta.dirname!;
|
|
|
|
let deaths = 0;
|
|
const clients = new Set<WebSocket>();
|
|
|
|
// Load persisted count
|
|
try {
|
|
const data = JSON.parse(await Deno.readTextFile("counter.json"));
|
|
deaths = Number(data.count) || 0;
|
|
} catch {
|
|
// No file yet — start at 0
|
|
}
|
|
|
|
function broadcast(payload: string) {
|
|
for (const ws of clients) {
|
|
if (ws.readyState === WebSocket.OPEN) ws.send(payload);
|
|
}
|
|
}
|
|
|
|
async function persist() {
|
|
await Deno.writeTextFile("counter.json", JSON.stringify({ count: deaths }));
|
|
}
|
|
|
|
function handleSocket(ws: WebSocket) {
|
|
ws.onopen = () => {
|
|
clients.add(ws);
|
|
ws.send(JSON.stringify({ count: deaths }));
|
|
};
|
|
|
|
ws.onmessage = async (e) => {
|
|
try {
|
|
const msg = JSON.parse(e.data);
|
|
const before = deaths;
|
|
|
|
if (msg.action === "increment") deaths = Math.min(3999, deaths + 1);
|
|
else if (msg.action === "decrement") deaths = Math.max(0, deaths - 1);
|
|
else if (msg.action === "reset") deaths = 0;
|
|
else return;
|
|
|
|
if (deaths !== before) await persist();
|
|
broadcast(JSON.stringify({ count: deaths }));
|
|
} catch {
|
|
// Ignore malformed messages
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => clients.delete(ws);
|
|
ws.onerror = () => clients.delete(ws);
|
|
}
|
|
|
|
async function handler(req: Request): Promise<Response> {
|
|
const url = new URL(req.url);
|
|
|
|
if (url.pathname === "/ws") {
|
|
const upgrade = req.headers.get("upgrade") ?? "";
|
|
if (upgrade.toLowerCase() !== "websocket") {
|
|
return new Response("WebSocket upgrade required", { status: 426 });
|
|
}
|
|
const { socket, response } = Deno.upgradeWebSocket(req);
|
|
handleSocket(socket);
|
|
return response;
|
|
}
|
|
|
|
if (url.pathname === "/mobile") {
|
|
const html = await Deno.readTextFile(join(dir, "mobile.html"));
|
|
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
}
|
|
|
|
if (url.pathname === "/obs") {
|
|
const html = await Deno.readTextFile(join(dir, "obs.html"));
|
|
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
}
|
|
|
|
// Default: desktop
|
|
const html = await Deno.readTextFile(join(dir, "desktop.html"));
|
|
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
}
|
|
|
|
console.log("Elden Ring Counter running on http://localhost:8080");
|
|
console.log("Mobile: http://<your-local-ip>:8080/mobile");
|
|
console.log("Obs: http://<your-local-ip>:8080/obs");
|
|
Deno.serve({ port: 8080 }, handler);
|