const PORT = parseInt(Deno.env.get("PORT") || "80"); const randomInt = (low: number, high: number) => Math.floor(Math.random() * (high - low + 1) + low); const randomSecret = () => new Array(8).map(() => randomInt(0, 10)).join(""); interface Client { socket: WebSocket; } // TODO: version comparison interface Lobby { name: string; clients: Client[]; // TODO: private vs public lobbies? } const lobbies = new Set(); const clients = new Set(); console.log("Listening on port", PORT); const listener = Deno.listen({ port: PORT }); for await (const conn of listener) { console.debug("Connection received:", conn); (async () => { const server = Deno.serveHttp(conn); for await (const { respondWith, request } of server) { console.debug("HTTP Request Received", request); try { const { socket, response } = Deno.upgradeWebSocket(request); const client: Client = { socket }; socket.onmessage = (ev) => { console.log("Client Message Received", ev); }; socket.onopen = (ev) => { console.log("New Client:", ev); if (!clients.has(client)) clients.add(client); socket.send("lobbies_start"); lobbies.forEach(({ name, clients }) => { socket.send(JSON.stringify({ name, numClients: clients.length })); }); socket.send("lobbies_end"); }; socket.onclose = (ev) => { console.log("Client Socket Close:", ev); if (clients.has(client)) clients.delete(client); }; socket.onerror = (ev) => { console.log("Client Socket Error:", ev); if (clients.has(client)) clients.delete(client); }; respondWith(response); } catch (e) { let body = "400 Bad Request"; if (e instanceof TypeError) { body += " - Expected to be able to upgrade to WebSocket connection"; console.log("Could not add client:", e); } else { console.log("Could not add client for unhandled reason:", e); } respondWith( new Response(body, { status: 400, headers: { "content-type": "text/html" }, }), ); } } })(); } /* function peerCreatesLobby(peer: Peer, lobbyName: string) { // TODO: ensure we can create a lobby lobbies.add({ }) } */ /* function peerJoinsLobby(peer: Peer, lobby: Lobby) { lobbyName = randomSecret(); lobbies.set(lobbyName, new Lobby(lobbyName, this.id)); console.log(`Peer ${this.id} created lobby ${lobbyName}`); console.log(`Open lobbies: ${lobbies.size}`); } const lobby = lobbies.get(lobbyName); if (!lobby) throw new ProtoError(4000, STR_LOBBY_DOES_NOT_EXIST); if (lobby.sealed) throw new ProtoError(4000, STR_LOBBY_IS_SEALED); this.lobby = lobbyName; console.log( `Peer ${this.id} joining lobby ${lobbyName} ` + `with ${lobby.peers.length} peers`, ); lobby.join(this); this.ws.send(`J: ${lobbyName}\n`); } // TODO: ensure peer not already in a lobby const assigned = this.getPeerId(peer); peer.ws.send(`I: ${assigned}\n`); this.peers.forEach((p) => { p.ws.send(`N: ${assigned}\n`); peer.ws.send(`N: ${this.getPeerId(p)}\n`); }); this.peers.push(peer); } */ /* function peerLeavesLobby(peer: Peer) { const idx = this.peers.findIndex((p) => peer === p); if (idx === -1) return false; const assigned = this.getPeerId(peer); const close = assigned === 1; this.peers.forEach((p) => { try { // room host disconnected if (close) p.ws.close(4000, STR_HOST_DISCONNECTED); // notify peers else p.ws.send(`D: ${assigned}\n`); } catch (e) { console.error(`Error when leaving: ${e}`); } }); this.peers.splice(idx, 1); if (close && this.closeTimer >= 0) { // we are closing already. clearTimeout(this.closeTimer); this.closeTimer = -1; } return close; } */ /* function graduateLobby(lobby: Lobby) { // only host can seal if (peer.id !== this.host) { throw new ProtoError(4000, STR_ONLY_HOST_CAN_SEAL); } this.sealed = true; this.peers.forEach((p) => { p.ws.send("S: \n"); }); console.log( `Peer ${peer.id} sealed lobby ${this.name} ` + `with ${this.peers.length} peers`, ); this.closeTimer = setTimeout(() => { // close peer connection to host (and thus the lobby) this.peers.forEach((p) => { p.ws.close(1000, STR_SEAL_COMPLETE); }); }, SEAL_CLOSE_TIMEOUT); } } */ /* function parseMsg(peer: Peer, msg: string) { // TODO: modify this? // O: Client is sending an offer. // A: Client is sending an answer. // C: Client is sending a candidate. let destId = parseInt(cmd.substr(3).trim()); // Dest is not an ID. if (!destId) throw new ProtoError(4000, STR_INVALID_DEST); if (destId === 1) destId = lobby.host; const dest = lobby.peers.find((p: Peer) => p.id === destId); // Dest is not in this room. if (!dest) throw new ProtoError(4000, STR_INVALID_DEST); function isCmd(what: string) { return cmd.startsWith(`${what}: `); } if (isCmd("O") || isCmd("A") || isCmd("C")) { dest.ws.send(cmd[0] + ": " + lobby.getPeerId(peer) + data); return; } throw new ProtoError(4000, STR_INVALID_CMD); } */