Communication between tabs or windows
Asked Answered
T

9

305

I was searching for a way how to communicate between multiple tabs or windows in a browser (on the same domain, not CORS) without leaving traces. There were several solutions:

  1. using the window object
  2. postMessage
  3. cookies
  4. localStorage

The first is probably the worst solution - you need to open a window from your current window and then you can communicate only as long as you keep the windows open. If you reload the page in any of the windows, you most likely lose the communication.

The second approach, using postMessage, probably enables cross-origin communication, but it suffers the same problem as the first approach. You need to maintain a window object.

The third way, using cookies, store some data in the browser, which can effectively look like sending a message to all windows on the same domain, but the problem is that you can never know if all tabs read the "message" already or not before cleaning up. You have to implement some sort of timeout to read the cookie periodically. Furthermore, you are limited by the maximum cookie length, which is 4 KB.

The fourth solution, using localStorage, seemed to overcome the limitations of cookies, and it can be even listen-to using events. How to use it is described in the accepted answer.

Taligrade answered 30/1, 2015 at 7:4 Comment(1)
If you looking for communications across tabs on the same domain not origin, then local storage wont work as it is origin specific, broadcastchannel should not as well.Forge
T
216

You may better use BroadcastChannel for this purpose. See other answers below. Yet if you still prefer to use localstorage for communication between tabs, do it this way:

In order to get notified when a tab sends a message to other tabs, you simply need to bind on 'storage' event. In all tabs, do this:

$(window).on('storage', message_receive);

The function message_receive will be called every time you set any value of localStorage in any other tab. The event listener contains also the data newly set to localStorage, so you don't even need to parse localStorage object itself. This is very handy because you can reset the value just right after it was set, to effectively clean up any traces. Here are functions for messaging:

// use local storage for messaging. Set message in local storage and clear it right away
// This is a safe way how to communicate with other tabs while not leaving any traces
//
function message_broadcast(message)
{
    localStorage.setItem('message',JSON.stringify(message));
    localStorage.removeItem('message');
}


// receive message
//
function message_receive(ev)
{
    if (ev.originalEvent.key!='message') return; // ignore other keys
    var message=JSON.parse(ev.originalEvent.newValue);
    if (!message) return; // ignore empty msg or msg reset

    // here you act on messages.
    // you can send objects like { 'command': 'doit', 'data': 'abcd' }
    if (message.command == 'doit') alert(message.data);

    // etc.
}

So now once your tabs bind on the onstorage event, and you have these two functions implemented, you can simply broadcast a message to other tabs calling, for example:

message_broadcast({'command':'reset'})

Remember that sending the exact same message twice will be propagated only once, so if you need to repeat messages, add some unique identifier to them, like

message_broadcast({'command':'reset', 'uid': (new Date).getTime()+Math.random()})

Also remember that the current tab which broadcasts the message doesn't actually receive it, only other tabs or windows on the same domain.

You may ask what happens if the user loads a different webpage or closes his tab just after the setItem() call before the removeItem(). Well, from my own testing the browser puts unloading on hold until the entire function message_broadcast() is finished. I tested to put some very long for() cycle in there and it still waited for the cycle to finish before closing. If the user kills the tab just in-between, then the browser won't have enough time to save the message to disk, thus this approach seems to me like safe way how to send messages without any traces.

