Cross-tab Communication

The upcoming SharedWorker API allows to transmit data across iframes and even browser tabs or windows. It landed in Chrome years ago, and not so long ago in Firefox, but it’s nowhere to be seen in IE or Safari. A wildly supported alternative exists that can be used today, but it’s largely unknown. Let’s explore it!

I wanted an elegant solution to the following scenario: suppose a human walks into your website, logs in, opens a second tab, and logs out in that tab. He’s still “logged in” on the first tab, except anything he touches will either redirect them to the login page or straight blow up in their face. A more inviting alternative would be to figure out that they’re logged out and do something about it, such as display a dialog asking them to re-authenticate, or maybe the login view itself.

You could use the WebSocket API for this, but that’d be overkill. I wanted a lower-level technology flyswatter, so I started looking for cross-tab communication options. The first option that popped up was using cookies or localStorage, and then periodically checking whether they were logged in or not via setInterval. I wasn’t satisfied with that answer because it would waste too many CPU cycles checking for something that might not ever come up. At that point I would’ve rather used a “comet” (also known as long-polling), Server-Sent Events, or WebSockets.

I was surprised to see that the answer was lying in front of my nose, it was localStorage all along!

Did you know that localStorage fires an event? More specifically, it fires an event whenever an item is added, modified, or removed in another browsing context. Effectively, this means that whenever you touch localStorage in any given tab, all other tabs can learn about it by listening for the storage event on the window object, like so:

Whenever a tab modifies something in localStorage, an event fires in every other tab. This means we’re able to communicate across browser tabs simply by setting values on localStorage. Consider the following pseudo_ish_-code example:

The basic idea is that when a user has two open tabs, logs out from one of them, and goes back to the other tab, the page is reloaded and (hopefully) the server-side logic redirects them to somewhere else. The check is being done only when the tab is focused as a nod to the fact that maybe they log out and they log back in immediately, and in those cases we wouldn’t want to log them out of every other tab.

We could certainly improve that piece of code, but it serves its purpose pretty well. A better implementation would probably ask them to log in on the spot, but note that this also works the other way around: when they log in and go to another tab that was also logged out, the snippet detects that change reloading the page, and then the server would redirect them to the logged-in fountain-of-youth blessing of an experience you call your website (again, hopefully).

A simpler API

The localStorage API is arguably one of the easiest to use APIs there are, when it comes to web browsers, and it also enjoys quite thorough cross-browser support. There are, however, some quirks such as incognito Safari throwing on sets with a QuotaExceededError, no support for JSON out the box, or older browsers bumming you out.

For those reasons, I put together local-storage which is a module that provides a simplified API to localStorage, gets rid of those quirks, falls back to an in-memory store when the localStorage API is missing, and also makes it easier to consume storage events, by letting you register and unregister listeners for specific keys.

API endpoints in [email protected](latest, at the time of this writing) are listed below.

It’s also worth mentioning that local-storage registers a single storage event handler and keeps track of every key you want to observe, rather than register multiple storage events.

I’d be interested to learn about other use cases for low-tech communication across tabs! Certainly sounds useful for offline-first development, particularly if we keep in mind that SharedWorker might take a while to become widely supported, and WebSockets are unreliable in offline-first scenarios.

All open tabs that share access to the same storage area. A storage area is either localStorage or sessionStorage for a particular domain.

Otherwise it’d be a glaring security hole, storage areas belong to a single domain. Much like cookies or session.

Even though I didn’t mention it in the article, sessionStorage also enjoys the benefits of the storage event.

Dennis Cheung wroteon January 10, 2015

sessionStorage is not that great compare to localStorage.

sessionStorage, however, does not always share same session for tabs in all browsers, even for same URL. They might share same session, if they share window.opener. But if you close/reload/reopen/enter URL, that is another story.

Ben Bankes wroteon January 9, 2015

Of course, if what you are looking for is cross-device synchronization, you would have to use web sockets over local storage or the service worker.

Johan wroteon January 9, 2015

Ben,

Cross device is not so easy as it sounds. You’re talking about eventual consistency there… You need something like a Cloud Type library… There is a JS implementation in the npm library called “cloudtypes” which has a nice working demo (cross device, online/offline handling, etc…)

Johan.

Ben Bankes wroteon January 9, 2015

Johan, it is not too awful hard. At least for PHP, there are some great tools like Ratchet, React, Supervisor, and Stunnel (for securing web sockets). I have a working implementation.

Since web sockets solves the problem “How do I do cross-device synchronization?”, it also solves the issue of cross-tab synchronization and cross-browser synchronization. It’s a one-stop shop solution.

Ben Bankes wroteon January 9, 2015

Johan, I see what you are saying. For offline apps, web sockets cannot solve the device/browser/tab synchronization problem immediately because they require a connection to the server to function. That would require an additional library like the one you mention. Thanks for the new info/library.

Using it for the same thing here. That and sharing any kind of updates I get or make in the active tab - I don’t want to have to make a request in each tab if I already know what the reply’ll be, that’s crazy.

Thanks for that little nugget of information about IE by the way. I haven’t even started testing in IE yet shudder Not something I look forward to.

A few years ago, I implemented an audio player for a musician-oriented hosting company. There were a few interesting requirements:

we needed to be able to store the time offset for the audio

we needed to synchronize the players across multiple tabs

we needed to be ensure only one player was playing ay any given time

we could not spend anytime changing server code or adding an API for storing player related state

we needed to support IE 6

I ended up using cookies to store the current player state and to pass messages (based on polling the cookie store). Each player created it’s own unique identifier. The first loaded or currently active player wrote it’s id to the cookie. Other players on other tabs would see this when they loaded and entered a disabled state if there was an existing active player. Enabling a player would cause any other active player to enter a disabled state once it updated it’s time offset. The newly enabled player would start playing from a brief instant before the last written time offset. The logic for all of this was implemented as a state machine.

This is a nice updated version of that tactic and has the benefit of being broadcast based.

Oleg Zasypkin wroteon January 10, 2015

Just note that in the future the most appropriate browser technology for your simple use case will be BroadcastChannel API [1]. Eg. see [2] for Firefox implementation.

I wrote this implementation of the Bully algorithm that let’s your code elect a “master” window. This way you could for example have one window that is responsible for having a web socket connection. The master could then forward all socket messages to the other windows.