import { uint_bytes_to_num, utf8_decode } from "./tools"; import { WSClient } from "./ws"; enum PythagorasIncomingMessageType { SubUpdateCur, SubFinishCur, SelectedMessage, }; type PythagorasIncomingMessage = ( { type: PythagorasIncomingMessageType.SubFinishCur | PythagorasIncomingMessageType.SubUpdateCur, text: string, } | { type: PythagorasIncomingMessageType.SelectedMessage, message: string, } ); export class PythagorasClient { private sock: WSClient | null; private addr: string; private buf: Uint8Array; private static max_recv_retry: number = 10; public constructor(addr: string) { this.sock = null; this.addr = addr; this.buf = new Uint8Array(0); } private async reconnect(): Promise { this.sock = new WSClient(this.addr); await this.sock.wait_for_connection(); } /** * Force receive from the underlying `WSClient`. * This will try to receive (or reconnect) up to `PythagorasClient.max_recv_retry` times. */ private async recv_inner(): Promise { if (this.sock === null) { await this.reconnect(); } for (let i = 0; i < PythagorasClient.max_recv_retry; ++i) { const received = await this.sock!.receive(); if (received !== null) { return received; } await this.reconnect(); } throw new Error("max reconnection attempts reached"); } /** * Receive `target` bytes from the underlying stream (or leftovers from previous reads). */ private async recv_length(target: number): Promise { if (target == 0) { return new Uint8Array(0); } while (this.buf.length < target) { const received = await (await this.recv_inner()).bytes(); const merged = new Uint8Array(this.buf.length + received.length); merged.set(this.buf); merged.set(received, this.buf.length); this.buf = merged; } const total = this.buf; this.buf = new Uint8Array(total.length - target); this.buf.set(total.slice(target)); return total.slice(0, target); } public async recv(): Promise { const advertised_length = uint_bytes_to_num(await this.recv_length(4)); const payload = utf8_decode(await this.recv_length(advertised_length)); return JSON.parse(payload); } }