美文网首页
跨域数据传递中遇到的问题

跨域数据传递中遇到的问题

作者: HoPGoldy | 来源:发表于2021-05-19 14:45 被阅读0次

前几天做了一个小项目,其中用到了跨域数据传递 window.postMessage,这里记录一下遇到的几个问题。

首先我们来简单了解下 postMessage 的用法,消息的传递包含发送方和接受方,首先双方要能够获取到对方的 window 实例,然后发送方调用接受方的 window.postMessage 来发送一条消息:

发送消息

targetWindow.postMessage('这是 a 发送的一条数据', 'http://localhost:8001')

第一个参数是发送数据,第二个参数是一个字符串,发送之前会拿这个网址去和目标 window 进行跨域校验,必须协议、域名、端口相同,信息才会正常发送。

这个是用于 避免发送方的数据泄露 而设置的规则。因为发送方无法通过直接读目标 window 对象的方式确认其身份(会被跨域拦下来)。发送方也不知道打开的目标网址会不会重定向到其他地方,为了避免发送密码之类的隐私数据泄露出去,浏览器提供了这个参数,由浏览器对目标窗口进行检查。如果检查不通过就会取消发送。也可以通过显式设置为 "*" 来一直允许发送消息。

注意,消息是否取消发送这件事,发送方是无法得知的,无论成功与否,都不会有任何返回值和异常抛出,这个有点坑。

接受消息

在消息接受方,可以通过 window.addEventListener 添加一个 message 事件监听来获取其他窗口发来的消息,如下:

window.addEventListener('message', console.log)
// MessageEvent {isTrusted: true, data: "这是 a 发送的一条数据", origin: "http://localhost:8002", lastEventId: "", source: Window, …}

接收到的 MessageEvent 里有两个属性需要关注,一个是 data,其他窗口发来的数据就保存在这里。另一个是 source,指向了消息的发送窗口,可以直接通过这个 window 回发消息(但是依旧无法直接访问)。


简单了解了怎么用之后,下面写一些开发的时候遇到的问题,主要都是 window.openiframe 的区别导致的。

问题 1:消息发送不过去

一开始写的代码是这样的:

// a.html

const targetWindow = window.open('http://localhost:8002/b.html');
targetWindow.onload = () => {
    targetWindow.postMessage('这是 b 发过来的一条数据', 'http://localhost:8001')   
}

但是并不行,发现 onload 事件无论如何都无法触发。这个的原因是网上的教程大多都是使用 iframe 打开的新窗口,监听的是 iframe 的 onload 事件。而使用 window.open 方式打开的新窗口是无法访问打开窗口 window 对象的 onload 事件的。

那么应该如何监听目标页面是否初始化完成的呢?直接发送消息肯定是不行的,新窗口在加载完成之前如果向其发送了消息,那么发送就会被直接取消。这里可以通过进行一个简单的“握手”操作来实现:

  • 发送方添加 message 监听,同时打开接受方窗口
  • 接受方加载完成后向父窗口 postMessage,通知父窗口我已就绪
  • 父窗口触发 message 回调,向接受方发送信息

具体代码如下:

发送方 a.html

window.addEventListener('message', ({ origin, data, source }) => {
    // 确认身份及对象是否准备完成
    if (origin !== 'http://localhost:8002' || data !== 'ready') return;
    // 确认完成,发送数据
    source.postMessage('我是真正的数据', 'http://localhost:8002');
})
window.open('http://localhost:8002/b.html');

接受方 b.html

window.addEventListener('message', ({ origin, data }) => {
    if (origin !== 'http://localhost:8001') return;
    console.log('收到数据', data);
})
window.opener.postMessage('ready', 'http://localhost:8001');

问题 2:信息错误的发给了自己

一开始,我在 b.html 中写的发送消息是这样的:

window.parent.postMessage('ready', 'http://localhost:8001');

结果无论如何 a.html 都无法获取到对应的 ready 信息,并且离谱的是 b.html 自己却收到了消息,后来查阅了 文档 后才发现,window.parent 默认指向自己,而不是指向空。并且只有在以 iframe 或者 object 标签打开时才会指向其父窗口!也就是说使用 window.open 打开的窗口是无法通过这个属性获取到打开者窗口的,那么怎么访问呢?

其实上文已经贴出来了,就是 winodw.opener 属性,这个属性指向了其打开者。但是用 iframe 标签打开新窗口时,这个属性并不会指向其父窗口,而是 null。总结一下就是下面这样:

打开方式 window.parent window.opener
<iframe /> 指向父窗口 null
window.open 指向自己 指向父窗口(打开者)

问题3:报错 Uncaught DOMException

具体错误内容如下:

这就是刚才提到的,如果直接访问其他 window 对象的话就会被跨域拦下来。无论是访问 window 本身还是其属性,但是 postMessage 是个特例(毕竟本身就是为了跨域通讯而生的):

console.log(window.parent)
// Uncaught DOMException: ...

console.log(window.parent.parent.window)
// Uncaught DOMException

console.log(window.parent.postMessage)
// ƒ () { [native code] }

这个异常不仅限于 window.parent,只要访问能得到的任意其他窗口的 window 实例,都会报这个错。

参考

相关文章

网友评论

      本文标题:跨域数据传递中遇到的问题

      本文链接:https://www.haomeiwen.com/subject/ckkwdltx.html