import { readLines } from "https://deno.land/std@0.157.0/io/buffer.ts"; const PORT = 5588; console.log(`Starting TCP listener on on 0.0.0.0:${PORT}`); const clients = new Map(); const tEnc = new TextEncoder(); async function chatSession(conn: Deno.Conn) { try { let myNick: string | null = null; await sendPrompt(conn); for await (const line of readLines(conn)) { console.debug("Received line:", line); const message = line.trim(); if (myNick == null) { if (isLegalNick(message)) { myNick = message; await join(myNick, conn); } else { console.error("Illegal nick:", message); break; } } else { const conns: Deno.Conn[] = []; for (const themConn of clients.keys()) { if (themConn != conn) conns.push(themConn); } await send(conns, `[${myNick}] ${message}`); } } } finally { if (clients.has(conn)) { try { await disconnect(conn); } catch (err) { console.error("Failed to disconnect:", err); } } try { if (conn.writable) { send(conn, "You are being disconnected..."); } } catch (err) { console.error("Failed to send disconnect notice:", err); } } try { conn.close(); } catch (err) { console.error("Failed to close connection:", err); } } async function send( connsArg: Deno.Conn | Array, text: string, ) { const conns = !Array.isArray(connsArg) ? [connsArg] : connsArg; const messages: Promise[] = []; const bytes = tEnc.encode(text + "\n"); conns.forEach((conn) => messages.push(conn.write(bytes))); return await Promise.all(messages); } const broadcast = async (text: string) => await send([...clients.keys()], text); const LEGAL_NICK_REGEXP = /^[a-z0-9]+$/i; const isLegalNick = (nick: string) => { console.debug( `Checking nick: ${nick} (Bytes: ${tEnc.encode(nick)})`, ); return LEGAL_NICK_REGEXP.exec(nick) != null; }; const sendPrompt = async (conn: Deno.Conn) => { console.debug("Sending prompt..."); return await send(conn, "Who you be?"); }; async function join(myNick: string, conn: Deno.Conn) { const userList: string[] = []; clients.forEach((nick, _) => userList.push(nick)); await broadcast(`* ${myNick} joined`); clients.set(conn, myNick); await send(conn, `* users: ${userList.join(", ")}`); } async function disconnect(conn: Deno.Conn) { const myNick = clients.get(conn); clients.delete(conn); await broadcast(`* ${myNick} left`); } for await (const conn of Deno.listen({ port: PORT })) { console.log("Connection established:", conn.remoteAddr); try { chatSession(conn); } catch (e) { console.error( "Exception occurred during chat session:", conn.remoteAddr, e, ); } console.log("Connection closed:", conn.remoteAddr); }