Is there a way to reply to only the sender, after receiving a BroadcastChannel message?

1.9k views Asked by At

Suppose I have a bunch of same-origin windows or tabs A, B, C, D, and E, that don't hold references to each other. (e.g. a user opened them independently). Suppose A sends a BroadcastChannel message to the others, and as a result, D needs to send some data back to A, ideally without involving B, C, or E.

Is this possible, using any of the message-passing APIs?

There's an event.source property on the broadcast message event, which looked as if it should maybe contain a WindowProxy or MessagePort object in this context, but (in my tests with Firefox 78 at least) it was simply null. There's also a ports array, but that was empty.

...I'm aware that you could start up a SharedWorker to assign each window a unique ID and act as a waystation for passing messages between them, but (a) that seems very complicated for the functionality desired, and (b) every message sent that way is going to need 2 hops, from window to sharedWorker and back to a window, crossing thread boundaries both times, and (usually) getting serialized & unserialized both times as well - even when the two windows share the same javascript thread! So it's not very efficient.

This seems like such an obvious thing to want to do, I'm finding it hard to believe there isn't something obvious I'm missing... but I don't see it, if so!

2

There are 2 answers

3
David784 On BEST ANSWER

Looks like the standards require source to be null for a BroadcastChannel. But it shares the MessageEvent interface with several other APIs that do use source, hence why it exists, but is null.

The postMessage(message) method steps are:
...
5. Remove source from destinations.

Looks like they intentionally kept BroadcastChannel very lightweight. Just a guess, but the functionality you're looking for might have required additional resources that they didn't want to allocate. This guess is based on a general note they have in the spec:

For elaborate cases, e.g. to manage locking of shared state, to manage synchronization of resources between a server and multiple local clients, to share a WebSocket connection with a remote host, and so forth, shared workers are the most appropriate solution.

For simple cases, though, where a shared worker would be an unreasonable overhead, authors can use the simple channel-based broadcast mechanism described in this section.

0
Kaiido On

SharedWorkers are definitely more appropriate for complicated cases, think of the BroadcastChannel really just as a one-to-many simple notification sender.

It isn't able to transfer data — Which of the receivers should become the owner then? — so except in the case of Blobs (which are just small wrappers with no data of their own), passing data through a BroadcastChannel means it has to be fully deserialized by all receivers, not the most performant way of doing.

So I'm not sure what kind of data you need to send, but if it's big data that should normally be transferable, then probably prefer a SharedWorker.

One workaround though if your data is not to be transfered, is to create a new BroadcastChannel that only your two contexts will listen at.

Live demo

In page A:

const common_channel = new BroadcastChannel( "main" );
const uuid = "private-" + Math.random();
common_channel.postMessage( {
  type: "gimme the data",
  from: "pageB",
  respondAt: uuid
} );
const private_channel = new BroadcastChannel( uuid );
private_channel.onmessage = ({data}) => {
  handleDataFromPageB(data);
  private_channel.close();
};

In page B:

const common_channel = new BroadcastChannel( "main" );
common_channel.onmessage = ({ data }) => {
  if( data.from === "pageB" && data.type === "gimme the data" ) {
    const private_channel = new BroadcastChannel( data.respondAt );
    private_channel.postMessage( the_data );
    private_channel.close();
  }
};

Regarding why you can't have a ports value on MessageEvent firing on BroadcastChannels it's because MessagePorts must be transfered, but as we already said, BroadcastChannels can't do transfers.
For why there is no source, it's probably because as you expected that should have been a WindowProxy object, but WorkerContexts can also post messages to BroadcastChannels, and they don't implement that interface (e.g their postMessage method wouldn't do the same thing at all than for a WindowContext).