Taligrade answered 30/1, 2015 at 7:4 Comment(14)
can you ignore the remove event before invoking JSON.parse()?Rubble
keep in mind the event data limit, including pre-existing localStorage data. it might be better/safer to use the storage events just for messaging instead of shipping. like when you get a postcard telling you to pickup a package at the post office... also, localstorage goes to the hard drive, so it might leave inadvertent caches and affect logs, which is another reason to consider a different transport mechanism for the actual data.Rubble
@Rubble regarding ignoring remove event before invoking JSON.parse(), probably yes, checking ev.originalEvent.newValue: null, but I see no difference, parsing "null" value adds no significant cost.Taligrade
@Rubble regarding data stored on disk, the main idea is that the local storage data is reset (emptied) right after it is set, so the browser doesn't store it on disk.Taligrade
i did something related a while back: danml.com/js/localstorageevents.js, that one has an event emmiter base and "local echo" so that you can use the EE for everything everywhere.Rubble
@Rubble regarding pre-existing localSotrage data while considering data limit, this is very important point, thank you. However if the limit is reached then even sending 'a postcard' won't be possible. Thus it is on the programmer to ensure that he doesn't have other things in localStorage which would fill up the space.Taligrade
depending on the application, you may be more inclined to call localStorage.clear() before adding data to it, in lieu of having to append a unique identifier to each message.Corey
Gorgeous Answer.... This was a pain in the butt to figure out and TBH postMessage is good for what? I will post an Angular 4 answer below. But again thank you @thomas-mSpurious
Safari doesn't support BroadcastChannel - caniuse.com/#feat=broadcastchannelFlour
Also I think it is worth noting that using localStorage.setItem('message',JSON.stringify(message)); and then localStorage.removeItem('message'); will fire two storage events since there will be two changes to the local storage - adding and then removing something.Haggadist
Just a heads up, you can also use shared workers : developer.mozilla.org/en-US/docs/Web/API/SharedWorker (which has better support across browsers)Emden
@Emden SharedWorker does not exist in Microsoft Edge and Safari.Leger
@ElisByberi Sure, I did not say it has support across all browsers. it is not an answer, just a tip so people would know it exists.Emden
@Flour Safari now supports BroadcastChannel starting v15.4 (released March 14, 2022) caniuse.com/broadcastchannelFullscale
N
215

There is a modern API dedicated for this purpose - Broadcast Channel

It is as easy as:

var bc = new BroadcastChannel('test_channel');

bc.postMessage('This is a test message.'); /* send */

bc.onmessage = function (ev) { console.log(ev); } /* receive */

Data sent to the channel is serialized using the structured clone algorithm. That means you can send a broad variety of data objects safely without having to serialize them yourself.

It is supported on all major browser, but you can find a polyfill that uses localStorage.

Narcissus answered 7/5, 2017 at 11:34 Comment(12)
Wait, how do you know where the message is from? Does it ignore messages that come from the same tab?Outhe
@zehelvion: The sender won't receive it, according to e.g. this nice overview. Besides, you can put into the message whatever you want, incl. some ID of the sender, if needed.Saunder
There is a nice project that wraps up this feature in a cross-browser library here: github.com/pubkey/broadcast-channelRichey
Have there been any public signals from Safari about whether or not support for this API will land in that browser?Hawn
@Outhe You check that MessageEvent.origin, MessageEvent.source or MessageEvent.ports are what you want. As always the documentation is the best place to start: developer.mozilla.org/en-US/docs/Web/API/MessageEventTrackless
It's not true that "any kind of object can be sent". In Firefox v78, I tried sending the window object, and got the error "Object cannot be cloned". So you can't use it to pass window references around. (I was hoping to be able to do that so as to bootstrap direct 1-on-1 window-to-window communication). In fact, according to the spec, only Serializable objects can be sent via BroadcastChannel.postMessage().Amends
I could be wrong - please reply if there's a way to do this - but I don't think you can reply to a broadcast message by initiating 1-to-1 communication. (i.e. if A broadcasts to B, C, D, E, then D cannot reply just to A). The event.source property looks promising, but in my experiments (Firefox 78) it's null, and without a window reference, you can't use the regular 1-to-1 postMessage() API. (I suppose you could relay messages through a SharedWorker, given some method of uniquely identifying windows... but that seems rather inefficient and cumbersome).Amends
@Doin, have you tried passing a MessageChannel port through the BroadcastChannel? Is a port serializable? If so, you can use port.postMessage to communicate with the sender directly.Panegyrize
@Mattias Nah, you can't do that, unfortunately. I thought of it, but it's not possible because (as you guessed) browser objects (like ports) aren't serializable. Roughly "serializable"="convertible to text and back", and the channel API only works with serializable data/objects. One important reason for this is JS objects are NOT generally thread-safe, and yet the channel API can send data between windows running on different threads.Amends
I second that "It's not true that any kind of object can be sent". postMessage sends messages using the structured clone algorithm, so only objects "structured cloneable" can be sent. Have a look here developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/…Gilliland
Currently supported also (beside Chrome and FF) by Edge, Opera and Safari.Qualitative
How can we pass "this" to the function ?Exoenzyme
M
56

