📜 ⬆️ ⬇️

Implementing messaging between browser tabs

This is the first article in our corporate blog. This time I will talk about our solution to the problem of messaging between browser tabs.

For example, I needed to solve this problem when implementing the JavaScript API to the Comet service . This task is encountered quite often and it has already been considered in Habré earlier here and here , but I decided to write my own solution for the problem based on the following code requirements:



I implemented my mini library in the style of signals and slots .
')
This is a very convenient model and it seems to me that in this example is the best fit. The advantage of this approach is the weak connectivity of interacting components. In short, the model of signals and slots gives us the following possibilities:

Here, for example, we need to notify all the subscribing functions of an event.
To do this, perform:

tabSignal().emitAll('', "") //      tabSignal().emit( '', "" ) //       
All the code worked and if someone was subscribed to this event, he will receive the data.

To subscribe to an event, you must pass the name of the event to which you subscribe and callBack to call in case the event occurs.

 tabSignal().connect('', function(param, signal_name){ }); 

You can also transfer the name of the slot, it may be needed if you suddenly decide to unsubscribe from event notifications.

 tabSignal().connect("",'', function(param, signal_name){} ); 

Here param will contain the message itself. And signal_name is the name of the signal, it is useful in case you signed one callBack for several different signals

Here is the code in case you need to unsubscribe from the event.

 tabSignal().disconnect("", ''); 

To transfer data to another contribution, the library simply writes them to the local storage browser. In order to receive data, the library subscribes to the onstorage event, it occurs in all tabs, when someone writes something in local storage.

I did not burden the library itself with the function of selecting the master tab, so I will bring it here. At the same time we will analyze the algorithm of its work. But to begin with, I’ll tell you what it took to find the master tab. As already said, I was developing the JavaScript API for the comet service.

Technology Comet allows you to send messages to the browser on the initiative of the server. This has many uses, the most obvious is to create a chat between users or a user and technical support. Or, for example, the dynamic loading of new tweets on Twitter as they appear.

To send push notifications to the browser, you must have a constantly open connection between the browser and the comets server. But many people open the site in more than one tab and it would be useful if only one of the open tabs kept the actual connection to the comets server, and all the open tabs used this connection. This approach not only saves server resources, but also solves a very important problem - a limit on the number of simultaneous open connections.

For example, chrome opens no more than 6 requests to a single domain and no more than 255 requests in the amount of all open tabs - no matter which of the domains. Accordingly, if you maintain a separate connection from the comets server on each tab, you can open no more than 6 tabs, and then everything.

Accordingly, based on this task, I decided that the master tab will be the first of the open tabs, and if it is closed, then the master will be the random of the remaining ones. To do this, the master tab sends a message to all tabs every 150 milliseconds that it exists.

When you open a tab, we subscribe to receive notifications from the master tab.

Then we set the timer at least for 300ms, and if during this time we do not receive notifications from the master, then we believe that there is no master, and we are for it. In such cases, we start sending notifications that we have a master tab every 50ms, and if we receive a notification from the master tab, we cancel the set timer and immediately put it back - and so on until the master tab has time to remind of its existence less than 300ms.

Implementation in code
 function tryStartMasterTab(masterCallback, slaveCallback) { var time_id = false; var start_timer = 2000; if( window.InTryStartMasterTab !== undefined ) { console.log(" "); return InTryStartMasterTab; } console.log(" tryStartMasterTab"); InTryStartMasterTab = 0; var setAsMaster = function(){ //        tabSignal().disconnect("comet_msg_connect", 'comet_msg_master_signal'); //           tabSignal().emitAll('comet_msg_master_signal'); //           setInterval(function() { tabSignal().emitAll('comet_msg_master_signal'); console.log(" !"); $("#consultantHolder").html(" !"); }, start_timer/8); InTryStartMasterTab = 1; if(masterCallback) { masterCallback(); } }; //             , //   start_timer         tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', function() { if(time_id !== false) //          { console.log(" slave!, clearTimeout(time_id="+time_id+")"); $("#consultantHolder").html(" slave!"); clearTimeout( time_id ); time_id = setTimeout(setAsMaster, start_timer ); } if(InTryStartMasterTab === 0) { if(slaveCallback) slaveCallback(); } InTryStartMasterTab = -1; }); //  ,        start_timer       time_id = setTimeout(setAsMaster, start_timer ); } 



But as beliyadm noted , this approach sometimes fails with a large number of tabs, and taking advantage of the advice from Marcus Aurelius, I introduced a system of priorities in the process of selecting the master tab.
The priority is the time of opening a tab with milliseconds + a random number from 0 to 10,000 and the smaller the result, the higher the priority is obtained.

Improved implementation with priority system
 function tryStartMasterTab(masterCallback, slaveCallback) { var time_id = false; var interval_id = false; var start_timer = 2000; if( window.InTryStartMasterTab !== undefined ) { console.log(" "); return InTryStartMasterTab; } console.log(" tryStartMasterTab"); InTryStartMasterTab = 0; var Today = new Date(); var TabId = (Today.getTime() *1000 + Today.getMilliseconds())*10000 + Math.floor( Math.random()*10000); var slaveloop = function(EventData) { if(time_id !== false) //          { console.log(" slave!, clearTimeout(time_id="+time_id+")"); clearTimeout( time_id ); time_id = setTimeout(setAsMaster, start_timer ); } if(InTryStartMasterTab === 0) { if(slaveCallback) slaveCallback(); } InTryStartMasterTab = -1; }; var setAsMaster = function(){ //    ,   TabId tabSignal().emitAll('comet_msg_new_master', TabId); time_id = setTimeout(function() { //        tabSignal().disconnect("comet_msg_connect", 'comet_msg_master_signal'); //           tabSignal().emitAll('comet_msg_master_signal', TabId); //           interval_id = setInterval(function() { tabSignal().emitAll('comet_msg_master_signal', TabId); console.log(" !"); }, start_timer/8); InTryStartMasterTab = 1; if(masterCallback) { masterCallback(); } }, start_timer); }; //        tabSignal().connect('comet_msg_new_master', function(EventTabId) { if(EventTabId == TabId) { //  ventTabId == TabId      . return; } if(EventTabId > TabId) { //   TabId                return; } //    EventTabId < TabId             //          .        . if(time_id !== false) //          { console.log(" slave!, clearTimeout(time_id="+time_id+")"); clearTimeout( time_id ); time_id = setTimeout(setAsMaster, start_timer ); } if(interval_id !== false) //    slave       { clearTimeout( interval_id ); //             , //   start_timer         tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', slaveloop); } slaveCallback(); }); //             , //   start_timer         tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', slaveloop); //  ,        start_timer       time_id = setTimeout(setAsMaster, start_timer ); } 



At the end cite online demo .
Repository TabSignal.js .

Source: https://habr.com/ru/post/250719/


All Articles