📜 ⬆️ ⬇️

Library for sharing events, data and tasks between browser tabs

Greetings, dear Habrasoobschestvo!

Somewhere from half a year ago, when implementing one of the projects, I had an urgent need to organize data exchange between browser tabs, track their number and assign tasks to some of them, without using communication with the server (i.e., all implementation completely on the client). There was no ready solution at that time in the network, and therefore it was decided to stock up on a coffee bucket and write your reusable crutch. As a result, the project itself did not take place safely, but the library was developed and, until now, it was gathering dust in the bays of the hard disk.

Now the library is laid out with a couple of examples on GitHub , and under Habrakat I would like to highlight some of the subtleties of its application and part of the internal logic. I would be glad if my library helps someone to save the n-th amount of time and will allow to avoid inventing their own bicycle.
')
Who cares - welcome under cat.

What is it for and where can it be useful


For example, in online stores, when a user opened a dozen tabs and added a product to the basket in one of them - we will be able to inform all other tabs that you need to change the state in accordance with the user's actions (update the number of products in the basket). Or, for example, if we want to create a player on our site, ala, VKontakte, which would jam the playback in all other tabs, if the user decided to include something else.

In general, this library may be useful for any applications where data synchronization between tabs may be required, but it is undesirable (or not possible) to use the server part. Or, if the user needs to prohibit opening several tabs in order to avoid damage / loss of data.

What the library can do


- Tracking the current number of open tabs, the reaction to the events of opening new / closing old tabs.
- Ability to set callback functions for certain events (with the ability to bind them only to certain tabs).
- The ability to transfer data in the Event object, if any tab reported on the event.
- Ability to report the event only a specific tab (or group of tabs).
- Run the code in the context of the desired tab, initiating this action from another tab.

A few words about the internal structure


First of all, it should be said that for its functioning the library requires support from Blob, Blob.URL, Worker and localStorage from the browser.

A lot of various ideas about the implementation of the exchange of messages between tabs, most of which rest against the impossibility of tracking the tab closing, walk around the Internet, or prevent sending a code to another tab from one tab, or using the inactive tab of some modern browsers only once per second (which, with 5-10 open tabs, results in a serious delay), either in the absence of imputed support for localStorage events, or realized through communication with the server. And a dozen more reasons "against."

As a solution, the following path was chosen:
1. Use localStorage to store the object (in a packed form) containing the schedule of the internal task scheduler, a list of current events, data on active tabs, configuration, and some other service data.
2. To track the appearance / closure of tabs, use Worker (it just requires Blob and Blob.URL), whose tasks are reduced to background pinging tabs (since Worker ignores browser restrictions on the timeout rate of inactive tabs).
3. Use the internal task scheduler for the sequential execution of necessary tasks by tabs (pinging from the worker).

API description


The library does not have any external dependencies. When it is connected in the global scope, the __SE__ object becomes available.

System events

By default, the library has three global events.
tabOpened - called when a new tab opens.
tabClosed - called when one of the tabs is closed (crashes out on timeout).
tabNameChanged - called when the tab name is changed.

Configuration options

__SE__. Name is the name of the current tab (the string is by the mask “a-zA-Z \ _ \ - \ d”). Used to report events to specific tabs. If several tabs have the same name, this parameter acquires the properties of the grouper when the event flies right away to the group of tabs of the same name.
Default value: "Default"

__SE__. SelfExecution - a flag that answers the question "Do you want to execute events that were initiated by herself in the current tab?". Simply put, if we have two tabs named “MyTabName” and one of them reports an event to tabs named “MyTabName”, then depending on the SelfExecution flag set, it will be decided whether to notify the initiator tab itself event.
The default value is false (do not notify about own events).
Note 1: this flag is relevant only when working with general event handlers , as described below.
Note 2: if the event is global (initiated without passing the third argument in the __SE __ method. DispatchEvent (), or passing the __SE __. GID constant as the third argument), then this flag will be ignored.

__SE__. Sync is a parameter in milliseconds that indicates to the Worker how often to ping the tab.
The default value is 100 (internal constant DEFAULT_WORKER_INTERVAL ).

__SE__. TabTimeoutMult is a factor that indicates how many cycles to expect a tab before considering that it is closed.
The default value is 2 (internal constant DEFAULT_TAB_TIMEOUT_MULTIPLIER ).

__SE__. SLockTimeoutMult is a multiplier indicating how many “ticks” to expect to unlock an object from localStorage.
The default value is 2 (internal constant DEFAULT_STORAGE_LOCK_MULTIPLIER ).

