godot-webrtc-mplayer-testing/server.ts

319 lines
8.3 KiB
TypeScript
Raw Normal View History

2021-12-02 15:13:12 -06:00
// import { randomInt } from "./deps.ts";
2021-11-15 15:43:33 -06:00
2021-11-17 13:57:45 -06:00
const SERVER_VERSION = "0.1.0";
// TODO: version comparison
2021-11-15 14:26:39 -06:00
2021-11-17 13:57:45 -06:00
type ID = string;
2021-11-15 14:26:39 -06:00
2021-11-17 13:57:45 -06:00
// app state
const allLobbies = new Map<ID, Lobby>();
const allClients = new Map<ID, Client>();
2021-12-02 16:30:24 -06:00
// TODO: client index by id
2021-11-17 13:57:45 -06:00
interface DataMessage {
type: string;
2021-11-17 08:18:12 -06:00
}
2021-11-15 14:26:39 -06:00
2021-11-17 16:24:00 -06:00
type ServerDataObject = Record<string, string | number | symbol | null>;
type ServerData = ServerDataObject[] | ServerDataObject | string;
2021-11-17 13:57:45 -06:00
type Message = string | DataMessage;
const broadcast = (message: Message) =>
allClients.forEach((client) => client.send(message));
2021-11-15 14:26:39 -06:00
2021-11-17 13:57:45 -06:00
const buildMessage = (
type: string,
data: ServerData | ServerData[],
) =>
Object.assign(
{ type },
Array.isArray(data)
? { data }
: (typeof data === "object" ? data : { data }),
);
class Client {
id: ID;
2021-11-17 16:24:00 -06:00
peerId: number | null;
2021-11-17 08:18:12 -06:00
name: string;
2021-11-17 13:57:45 -06:00
socket: WebSocket;
lobby: Lobby | null;
constructor(socket: WebSocket) {
this.id = crypto.randomUUID();
this.socket = socket;
2021-11-17 16:24:00 -06:00
this.peerId = null;
2021-11-17 13:57:45 -06:00
this.name = "Client";
this.lobby = null;
allClients.set(this.id, this);
}
isConnected() {
return this.socket.readyState == WebSocket.OPEN;
}
remove() {
this.lobbyLeave();
if (this.isConnected()) {
this.socket.close();
}
allClients.delete(this.id);
}
send(message: Message) {
try {
2021-11-17 14:01:35 -06:00
if (this.isConnected()) {
this.socket.send(
typeof message === "object"
? ("json:" + JSON.stringify(message))
: message,
);
}
2021-11-17 13:57:45 -06:00
} catch (e) {
console.error(
`Failed to send on socket ${this.socket} to client ${this.id}. Disconnecting and removing...`,
);
}
}
clientList() {
if (!this.lobby) return;
2021-11-17 16:24:00 -06:00
const netClients: { id: ID; name: string; peerId: number | null }[] = [];
this.lobby.clients.forEach(({ id, name, peerId }) =>
netClients.push({ id, name, peerId })
);
2021-11-17 13:57:45 -06:00
// TODO: chunk async?
}
lobbyList() {
const netLobbies: { id: ID; name: string }[] = [];
allLobbies.forEach(({ id, name }) => netLobbies.push({ id, name }));
// TODO: chunk async?
this.send(buildMessage("lobby_list", netLobbies));
}
lobbyNew({ id, name }: Lobby) {
// if the client is already in a lobby, we don't care about new lobbies
if (this.lobby) return;
this.send(buildMessage("lobby_new", { id, name }));
}
lobbyDelete({ id }: Lobby) {
if (this.lobby) return;
this.send(buildMessage("lobby_delete", { id }));
}
lobbyCreate() {
if (this.lobby) {
this.send(
`[info] cannot create lobby (already in lobby ${this.lobby.id})`,
);
return;
}
new Lobby(this);
}
lobbyJoin(lobby: Lobby) {
if (this.lobby) {
this.send(`[info] cannot join lobby (already in lobby ${this.lobby.id})`);
return;
}
this.lobby = lobby;
lobby.addClient(this);
2021-11-17 16:24:00 -06:00
this.send(
buildMessage("lobby_joined", { id: lobby.id, peerId: this.peerId }),
);
2021-11-17 13:57:45 -06:00
}
lobbyLeave() {
const leavingLobby = this.lobby;
if (!leavingLobby) {
this.send(`[info] cannot leave lobby (not in a lobby)`);
return;
}
2021-11-17 16:24:00 -06:00
this.peerId = null;
2021-11-17 13:57:45 -06:00
this.lobby = null;
if (this.isConnected()) {
this.send(buildMessage("lobby_left", { id: leavingLobby.id }));
}
leavingLobby.removeClient(this);
}
2021-11-15 14:26:39 -06:00
}
2021-11-17 13:57:45 -06:00
class Lobby {
id: ID;
name: string;
clients: Map<ID, Client>;
hostClientId: ID;
constructor(host: Client, name?: string) {
this.id = crypto.randomUUID();
this.hostClientId = host.id;
this.clients = new Map<ID, Client>();
this.name = name || this.id;
allLobbies.set(this.id, this);
2021-11-17 16:24:00 -06:00
host.peerId = 1;
2021-11-17 13:57:45 -06:00
host.lobbyJoin(this);
allClients.forEach((client) => client.lobbyNew(this));
}
remove() {
allClients.forEach((client) => client.lobbyDelete(this));
this.clients.forEach((client) => {
client.lobbyLeave();
});
allLobbies.delete(this.id);
}
broadcast(message: Message) {
this.clients.forEach((client) => client.send(message));
}
addClient(client: Client) {
2021-11-17 16:24:00 -06:00
if (!client.peerId) {
const arr = new Int32Array(1);
crypto.getRandomValues(arr);
client.peerId = Math.abs(arr[0]);
}
2021-12-02 15:13:12 -06:00
client.send(buildMessage("your_peer_id", client.peerId.toString()));
2021-11-17 13:57:45 -06:00
this.broadcast(
2021-11-17 16:24:00 -06:00
buildMessage("peer_joined", {
id: client.id,
name: client.name,
peerId: client.peerId,
}),
2021-11-17 13:57:45 -06:00
);
2021-12-06 16:17:36 -06:00
console.log("Sending peer_joined...");
client.send(
buildMessage(
"peer_joined",
Array.from(this.clients.values()).map(
({ id, name, peerId }) => ({ id, name, peerId }),
),
),
2021-12-02 15:13:12 -06:00
);
2021-11-17 16:24:00 -06:00
this.clients.set(client.id, client);
2021-11-17 13:57:45 -06:00
}
2021-12-06 16:17:36 -06:00
clientList() {
return Array.from(this.clients.values()).map(
({ id, name, peerId }) => ({ id, name, peerId }),
);
}
2021-11-17 13:57:45 -06:00
removeClient({ id }: Client) {
this.clients.delete(id);
this.broadcast(buildMessage("peer_left", { id }));
if (id === this.hostClientId) {
console.warn("Host left!");
this.remove();
}
}
}
2021-11-15 14:26:39 -06:00
2021-11-17 16:24:00 -06:00
interface ClientMessage {
type: "candidate" | "offer" | "answer";
data: ServerDataObject;
}
2021-11-17 13:57:45 -06:00
// events
function onMessage(client: Client, ev: MessageEvent) {
// TODO: log who from?
2021-12-03 17:05:31 -06:00
const msg = ev.data.trim();
console.log("Client Message Received", msg);
if (msg === "init") client.send(buildMessage("your_id", client.id));
if (msg === "lobby_create") client.lobbyCreate();
if (msg === "lobby_leave") client.lobbyLeave();
if (msg === "request_lobby_list") client.lobbyList();
2021-12-06 16:17:36 -06:00
if (msg === "request_peer_list") {
if (client.lobby == null) {
client.send(`[info] not in a lobby`);
} else {
client.send(buildMessage("peer_joined", client.lobby.clientList()));
}
}
2021-12-03 17:05:31 -06:00
if (msg.startsWith("lobby_join:")) {
const id = msg.substr(11);
2021-11-17 13:57:45 -06:00
const lobby = allLobbies.get(id);
2021-12-02 15:13:12 -06:00
if (lobby) {
client.lobbyJoin(lobby);
client.clientList();
} else client.send(`[info] could not find lobby ${id}`);
2021-11-17 13:57:45 -06:00
}
2021-12-03 17:05:31 -06:00
if (msg.startsWith("json:")) {
const data: ClientMessage = JSON.parse(msg.substr(5));
2021-12-02 15:13:12 -06:00
if (["candidate", "answer", "offer"].includes(data.type)) {
console.log("Received WebRTC Negotiation Message...");
2021-11-17 16:24:00 -06:00
if (typeof data.data === "object") {
const subdata = data.data;
if (typeof subdata["peerId"] === "number") {
2021-12-02 16:09:43 -06:00
const destPeerId: number = subdata["peerId"];
2021-12-02 15:13:12 -06:00
for (const iClient of client.lobby?.clients.values() || []) {
2021-12-02 16:09:43 -06:00
if (iClient.peerId == destPeerId) {
const payload = Object.assign({}, data);
const srcPeerId = client.peerId;
payload.data.peerId = srcPeerId;
2021-12-02 15:13:12 -06:00
console.log(
2021-12-02 16:09:43 -06:00
`Forwarding WebRTC Negotiation Message from peer ${srcPeerId} to peer ${destPeerId}...`,
2021-12-02 15:13:12 -06:00
);
2021-12-02 16:09:43 -06:00
iClient.send(payload);
2021-12-02 15:13:12 -06:00
break;
}
2021-11-17 16:24:00 -06:00
}
}
}
}
}
2021-11-17 13:57:45 -06:00
}
2021-12-02 15:13:12 -06:00
function onSocketOpen(client: Client, _ev: Event) {
console.log("New Client", client.id);
2021-11-17 13:57:45 -06:00
}
function onClientLeave(client: Client) {
client.remove();
}
function onSocketClose(client: Client, _ev: Event) {
console.log("Client Close");
onClientLeave(client);
}
function onSocketError(client: Client, _ev: Event) {
console.log("Client Error");
onClientLeave(client);
}
const PORT = parseInt(Deno.env.get("PORT") || "80");
2021-11-17 08:18:12 -06:00
console.log("Listening on port", PORT);
2021-11-17 08:52:25 -06:00
const listener = Deno.listen({ port: PORT });
for await (const conn of listener) {
2021-11-17 13:57:45 -06:00
// console.debug("Connection received:", conn);
2021-11-17 08:18:12 -06:00
(async () => {
2021-11-17 08:52:25 -06:00
const server = Deno.serveHttp(conn);
for await (const { respondWith, request } of server) {
2021-11-17 13:57:45 -06:00
// console.debug("HTTP Request Received", request);
2021-11-17 08:52:25 -06:00
try {
2021-12-02 15:13:12 -06:00
// console.warn(JSON.stringify([allClients, allLobbies]));
2021-11-17 08:52:25 -06:00
const { socket, response } = Deno.upgradeWebSocket(request);
2021-11-17 13:57:45 -06:00
const client = new Client(socket);
socket.onmessage = (ev) => onMessage(client, ev);
socket.onopen = (ev) => onSocketOpen(client, ev);
socket.onclose = (ev) => onSocketClose(client, ev);
socket.onerror = (ev) => onSocketError(client, ev);
2021-11-17 08:52:25 -06:00
respondWith(response);
} catch (e) {
2021-11-17 13:57:45 -06:00
console.log("Could not add client for unhandled reason:", e);
2021-11-17 08:52:25 -06:00
respondWith(
2021-11-17 13:57:45 -06:00
new Response("400 Bad Request", {
2021-11-17 08:52:25 -06:00
status: 400,
headers: { "content-type": "text/html" },
}),
);
}
2021-11-17 08:18:12 -06:00
}
})();
}