diff --git a/3.ts b/3.ts index e052fbe..9fa8c7a 100644 --- a/3.ts +++ b/3.ts @@ -1,25 +1,108 @@ -const PORT = 5588; -console.log(`Starting UDP listener on port ${PORT}`); +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 tDec = new TextDecoder(); const tEnc = new TextEncoder(); -const reservedData: Record = { version: "KeyVal 1.0" }; -const data = new Map(); - -const listener = Deno.listenDatagram({ port: PORT, transport: "udp" }); - -for await (const [bytes, addr] of listener) { - const message = tDec.decode(bytes).trim(); - console.debug("Datagram Received from:", addr, message); - if (message.includes("=")) { - const [key, val] = message.split("=", 1); - console.debug(`Setting ${key} to ${val}`); - if (!(key in reservedData)) data.set(key, val); - } else { - const value = reservedData[message] || data.get(message) || ""; - const bytes = tEnc.encode(`${message}=${value}`); - console.debug(`Sending ${message}=${value}`); - await listener.send(bytes, addr); +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); +}