When changing any of the three parameters (__SE __. Sync, __SE __. TabTimeoutMult and __SE __. SLockTimeoutMult), the new values ​​are automatically synchronized with the other tabs and take effect only after full synchronization. These three parameters affect the internal mechanics of the synchronizer tabs, in particular:
1) Access to the object that stores the library configuration in localStorage has an internal lock mechanism (so that the tabs do not spoil the stored data and the tasks are performed strictly in turn from tab to tab). The built-in “lock” has a statute of limitations, which unlocks the storage by timeout if the active tab (working with the storage and installing the lock) was closed. This timeout is calculated by the formula:
__SE__.Sync * __SE__.SLockTimeoutMult
2) Indication of the closing tab is determined by the formula:
__SE__.Sync * __SE__.ActiveTabsCount * __SE__.TabTimeoutMult

Constants

__SE__. GID - global event identifier or global handler (default is "__Global__"): if you specify this constant as a pointer to the name of the tab to which the event is to be sent, then all open tabs will receive the event. This identifier is passed by default unless the target tab is specified in the __SE __ method. DispatchEvent (). If, however, you pass this identifier as the third argument to the __SE __. AddEventListener () method, then the handler for the corresponding event will become global and will be processed immediately in all tabs.

__SE__. ID - unique identifier of the current tab. Generated during library initialization.

Variables

__SE__. ActiveTabsCount - stores the value of the current number of open tabs. It is updated with each cycle of the internal task scheduler and the frequency of updates (in general) is equal to the product __SE __. Sync and the number of open tabs.

Methods

__SE__. getActiveTabs (void)
Returns an array of objects describing the current open tabs:
 //   . var TabObjectExample = { 'Checked' : true , //       . 'ConfigCache' : Object , //     . 'ID' : "152644ab-7138-297c-60a4-efd8d8a8380c" , //   ID . 'Name' : "Default" , //  . 'Ping' : 1398107406052 // TimeStamp   . }; 


__SE__. addEventListener (Type, Listener [, TabName] )
Adds an event handler.
Event handlers are local and generic .
Local event handlers : stored in the wilds of the __SE__ object and operate within the current tab. To create a local event handler, it is enough just not to pass the third argument to this method.
General event handlers : stored in the library's configuration object in localStorage, as SharedEventListener (a common handler accessible to all tabs). This type of handler is created by passing the argument TabName .
If the __SE __. GID constant is used as the TabName argument, the handler will become global and will be executed in all tabs when the corresponding event occurs in any of them.
Type - the type of event to respond to. Line by mask "a-zA-Z". Required.
Listener is a handler function that will be executed when the corresponding event occurs. Required.
When a function is executed, an object is passed to it containing event data and user data:
 //   . var EventObjectExample = { 'Data' : false , //    (   __SE__.dispatchEvent()). 'Sender' : //     . { ID : "81e0eaf0-3a02-15e1-b28c-7aa1629801c0" , //   . Name : "Default" // . } , 'Timestamp' : 1398113182091 // TimeStamp  . }; 

TabName is the name of the tab to which the event handler is assigned. The string by the mask "a-zA-Z \ _ \ - \ d". If you pass the constant __SE __. GID as the tab name, the handler will become global and will work in all tabs.
Note 1: you should pay attention to the fact that Listener will be executed in the context of the tab in which it was launched by an event, therefore all data necessary for its operation must be specified in it explicitly, or transmitted in the event object.
Note 2: common handlers cease to exist when closing a tab that initiated their appearance.

__SE__. hasEventListener (Type [, Listener [, TabName, Callback]] )
Checks for event handlers. Takes one, two, or four arguments (but not three).
Type - the type of event being checked. Required.
Listener is a function that is checked as an event handler.
Note: The Listener argument can be false if used along with the third and fourth arguments, and the goal is to determine if there is not a specific handler, but only the fact that there is a handler as such.
If only one or the first two arguments are passed, then the check is performed on local event handlers and the result of the check is returned immediately. Example:
 /* *    . */ var tabOpenedCallback = function(){ console.log( 'Local __SE__ event called on tabOpened.' ); }; //       . __SE__.addEventListener( 'tabOpened' , tabOpenedCallback ); __SE__.hasEventListener( 'tabOpened' , tabOpenedCallback ); //=> true __SE__.hasEventListener( 'tabOpened' ); //=> true __SE__.hasEventListener( 'someOtherEvent' ); //=> false /* *        : * => Local __SE__ event called on tabOpened. */ 

