Cross-Context Communication in BroadcastChannel API

肥仔John發表於2021-09-27

The broadcast channel API allows basically communication between browsing contexts(that is, tabs, windows, frames or iframes) and workers on the same origin.
You don't have to maintain the reference to the frames or workers you wish to communicate with, just constructing your own BroadcastChannel instance with the channel name which is capable of bidirectional communication between all of them.

It's self-contained interface and allows cross-context communication, what a perfect solution to detect user actions in other tabs within the same origin, like when user logs in or out, to transmit data across them and keep the states in sync. That's of great benefit to multiple tabs apps.

Scenario: log in status

It's a way common feature to show the log in status after user pass authentication and walk around, and open the other tabs to log out. But he or she is still in logged in status on the first tab, which will make user confused, until anything he or she touch will either redirect to the login page or straight blow up in their face. A more inviting alternative is to figure out that they've logged out and do something about it, like, display a hint to asking them to log in again.

let loginStatus = 'login', originalLoginStatus = 'login'
window.addEventListener('focus', () => {
    if (loginStatus !== originalLoginStatus) {
        alert('You have logged out on other tab already!')
        location.reload()
    }
})

const bc = new BroadcastChannel('login-status')
bc.onmessage = evt => {
    loginStatus = evt.data
}

document.querySelector('#logout').addEventListener('click', () => {
    bc.postMessage('logout')
})

Creating or Joining a Channel

To their credit, the form of creating a channel is identical to that of joining, it means the new channel would be created automatically for us if it isn't existing yet.
So there is no more simple to do like below.

const channelName = 'update'
const bc = new BroadcastChannel(channelName)

Sending Messages

It's enough to call the postMessage method on the created BroadcastChannel instance with an argument for any types.

Note that this API doesn't associate any sematics(messaging protocol) to messages (there is no negotiation nor requirement from specifcation), it's all up to you to know what kind of messages to expect and what to do with them.

And what exactly types could we pass in? The answer is all types that can be cloned using the structured clone algorithm, including the followings:

  • All primitive types except symbol(boolean, string, number, null, undefined)
  • Dates
  • Regular Expressions
  • Blobs
  • Files and FileLists
  • ArrayBuffers and ArrayBufferViews
  • ImageBitmaps, ImageDatas
  • Boolean, Number, Arrays, Objects, Maps and Sets
br.postMessage('hi')

Receiving Messages

Once a message is posted, a message event will be dispatched to the BroadcastChannel instance connected to this channel. And the event handler binding by onmessage method on the created BroadcastChannel object will be run.

br.onmessage = evt => {
    const message = evt.data
    // processing
}

Disconnecting a Channel

It might make sense to call close method on the BroadcastChannel object when we do not use it anymore, this would disconnect the object from the underlying channel, and once there is no references to that channel any longer, allowing garbage collection.

br.close()

Compatiblity

This API has landed in Chrome years ago, but if you have to make your app work on the legacy browser, to my knowledge, the most popular one is here(https://github.com/pubkey/bro...). You can use it almost exactly like the native API. It will switch up to the native API automatically if supporting for the more efficient result, otherwise, it will utilize IndexedDB or LocalStorage. We can register storage event on window object to detect any changes(additions, updates and deletions) of value on LocalStorage.

window.addEventListener('storage', (evt) => {
    if (evt.key == 'myname') {
        console.log(evt.newValue)
    }
})

localStorage.setItem('myname', 'hi')

相關文章