Edit me

Reconnecting WebSocket Client Library

There is a TypeScript library(Reconnecting WebSocket) featuring automatic reconnection when the connection is lost.

Since the initial connection or reconnection occurs asynchronously, if the code requires establishing a WebSocket connection before execution, the code below with delay() and timeout methods is useful.

  • Waiting for connection

      ...
      async delay(milliseconds: number) {
          return new Promise(resolve => {
              setTimeout(resolve, milliseconds);
          });
      }
    
      async waitForConnecting(waitTimeoutMS: number = 3000): Promise<void> {
          await this.delay(100);
          console.log('initDesign() - wait for connection...');
          let timeElapsed = 0;
          while (timeElapsed <= waitTimeoutMS) {
              if (this.webSocketCfg.reconnWebSock
                  && this.webSocketCfg.reconnWebSock.readyState === ReconnectingWebSocket.OPEN) {
                  console.log("WebSocket Connected!!");
                  break;
              }
              await this.delay(100);
              timeElapsed += 100;
          }
          if (timeElapsed > waitTimeoutMS) {
              console.log("WebSocket Connection Timeout!!");
          }
      }
    
      async initDesign(waitForConnecting: boolean = false, waitTimeoutMS: number = 3000): Promise<void> {
          await this.webSocketCfg.connectWebSocket();
          if (waitForConnecting) {
              await this.waitForConnecting(waitTimeoutMS);
          }
      ...
    

Send Data Timeout and Callback

There are two types of messages: one-way and round-trip. Every message sent to the Orion Report Server receives a response to confirm that the message was processed by the Orion. The Message Handler class keeps track of each message, including handling message timeouts.

  • MessageHandler Class

      export class MessageHandler {
          reconnWebSock: ReconnectingWebSocket;
          msgID: number;
          resultCallback: (result: ApiResult) => void | null;
          timeSpan: TimeSpan;
          timeoutMS: number;
          timeoutID: any;
          retryCount: number;
          retryMax: number;
          data: string;
          timeoutCallback: (event: MessageEvent) => void | null;
          result: ApiResult | null;
          success: boolean;
          isDisposed: boolean;
    
          constructor(reconnWebSock: ReconnectingWebSocket, msgID: number, resultCallback: (result: ApiResult) => void,
              timeoutMS: number, data: string, timeoutCallback: (event: MessageEvent) => void | null) {
              this.reconnWebSock = reconnWebSock;
              this.msgID = msgID;
              this.resultCallback = resultCallback;
              this.timeSpan = TimeSpan.zero;
              this.timeoutMS = timeoutMS; // 0 = no timeout
              this.retryCount = 0;
              this.retryMax = 3;
              this.data = data;
              this.timeoutCallback = timeoutCallback;
              this.result = null;
              this.success = false;
              this.isDisposed = false
              //
              this.dispose = this.dispose.bind(this);
              this.timerTick = this.timerTick.bind(this);
              //
              this.timeoutID = null;
              if (this.timeoutMS > 0) {
                  this.timeoutID = setTimeout(this.timerTick, this.timeoutMS);
              }
    
          }
    
          dispose() {
              this.isDisposed = true;
              if (this.timeoutID !== null) {
                  clearTimeout(this.timeoutID);
              }
          }
    
          timerTick() {
              this.retryCount++;
              if (this.retryCount < this.retryMax) {
                  this.timeoutID = setTimeout(this.timerTick, this.timeoutMS);
              } else {
                  if (this.timeoutCallback) {
                      const apiResult: ApiResult = new ApiResult();
                      apiResult.msgID = this.msgID;
                      apiResult.success = false;
                      apiResult.message = `Sending msgID: ${this.msgID} exceeded retry count. data = ${this.data}`;
                      const msgEvent: MessageEvent = new MessageEvent('timeout', { data: JSON.stringify(apiResult) });
                      this.timeoutCallback(msgEvent);
                  }
                  this.dispose();
              }
          }
      }
    
  • One-way message

    • No callback function is provided.
    • When the dummy response (the receipt for successful transfer) from Orion Server is not received within the timeout period, it records a timeout message to the console.
  • Round-trip message

    • The sendData() function adds a new message handler to the message list and sends it to the Orion WebSocket Server.

        ...
        async sendData(data: object, resultCallback: ((result: ApiResult) => void), timeoutMS: number = this.defaultTimeoutMS): Promise<void> {
            this.checkDisposedMessageHandlers();
      
            if (this.reconnWebSock?.readyState !== ReconnectingWebSocket.OPEN) {
                console.log("sendData() ERR : this.reconnWebSock?.readyState !== WebSocket.OPEN");
                return;
            }
      
            data = { '@MSGID': this.messageCount, ...data };
            const jsonData = JSON.stringify(data);
            const msgHandle = new MessageHandler(
                this.reconnWebSock, this.messageCount++,
                resultCallback, timeoutMS, jsonData, this.sockOnMessage);
            msgHandle.retryMax = this.defaultRetryMax;
            this.messagesWithCallback.push(msgHandle);
            this.reconnWebSock.send(jsonData);
        }
        ...
      
    • When the response message is received within the timeout period, the message handler calls the callback function, clears itself, and is deleted from the message list.

        ...
        sockOnMessage(event: MessageEvent) {
            if (this.messagesWithCallback === null || this.messagesWithCallback.length == 0) {
                return;
            }
            const result: ApiResult = JSON.parse(event.data);
            const handlerIndex: number = this.messagesWithCallback.findIndex(MSG => MSG.msgID === result.msgID);
            if (handlerIndex < 0) {
                console.log("handlerIndex < 0 : sockOnMessage() : " + result.message);
                return;
            }
            this.messagesWithCallback[handlerIndex].dispose();
            const resultCallback = this.messagesWithCallback[handlerIndex].resultCallback;
            this.messagesWithCallback.splice(handlerIndex, 1);
            if (resultCallback) {
                resultCallback(result);
            } 
            this.checkDisposedMessageHandlers();
        }
        ...
      
Tags: