export class WSClient { private is_connected: boolean; private is_closed: boolean; private sock: WebSocket; private messages_received: Blob[]; // callbacks private callbacks_connected: (() => void)[]; private callbacks_receive: ((data: Blob | null) => void)[]; public constructor(addr: string) { this.is_connected = false; this.is_closed = false; this.messages_received = []; this.callbacks_connected = []; this.callbacks_receive = []; this.sock = new WebSocket(addr); this.sock.binaryType = "blob"; this.sock.addEventListener("open", (_) => { this.is_connected = true; for (const callback of this.callbacks_connected) { callback(); } this.callbacks_connected = []; }); this.sock.addEventListener("message", (e) => { const callback = this.callbacks_receive.shift(); if (callback === undefined) { this.messages_received.push(e.data); return; } callback(e.data); }); this.sock.addEventListener("error", (_) => { this.end(); }); this.sock.addEventListener("close", (_) => { this.end(); }); } public async wait_for_connection(): Promise { if (this.is_closed) { throw new Error("connetion was already closed"); } if (this.is_connected) { return; } await new Promise((resolver) => { this.callbacks_connected.push(resolver); }); } private end(): void { this.is_connected = false; this.is_closed = true; for (const callback of this.callbacks_connected) { callback(); } this.callbacks_connected = []; for (const callback of this.callbacks_receive) { callback(null); } this.callbacks_receive = []; } /** * @returns `true` if the send call was placed successfully, `false` if the client is not * active anymore */ public send(message: string | ArrayBufferLike | Blob | ArrayBufferView): boolean { if (!this.is_connected) { return false; } this.sock.send(message); return true; } /** * Waits for a new incoming message or connection close. * * @returns a `Blob` if there was an incoming message, `null` if the connection closed before * a message could be received */ public receive(): Promise { const queued = this.messages_received.shift(); if (queued !== undefined) { return Promise.resolve(queued); } if (this.is_closed) { return Promise.resolve(null); } return new Promise((resolver) => { this.callbacks_receive.push(resolver); }); } }