Skip to content
Snippets Groups Projects
SkribblServer.js 2.98 KiB
Newer Older
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"}} MessageToServer
 * Format of the messages send to the clients by the server.
 * @typedef {"yup"|"nope"|{action:"stateUpdate",newState:{players:string[],host:boolean,hasGameStarted:boolean}}} 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._hasGameStarted = false;
			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}});
					});
					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{
					dataChannel.send("nope");
					dataChannel.close();
				}
			}else{
				dataChannel.send("nope");
				dataChannel.close();
			}
		})();