在收到 BroadcastChannel 消息后,有没有办法只回复发件人?

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

假设我有一堆同源 windows 或标签 A, B, CDE,它们之间没有相互引用。 (例如,用户独立打开它们)。假设A向其他人发送BroadcastChannel消息,结果D需要发送一些数据回到 A,最好不涉及 BCE.

这是否可能,使用任何消息传递 API?

在广播消息事件中有一个 event.source 属性,在这个上下文中看起来它应该包含一个 WindowProxyMessagePort 对象,但是 (至少在我使用 Firefox 78 的测试中)它只是 null。还有一个 ports 数组,但它是空的。

...我知道您可以启动一个 SharedWorker 来为每个 window 分配一个唯一的 ID 并充当它们之间传递消息的中转站,但是 (a) 这对于所需的功能,以及 (b) 以这种方式发送的每条消息都需要 2 跳,从 window 到 sharedWorker 再回到 window,两次都跨越线程边界,并且(通常)被序列化& 也反序列化了两次 - 即使两个 windows 共享同一个 javascript 线程!所以效率不是很高。

这似乎是一件很明显想要做的事情,我发现很难相信我没有明显遗漏的东西......但我没有看到它,如果是的话!

看起来像 standards require source to be null for a BroadcastChannel. But it shares the MessageEvent 与其他几个确实使用 source 的 API 的接口,因此它存在的原因是空的。

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

看起来他们有意让 BroadcastChannel 非常轻便。只是一个猜测,但您正在寻找的功能可能需要他们不想分配的额外资源。这个猜测是基于他们在规范中的general note

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.

SharedWorkers 绝对更适合复杂的情况,将 BroadcastChannel 视为一对多的简单通知发送者。

它无法传输数据——那么哪个接收者应该成为所有者?——所以除了这种情况Blob(它们只是没有自己数据的小包装器),通过 BroadcastChannel 传递数据意味着它必须被所有接收者完全反序列化,而不是最高效的方式。

所以我不确定你需要发送什么样的数据,但如果是通常应该可以传输的大数据,那么可能更喜欢 SharedWorker

如果您的数据不被传输,一个解决方法是创建一个新的 BroadcastChannel,只有您的两个上下文会收听。

Live demo

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();
};

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();
  }
};

关于为什么不能在 BroadcastChannels 上触发的 MessageEvent 上设置 ports 值,这是因为必须传输 MessagePorts,但正如我们已经说过的,BroadcastChannels 不能进行传输。
为什么没有 source,可能是因为如你所料,它应该是一个 WindowProxy 对象,但是 WorkerContexts 也可以 post 向 BroadcastChannels 发送消息,并且它们没有实现该接口(例如它们的 postMessage 方法根本不会做与 WindowContext 相同的事情。