基于postMessage实现跨源通信---七日打卡

293 阅读4分钟

前言
出于浏览器的同源策略,运行在同一浏览器中的框架、标签页、窗口间的通信都受到了严格的限制。只有同源的文档才可以通信。这个安全策略虽然防止恶意网站与其他内容交互,但是也让制作有多个数据来源的聚合应用变得很困难。

使用场景

在工作中碰到这样一个问题,需要新建一个vue项目,然后多页面打包,把打包之后的页面通过iframe的方式嵌入到一个旧的项目里,并且要让这个旧项目中的页面和嵌入的页面进行消息传收。
这个时候问题来了,如何让两个项目的页面之前进行通信。
通过一番查资料,发现了window.postmessage这个方法可以实现我们的需求。 那么window.postmessage是什么呢?

window.postmessage是浏览器厂商和标准制定机构引入一种新功能:跨文档消息通信。 它使跨源通信成为一种安全的方法。window.postMessage 通过安全可靠的方式实现了对这个限制的突破,很方便的实现了跨文档消息通信。

postMessage(data,origin)方法接受两个参数:
1.data:
要传递的数据\color{red}{要传递的数据}
html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。

2.origin:
字符串参数\color{red}{字符串参数}
指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

如想实现父页面和嵌入到它里面的iframe之前的通信,可以这样写:

父页面:

window.addEventListener('message',function(e){
	console.log(e)
})

iframe页:

window.parent.postMessage({data:'params'},'*')

为了方便传递消息,统一接收的数据格式,个人封装了一个cPostMsg方法,代码如下:

const cPostMsg={
 _tool:{
   cIndexOf:function(data,obj,last){
     //功能: 兼容低版本的indexOf方法;
     //参数: data是要查找的数据,可以是数组,也可以是字符串;obj是需要匹配的字符串;last为true时,从后面开始匹配
     //返回: 查找到的开始索引,没找到返回 数值:-1
     if(typeof(data)==="string"){
       if(last){
         return data.lastIndexOf(obj)
       }else{
         return data.indexOf(obj)
       }
     }else if(!last&&Array.prototype.indexOf){
       return data.indexOf(obj)
     }else if(!last){
       for(var i=0; i<data.length; i++){
         if(data[i]==obj){
           return i;
         }
       }
     }else if(Array.prototype.lastIndexOf){
       return data.lastIndexOf(obj)
     }else{
       var len=data.length;
       for(var i=len;i>=0;i--){
         if(data[i]===obj){
           return len-i;
         }
       }
     }
     return -1;
   },
   getRootPath:function (noProjectName) {
     //功能: 获取web根路径;
     var curWwwPath = window.document.location.href;  //获取当前网址,如: http://localhost:8083/uimcardprj/share/meun.jsp
     var pathName = window.document.location.pathname;  //获取主机地址之后的目录,如: uimcardprj/share/meun.jsp
     var pos = cPostMsg._tool.cIndexOf(curWwwPath,pathName);
     var localhostPaht = curWwwPath.substring(0, pos);  //获取主机地址,如: http://localhost:8083
     var projectName = pathName.substring(0,  cPostMsg._tool.cIndexOf(pathName.substr(1),"/") + 1);  //获取带"/"的项目名,如:/uimcardprj
     if(noProjectName){
       projectName=""
     }
     return (localhostPaht + projectName);
   }
 },
 sendMsg:function(op){
   //功能: 发送信息;
   //参数说明:op={"win":目标win对象,默认为window.top,"url":发送源URL,不提供url将自动通过_tool.getRootPath获取,"data":你要发送的数据,"code":数据标识,"iframeId":如果不提供win,也可以提供iframe的Id}
   if(!(op&&typeof(op)=="object")){
     console.error("参数错误,参考:{win:目标win对象,默认为window.top,url:发送源URL,不提供url将自动通过_tool.getRootPath获取,data:你要发送的数据,code:数据标识,iframeId:如果不提供win,也可以提供iframe的Id}")
     return
   }
   var myWin=op["win"]?op["win"]:window.top
   var iframeId=op["iframeId"]
   if(iframeId){
     var iframeDom=document.getElementById(iframeId)
     if(iframeDom){
       myWin=iframeDom.contentWindow
       iframeDom=null
     }else{
       myWin=window
     }
   }
   var myUrl=op["url"]?op["url"]:cPostMsg._tool.getRootPath(true)
   var msg=op["data"]
   var myCode=op["code"]?op["code"]:"_cpm_"
   try{
     if(myCode){
       msg={"__code__":myCode,"msg":msg}
     }
     myWin.postMessage(msg,myUrl)
   }
   catch(err){
     console.log(err)
   }

 },
 onMsg:function(op){
   //功能: 信息监听;
   //参数说明:op={callback:接收到数据后触发回调函数,"url":针对来源是否屏蔽信息,默认是本地地址,"code":数据标识}
   if(!(op&&typeof(op)=="object")){
     console.error("参数错误,参考:{callback:接收到数据后触发回调函数,url:针对来源是否屏蔽信息,默认是本地地址,code:数据标识}")
     return
   }
   var myCode=op["code"]?op["code"]:"_cpm_"
   var callback=op["callback"]
   var myUrl=op["url"]?op["url"]:cPostMsg._tool.getRootPath(true)
   if(!callback){
     console.error("需要回调参数")
     return
   }

   window.addEventListener("message", function(e){
     if(myUrl=='all'||e.origin==myUrl){  //判断来源url(e.origin) 是否是被允许的
       if(myCode){
         if(e.data&&typeof(e.data)=="object"&&e.data["__code__"]==myCode){  //仅带有指定数据标识的才触发回调
           callback(e)
         }
       }else{
         callback(e)
       }
     }
   }, true);

 }

}

export default cPostMsg;

使用方法
1.发送消息示例:

cPostMsg.sendMsg({
	data:{
    name:"张三"
   },
   iframeId:"Iframe"//指定要发送给哪个iframe
})

2.接收消息示例:

cPostMsg.onMsg({
	callback:function(e){
   	console.log(' 收到的消息是:',e.data.msg.name)
   }
})