From d196a3c0f5a1ae3bddd21fcd4463d5489d43e789 Mon Sep 17 00:00:00 2001 From: Ben Eltschig <43812953+peabrainiac@users.noreply.github.com> Date: Wed, 3 Mar 2021 22:35:51 +0100 Subject: [PATCH] CustomSocket-Klasse durch allgemeinere DataChannel-Klasse ersetzt --- client/DataChannel.js | 120 +++++++++++++++++++++++++++++++++++ client/Signaler.js | 141 +++--------------------------------------- 2 files changed, 130 insertions(+), 131 deletions(-) create mode 100644 client/DataChannel.js diff --git a/client/DataChannel.js b/client/DataChannel.js new file mode 100644 index 0000000..b29c2f5 --- /dev/null +++ b/client/DataChannel.js @@ -0,0 +1,120 @@ +/** + * Class representing any connection that can be used to send data back and forth, like a WebSocket, + * an RTCDataChannel or even just a plain object used to communicate between two scripts. + */ +export default class DataChannel { + /** + * @callback MessageCallback + * @param {string} message + * @returns {void} + */ + /** + * Constructs a new DataChannel endpoint from the given callbacks once the promise resolves. + * @param {()=>Promise<{send:(data:string)=>void,onMessage:(callback:MessageCallback)=>void,close:()=>void}>} callback + */ + constructor(callback){ + /** @type {MessageCallback[]} */ + this._onMessageCallbacks = []; + /** @type {Promise<void>} */ + this._readyPromise = new Promise(async resolve=>{ + // halts execution of this and puts in the microtask queue so the constructor finishes execution first, before the callback is called, + // because otherwise the callback can't access the this object without throwing errors. + // timing is difficult, okay? I'm sorry + await Promise.resolve(); + let c = await callback(); + this._send = c.send; + c.onMessage(message=>{ + this._onMessageCallbacks.slice().forEach(callback=>callback(message)); + }); + this._close = c.close; + resolve(); + }); + } + + /** + * Waits until the connection is ready. + */ + async waitUntilReady(){ + return this._readyPromise; + } + + /** + * Sends the given message to the other endpoint of the connection. + * @param {string} message + */ + async send(message){ + await this._readyPromise; + this._send(message); + } + + /** + * Registers a callback to be called whenever a message is received. + * @param {MessageCallback} callback + */ + onMessage(callback){ + this._onMessageCallbacks.push(callback); + } + + /** + * Removes a callback registered using `onMessage(callback)`. + * @param {MessageCallback} callback + */ + removeMessageCallback(callback){ + let index = this._onMessageCallbacks.indexOf(callback); + if (index!==-1){ + this._onMessageCallbacks.splice(index,1); + } + } + + /** + * Returns a promise that eventually resolves to the next message received. + * @returns {Promise<string>} + */ + next(){ + return new Promise(resolve=>{ + let callback = message=>{ + this.removeMessageCallback(callback); + resolve(message); + } + this.onMessage(callback); + }); + } + + /** + * Closes the connection. + */ + close(){ + this._close(); + } +} +/** + * Class representing a connection to a server via a WebSocket. + */ +export class SocketDataChannel extends DataChannel { + /** + * Creates a new WebSocket connection to the given url. + * @param {string} url + */ + constructor(url){ + super(async()=>{ + this._webSocket = new WebSocket(url); + /** @type {(message:string)=>void} */ + let send = message=>{ + this._webSocket.send(message); + }; + /** @type {(callback:(message:string)=>void)=>void} */ + let onMessage = callback=>{ + this._webSocket.addEventListener("message",e=>{ + callback(e.data+""); + }); + }; + let close = ()=>{ + this._webSocket.close(); + } + await new Promise(resolve=>{ + this._webSocket.onopen = resolve; + }); + return {send,onMessage,close}; + }); + } +} \ No newline at end of file diff --git a/client/Signaler.js b/client/Signaler.js index 00fa58c..77218a0 100644 --- a/client/Signaler.js +++ b/client/Signaler.js @@ -1,3 +1,5 @@ +import {SocketDataChannel} from "./DataChannel.js"; + export const baseURL = `ws://${document.location.hostname}:443/skribbl`; /** * @typedef {{connection:RTCPeerConnection,dataChannel:RTCDataChannel}} ConnectionData @@ -12,13 +14,14 @@ export default class Signaler { * @param {(connectionData:ConnectionData)=>void} onConnect */ static async host(onConnect){ - const socket = new CustomSocket(baseURL+"/host"); + const socket = new SocketDataChannel(baseURL+"/host"); //await socket.waitUntilReady(); - let response = socket.next(message=>{ - console.log("possible response:",message); - return message.startsWith("started "); - }); - let id = (await response).split(" ")[1]; + let response = await socket.next(); + console.log("response:",response); + if (!response.startsWith("started ")){ + throw new Error(`Unexpected first response or something: "${response}"`); + } + let id = response.split(" ")[1]; socket.onMessage(async message=>{ if (message.startsWith("join ")){ let playerID = message.split(" ",2)[1]; @@ -46,7 +49,7 @@ export default class Signaler { * @param {string} id */ static async join(id){ - const socket = new CustomSocket(baseURL+"/join/"+id); + const socket = new SocketDataChannel(baseURL+"/join/"+id); await socket.waitUntilReady(); /** @type {((message:string)=>void)[]} */ let callbacks = []; @@ -118,128 +121,4 @@ export async function perfectNegotiation(onMessage,sendMessage,polite){ } }; }); -} -/** - * Custom socket class to pass strings to and from a server. - * - * Originally, the idea was to let this pass json data instead, but since json is difficult to work with in java I've stuck with normal strings for now. - */ -export class CustomSocket { - /** - * @callback MessageCallback - * @param {string} message - * @param {()=>void} remove removes this callback again - * @returns {void} - */ - /** - * @callback ErrorCallback - * @param {Event} error - * @param {()=>void} remove removes this callback again - * @returns {void} - */ - /** - * Creates a new custom socket. - * @param {string} url - */ - constructor(url){ - /** @type {MessageCallback[]} */ - this._onMessageCallbacks = []; - /** @type {ErrorCallback[]} */ - this._onErrorCallbacks = []; - this._webSocket = new WebSocket(url); - /** @type {Promise<void>} */ - this._readyPromise = new Promise((resolve,reject)=>{ - this._webSocket.onopen = ()=>resolve(); - this._webSocket.addEventListener("error",reject,{once:true}); - }); - this._webSocket.onmessage = (e)=>{ - console.log(`onmessage event! e.data: "${e.data}", registered callbacks: ${this._onMessageCallbacks.length}`); - this._onMessageCallbacks.slice().forEach(callback=>callback(e.data+"",()=>this.removeMessageCallback(callback))); - }; - this._webSocket.onerror = (e)=>{ - this._onErrorCallbacks.slice().forEach(callback=>callback(e,()=>this.removeErrorCallback(callback))); - }; - } - - async waitUntilReady(){ - return this._readyPromise; - } - - /** - * Sends the given object as json to the server. - * @param {string} data - */ - send(data){ - this._webSocket.send(data); - } - - /** - * Registers a callback to be called whenever a message is received. - * @param {MessageCallback} callback - */ - onMessage(callback){ - this._onMessageCallbacks.push(callback); - } - - /** - * Removes a callback registered using `onMessage(callback)`. - * @param {MessageCallback} callback - */ - removeMessageCallback(callback){ - let index = this._onMessageCallbacks.indexOf(callback); - if (index!==-1){ - this._onMessageCallbacks.splice(index,1); - } - } - - /** - * Returns a Promise that resolves to the next message received that passen the given filter, or `null` if no such message is received in the specified timeframe. - * If an error occurs before, the promise will instead be rejected with that. - * @param {(message:string)=>boolean} filter - * @param {number} time time to wait before just returning `null`, in seconds. Defaults to 30. If the given number is smaller than or equal to 0, it gets treated as `Infinity`. - * @return {Promise<string>} - */ - async next(filter=null,time=30){ - return new Promise((resolve,reject)=>{ - this.onMessage((message,remove)=>{ - if (!filter||filter(message)){ - resolve(message); - remove(); - } - }); - this.onError((error,remove)=>{ - reject(error); - remove(); - }); - if (time>0){ - setTimeout(()=>resolve(null),time*1000); - } - }); - } - - /** - * Registers a callback to be called whenever there's an error. - * @param {ErrorCallback} callback - */ - onError(callback){ - this._onErrorCallbacks.push(callback); - } - - /** - * Removes a callback registered using `onError(callback)`. - * @param {ErrorCallback} callback - */ - removeErrorCallback(callback){ - let index = this._onErrorCallbacks.indexOf(callback); - if (index!==-1){ - this._onErrorCallbacks.splice(index,1); - } - } - - /** - * Closes the connection. - */ - close(){ - this._webSocket.close(); - } } \ No newline at end of file -- GitLab