2019-10-16 08:31:03 +00:00
|
|
|
import EventEmitter from "eventemitter3";
|
2019-09-06 12:36:11 +00:00
|
|
|
import Peer, { DataConnection } from "peerjs";
|
2019-10-16 08:31:03 +00:00
|
|
|
|
|
|
|
import { LocalClient } from ".";
|
2019-09-06 12:36:11 +00:00
|
|
|
import {
|
2019-10-16 08:31:03 +00:00
|
|
|
AckMessage,
|
|
|
|
ChatMessage,
|
2019-09-06 12:36:11 +00:00
|
|
|
ErrorMessage,
|
2019-10-16 08:31:03 +00:00
|
|
|
JoinMessage,
|
|
|
|
LeaveMessage,
|
|
|
|
NetworkMessage,
|
|
|
|
NetworkPlayer,
|
|
|
|
PasswordRequest,
|
2019-09-06 12:36:11 +00:00
|
|
|
PasswordResponse,
|
|
|
|
PeerMetadata,
|
|
|
|
Player,
|
|
|
|
RenameMessage,
|
2019-10-16 08:31:03 +00:00
|
|
|
Room,
|
|
|
|
RoomInfo,
|
|
|
|
RoomInfoMessage,
|
2019-09-06 12:36:11 +00:00
|
|
|
} from "./types";
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2019-10-16 08:31:03 +00:00
|
|
|
function connectionOpen(conn: DataConnection): Promise<void> {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
conn.on("open", () => resolve());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:36:11 +00:00
|
|
|
export class PeerServer extends EventEmitter {
|
|
|
|
protected peer: Peer;
|
|
|
|
private room: Room;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
roomInfo: RoomInfo,
|
|
|
|
local: LocalClient,
|
|
|
|
customPeer?: Peer
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
let players: Record<string, Player> = {};
|
|
|
|
|
|
|
|
// 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();
|
2019-10-16 08:31:03 +00:00
|
|
|
this.peer.on("open", id => {
|
2019-09-06 12:36:11 +00:00
|
|
|
console.info("Peer ID assigned: %s", id);
|
2019-10-16 08:31:03 +00:00
|
|
|
this.emit("open", id);
|
2019-09-06 12:36:11 +00:00
|
|
|
});
|
|
|
|
this.peer.on("connection", conn => {
|
|
|
|
this._connection(conn);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-10-16 08:31:03 +00:00
|
|
|
private async _connection(conn: DataConnection) {
|
2019-09-06 12:36:11 +00:00
|
|
|
const metadata = conn.metadata as PeerMetadata;
|
|
|
|
|
2019-10-16 08:31:03 +00:00
|
|
|
// Wait for connection to be open
|
|
|
|
await connectionOpen(conn);
|
|
|
|
|
2019-09-06 12:36:11 +00:00
|
|
|
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<ErrorMessage>(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<RenameMessage>(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<ErrorMessage>(player, {
|
|
|
|
kind: "error",
|
|
|
|
error: "invalid password"
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
conn.off("data", checkPasswordResponse);
|
|
|
|
this.addPlayer(player);
|
|
|
|
} catch (e) {
|
|
|
|
this.send<ErrorMessage>(player, {
|
|
|
|
kind: "error",
|
|
|
|
error: "not a password"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.send<PasswordRequest>(player, { kind: "password-req" });
|
|
|
|
conn.on("data", checkPasswordResponse.bind(this));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.addPlayer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
private addPlayer(player: NetworkPlayer) {
|
|
|
|
const playerName = player.name;
|
2019-10-16 08:31:03 +00:00
|
|
|
|
|
|
|
// Hacky: Give player list before this player was added, so join
|
|
|
|
// message doesn't mess things up later
|
|
|
|
const players = Object.keys(this.room.players);
|
|
|
|
|
|
|
|
// Add player to room
|
2019-09-06 12:36:11 +00:00
|
|
|
this.room.players[playerName] = player;
|
|
|
|
|
|
|
|
// Start listening for new messages
|
|
|
|
player.conn.on(
|
|
|
|
"data",
|
|
|
|
this._received.bind(this, this.room.players[playerName])
|
|
|
|
);
|
|
|
|
|
2019-10-16 08:31:03 +00:00
|
|
|
player.conn.on("error", err => {
|
|
|
|
throw err;
|
|
|
|
});
|
|
|
|
|
2019-09-06 12:36:11 +00:00
|
|
|
// Send the player info about the room
|
|
|
|
this.send<RoomInfoMessage>(player, {
|
|
|
|
kind: "room-info",
|
|
|
|
room: {
|
|
|
|
...this.room.info,
|
|
|
|
password: ""
|
|
|
|
},
|
2019-10-16 08:31:03 +00:00
|
|
|
players
|
2019-09-06 12:36:11 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Notify other players
|
|
|
|
this.broadcast<JoinMessage>({
|
|
|
|
kind: "player-joined",
|
|
|
|
name: playerName
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private removePlayer(player: NetworkPlayer) {
|
|
|
|
// Tell the player everything's fine
|
|
|
|
this.send<AckMessage>(player, { kind: "ok", what: "leave-req" });
|
|
|
|
|
|
|
|
// Close connection with player
|
|
|
|
player.conn.close();
|
|
|
|
|
2019-10-16 08:31:03 +00:00
|
|
|
// Remove player from player list
|
|
|
|
delete this.room.players[player.name];
|
|
|
|
|
2019-09-06 12:36:11 +00:00
|
|
|
// Notify other players
|
|
|
|
this.broadcast<LeaveMessage>({
|
|
|
|
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
|
2019-10-16 08:31:03 +00:00
|
|
|
if (!(data.to in this.players)) {
|
2019-09-06 12:36:11 +00:00
|
|
|
this.send<ErrorMessage>(player, {
|
|
|
|
kind: "error",
|
|
|
|
error: `player not found: ${data.to}`
|
|
|
|
});
|
2019-10-16 08:31:03 +00:00
|
|
|
return;
|
2019-09-06 12:36:11 +00:00
|
|
|
}
|
2019-10-16 08:31:03 +00:00
|
|
|
this.send<ChatMessage>(this.players[data.to], data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// Player wants to change name
|
|
|
|
case "rename":
|
|
|
|
// Make sure new name is valid
|
|
|
|
data.oldname = player.name;
|
|
|
|
if (data.newname in this.players) {
|
|
|
|
this.send<ErrorMessage>(player, {
|
|
|
|
kind: "error",
|
|
|
|
error: "name not available"
|
|
|
|
});
|
|
|
|
return;
|
2019-09-06 12:36:11 +00:00
|
|
|
}
|
2019-10-16 08:31:03 +00:00
|
|
|
player.name = data.newname;
|
|
|
|
this.broadcast<RenameMessage>(data);
|
2019-09-06 12:36:11 +00:00
|
|
|
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<T extends NetworkMessage>(player: Player, message: T) {
|
|
|
|
if (player.kind == "remote") {
|
|
|
|
player.conn.send(message);
|
|
|
|
} else {
|
|
|
|
player.client.receive(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private broadcast<T extends NetworkMessage>(message: T) {
|
|
|
|
for (const playerName in this.room.players) {
|
|
|
|
const player = this.room.players[playerName];
|
|
|
|
this.send<T>(player, message);
|
|
|
|
}
|
|
|
|
}
|
2019-09-17 13:22:43 +00:00
|
|
|
|
|
|
|
public get id(): string {
|
|
|
|
return this.peer.id;
|
|
|
|
}
|
2019-09-06 12:36:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export default PeerServer;
|