For those searching for a solution not based on jQuery, this is a plain JavaScript version of the solution provided by Thomas M:

window.addEventListener("storage", message_receive);

function message_broadcast(message) {
    localStorage.setItem('message',JSON.stringify(message));
}

function message_receive(ev) {
    if (ev.key == 'message') {
        var message=JSON.parse(ev.newValue);
    }
}
Mou answered 25/9, 2015 at 10:27 Comment(3)
Why you omitted the removeItem call?Taligrade
I was just focusing on the differences between jQuery and JavaScript.Mou
i always use a lib because of polyfill and possibility of not supported feature!Exanthema
B
30

Checkout AcrossTabs - Easy communication between cross-origin browser tabs. It uses a combination of the postMessage and sessionStorage APIs to make communication much easier and reliable.


There are different approaches and each one has its own advantages and disadvantages. Let’s discuss each:

  1. LocalStorage

    Pros:

    1. Web storage can be viewed simplistically as an improvement on cookies, providing much greater storage capacity. If you look at the Mozilla source code we can see that 5120 KB (5 MB which equals 2.5 million characters on Chrome) is the default storage size for an entire domain. This gives you considerably more space to work with than a typical 4 KB cookie.
    2. The data is not sent back to the server for every HTTP request (HTML, images, JavaScript, CSS, etc.) - reducing the amount of traffic between client and server.
    3. The data stored in localStorage persists until explicitly deleted. Changes made are saved and available for all current and future visits to the site.

    Cons:

    1. It works on same-origin policy. So, data stored will only be able available on the same origin.
  2. Cookies

    Pros:

    1. Compared to others, there's nothing AFAIK.

    Cons:

    1. The 4 KB limit is for the entire cookie, including name, value, expiry date, etc. To support most browsers, keep the name under 4000 bytes, and the overall cookie size under 4093 bytes.
    2. The data is sent back to the server for every HTTP request (HTML, images, JavaScript, CSS, etc.) - increasing the amount of traffic between client and server.

    Typically, the following are allowed:

    • 300 cookies in total
    • 4096 bytes per cookie
    • 20 cookies per domain
    • 81920 bytes per domain (given 20 cookies of the maximum size 4096 = 81920 bytes.)
  3. sessionStorage

    Pros:

    1. It is similar to localStorage.
    2. Changes are only available per window (or tab in browsers like Chrome and Firefox). Changes made are saved and available for the current page, as well as future visits to the site on the same window. Once the window is closed, the storage is deleted

    Cons:

    1. The data is available only inside the window/tab in which it was set.
    2. The data is not persistent, i.e., it will be lost once the window/tab is closed.
    3. Like localStorage, tt works on same-origin policy. So, data stored will only be able available on the same origin.
  4. PostMessage

    Pros:

    1. Safely enables cross-origin communication.
    2. As a data point, the WebKit implementation (used by Safari and Chrome) doesn't currently enforce any limits (other than those imposed by running out of memory).

    Cons:

    1. Need to open a window from the current window and then can communicate only as long as you keep the windows open.
    2. Security concerns - Sending strings via postMessage is that you will pick up other postMessage events published by other JavaScript plugins, so be sure to implement a targetOrigin and a sanity check for the data being passed on to the messages listener.
  5. A combination of PostMessage + SessionStorage

    Using postMessage to communicate between multiple tabs and at the same time using sessionStorage in all the newly opened tabs/windows to persist data being passed. Data will be persisted as long as the tabs/windows remain opened. So, even if the opener tab/window gets closed, the opened tabs/windows will have the entire data even after getting refreshed.

I have written a JavaScript library for this, named AcrossTabs which uses postMessage API to communicate between cross-origin tabs/windows and sessionStorage to persist the opened tabs/windows identity as long as they live.

