]> git.morado.dev Git - env.morado.dev/commitdiff
Add .env support
authorRoberto Morado <roramigator@duck.com>
Wed, 20 May 2026 21:47:09 +0000 (17:47 -0400)
committerRoberto Morado <roramigator@duck.com>
Wed, 20 May 2026 21:47:09 +0000 (17:47 -0400)
main.ts

diff --git a/main.ts b/main.ts
index e19c87be4018bb4e2fe1ea1e27ac5af4901d0bb5..ef3f729bfdcca1f7f4457df0e80392c55e58ad48 100644 (file)
--- 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<string, { count: number; reset: number }>();
 
-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<Response> {
 
   // 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<Response> {
     let controller!: ReadableStreamDefaultController;
     let heartbeat: ReturnType<typeof setInterval>;
     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<Response> {
     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<Response> {
 
   // 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 ──────────────────────────