101
votes
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
  ws.send(JSON.stringify({
      .... some message the I must send when I connect ....
  }));

};

ws.onmessage = function (e) {
  console.log('Got a message')
  console.log(e.data);
};

ws.onclose = function(e) {  
  console.log('socket closed try again'); 

}

ws.onerror = function(err) {
  console.error(err)
};

When I first connect to the socket, I must first send a message to the server to authenticate myself and subscribe to channels.

The problem I have is that sometimes the socket server is unreliable and that triggers the onerror and onclose events of the 'ws' object.

Question: What is a good design pattern that would allow me, whenever the socket closes or encounters an error, wait for 10 seconds and then reconnect to the socket server (and resend the initial message to the server)

7

7 Answers

185
votes

Here is what I ended up with. It works for my purposes.

function connect() {
  var ws = new WebSocket('ws://localhost:8080');
  ws.onopen = function() {
    // subscribe to some channels
    ws.send(JSON.stringify({
        //.... some message the I must send when I connect ....
    }));
  };

  ws.onmessage = function(e) {
    console.log('Message:', e.data);
  };

  ws.onclose = function(e) {
    console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
    setTimeout(function() {
      connect();
    }, 1000);
  };

  ws.onerror = function(err) {
    console.error('Socket encountered error: ', err.message, 'Closing socket');
    ws.close();
  };
}

connect();
5
votes

This worked for me with setInterval, because client connection can be lost.

ngOnInit(): void {
    if (window.location.protocol.includes('https')) {
        this.protocol = 'wss';
    }

    this.listenChanges();
}


listenChanges(): void {
    this.socket = new WebSocket(`${this.protocol}://${window.location.host}/v1.0/your/url`);

    this.socket.onmessage = (event): void => {
        // your subscription stuff
        this.store.dispatch(someAction);
    };

    this.socket.onerror = (): void => {
        this.socket.close();
    };


    this.socket.onopen = (): void => {
        clearInterval(this.timerId);

        this.socket.onclose = (): void => {
            this.timerId = setInterval(() => {
                this.listenChanges();
            }, 10000);
        };
    };
}

Don't forget to call clearInterval when the socket has been opened.

2
votes

A too interesting wrapper above the native Websocket api to add that and nicely

https://github.com/joewalnes/reconnecting-websocket

2
votes

UPDATED answer:

At last, (if you are not using java) I found you'd better implement your own "ping/pong" strategy. (if you are using java, please take a look at ping/pong "action type", I don't remember very clear... )

  1. client sent "ping" to server every 5 seconds.
  2. server should echo a "pong" to the client once it receive "ping".
  3. client should reconnect server if doesn't receive "pong" in 5 seconds.

Don't rely on any third party libs.

WARNING: DO NOT use these tools:

  1. check if the network is available: https://github.com/hubspot/offline
  2. to re-connect: https://github.com/joewalnes/reconnecting-websocket
1
votes

I found that this package https://github.com/pladaria/reconnecting-websocket can solve the reconnection issues for Websocket connections. And it has the list of configurable options, one of them is reconnectionDelayGrowFactor which determines how fast the reconnection delay grows.

1
votes

using async await if socket closed or any error occurred on the server the client will try to connect automatically every 5 sec forever

"use strict";

var ws = require("ws");
var url = 'ws://localhost:3000';
var openedSocket = null;
var timeInterval = 5000;

function connect() {
  var client = new ws(url);
  return new Promise((resolve, reject) => {
    console.log("client try to connect...");

    client.on("open", () => {
      console.log(
        "WEBSOCKET_OPENED: client connected to server at port %s", port);
      openedSocket = true;
      resolve(openedSocket);
    });

    client.on("message", (data) => {
      console.log(data);
    });

    client.on("close", (err) => {
      console.log("WEBSOCKET_CLOSE: connection closed %o", err);
      openedSocket = false;
      reject(err);
    });

    client.on("error", (err) => {
      console.log("WEBSOCKET_ERROR: Error", new Error(err.message));
      openedSocket = false;
      reject(err);
    });
  });
}

async function reconnect() {
  try {
    await connect();
  } catch (err) {
    console.log("WEBSOCKET_RECONNECT: Error", new Error(err.message));
  }
}

reconnect();

// repeat every 5 seconds
setInterval(() => {
  if (openedSocket == false) {
    reconnect();
  }
}, timeInterval);
0
votes

This isn't explicitly a react question but here is a react style answer:

TLDR: You can use setInterval to periodically check the websocket connection status and try to re-connect if the connection is closed. https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState

class TestComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};

    this.connect = this.connect.bind(this);
  }

  componentDidMount() {
    this.interval = setInterval(this.connect, 1000);
  }

  componentWillUnmount() {
    if (this.ws) this.ws.close();
    if (this.interval) clearInterval(this.interval);
  }

  connect() {
    // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
    if (this.ws === undefined || (this.ws && this.ws.readyState === 3)) {
      this.ws = new WebSocket(`ws://localhost:8080`);

      this.ws.onmessage = (e) => {
        console.log(JSON.parse(e.data));
      };
    }
  }

  render() {
    return <div>Hey!</div>;
  }
}