Bile answered 9/4, 2017 at 8:54 Comment(3)
Using AcrossTabs, is it possible to open a different website in another tab, and get the data from it to the parent tab ? I will have authentication details for the another website.Cobia
Yes, you can @MadhurBhaiyaBile
The biggest advantage of cookie is it allows cross origin same domain, which is commonly useful when you have a set of origins such as "a.target.com", "b.target.com" etc.Forge
P
19

I've created a library sysend.js for sending messages between browser tabs and windows. The library doesn't have any external dependencies.

You can use it for communication between tabs/windows in the same browser and domain. The library uses BroadcastChannel, if supported, or storage event from localStorage.

The API is very simple:

sysend.on('foo', function(data) {
    console.log(data);
});
sysend.broadcast('foo', {message: 'Hello'});
sysend.broadcast('foo', "hello");
sysend.broadcast('foo', ["hello", "world"]);
sysend.broadcast('foo'); // empty notification

When your browser supports BroadcastChannel it sends a literal object (but it's in fact auto-serialized by the browser) and if not, it's serialized to JSON first and deserialized on another end.

The recent version also has a helper API to create a proxy for cross-domain communication (it requires a single HTML file on the target domain).

Here is a demo.

The new version also supports cross-domain communication, if you include a special proxy.html file on the target domain and call proxy function from the source domain:

sysend.proxy('https://target.com');

(proxy.html is a very simple HTML file, that only have one script tag with the library).

If you want two-way communication you need to do the same on other domains.

NOTE: If you will implement the same functionality using localStorage, there is an issue in Internet Explorer. The storage event is sent to the same window, which triggers the event and for other browsers, it's only invoked for other tabs/windows.

Phenix answered 4/1, 2019 at 11:44 Comment(2)
Just wanted to give you some kudo's for this. Nice sweet little addition that is simple and allows me communicate between my tabs to keep the logout warning software from kicking people off. Nice job. I highly recommend this if you want a simple to use messaging solution.Disorganize
Absolutely awesome, I'm going to try using this lib over the next month, and definitely come back and give a thank you on the github sponsor. Thanks for making this and it looks pretty well maintained. I'm going to be trying this instead of AcrossTabs, it seems more simpler your API and robust fallback to localStorage.Lather
P
13

Another method that people should consider using is shared workers. I know it's a cutting-edge concept, but you can create a relay on a shared worker that is much faster than localstorage, and doesn't require a relationship between the parent/child window, as long as you're on the same origin.

See my answer here for some discussion I made about this.

Perrin answered 11/2, 2016 at 21:21 Comment(0)
P
7

There's a tiny open-source component to synchronise and communicate between tabs/windows of the same origin (disclaimer - I'm one of the contributors!) based around localStorage.

TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);

TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
    DoSomething();
});

TabUtils.CallOnce("lockname", function () {
    alert("I run only once across multiple tabs");
});

P.S.: I took the liberty to recommend it here since most of the "lock/mutex/sync" components fail on websocket connections when events happen almost simultaneously.

Peppel answered 8/9, 2017 at 11:28 Comment(0)
P
1

I wrote an article on this on my blog: Sharing sessionStorage data across browser tabs.

Using a library, I created storageManager. You can achieve this as follows:

storageManager.savePermanentData('data', 'key'): //saves permanent data
storageManager.saveSyncedSessionData('data', 'key'); //saves session data to all opened tabs
storageManager.saveSessionData('data', 'key'); //saves session data to current tab only
storageManager.getData('key'); //retrieves data

There are other convenient methods as well to handle other scenarios as well.

Peltate answered 14/12, 2016 at 11:37 Comment(0)
G
1

This is a development storage part of Tomas M's answer for Chrome. We must add a listener:

window.addEventListener("storage", (e)=> { console.log(e) } );

Load/save the item in storage will not fire this event - we must trigger it manually by

window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME

And now, all open tabs will receive the event.

Gunman answered 30/4, 2020 at 22:52 Comment(1)
What do you mean by "not runt this event" (seems incomprehensible)?Messier

© 2022 - 2024 — McMap. All rights reserved.