const http = require("http"); const { WebSocketServer } = require("ws"); const { randomUUID } = require("crypto"); const PORT = process.env.PORT ? Number(process.env.PORT) : 3000; const html = ` Portfolio World
NAV
Offline
Teleport by UI. Multiplayer shows other visitors as bodies. Esc unlocks mouse.
`; const server = http.createServer((req, res) => { if (req.url === "/" || req.url === "/index.html") { res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" }); res.end(html); return; } res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); res.end("Not found"); }); const wss = new WebSocketServer({ server }); const clients = new Map(); function broadcast(obj, exceptId) { const msg = JSON.stringify(obj); for (const [id, c] of clients) { if (id === exceptId) continue; if (c.ws.readyState === 1) c.ws.send(msg); } } wss.on("connection", (ws) => { const id = randomUUID(); clients.set(id, { ws, last: Date.now(), state: null }); ws.send(JSON.stringify({ t: "welcome", id, peers: Array.from(clients.entries()).filter(([pid]) => pid !== id).map(([pid, v]) => ({ id: pid, s: v.state })) })); broadcast({ t: "join", id }, id); ws.on("message", (buf) => { let msg; try { msg = JSON.parse(buf.toString()); } catch { return; } const c = clients.get(id); if (!c) return; c.last = Date.now(); if (msg && msg.t === "state" && msg.s) { c.state = msg.s; broadcast({ t: "state", id, s: msg.s }, id); } }); ws.on("close", () => { clients.delete(id); broadcast({ t: "leave", id }, id); }); }); setInterval(() => { const t = Date.now(); for (const [id, c] of clients) { if (t - c.last > 30000) { try { c.ws.close(); } catch {} clients.delete(id); broadcast({ t: "leave", id }, id); } } }, 2000); server.listen(PORT, () => console.log("http://localhost:" + PORT));