一、用法
window.postMessage
是一个用于在不同的窗口或 frame 之间进行安全跨域通信的方法。它允许一个窗口向另一个窗口发送消息,而无需知道对方的具体来源。
这个方法允许在不同的窗口间建立一种通信机制,可以安全地在跨域环境中传递数据。通过postMessage
,你可以向其他窗口发送消息,并在接收窗口中监听和处理这些消息。
基本用法如下:
1、在发送窗口中
otherWindow.postMessage(message, targetOrigin);
otherWindow
:是一个指向其他窗口的引用,可以是 iframe 的 contentWindow,也可以是打开窗口的引用。message
:是要发送的消息,可以是字符串、对象等。targetOrigin
:是一个字符串,指定接收消息的窗口的源。只有当接收窗口的 origin(协议、主机和端口)与指定的 targetOrigin 相匹配时,消息才会被发送。
2、在接收窗口中
window.addEventListener('message', function(event) {
// 处理接收到的消息
});
event
参数是一个MessageEvent
对象,包含了有关接收到的消息的详细信息
data
:包含传递的消息数据。这可以是一个字符串、对象或其他类型的数据,取决于发送方发送的内容。origin
:消息的来源。这是一个字符串,表示发送消息的文档的源。它包括协议、主机和端口,如 “http://example.com”。lastEventId
:表示消息的 ID,对于服务器发送的事件 (Server-Sent Events) 来说特别有用。source
:发送消息的窗口、iframe 或 Worker 对象的引用。这允许你在回复消息时知道消息的来源。ports
:如果消息是通过 MessageChannel 对象发送的,这是一个包含所有与通道相关的 MessagePort 对象的数组。
这些属性使得你能够对接收到的消息进行有效地处理,并根据需要采取相应的操作。例如,你可以通过 event.data
访问消息内容,通过 event.origin
验证消息来源的合法性,然后基于这些信息执行相应的逻辑。
二、实践
1、场景:有两个独立部署在不同服务器上的系统A和B的vue项目,想把B嵌入A中,B相当于A里面的一个模块,并且两个系统共用一套人员信息,登录了A系统后打开B系统来,不单独打开新浏览器窗口,只是单纯的路由跳转,并且B系统不需要手动登录,直接用A系统的token或者信息调用接口自动登录。
2、实现:iframe
及window.postMessage
相结合
错误想法:一般登录信息获取到会存在浏览器的sessionStorage
中,既然是在同一个浏览器窗口打开来的项目,那么进入B系统的时候也直接从sessionStorage
里面获取一下就好了。实际上有一个前提,两个系统是独立部署在不同服务器上的,A系统的登录信息存在A服务的sessionStorage
中,在B系统里面是获取不到的。
在A系统中新建一个路由页面,通过iframe
将B系统嵌入进来,获取到A系统sessionStorage
中的用户信息,通过window.postMessage
发送给B系统。
假设A系统的服务是http://192.168.62.133:8081
,B系统的服务是http://192.168.62.133:8080
<template>
<!-- 动态路由页面 -->
<el-main>
<iframe ref="iframeId" id="iframeId" :src="url" frameborder="0" width="100%" :style="{height:calHeight}" scrolling="auto"></iframe>
</el-main>
</template>
<script>
export default {
data () {
return {
url: 'http://192.168.62.133:8080'
}
},
mounted () {
const targetOrigin = this.url;
const userInfo = JSON.parse(sessionStorage.getItem("UserInfo"))
var iframe = document.getElementById('iframeId');
this.$refs.iframeId.onload = () => {
iframe.contentWindow.postMessage({type: 'login', userInfo}, targetOrigin);
}
},
computed: {
//计算属性 , 设置iframe高度为窗口高度少100px
calHeight () {
return (window.innerHeight - 140) + 'px';
},
}
}
</script>
<style lang='scss' scoped>
iframe {
width: 100%;
overflow-x: hidden;
}
</style>
在B系统中某个合适的页面如登录页的mounted中执行window.addEventListener
开启监听
data () {
return {
url: 'http://192.168.62.133:8081'
}
},
mounted () {
window.addEventListener('message', this.handleMessage, false)
},
methods: {
handleMessage (event) {
if (event.origin == this.url) {
if (event.data.type == 'login') {
const userInfo = event.data.userInfo; // 接收到的用户信息
// 逻辑处理,如登录B系统
// 及时关闭监听
window.removeEventListener('message', this.handleMessage);
}
}
}
}
在A系统中进行某些操作,B系统中需要联动的,都可以类似上述在A系统中发送消息,到B系统中接收,如果想要A系统收到B接收到消息的确认,B收到后也可以通过event.source.postMessage
反馈给A,A同样使用window.addEventListener
监听子系统的消息。
handleMessage (event) {
if (event.origin == this.url) {
if (event.data.type == 'logout') {
console.log('离开子系统');
sessionStorage.removeItem("UserInfo")
} else if (event.data.type == 'reLogin') {
console.log('重新登录');
sessionStorage.removeItem("UserInfo")
// 通知父系统页面刷新
event.source.postMessage({ type: 'childResponseReLogin' }, event.origin);
}
}
}
在window.addEventListener
的回调方法中,除了event
参数外还想传递其他参数,可以利用闭包
来实现
handleChildResponseFactory(extraParam) {
// 返回一个处理子系统回复消息的函数
return function handleMessage (event) {
// 在函数内部可以访问到除了event外的额外参数
console.log('Extra parameter:', extraParam);
};
}
// 创建一个具有额外参数的处理函数
let extraParam = 'someValue'; // 额外参数的值
let handleChildResponseWithExtraParam = this.handleChildResponseFactory(extraParam)
// 监听回复消息,并调用带有额外参数的处理函数
window.addEventListener('message', this.handleChildResponseWithExtraParam);
在父系统的其他页面想要获取到子系统的iframe
节点可以通过parent.document.getElementById
,如果没有打开子系统来,则返回值为null
如A系统点击侧边栏,想要给B系统发消息
clickMenu(info) {
let iframe = parent.document.getElementById('iframeId');
iframe && iframe.contentWindow.postMessage({ type: 'logout' }, this.url);
this.$router.push(info.path)
}
注意:
① 不能在created
或者mounted
中直接使用postMessage
发送消息,因为可能此时目标系统的页面还未完全加载,可能会导致消息发送失败。在目标系统完全加载后再发送,比如在目标系统的load
事件中。
② 接收消息的系统中一定要设置消息的来源且要与发送消息的系统的源一致,否则报错如下:
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://192.168.62.133:8080') does not match the recipient window's origin ('http://192.168.62.133:8081').
③ iframe.contentWindow.postMessage
是用于向嵌套的子系统(例如 iframe 中加载的文档)发送消息的方法
④ iframe.contentWindow.origin
和window.origin
它们可能会返回相同的值,表明这两个窗口的源是相同的。然而, window.postMessage
是在全局作用域下调用的,浏览器在处理这个方法时会对目标窗口的源进行严格的校验,具体来说,浏览器会将消息的目标源设置为与当前文档关联的源
,而不是 window.origin 返回的值,这样做可以确保消息只能发送到受信任的源,从而防止恶意代码窃取数据或进行其他恶意行为。相比之下,当你使用 iframe.contentWindow.postMessage 时,它是针对特定的 iframe 对象调用的,浏览器会自动将消息的目标源设置为 iframe的源
,从而避免了源不匹配的错误。
⑤ 通过window.removeEventListener
开启的监听要在合适的时机通过window.removeEventListener
及时关闭