跨页面通信 (postMessage)

场景

最近在工作中碰到这样一个业务场景。

有 A、B 两个页面(不同域名)。A 是一个显示信息页面;B 是一个电话呼叫系统。

存在问题:
A 页面中可以查看当前需要拨打的客户的电话号码。
B 页面用于打对外客服电话,由于己方客服可代表多个角色,所以在每次呼出电话前要手动选择己方角色。当角色和号码很多时,客服在使用系统时很容易选错角色或者拨错号码。

需求:
由于存在上述问题,所以希望实现这样功能:从 A 页面提供一个链接,点击该链接后跳转到 B 页面,跳转后的 B 页面中的己方角色已经选好,并且待呼出号码也填好。

思路

1. 利用 url 传值

我看到这个问题第一个想法就是给 A 页面增加一个跳转链接,通过 window.location 跳转过去,将所需数据放在 urlhash 值里面传递,然后在 B 页面中取出 hash 里的值做处理。A 页面语法如下:

window.location.href = 'https://www.xxx.com#id=1&name=jaakko'

在 B 页面就可以取出 hash 里的值:

window.location.hash
// TODO

由于 hash 值不会引起页面刷新,所以可以放心利用 hash 传值。

于是我兴高采烈地尝试了一下,结果让我很失望。问题在于:
B 页面是一个很古老的系统,该系统没有保存登录状态,这意味着,首次进入该页面或每次刷新页面时,都需要重新登录。所以每次跳转过去时,都需要重新登录,这有违我们减少工作量的初衷,所以只有另辟蹊径了。

2. 利用 postMessage 传值

第一次尝试失败后,我又想到了利用 postMessage 传值。
关于 postMessage 这里有一篇详细的介绍

这里我就不详细介绍了,简单说一下。
postMessage 用于实现页面间跨域通信,由于它可以捕获通信对方来源,所以相当于提供了一种安全机制。

发送方

语法
win.postMessage(msg, targetOrigin, [transfer]);
参数

win

win 表示对目标窗口的一个引用,这个值可以是 iframecontentWindow 属性、执行 window.open 返回的窗口对象、或是命名过或数值索引的 window.freames

msg

msg 是需要传递的数据,这个值可以是对象

targetOrigin

通过窗口的 origin 属性可以指定哪些窗口能接到消息事件,可以为通配符 * (表示所有窗口),也可以为一个明确的 url

[transfer]

是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权

接收方

语法
window.addEventListener("message", function (event) {
    // TODO
});

event 对象有以下属性:

  • data:从发送方传过来的数据
  • origin:发送方窗口的 origin
  • source:发送方 window 对象的引用,可通过该属性向发送方发送消息

demo

发送方 sender.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>发送方</title>
</head>

<body>
    <h1>发送方</h1>
    <input type="text" id="msg">
    <button id="send">sendMsg</button>
</body>
<script>
    const input = document.getElementById('msg');
    const btn = document.getElementById('send');
    const win = window.open('./receiver.html', '_blank');    // 获取接收方窗口对象
    btn.addEventListener('click', () => {
        let msg = input.value;

        win.postMessage(msg, '*');
    })

</script>

</html>

接收方 receiver.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>接收方</title>
</head>

<body>
    <h1>接收方</h1>
    <div id="content"></div>
</body>
<script>
    const content = document.getElementById('content');
    window.addEventListener('message', event => {
        console.log(event);

        content.innerText = event.data;
    })
</script>

</html>