SignalR and Browser Connection limit
This problem would be best addressed by the future Channel Messaging specification, which has not been implemented by any browsers to-date, but I managed to solve it by limiting the number of connections as described by Alex Ford and using localStorage
as a message bus between tabs.
The storage
event lets you propagate data between tabs while keeping a single SignalR connection open (thereby preventing connection saturation). Calling localStorage.setItem('sharedKey', sharedData)
will raise the storage
event in all other tabs (not the caller):
$(window).bind('storage', function (e) {
var sharedData = localStorage.getItem('sharedKey');
if (sharedData !== null)
console.log(
'A tab called localStorage.setItem("sharedData",'+sharedData+')'
);
});
You can test if ($.connection.hub.state === 1)
to determine if a given tab should notify the other tabs via localStorage (courtesy of Alex) to prevent duplicate localStorage.setItem
calls.
Facebook overcomes this browser limitation by serving persistent connections over several sub-domains, but this can complicate deployment and testing.
Caveats
Old Connections: In Alex's solution, you need to be careful of Disconnect()
not being called (e.g. exception), and you filling up your HubConnections
bucket (or repository) with old hub connections. If the Session ID does not change (can happen), this may prevent new clients from establishing a SignalR connection even though none are active. Alternatively, timestamp new connections and have a sliding expiration to minimise potential impact.
Locking: localStorage
may be subject to race conditions as it does not implement any locking as described here.
To support different types of events, you should encode an eventType in your JSON messages and test for it on the storage
event.
Fallbacks
If a SignalR connection cannot be established, I fall back onto polling the server every 45 seconds to retrieve a notification count.
If you don't want to use localStorage, you can use cookies, but it's not as clean.
To expand on @FreshCode's answer, this is how I implemented his idea.
I had a need to pass two different actions between tabs. I can either set notifications or remove notifications. These notifications are received by the browser and stored as timeouts that will fire at a specified time. The first tab has the SignalR connection and all the others do not. One issue I had to overcome was that the "storage" event would fire no matter which action I was intending to perform (set/remove).
What I ended up doing was passing along a custom JSON object with a property containing the action I wanted to perform:
$(window).bind('storage', function () {
var updateInfo = JSON.parse(localStorage.getItem('updateInfo'));
if (updateInfo.action == 'removeNotification')
removeNotification(updateInfo.notificationId);
else if (updateInfo.action == 'setNotification')
setNotification(updateInfo.notification);
});
This way each time I set an item in local storage, I just specify the action that needs to occur and only that action will happen on the other tabs. For example, if I update a notification it is a combination of both actions when it is received by the client. It removes the notification and sets the notification with the updated values. So two calls to localStorage.setItem
are being made.
function removeNotification(id) {
// Check if signalR is connected. If so, I am the tab that will update
// the other tabs.
if ($.connection.hub.state === 1) {
var updateInfo = {
action: 'removeNotification',
notificationId: id
};
localStorage.setItem('updateInfo', JSON.stringify(updateInfo));
}
// brevity brevity
}
Likewise, the setNotification
function.
function setNotification(notification) {
if ($.connection.hub.state === 1) {
var updateInfo = {
action: 'setNotification',
notification: notification
};
localStorage.setItem('updateInfo', JSON.stringify(updateInfo));
}
// brevity brevity
}
I've created IWC-SignalR utility which allows to have single SignalR connection for all windows (tabs) of the same application.
How it works
One of the windows becomes a connection owner (choosen randomly) and holds the real SignalR connection. If connection owner is closed or crashed another window becomes a connection owner - this happens automatically. Inter-window communication is done by means of inter-window communication library (based on localStorage
). This library provides functionality to communicate between windows as between parallel processes (locks, shared data, event bus...). Hope it will be useful for someone.