Best Practices for reconnecting a disconnected SignalR client (JS)

SB2055 picture SB2055 · Jul 30, 2017 · Viewed 10.7k times · Source

I'd like to improve the resilience of my clientside implementation of a signalR client.

Currently, I do this:

hub.server.sendClientNotification(string, appSettings.username());

However occasionally, an exception related to connectivity is thrown because either the server is not responding or the client's internet connection has become unusable.

I'd like to solve this by queuing up requests and then doing something like this:

try {
    // pop from queue
    hub.server.sendClientNotification(string, appSettings.username());
    // success - discard element
} catch (e) {
    // requeue element
}

With this kind of implementation, would I need to re-initialize my signalR connection using $.connection.hub.start between disconnects, or can I just continue attempting hub transmits within an interval?

This is what I'm proposing:

var hub = null;

const internalQueue = [];

const states = {
    connecting: 0, connected: 1, reconnecting: 2, disconnected: 4
}

const signalrModule = {};

var isInitialized = false;

const connectSignalR = function () {
    return new Promise(function (resolve, reject) {
        if ($.connection.hub.state == states.connected) {
            resolve();
        } else {
            window.hubReady = $.connection.hub.start({ transport: ["serverSentEvents", "foreverFrame", "longPolling"] });
            window.hubReady.done(function () {
                isInitialized = true;
                resolve();
            });
            window.onbeforeunload = function (e) {
                $.connection.hub.stop();
            };
        }
    })
}

signalrModule.init = function (handleNotification) {
    hub = $.connection.appHub;
    hub.client.clientNotification = handleNotification;
    $.connection.hub.qs = {
        "username": appSettings.username()
    };
    connectSignalR();
}

const tryEmptyQueue = function () {
    connectSignalR().then(function() {
        if (isInitialized) {
            var continueTrying = true;
            while (internalQueue.length && continueTrying) {
                const nextMessage = internalQueue.shift();
                try {
                    hub.server.sendClientNotification(nextMessage, appSettings.username());
                } catch (e) {
                    internalQueue.push(nextMessage);
                    continueTrying = false;
                }
            }
        }
    })
}

signalrModule.sendClientNotification = function (message) {
    if (typeof message != "string") {
        message = JSON.stringify(message);
    }
    if (isInitialized) {
        try {
            hub.server.sendClientNotification(message, appSettings.username());
            tryEmptyQueue();
        } catch (e) {
            logger.log("SignalR disconnected; queuing request", logger.logLevels.warning);
            internalQueue.push(message);
        }
    } else {
        internalQueue.push(message);
    };
}

const internalQueueInterval = setInterval(function () {
    tryEmptyQueue();
}, 5000);

return signalrModule;

Answer

Frank M picture Frank M · Jul 31, 2017

Follow the documentation

In some applications you might want to automatically re-establish a connection after it has been lost and the attempt to reconnect has timed out. To do that, you can call the Start method from your Closed event handler (disconnected event handler on JavaScript clients). You might want to wait a period of time before calling Start in order to avoid doing this too frequently when the server or the physical connection are unavailable. The following code sample is for a JavaScript client using the generated proxy.

    $.connection.hub.disconnected(function() {
   setTimeout(function() {
       $.connection.hub.start();
   }, 5000); // Restart connection after 5 seconds.
});