diff --git a/main.ts b/main.ts index 8cf931e..9fa8c7a 100644 --- a/main.ts +++ b/main.ts @@ -1,72 +1,108 @@ -import { BufReader } from "https://deno.land/std@0.157.0/io/buffer.ts"; -import { ByteSet } from "https://deno.land/x/bytes@1.0.3/mod.ts"; +import { readLines } from "https://deno.land/std@0.157.0/io/buffer.ts"; -const QUERY_SIZE = 9; const PORT = 5588; +console.log(`Starting TCP listener on on 0.0.0.0:${PORT}`); -const listener = Deno.listen({ port: 5588 }); +const clients = new Map(); -console.log(`Listening on 0.0.0.0:${PORT}`); +const tEnc = new TextEncoder(); -for await (const conn of listener) { +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 { - meansToAnEnd(conn); + chatSession(conn); } catch (e) { - console.error(conn.remoteAddr, e); + console.error( + "Exception occurred during chat session:", + conn.remoteAddr, + e, + ); } console.log("Connection closed:", conn.remoteAddr); } - -// problem 2 -async function meansToAnEnd(conn: Deno.Conn) { - try { - const prices = []; - for await (const query of queries(conn)) { - if (query == null) break; - const bs = ByteSet.from(query, "big"); - const queryChar = String.fromCharCode(bs.read.uint8()); - const a1 = bs.read.int32(); - const a2 = bs.read.int32(); - if (queryChar == "I") { - prices.push([a1, a2]); - } else if (queryChar == "Q") { - let numMatches = 0; - let sum = 0; - for (const [ts, price] of prices) { - if (ts < a1 || ts > a2) continue; - numMatches++; - sum += price; - } - let result = Math.floor(sum / numMatches); - if (isNaN(result)) result = 0; - console.log( - conn.remoteAddr, - `Result for ${prices.length} prices: ${sum} / ${numMatches} = ${result}`, - ); - const response = new ByteSet(4, "big"); - response.write.int32(result); - conn.write(response.buffer); - } - } - console.log("Done!"); - } catch (err) { - console.error("Error processing query:", err); - } -} - -function queries(conn: Deno.Conn) { - return { - async *[Symbol.asyncIterator]() { - const b = new Uint8Array(9); - const reader = new BufReader(conn, QUERY_SIZE); - try { - while (await reader.readFull(b) != null) { - yield b; - } - } catch (err) { - console.error("Error receiving query:", err); - } - }, - }; -}