TabName is the name of the tab for which you are checking for a common event handler . If you pass false as this argument, then a search will be made for the principle presence of at least some common handler for the event you are looking for. If we pass the constant __SE __. GID as this argument, then the search will be performed only by global handlers.
Callback is a function that takes the result of a check as an argument.
When passing all four arguments to the __SE __. HasEventListener () method, the check is performed on common event handlers and the test results are returned to the Callback function.
Note 1: when checking common event handlers, you can pass Listener and TabName arguments as false - in this case, the existence of any common handler for this event will be checked in principle.
Note 2: if you need to check the existence of local handlers from another tab, you can do this by assigning a general handler to the required tab (returning an event with test results) and immediately trigger the calling event.
Example:
 /* *    : */ var tabOpenedCallback = function(){ document.write( 'Shared __SE__ event called on tabOpened.' ); }; //       "TestTab"      . __SE__.addEventListener( 'tabOpened' , tabOpenedCallback , 'TestTab' ); /* *   .   : */ __SE__.Name = 'TestTab'; /* *   .            : * => Shared __SE__ event called on tabOpened. *    : */ var CheckCallback = function( CheckResult ){ console.log( CheckResult ); }; __SE__.hasEventListener( 'tabOpened' , false , 'TestTab' , CheckCallback ); //=>    true __SE__.hasEventListener( 'tabOpened' , false , false , CheckCallback ); //=>    true, ..         __SE__.hasEventListener( 'tabOpened' , false , __SE__.GID , CheckCallback ); //=>    false, ..    __SE__.hasEventListener( 'tabOpened' , false , 'NotExistingTab' , CheckCallback ); //=>    false 


__SE__. removeEventListener (Type [, Listener [, TabName, Callback]] )
Removes an event handler. Takes one, two, or four (but not three) arguments.
According to the general mechanics, it completely coincides with the principles of the __SE __. HasEventListener () method: with one / two arguments it works with local event handlers, with four with general handlers.
Note 1: always returns true.
Note 2: if instead of Listener and TabName in both cases pass false, then all common handlers associated with a specific event will be deleted. If TabName is specified, but false is passed instead of Listener, then all common handlers for the selected tab will be deleted. If Listener is passed, but TabName == false, then all the tabs will delete the desired common handler.
Note 3: to delete a local event handler from another tab, you must execute the deletion code in the context of this tab. To do this, you need to assign the additional general handler to the specified event for a specific event and report this event. The main thing is not to forget to clean up the ends later.
Example for note 3:
 /* *    : */ __SE__.Name = 'MainTab'; //     var someUserEventCallback = function( Event ){ document.write( 'Local __SE__ event called by tab "' + Event.Sender.Name + '" on ' + Event.Timestamp + '<br>' ); }; //        -  . __SE__.addEventListener( 'someUserEvent' , someUserEventCallback ); /* *   .   : */ __SE__.dispatchEvent( 'someUserEvent' ); //   ,        //           . var TabCallbackRemover = function(){ __SE__.removeEventListener( 'someUserEvent' ); //     __SE__.removeEventListener( 'removeListener' , false , 'MainTab' , function(){} ); //   }; __SE__.addEventListener( 'removeListener' , TabCallbackRemover , 'MainTab' ); //      __SE__.dispatchEvent( 'removeListener' ); //    __SE__.dispatchEvent( 'someUserEvent' ); //        , ..        


__SE__. dispatchEvent (Type [, Data [, TabName]] )
Reports an event. It can take from one to three arguments (depending on the desired behavior).
Type - the type of event reported. Line by mask "a-zA-Z". Required.
Data - the data that will be transmitted in the Data field of the event object that is passed to the handler function. The format of the transmitted data is arbitrary (string, array, object). The default value is false.
TabName - the name of the tab or tabs (if grouping by name is used), which should be informed about the event. The default value is the constant __SE __. GID - i.e. The event message flies to all tabs without exception.
A couple of examples:
 //     . __SE__.dispatchEvent( 'MyGlobalEvent' ); //     . __SE__.dispatchEvent( 'MyGlobalEvent' , { SomeField : 'SomeValue' } ); //       . __SE__.dispatchEvent( 'MyTargetedEvent' , false , 'TargetTabName' ); 


As a conclusion


I hope that this library will be useful to someone for solving relevant problems, because if I had gotten her half a year ago, I would have saved a lot of time, and would not invent my crutch bicycle. Yes, much of it is not implemented as it could be, but you can always do everything better. Right?

If anyone is wondering what the configuration object in localStorage is, then please:
 JSON.parse( localStorage[ '__SE__' ] ); 

In general, I give everything to the public and into the hands of the OpenSource community, if someone considers that the library deserves further development or refinement / rework. Therefore once again I will duplicate the link to GitHub .

Behind this, I hasten to bow out. :-)

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


All Articles