shanewholloway/js-u8-mqtt

Question: Automatic WS reconnection on network interrupts

Closed this issue · 10 comments

0ip commented

First of all, thank you for this library! I'm using it with WebSockets to power a Vue-based info dashboard.

I'm now wondering how to handle network interrupts with u8-mqtt. mqttClient.on_reconnect = (m) => console.log("Reconn") shows me in the console that apparently it notices no longer being connected, but I don't see any attempts to reestablish a WS connection in the network tab.

Is this intentional (should I take care of it myself?) or am I missing something else?

Thanks for your question!

From reviewing my code, the on_reconnect() callback is triggered upon connection close (in with_websock() close handler in code/core.jsy), stream end (in with_async_iter() of code/core.jsy), or a call to mqttClient.disconnect().

So yes, your user code is responsible what you want to do for reconnection, if anything. The initial use case I started the project for did not require long-lived connections, so I did not need this functionality at the time. I welcome your suggestions on what you might like to see happen for a new revision of the client.

I believe the MQTT spec allows reconnecting with the same client ID and the server will resume existing subscriptions for that ID if the connection was interrupted. However, an explicit disconnect mqtt packet sent directs the server to clear out the state for that Client Id.

0ip commented

Really appreciate your detailed answer.

For my small (hobbyist level) dashboard project, automatic connection recovery is a bit of a must to always display the latest data. I don't know if I would be able to implement this feature myself properly, so I would actually be happy to see it in one of the next versions of the client.

0ip commented

Just saw the commit implementing this feature. Incredible, thank you so much!
I tested it in my project and it seems to be working.

I hope it's okay if I ask a few (un)related questions here.

  1. Is there an event/a way to be notified in case of disconnects? Edit: on_reconnect appears to answer my question, but it doesn't fire on disconnects (/reconnects) for some reason.
  2. Without overwriting on_live, starting from 0.2.0-0, the WS connection appears to close itself immediately. Overwriting on_live fixes this. All of my app logic still lives outside this function, e.g. subscribe_topic and on_topic. I suspect that's not exactly the way it is intended to be used, or is it?

Thanks for testing the new version directly from git!

On question 1, on_reconnect was the correct answer. I realized I didn't like the event name and wanted to pass an additional parameter. I've introduced on_disconnect(client, intentional) to provide more information. Additionally, with_autoreconnect() provides a default implementation of on_reconnect, which is now called by the default implementation of on_disconnect.

On question 2, multiple calls to client.connect() cause MQTT servers to hang up the connection due to a protocol error. I've moved the default reconnection implementation case into on_disconnect in the case the user doesn't provide an on_live handler.

I've pushed v0.2.0-1 with these changes. Please keep the testing and questions coming. They are very much appreciated!

0ip commented

With pleasure! I was so happy to see the commit that I had to test it right away :)

So assuming I don't want to touch/override the internal reconnect logic at all, but just want to know when the client is (re)connected (or disconnected), I was expecting it could work like that, but it appears to be too intrusive and fails.

mqttClient = mqtt_client().with_websock(MQTT_ADDR).with_autoreconnect()
mqttClient.on_live = (c) => console.log("Connected")
// mqttClient.on_reconnect = (c) => console.log("Reconnected")
mqttClient.on_disconnected = (c, intentional) => console.log("Disconnected")
mqttClient.connect()

You are correct, adding on_live, on_reconnect, or on_disconnect indicates your client wants to override the default behavior.

To that end, I've reduced the amount of logic each of those hook defaults are doing and I've added a log_conn(evt, arg, err_arg) hook specifically for visibility into connection changes for you.

mqtt_client({
  log_conn(evt, arg, err_arg) {
    console.info('[[u8-mqtt log: %s]]', evt, arg, err_arg)
  },
})

The with_autoreconnect() method installs a default on_reconnect implementation to reconnect after a delay. The default implementations for on_live, on_ready, and on_disconnect hooks:

mqtt_client({
  on_live(client, has_connected) {
    if (has_connected)
      return client.connect()
  },
  on_ready(client) {
  },
  on_disconnect(client, intentional) {
    if (! intentional && client.on_reconnect)
      return client.on_reconnect()
  },
})

I'll add this to the documentation when this iteration stabilizes and I publish v0.2.0.

0ip commented

Excellent, log_conn is exactly what I was looking for, thank you for implementing it for me specifically!

Out of curiosity: Why did you decide not to use the camelCase naming convention?

Thanks for using this library and providing feedback!

Regarding my naming conventions, it has been more of stylistic habit than an explicit decision. Culturally, I've written a lot of Python and C/C++ that tend to prefer naming with underscores. I hadn't noticed how consistently builtin JavaScript APIs use camelCase.

0ip commented

It's rare to find in the JS world, but then again, snake_case appears to be the more readable one of the two. I'm using Python quite often as well, so I'm clearly leaning towards that convention, but for the sake of consistency, I'm forcing myself to apply both of them in my projects according to the respective environment. In the end, of course, it does not matter much, does it.

Published in v0.3.0