// TODO // Peer mssages // Routing // Video files being fully sent // Use Deno static serving for static import { serveDir } from "jsr:@std/http/file-server" // deno-lint-ignore-file prefer-const no-explicit-any function serveFile(filename: string) { // console.log(filename) const responseText = Deno.readFileSync("../" + filename); // console.log(responseText) const response = new Response(responseText); if (filename.endsWith('.js')) { response.headers.set('content-type', 'application/javascript') } return response; } function hashIdToNumber(id: string, range:number) { let number = 0; let hash = 0x811c9dc5 for (let char of id) { if (char !== '0' && char !== '-') { hash ^= char.charCodeAt(0); hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); } } return (hash >>> 0) % range; } const colors = [ 160, 196, 202, 208, 214, 220, 226, 190, 154, 118, 82, 46, 47, 48, 49, 51, 45, 44, 43, 42, 41, 40, 39, 33, 27, 21, 57, 93, 129, 165, 201, ]; const resetCode = "\x1b[0m"; function colorID(id) { const colorCode = `\x1b[38;5;${colors[hashIdToNumber(id, colors.length)]}m` return `${colorCode}${id.substring(0,5)}${resetCode}` } function pingHandler(m: any) { console.log(colorID(m.peer_id), "pong handler", m); return '{"type":"pong"}' } interface HelloMessage { type: string user_id: string user_name: string peer_id: string peer_name: string known_users: string[] } const userPeers: Map> = new Map(); const peerSockets: Map = new Map(); const socketPeers: Map = new Map(); function helloHandler(m: HelloMessage, socket: WebSocket) { console.log(`Received hello from peer ${colorID(m.peer_id)}:${m.peer_name}, user ${colorID(m.user_id)}:${m.user_name}`); if (!userPeers.has(m.user_id)) { userPeers.set(m.user_id, new Set()); } userPeers.get(m.user_id)?.add(m.peer_id); peerSockets.set(m.peer_id, socket); socketPeers.set(socket, m.peer_id); for (const knownUserID of m.known_users) { console.log(`Adding user ${knownUserID} from peer ${colorID(m.peer_id)}`); if (!userPeers.get(knownUserID)) { userPeers.set(knownUserID, new Set()); } userPeers.get(knownUserID)?.add(m.peer_id); } let returnValue: any = {}; for (let key of userPeers.keys()) { let peers = userPeers.get(key); if (!peers) { continue; } returnValue[key] = [...peers.keys()]; } // console.log(returnValue); return JSON.stringify({ type: 'hello', userPeers: returnValue }); } interface InnerMessage { type: string user_id: string } interface PeerMessage { type: string from: string from_username: string from_peername: string to: string message: InnerMessage } function peerMessageHandler(m: PeerMessage, _socket: WebSocket) { console.log(`Peer message type ${m.message.type} from ${colorID(m.from)}:${m.from_peername}:${m.from_username} to ${colorID(m.to)}`) let toPeer = peerSockets.get(m.to); if (!toPeer) { console.log(`Couln't find peer ${m.to}`) return null; } if (toPeer.readyState !== WebSocket.OPEN) { console.log("Peer socket is not open:", toPeer); return null; } let messageToSend = JSON.stringify(m); // console.log("ws->", toPeer, messageToSend); toPeer.send(messageToSend) return null; } const messageDispatch: Map string | null> = new Map(); function connectWebsocket(request: Request) { if (request.headers.get("upgrade") != "websocket") { return new Response(null, { status: 501 }); } const { socket, response } = Deno.upgradeWebSocket(request); socket.addEventListener("open", () => { console.log("a client connected!"); }); socket.addEventListener("message", (event) => { // console.log(event); let message: any; try { message = JSON.parse(event.data); } catch (e) { console.error("socket.message: ", e); return null; } const dispatchHandler = messageDispatch.get(message?.type) if (!dispatchHandler) { console.log("Got message I don't understand: ", event.data); return; } const response = dispatchHandler(message, socket); // console.log(response); if (response) { socket.send(response); } }); socket.addEventListener("close", (event) => { }); return response; } function handler(request: Request, info: any) { // console.log(info.remoteAddr); const url = new URL(request.url); if (url.pathname === "/") { return serveFile("/static/index.html") } if (url.pathname === "/ws") { return connectWebsocket(request); } if (url.pathname === "/sw.js") { return serveFile("static/sw.js") } if (url.pathname === "/robots).txt") { return serveFile("static/robots.txt") } if (url.pathname === "/favicon.ico") { return serveFile("static/favicon.ico") } if (url.pathname.includes("/static/")) { return serveDir(request, { fsRoot: "../" }); } return serveFile("/static/index.html") } function main() { messageDispatch.set('ping', pingHandler); messageDispatch.set('hello', helloHandler); messageDispatch.set('peer_message', peerMessageHandler); Deno.serve({ port: 6789, cert: Deno.readTextFileSync("/etc/letsencrypt/live/ddlion.net/fullchain.pem"), key: Deno.readTextFileSync("/etc/letsencrypt/live/ddlion.net/privkey.pem"), }, handler); } main();