import DataChannel from "./networking/DataChannel.js"; import Signaler from "./networking/Signaler.js"; /** * Format of the messages send to the server by the clients. * @typedef {{action:"join",name:string}|{action:"startGame"}|{action:"changeSettings",rounds:number,drawTime:number}} MessageToServer */ /** * Format of the messages send to the clients by the server. * @typedef {"yup"|"nope"|{action:"stateUpdate",newState:{players:string[],host:boolean,hasGameStarted:boolean}}|{action:"settingsUpdate",rounds:number,drawTime:number}} MessageToClient */ /** * A local server. Handles all the important game logic, and communicates with clients via DataChannels. */ export default class SkribblServer { /** * Starts a new SkribblServer. */ constructor(){ this._readyPromise = (async()=>{ /** @type {{name:string,dataChannel:DataChannel<MessageToClient,MessageToServer>}[]} */ this._clients = []; this._hasGameStarted = false; this._rounds = 3; this._drawTime = 180; this._id = await Signaler.host(dataChannel=>{ this.connect(DataChannel.from(dataChannel,JSON.stringify,JSON.parse)); }); /** @type {[DataChannel<MessageToServer,MessageToClient>,DataChannel<MessageToClient,MessageToServer>]} */ let [endpointA,endpointB] = DataChannel.createPair(); this._dataChannel = endpointA; this.connect(endpointB); })(); } /** * Waits until the server is ready. */ async waitUntilReady(){ return this._readyPromise; } /** * A dataChannel talking to this server like any other client. * @readonly */ get dataChannel(){ return this._dataChannel; } /** * The ID others can use to connect to this server. * @readonly */ get id(){ return this._id; } /** * Returns the full url others can use to connect to this server. * @readonly */ get url(){ return document.location.host+document.location.pathname+"#"+this._id; } /** * Adds an incoming connection as a client. * @param {DataChannel<MessageToClient,MessageToServer>} dataChannel */ connect(dataChannel){ (async()=>{ let message = await dataChannel.next(); console.log("message: ",message); if (message.action==="join"){ let name = message.name; if (name.length<30&&name.length>=1){ dataChannel.send("yup"); this._clients.push({name,dataChannel}); let players = this._clients.map(({name})=>name); this._clients.forEach(({dataChannel},index)=>{ dataChannel.send({action:"stateUpdate",newState:{players,host:index==0,hasGameStarted:this._hasGameStarted}}); }); if (!this._hasGameStarted){ dataChannel.send({action:"settingsUpdate",rounds:this._rounds,drawTime:this._drawTime}); } dataChannel.onMessage(message=>{ let isHost = this._clients[0].dataChannel = dataChannel; if (message.action=="startGame"){ if (isHost&&!this._hasGameStarted){ this._hasGameStarted = true; let players = this._clients.map(({name})=>name); this._clients.forEach(({dataChannel},index)=>{ dataChannel.send({action:"stateUpdate",newState:{players,host:index==0,hasGameStarted:this._hasGameStarted}}); }); } }else if(message.action=="changeSettings"){ if (isHost&&!this._hasGameStarted){ this._rounds = message.rounds; this._drawTime = message.drawTime; this._clients.forEach(({dataChannel})=>{ dataChannel.send({action:"settingsUpdate",rounds:this._rounds,drawTime:this._drawTime}); }); } } }); }else{ dataChannel.send("nope"); dataChannel.close(); } }else{ dataChannel.send("nope"); dataChannel.close(); } })(); } }