186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
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<Lobby>();
|
|
const clients = new Set<Client>();
|
|
|
|
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);
|
|
}
|
|
*/
|