import Peer, { DataConnection } from "peerjs"; import { RoomInfo, PasswordRequest, Room, ErrorMessage, PasswordResponse, PeerMetadata, JoinMessage, RoomInfoMessage, Player, NetworkMessage, RenameMessage, LeaveMessage, NetworkPlayer, AckMessage, ChatMessage } from "./types"; import { LocalClient } from "."; import EventEmitter from "eventemitter3"; // Increment name, add number at the end if not present // Examples: // Guest -> Guest1 // Guest1 -> Guest2 // Guest9 -> Guest10 function nextName(name: string): string { let i = 1; for (; i < name.length; i++) { if (!isNaN(Number(name.slice(i - name.length)))) { break; } } return name.substr(0, i) + (Number(name.slice(i)) + 1); } export class PeerServer extends EventEmitter { protected peer: Peer; private room: Room; public constructor( roomInfo: RoomInfo, local: LocalClient, customPeer?: Peer ) { super(); let players: Record = {}; // Add local player to server players[local.name] = { kind: "local", name: local.name, client: local }; local.receiver = this._received.bind(this, players[local.name]); this.room = { info: roomInfo, players }; local.players = Object.keys(this.players); local.roomInfo = this.room.info; // Setup peer this.peer = customPeer ? customPeer : new Peer(); this.peer.on("open", function(id) { console.info("Peer ID assigned: %s", id); }); this.peer.on("connection", conn => { this._connection(conn); }); } private _connection(conn: DataConnection) { const metadata = conn.metadata as PeerMetadata; let player: NetworkPlayer = { kind: "remote", name: metadata.name, conn: conn }; // // Check if this connection should be allowed // // Check if room is full if (this.playerCount >= this.room.info.max_players) { this.send(player, { kind: "error", error: "room is full" }); conn.close(); return; } // Check if there is already a player called that way if (this.room.players[metadata.name]) { // Force rename let newname = metadata.name; while (this.room.players[newname]) { newname = nextName(metadata.name); } this.send(player, { kind: "rename", oldname: metadata.name, newname: newname }); player.name = newname; player.conn.metadata.name = newname; } // Check for password if (this.room.info.password != "") { const checkPasswordResponse = (data: any) => { try { let resp = data as PasswordResponse; if (resp.password != this.room.info.password) { this.send(player, { kind: "error", error: "invalid password" }); return; } conn.off("data", checkPasswordResponse); this.addPlayer(player); } catch (e) { this.send(player, { kind: "error", error: "not a password" }); } }; this.send(player, { kind: "password-req" }); conn.on("data", checkPasswordResponse.bind(this)); return; } this.addPlayer(player); } private addPlayer(player: NetworkPlayer) { const playerName = player.name; this.room.players[playerName] = player; // Start listening for new messages player.conn.on( "data", this._received.bind(this, this.room.players[playerName]) ); // Send the player info about the room this.send(player, { kind: "room-info", room: { ...this.room.info, password: "" }, players: Object.keys(this.room.players) }); // Notify other players this.broadcast({ kind: "player-joined", name: playerName }); } private removePlayer(player: NetworkPlayer) { // Tell the player everything's fine this.send(player, { kind: "ok", what: "leave-req" }); // Close connection with player player.conn.close(); // Notify other players this.broadcast({ kind: "player-left", name: player.name }); } private _received(player: Player, data: NetworkMessage) { switch (data.kind) { // Player wants to say something in chat case "chat": data.from = player.name; if (data.to == "") { // Players is saying that out loud this.broadcast(data); } else { // Player is telling someone specifically if (data.to in this.players) { this.send(this.players[data.to], data); } else { this.send(player, { kind: "error", error: `player not found: ${data.to}` }); } } break; // Player is leaving! case "leave-req": // If we're leaving, end the server if (player.kind == "local") { //TODO } else { // Remove and disconnect player this.removePlayer(player); } break; } } public get playerCount(): number { return Object.keys(this.room.players).length; } public get players() { return this.room.players; } protected send(player: Player, message: T) { if (player.kind == "remote") { player.conn.send(message); } else { player.client.receive(message); } } private broadcast(message: T) { for (const playerName in this.room.players) { const player = this.room.players[playerName]; this.send(player, message); } } } export default PeerServer;