init: Elden Ring death counter with multi-client server
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.
This commit is contained in:
84
server.ts
Normal file
84
server.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user