[fa icon="calendar"] Aug 15, 2015 / by Adam Neary
— tl;dr plenty of code below —
At Wearable Intelligence we need reliable real time connection for workers out in the field. Most critically, we need to display a disconnected state accurately on web and mobile clients.
There’s nothing revolutionary about the Gmail-style reconnect banner, but how we manage it using React and NuclearJS (the flux implementation we use and love) is pretty cool, I think, and after a few iterations we have the quirks of native WebSockets wrestled to the ground quite elegantly. Hopefully there’s some useful stuff in here if you’re playing in similar areas.
We use a “Signaling” server to broadcast real time events to web and mobile clients. To drop this capability into any of our web apps, we just need to include a Signaling component in any Authenticated parent component.
Nuclear allows us to bind the status of the signaling connection as an Immutable map. We can fire off a connect action when the component mounts and when the user explicitly requests one, and we can disconnect when the component is unmounted.
The rest is UI. It’s dead simple for our designers to see how it all works without giving any thought to all the machinery that drives it, and they can happily edit the markup or related SCSS. Boom.
In the past, my real time connection life has been a lot simpler through the use of Socket.io, but in this case, we need to build and manage WebSockets natively to connect to an existing server (perhaps a topic for another post).
The tricky part is that we will need Nuclear actions to reference the current socket instance, so we need a singleton we can persist and mutate, something best kept out of Nuclear entirely.
This provider can safely be called from any action and be counted on to deliver the current instance. As requested, the instance can be hard reset, which is an unfortunate necessity during the reconnect process.
As a bonus, the provider also maintains a persistent map of timers, preventing Nuclear stores from having to set and clear intervals.
Our specific socket implementation requires that we ping the server with a presence packet every ~30 seconds, so the provider has this capability built into it, and it maintains a map of message type to its handlers (each of which calls Nuclear actions from other modules).
To keep everything neat and clear, we only want to expose “connect” and “disconnect” actions. Everything else is an internal implementation detail.
“Connect” is the only method that triggers a hard reset of the socket by passing true as the third argument. Other instantiations of the WebSocketProvider simply return the existing instance.
The rest of the internal functions are where the real action takes place. The event handlers also start and stop the appropriate timers.
If the connection is in a ready state, the identify timer triggers a ping every 30 seconds, meeting our presence requirement for the signaling server itself.
If the connection closes or goes “cold” (no close event, but no response to pings for over a minute), the identify timer is stopped, and the reconnect timer is started. Every second, the UPDATE_RECONNECT_TIMER action is fired, which our store uses to update its state so that our React component doesn’t need to get its hands dirty with the same.
Since the WebSocketProvider checks for the existence of a timer before setting interval, there’s no risk in functions like checkConnection getting called too many times.
When is the last time you heard that? Here’s what I see when debugging a new change:
Any of those log objects can be expanded to display the action payload as well as the app state as of that point in time. No more dropping in console.log all over to determine what happened between events.
In this case, on login you can see the app initiate a connection attempt, report the change in state when the connection is opened, fire off a presence ping, and receive the result. A pair of identify and receive pings come in 30 seconds later.
Then I killed the signaling server manually (locally!), and you see the close event come through followed by an immediate and unsuccessful reconnect attempt. The reconnect timer updates every second, but once I turn signaling back on, we get the expected connection events.
On logout, everything is disconnected and cleaned up, and we see our login page rendered.
If you found this helpful, don’t feel bad about recommending it.
Oh yeah, and we are hiring our third Front End Engineer if you feel like getting paid to get scrappy with React, Nuclear, and ImmutableJS at a Series-A funded startup backed by Andreessen Horowitz, First Round, Kleiner Perkins, and Google Ventures, among others.