From 8fc12914d24d6600630d7d7541920a1100933b7d Mon Sep 17 00:00:00 2001 From: Roberto Morado Date: Wed, 20 May 2026 17:47:09 -0400 Subject: [PATCH] Add .env support --- main.ts | 93 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/main.ts b/main.ts index e19c87b..ef3f729 100644 --- a/main.ts +++ b/main.ts @@ -4,7 +4,7 @@ // run: deno run --allow-net main.ts // ───────────────────────────────────────────── -const PORT = 8080; +const PORT = Number(Deno.env.get("PORT")) ?? 8080; const SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour const CHUNK_SIZE = 16 * 1024; // 16KB DataChannel chunks @@ -42,8 +42,9 @@ function generateId(len = 6): string { function generateSessionId(): string { let id: string; - do { id = generateId(4) + "-" + generateId(4); } - while (sessions.has(id)); + do { + id = generateId(4) + "-" + generateId(4); + } while (sessions.has(id)); return id; } @@ -65,8 +66,15 @@ function createSession(): Session { const enc = new TextEncoder(); -function broadcast(session: Session, fromPeerId: string, event: string, data: unknown) { - const payload = enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); +function broadcast( + session: Session, + fromPeerId: string, + event: string, + data: unknown, +) { + const payload = enc.encode( + `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`, + ); for (const [pid, peer] of session.peers) { if (pid !== fromPeerId) { try { @@ -79,11 +87,18 @@ function broadcast(session: Session, fromPeerId: string, event: string, data: un } } -function sendToPeer(session: Session, toPeerId: string, event: string, data: unknown) { +function sendToPeer( + session: Session, + toPeerId: string, + event: string, + data: unknown, +) { const peer = session.peers.get(toPeerId); if (!peer) return; try { - peer.controller.enqueue(enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)); + peer.controller.enqueue( + enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`), + ); peer.lastSeen = Date.now(); } catch (_) { // peer disconnected @@ -92,7 +107,9 @@ function sendToPeer(session: Session, toPeerId: string, event: string, data: unk function sendToSelf(peer: Peer, event: string, data: unknown) { try { - peer.controller.enqueue(enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)); + peer.controller.enqueue( + enc.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`), + ); } catch (_) { /* ignore */ } } @@ -112,7 +129,9 @@ setInterval(() => { for (const [id, session] of sessions) { if (now - session.lastActivity > SESSION_TTL_MS) { for (const peer of session.peers.values()) { - try { peer.controller.close(); } catch (_) { /* ignore */ } + try { + peer.controller.close(); + } catch (_) { /* ignore */ } } sessions.delete(id); } @@ -123,11 +142,19 @@ setInterval(() => { const rlStore = new Map(); -function rateLimit(ip: string, key: string, max: number, windowMs = 60_000): boolean { +function rateLimit( + ip: string, + key: string, + max: number, + windowMs = 60_000, +): boolean { const k = ip + ":" + key; const now = Date.now(); const e = rlStore.get(k); - if (!e || now > e.reset) { rlStore.set(k, { count: 1, reset: now + windowMs }); return true; } + if (!e || now > e.reset) { + rlStore.set(k, { count: 1, reset: now + windowMs }); + return true; + } if (e.count >= max) return false; e.count++; return true; @@ -153,21 +180,27 @@ async function handler(req: Request, ip: string): Promise { // POST /session → create new session if (req.method === "POST" && path === "/session") { - if (!rateLimit(ip, "session-create", 10)) return json({ error: "Too many requests" }, 429); + if (!rateLimit(ip, "session-create", 10)) { + return json({ error: "Too many requests" }, 429); + } const session = createSession(); return json({ sessionId: session.id }); } // GET /session/:id/exists → check if session exists if (req.method === "GET" && path.match(/^\/session\/[^/]+\/exists$/)) { - if (!rateLimit(ip, "session-check", 30)) return json({ error: "Too many requests" }, 429); + if (!rateLimit(ip, "session-check", 30)) { + return json({ error: "Too many requests" }, 429); + } const sessionId = path.split("/")[2]; return json({ exists: sessions.has(sessionId) }); } // GET /connect/:sessionId → SSE stream, join session if (req.method === "GET" && path.match(/^\/connect\/[^/]+$/)) { - if (!rateLimit(ip, "connect", 10)) return json({ error: "Too many requests" }, 429); + if (!rateLimit(ip, "connect", 10)) { + return json({ error: "Too many requests" }, 429); + } const sessionId = path.split("/")[2]; const session = sessions.get(sessionId); if (!session) { @@ -184,8 +217,13 @@ async function handler(req: Request, ip: string): Promise { let controller!: ReadableStreamDefaultController; let heartbeat: ReturnType; const stream = new ReadableStream({ - start(c) { controller = c; }, - cancel() { clearInterval(heartbeat); removePeer(session!, peerId); }, + start(c) { + controller = c; + }, + cancel() { + clearInterval(heartbeat); + removePeer(session!, peerId); + }, }); const peer: Peer = { id: peerId, controller, lastSeen: Date.now() }; @@ -193,13 +231,21 @@ async function handler(req: Request, ip: string): Promise { touch(session); heartbeat = setInterval(() => { - try { peer.controller.enqueue(enc.encode("event: heartbeat\ndata: {}\n\n")); } - catch (_) { clearInterval(heartbeat); } + try { + peer.controller.enqueue(enc.encode("event: heartbeat\ndata: {}\n\n")); + } catch (_) { + clearInterval(heartbeat); + } }, 30_000); // Send welcome setTimeout(() => { - sendToSelf(peer, "welcome", { peerId, sessionId, isHost, chunkSize: CHUNK_SIZE }); + sendToSelf(peer, "welcome", { + peerId, + sessionId, + isHost, + chunkSize: CHUNK_SIZE, + }); if (!isHost) { broadcast(session!, peerId, "peer-joined", { peerId }); } @@ -217,7 +263,9 @@ async function handler(req: Request, ip: string): Promise { // POST /signal/:sessionId → WebRTC signaling relay if (req.method === "POST" && path.match(/^\/signal\/[^/]+$/)) { - if (!rateLimit(ip, "signal", 200)) return json({ error: "Too many requests" }, 429); + if (!rateLimit(ip, "signal", 200)) { + return json({ error: "Too many requests" }, 429); + } const sessionId = path.split("/")[2]; const session = sessions.get(sessionId); if (!session) return json({ error: "Session not found" }, 404); @@ -259,7 +307,10 @@ function json(data: unknown, status = 200): Response { // ── Start ──────────────────────────────────── console.log(`drop. running → http://localhost:${PORT}`); -Deno.serve({ port: PORT }, (req, info) => handler(req, (info.remoteAddr as Deno.NetAddr).hostname)); +Deno.serve( + { port: PORT }, + (req, info) => handler(req, (info.remoteAddr as Deno.NetAddr).hostname), +); // ── HTML / CSS / JS ────────────────────────── -- 2.39.5