Reputation: 26129
For example I want to refresh chat messages by multiple tabs with socket.io, longpolling, etc... whatever I have... For that I want to use only a single connection for all of the tabs. How can I make this? I can store the common data in localStore, cookies, etc... And I need some kind of semaphore which gives only a single synchronizer resource to one of the tabs, and after that tab is closed, it gives to another tab, etc... How is that possible? The only solution came in my mind to tell the localStore with onbeforeunload that the resource is free, but this does not work in every browser. Is there another option?
Upvotes: 4
Views: 4559
Reputation: 5236
The best way to do this in my view is to use a shared worker to arbitrate the resource between tabs and a broadcast channel to communicate from the shared worker to all browser tabs.
A problem description: you have a single resource that you want to share among browser tabs. In my case this was a inbound audio connection. The nature of the problem is that the resource has to be owned by an individual browser tab. The fundamental problem is that no reliable notice is given if a browser tab is closed (beforeunload and unload events cannot be used for this). What needs to happen is that if the browser tab that owns the resource is closed another browser tab needs to take over ownership.
My solution is to borrow an approach from SCADA system implementation called failover. For a SCADA system to be reliable it must guard against the failure of the primary computer system. To do this the backup computer system continuously sends a hello message to the primary computer. If ever the primary computer fails to respond to the hello message, the backup computer is instructed to "go prime". The failover system then switches all the I/O channels from the old prime to the new prime system. The old prime computer is then rebooted.
I've implemented this same approach for browser tabs. A shared worker first choses among browser tabs to determine which tab is "prime". A hello message is continually broadcast to all browser tabs and each response with whether they are prime or backup. If the prime tab doesn't respond, then the shared worker selects a backup tab and instructs it to go prime.
A key part of the code is the heartbeat function which every 5 seconds (by default) (1) removes unresponsive pages; (2) selects a new prime if necessary; (3) pings all browser tabs.
this.heartbeat = function() {
self.purgeDeadPages();
self.selectPrime();
self.ping();
}
The selectPrime function counts the number of pages reporting themselves prime: if 0, then a new prime must be selected. if >1, a degenerate condition, all but one prime must "go backup".
this.selectPrime = function() {
let primes = this.getPrimes();
if(primes.length == 0) {
let backups = this.getBackups();
if(backups.length > 0) {
let newPrime = backups.length == 1 ? backups[0] : backups[Math.floor(Math.random() * backups.length)];
this.goPrime(newPrime);
self.pages[newPrime].setStatus(iAmPrimeMessage);
}
} else if(primes.length > 1) {
let newPrime = primes[Math.floor(Math.random() * primes.length)];
primes.forEach((prime) => {
if(prime != newPrime) {
this.goBackup(prime);
self.pages[prime].setStatus(iAmBackupMessage);
}
});
}
}
All the code and a test scaffold is at https://github.com/RundleAutomation/Failover
Upvotes: 0
Reputation: 26129
The keywords in this problem is "inter-tab communication", "cross-window messaging", etc...
One solution is similar to long-polling: inter-tab-communication-using-local-storage/ Periodically ask the localStore/cookies for changes, and add a queue to allocate common resources (e.g. socket.io connection). You can use onbeforeunload or timeout to determine whether a tab/window is navigated away or closed. After that shortly the next tab in the queue will allocate the resource...
The second solution is to use "localStore storage events" for the same. In that case you don't have to periodically ask the localStore (if onbeforeunload event is available). According to this: localStorage eventHandler Doesn't Get Called storage events are designed to affect only other tabs, so they are a good choice for intertab communication. The only problem is onunload: local storage on window unload event . So because of the lack of onunload support the first solution could be better.
The third solution would be the usage of "shared webworkers", but they have not been implemented yet in several browsers (internet explorer), or they cannot open socket (firefox). So currently they are not an option, maybe 1-2 years later after bug fixes... Here is a demo - works chrome only - html5-shared-web-worker-examples.
The fourth solution would be window.postMessage, which has not complete browser support currently. I read about it in some sto questions, and they all wrote that postMessage is not capable for what we want. I did not check the exact details about that function, it does not worth the time I think... There is an example about cross domain cross iframe communication: Cross-Domain iframe communication but same domain cross window communication is not possible with that I think.
The fifth solution would be using cookies but in that case every tab should ping document.cookie because there in no cookie change event, like storage event in localstore. BNC Connector uses this approach.
The sixth solution would be using WebSQL. Its driver is async non blocking, so it would be better than localStorage, but currently it is not supported by firefox and msie.
Conclusion:
Nowadays - 2013.10.03 - the best option to periodically ping the localStore from the resource utilizer tabs. The other tabs should listen to the storage event of the timestamp updates. If that event does not come in time, than the resource utilizer tab has timeout, and the next tab in the queue should get the resource. Maybe later the onunload event or the shared workers will be reliable, but currently they are not good enough...
Solution:
I found an implementation of the approach described in the conclusion: intercom.js I added an issue about a general interface of resource sharing, but in my case the single socket.io resource is good enough...
Upvotes: 9