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