Newer
Older
import DataChannel from "./networking/DataChannel.js";
import Signaler from "./networking/Signaler.js";

Ben Eltschig
committed
/**
* @typedef {import("./SkribblServer.js").MessageToServer} MessageToServer
*/
/**
* @typedef {import("./SkribblServer.js").MessageToClient} MessageToClient
*/

Ben Eltschig
committed
/**
* Client/endpoint that manages a connection to a SkribblServer
*/
export default class SkribblClient {
/**
* Constructs a new client for a given game ID or DataChannel.

Ben Eltschig
committed
* @param {string|DataChannel<MessageToServer,MessageToClient>} idOrDataChannel
*/
constructor(idOrDataChannel){

Ben Eltschig
committed
/** @type {string[]} */
this._players = [];
/** @type {((host:boolean)=>void)[]} */
this._onHostChangeCallbacks = [];

Ben Eltschig
committed
/** @type {((players:string[])=>void)[]} */
this._onPlayersListChangeCallbacks = [];
this._readyPromise = (async()=>{

Ben Eltschig
committed
if (idOrDataChannel instanceof DataChannel){
this._dataChannel = idOrDataChannel;
}else{
/** @type {DataChannel<MessageToServer,MessageToClient>} */
this._dataChannel = DataChannel.from(await Signaler.join(idOrDataChannel),JSON.stringify,JSON.parse);
}
await this._dataChannel.waitUntilReady();
this._dataChannel.onMessage(message=>{
console.log("message from server:",message);
if (message!=="yup"&&message!=="nope"&&message.action=="stateUpdate"){
let hostChanged = this._host!==message.newState.host;
let playersChanged = JSON.stringify(this._players)!==JSON.stringify(message.newState.players);
this._hasGameStarted = message.newState.hasGameStarted;
this._host = message.newState.host;
this._players = message.newState.players.slice();
if (hostChanged){
this._onHostChangeCallbacks.forEach(callback=>callback(this._host));
}
if (playersChanged){
this._onPlayersListChangeCallbacks.forEach(callback=>callback(this._players));

Ben Eltschig
committed
}

Ben Eltschig
committed
}
});
})();
}
/**
* Waits until the connection to the server is ready.
*/
async waitUntilReady(){
return this._readyPromise;
}

Ben Eltschig
committed
/**
* Joins the game with the given name.
* @param {string} name
*/
async join(name){

Ben Eltschig
committed
this._dataChannel.send({action:"join",name});

Ben Eltschig
committed
let message = await this._dataChannel.next();
if (message!=="yup"){
throw new Error(`Failed to join! Server response: "${message}"`);
}
}
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
* Tells the server to start the game and then immediately returns, without waiting for a response.
*/
startGame(){
this._dataChannel.send({action:"startGame"});
}
/**
* Whether the game has started yet.
*/
get hasGameStarted(){
return this._hasGameStarted;
}
/**
* If the game hasn't started yet, waits until the host of the game has clicked the start button in the lobby, otherwise immediately returns.
*/
async waitUntilGameStarted(){
if (!this._hasGameStarted){
while(true){
let message = await this._dataChannel.next();
if (message!=="yup"&&message!=="nope"&&message.action=="stateUpdate"&&message.newState.hasGameStarted){
break;
}
}
}
}
/**
* Whether this client is currently "host" in the sense that they have permissions to alter settings and start the game.
* Doesn't actually always correlate with who is hosting the game.
*/
get host(){
return this._host;
}
/**
* Registers a callback to be called whenever the host of the game changes, and once when it is registered.
* @param {(host:boolean)=>void} callback
*/
onHostChange(callback){
this._onHostChangeCallbacks.push(callback);
callback(this._host);
}
/**
* Removes a callback registered using `onHostChange(callback)`.
* @param {(host:boolean)=>void} callback
*/
removeOnHostChangeCallback(callback){
let index = this._onHostChangeCallbacks.indexOf(callback);
if (index!==-1){
this._onHostChangeCallbacks.splice(index,1);
}
}

Ben Eltschig
committed
/**
* Registers a callback whenever the list of players in the game changes, and once when it is registered.
* @param {(players:string[])=>void} callback
*/
onPlayersListChange(callback){
this._onPlayersListChangeCallbacks.push(callback);
callback(this._players);
}