浏览器跨页面通信 Broadcast Channel 与 window.postMessage 详解

前言

首先,我们先来了解一个应用场景:

  1. 页面a有一个列表,每一行有一个"编辑"按钮,点击按钮打开一个新的 tab 标签页b;
  2. 页面b是某一行内容的编辑页面,页面b有一个"保存并关闭"的按钮;
  3. 点击页面b的"保存并关闭"的按钮,页面b关闭,向页面a发起一次通信,告知页面a需要刷新列表;
  4. 页面a接收页面b的通信,根据这两个页面约定好的标识,页面a进行刷新列表操作。

这个需求在一些PC端的后台管理系统中经常会遇到,也就是我们所谓的浏览器不同标签页之间进行通信,实现方法也不难,我们可以用今天要讲的两个方法来实现。

一、Broadcast Channel

1. 作用

Broadcast Channel API 可以实现同源下浏览器不同窗口,Tab页,frame或者 iframe 下的浏览器上下文(通常是同一个网站下不同的页面)之间的简单通讯。

2. 特点

通过创建一个监听某个频道下的 BroadcastChannel 对象,你可以接收发送给该频道的所有消息。它们可以通过构造 BroadcastChannel 来简单地“订阅”特定频道,并在它们之间进行全双工(双向)通信。

image.png

3. 使用方式

3.1 创建或加入某个频道

通过创建一个 BroadcastChannel 对象,一个客户端就加入了某个指定的频道。只需要向构造函数传入一个参数:频道名称。

// 创建广播频道
const bc = new BroadcastChannel('myChannel');
复制代码

3.2 发送消息

只需要调用 BroadcastChannel 对象上的 postMessage() 方法即可。该方法的参数可以是任意对象。最简单的例子就是发送文本消息,相当于给两个页面定义了一个唯一的标识。

// 发送消息
bc.postMessage('updateList');
复制代码

3.3 接收消息

当消息被发送之后,所有连接到该频道的 BroadcastChannel 对象上都会触发 message 事件。该事件没有默认的行为,但是可以使用 onmessage 事件来处理消息。

// 加入到广播频道
const bc = new BroadcastChannel('myChannel');
// 接收消息
bc.onmessage = function (e) { console.log(e); }
复制代码

我们打印e可以看到里面的内容如下:

image.png

其中最有用的属性就是被框出来的三个:

扫描二维码关注公众号,回复: 13471194 查看本文章
  • currentTarget.name:当前加入的广播频道
  • data:发送的消息
  • origin:当前所在源

3.4 与频道断开连接

通过调用 BroadcastChannel 对象的 close() 方法,可以离开频道。这将断开该对象和其关联的频道之间的联系,并允许它被垃圾回收。

// 断开频道连接
bc.close()
复制代码

4. 实现案例

接下来,回到开篇所讲的应用场景,我们用该API来实现此需求。

页面b代码

// 创建频道
const bc = new BroadcastChannel("myChannel");

// 发送消息,消息内容为唯一标识
bc.postMessage("updateList");
复制代码

页面a代码

// 加入频道
const bc = new BroadcastChannel("myChannel");

// 接收消息
bc.onmessage = function (e) {

// 这里我们需要判断一下是否同源
// 过滤掉其他origin传递过来的信息,这个判断很重要,不加就很多可能会被 XSS 攻击!
if (e.origin === location.origin) {

  // 其次最好也判断下发送的消息是否是两边约定好的唯一标识,这样才能进行精准操作
  if (e.data === "updateList") {
    // 刷新列表操作
    ...
    
    // 断开频道连接
    bc.close()
  }
}
};
复制代码

5. 浏览器兼容性

image.png

6.小结

Broadcast Channel API 是一个非常简单的API,内部包含了跨上下文通信的接口。没有定义消息传输协议,故不同上下文中的不同文档需要自己实现它:规范没有对此提出协议或要求。其次就是他只支持同源,对于不同源的情况,我们就得用另一个方法来实现,那就是window.postMessage。

二、window.postMessage

1. 作用

Broadcast Channel API作用一样,都是实现不同页面之间相互通信。

2. 特点

window.postMessage() 方法可以在不同源的情况下,任意页面之间进行通信,它提供了一种受控机制来规避跨域的限制。该方法也有一定的安全隐患,如果在没有任何限制的情况下,不同源的页面可能会对你进行xss攻击。不过,只要正确的使用,这种方法就很安全。

3. 使用方式

3.1 发起通信

window.opener.postMessage(message,targetOrigin);
复制代码

message为:将要发送到其他 window的数据。它将会被结构化克隆算法序列化。这意味着你可以不受任何限制的将数据对象安全的传送给目标窗口而无需自己序列化。此处我们就简单发送一个字符,以作为双方的唯一标识。

targetOrigin为:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串'*'(表示无限制)或者一个URI。

注意

  • 在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
  • 这个机制用来控制消息可以发送到哪些窗口,例如,当用postMessage传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的origin属性完全一致,来防止密码被恶意的第三方截获。
  • 如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

3.2 监听通信

window.addEventListener("message", (e) => { console.log(e); });
复制代码

直接监听message事件,这里打印的e与Broadcast ChannelAPI接收到的内容是一样的,我们可以直接通过返回的相关属性进行操作。

4. 实现案例

页面b代码

// 发起通信,约定好唯一标识,以及能够被监听到的页面源
window.opener.postMessage("updateLiveList", location.origin);
复制代码

页面a代码

// 监听message事件,如果有监听到消息内容就执行以下内容
window.addEventListener("message", (e) => {
  // 判断是否同源
  if (e.origin === location.origin) {
  
    // 接收到的消息是否为约定好的唯一标识
    if(e.data === "updateList") {
      // 刷新列表操作
      ...
    }
  }
});
复制代码

5. 浏览器兼容性

image.png

6. 安全问题

  • 如果你不希望从其他网站接收message,请不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。
  • 如果你确实希望从其他网站接收message,请始终使用origin和source属性验证发件人的身份。 任何窗口都可以向任何其他窗口发送消息,你不能保证未知发送人会不会发送恶意消息,以及对你进行xss攻击。
  • 当你使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是'*'。 恶意网站可以在您不知情的情况下更改窗口的位置,因此它可以拦截使用postMessage发送的数据。

三、总结

  • Broadcast Channel 与 window.postMessage 都能进行跨页面通信
  • Broadcast Channel 只能用于同源页面之间进行通信,而window.postMessage可以任何页面之间通信
  • 基于 Broadcast Channel 的同源策略,它无法完成跨域的数据传输;跨域的情况,我们只能使用window.postMessage 来处理
  • Broadcast Channel 相对于 window.postMessage 来说,Broadcast Channel 更加安全,没有特殊要求的情况下,一般推荐使用 Broadcast Channel 来进行跨页面通信

以上就是关于浏览器跨页面通信 Broadcast Channel 与 window.postMessage 的详解,如果觉得本文对你有所帮助,请点个赞以表鼓励,谢谢!

猜你喜欢

转载自juejin.im/post/7037001095075348517