UNPKG

@robotical/ricjs

Version:

Javascript/TS library for Robotical RIC

237 lines (188 loc) 6.52 kB
///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RICJS // Communications Library // // Rob Dobson & Chris Greening 2020-2022 // (C) 2020-2022 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// import RICChannel from "./RICChannel"; import WebSocket from "isomorphic-ws"; import RICMsgHandler from "./RICMsgHandler"; import RICLog from "./RICLog"; import RICUtils from "./RICUtils"; import { RICConnEvent, RICConnEventFn } from "./RICConnEvents"; export default class RICChannelWebSocket implements RICChannel { // Message handler private _ricMsgHandler: RICMsgHandler | null = null; // Websocket we are connected to private _webSocket: WebSocket | null = null; // Last message tx time // private _msgTxTimeLast = Date.now(); // private _msgTxMinTimeBetweenMs = 15; // Is connected private _isConnected = false; // Conn event fn private _onConnEvent: RICConnEventFn | null = null; // File Handler parameters private _requestedBatchAckSize = 10; private _requestedFileBlockSize = 500; fhBatchAckSize(): number { return this._requestedBatchAckSize; } fhFileBlockSize(): number { return this._requestedFileBlockSize; } // isConnected isConnected(): boolean { return this._isConnected; } // Set message handler setMsgHandler(ricMsgHandler: RICMsgHandler): void { this._ricMsgHandler = ricMsgHandler; } // WebSocket interfaces require subscription to published messages requiresSubscription(): boolean { return true; } // Set onConnEvent handler setOnConnEvent(connEventFn: RICConnEventFn): void { this._onConnEvent = connEventFn; } // Get connected locator getConnectedLocator(): string | object { return this._webSocket; } // Connect to a device async connect(locator: string | object): Promise<boolean> { // Debug RICLog.debug("RICChannelWebSocket.connect " + locator.toString()); // Connect const connOk = await this._wsConnect("ws://" + locator + "/ws"); return connOk; } // Disconnect async disconnect(): Promise<void> { // Not connected this._isConnected = false; // Disconnect websocket this._webSocket?.close(1000); // Debug RICLog.debug(`RICChannelWebSocket.disconnect attempting to close websocket`); } pauseConnection(pause: boolean): void { RICLog.verbose(`pauseConnection ${pause} - no effect for this channel type`); return; } // Handle notifications _onMsgRx(msg: Uint8Array | null): void { // Debug if (msg !== null) { RICLog.verbose(`RICChannelWebSocket._onMsgRx ${RICUtils.bufferToHex(msg)}`); } // Handle message if (msg !== null && this._ricMsgHandler) { this._ricMsgHandler.handleNewRxMsg(msg); } } // Send a message async sendTxMsg( msg: Uint8Array, sendWithResponse: boolean ): Promise<boolean> { // Check connected if (!this._isConnected) return false; // Debug RICLog.verbose(`RICChannelWebSocket.sendTxMsg ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`); // Send over websocket try { await this._webSocket?.send(msg); } catch (error: unknown) { RICLog.warn(`RICChannelWebSocket.sendTxMsg - send failed ${error}`); return false; } return true; } async sendTxMsgNoAwait( msg: Uint8Array, sendWithResponse: boolean ): Promise<boolean> { // Check connected if (!this._isConnected) return false; // Debug RICLog.verbose(`RICChannelWebSocket.sendTxMsgNoAwait ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`); // Send over websocket this._webSocket?.send(msg); return true; } async _wsConnect(locator: string | object): Promise<boolean> { // Check already connected if (await this.isConnected()) { return true; } // Form websocket address const wsURL = locator.toString(); // Connect to websocket // try { // this._webSocket = await this._webSocketOpen(wsURL); // } catch (error: any) { // RICLog.debug(`Unable to create WebSocket ${error.toString()}`); // return false; // } this._webSocket = null; return new Promise((resolve: (value: boolean | PromiseLike<boolean>) => void, reject: (reason?: unknown) => void) => { this._webSocketOpen(wsURL).then((ws) => { this._webSocket = ws; RICLog.debug(`_wsConnect - opened connection`); // Handle messages this._webSocket.onmessage = (evt: WebSocket.MessageEvent) => { // RICLog.debug("WebSocket rx"); if (evt.data instanceof ArrayBuffer) { const msg = new Uint8Array(evt.data); this._onMsgRx(msg); } } // Handle close event this._webSocket.onclose = (evt: WebSocket.CloseEvent) => { RICLog.info(`_wsConnect - closed code ${evt.code} wasClean ${evt.wasClean} reason ${evt.reason}`); this._webSocket = null; this._isConnected = false; // Event handler if (this._onConnEvent) { this._onConnEvent(RICConnEvent.CONN_DISCONNECTED_RIC); } } // Resolve the promise - success resolve(true); }).catch((err: unknown) => { if (err instanceof Error) { RICLog.verbose(`WS open failed ${err.toString()}`) } // Resolve - failed reject(false); }) }); } private async _webSocketOpen(url: string): Promise<WebSocket> { return new Promise((resolve, reject) => { // Debug // RICLog.debug('Attempting WebSocket connection'); // Open the socket try { const webSocket = new WebSocket(url); // Open socket webSocket.binaryType = "arraybuffer"; webSocket.onopen = (_evt: WebSocket.Event) => { RICLog.debug(`RICChannelWebSocket._webSocketOpen - onopen ${_evt.toString()}`); // // We're connected this._isConnected = true; resolve(webSocket); }; webSocket.onerror = function (evt: WebSocket.ErrorEvent) { RICLog.warn(`RICChannelWebSocket._webSocketOpen - onerror: ${evt.message}`); reject(evt); } } catch (error: unknown) { RICLog.warn(`RICChannelWebSocket._webSocketOpen - open failed ${error}`); reject(error); } }); } }