Skip to content
Snippets Groups Projects
Unverified Commit d196a3c0 authored by Ben Eltschig's avatar Ben Eltschig
Browse files

CustomSocket-Klasse durch allgemeinere DataChannel-Klasse ersetzt

parent c8d436ae
Branches
1 merge request!5Custom server
/**
* 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
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
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment