PSA: Keeping WebSocket Heartbeats alive in browser-based apps

Hey folks, just a quick PSA after several hours of head-scratching.

The Tradovate WebSockets require a heartbeat ('[]' every ~2.5 seconds per the documentation). The simplest and most convenient way to handle this in JavaScript is based on the example JS documentation, and uses setInterval().

Because the example documentation is for a web app, namely via use of webpack, the setInterval() support (much less setTimeout(), etc) is up to the browser. As I found out with testing my app in Chrome, if you don’t have the tab running the app active, Chrome will lower the priority on the setInterval() and setTimeout() functions. As a result, your WebSocket heartbeats may be delayed, and the socket connections could close prematurely (or just simply stop sending you data). It may not be convenient to leave the browser tab in focus 100% of the time, so a different approach may be in order.

One approach could be to use Web Workers (see StackOverflow here and here and here).

Alternatively, running the app as a Node.js app should get around this, as I don’t believe Node.js throttles setInterval() etc like this (mainly because since you’d run the app in the terminal, there isn’t a browser tab to “be inactive”). Using Node.js would take some reconfiguration of the example docs, and can be done with webpack but is not as straightforward as building a standalone Node.js app. If you still want to use a browser-based client, you could also run the WebSockets in a Node server, and serve the client front end via webpack, but note that this adds a small layer of latency as you have to use the server to route your API requests.

In any case, something to keep in mind and think about as you design your trading apps.

2 Likes

Hi @Jackson_Graves,
We’ve recently released updates to the WebSockets Heartbeats example project to utilize a setInterval agnostic solution.

1 Like

Alternatively just ran the following code every time a message was received. This may be too much overhead for your app… but it worked well for my solution

//pseudo code
onmessage = function(){
     //do stuff
     sendHeartBeat()
}
//working code example assuming this/ws is initialized 
sendHeartBeat(){
        const now = new Date()
        if(Math.abs(this.lastQuoteHeartBeatTime - now) > 2500) {
            console.log('Sending hearbeat because its been 2.5 seconds since last quote')
            ws.send(JSON.stringify([]))
            this.lastQuoteHeartBeatTime = now
        }
    }

This might not work for all use cases…it presupposes messages will stream in at an interval less than the max timeout interval to keep the connection alive.