import type { Types } from "@meshtastic/core"; export class TransportHTTP implements Types.Transport { private _toDevice: WritableStream; private _fromDevice: ReadableStream; private url: string; private receiveBatchRequests: boolean; private fetchInterval: number; private fetching: boolean; private interval: ReturnType | undefined; public static async create( address: string, tls?: boolean, ): Promise { const connectionUrl = `${tls ? "https" : "http"}://${address}`; await fetch(`${connectionUrl}/api/v1/toradio`, { method: "OPTIONS", }); await Promise.resolve(); return new TransportHTTP(connectionUrl); } constructor(url: string) { this.url = url; this.receiveBatchRequests = false; this.fetchInterval = 3000; this.fetching = false; this._toDevice = new WritableStream({ write: async (chunk) => { await this.writeToRadio(chunk); }, }); let controller: ReadableStreamDefaultController; this._fromDevice = new ReadableStream({ start: (ctrl) => { controller = ctrl; }, }); this.interval = setInterval(async () => { if (this.fetching) { // We still have the previous request open return; } this.fetching = true; try { await this.readFromRadio(controller); } catch { // TODO: Emit disconnection events for certain types of errors } this.fetching = false; }, this.fetchInterval); } private async readFromRadio( controller: ReadableStreamDefaultController, ): Promise { let readBuffer = new ArrayBuffer(1); while (readBuffer.byteLength > 0) { const response = await fetch( `${this.url}/api/v1/fromradio?all=${ this.receiveBatchRequests ? "true" : "false" }`, { method: "GET", headers: { Accept: "application/x-protobuf", }, }, ); readBuffer = await response.arrayBuffer(); if (readBuffer.byteLength > 0) { controller.enqueue({ type: "packet", data: new Uint8Array(readBuffer), }); } } } private async writeToRadio(data: Uint8Array): Promise { await fetch(`${this.url}/api/v1/toradio`, { method: "PUT", headers: { "Content-Type": "application/x-protobuf", }, body: data, }); } get toDevice(): WritableStream { return this._toDevice; } get fromDevice(): ReadableStream { return this._fromDevice; } disconnect(): Promise { this.fetching = false; if (this.interval) { clearInterval(this.interval); } this.interval = undefined; return Promise.resolve(); } }