基于postMessage+BroadcastChannel+localStorage+互信域名的前端页面数据通信解决方案
- 继承了BroadcastChannel通信的优点:支持跨TAB通信
- 继承了localStorage通信的优点:兼容性好
- 继承了postMessage通信的优点:支持跨域通信
- 一套脚本解决前端数据通信面临的众多情况
- 支持同页、弹窗页、父子嵌套页、跨域、跨TAB页的通信
# npm
npm install broadcast-message
# yarn
yarn add broadcast-message
中国大陆用户可使用阿里源进行加速安装
# npm
npm install broadcast-message --registry=https://registry.npmmirror.com
# yarn
yarn add broadcast-message --registry=https://registry.npmmirror.com
import BroadcastMessage from 'broadcast-message'
const bmOpts = { channelId: 'demo', allowLocalBroadcast: true }
const broadcastMessage = new BroadcastMessage(bmOpts)
/* 注册接收消息事件 */
broadcastMessage.addEventListener("message", event => {
console.log(`[BroadcastMessage-Event]`, event.data)
})
/* 发送消息 */
broadcastMessage.postMessage(`[demo] ${Date.now()}`)
import BroadcastMessage from 'broadcast-message'
const bmOpts = { channelId: 'localStorage-demo', allowLocalBroadcast: true, transportType: 'localStorage' }
const broadcastMessage = new BroadcastMessage(bmOpts)
/* 注册接收消息事件 */
broadcastMessage.addEventListener("message", function (event) {
console.log(`[BroadcastMessage-Event]`, event.data)
})
/* 发送消息 */
broadcastMessage.postMessage(`[localStorage-demo] ${Date.now()}`)
PS: 一般不建议手动指定transportType类型,broadcast-message默认会根据浏览器的支持情况自动选择BroadcastChannel还是localStorage,另外你应该了解的是:BroadcastChannel的通信效率比localStorage高
假设:
- 域名A是消息接收方
- 域名B是消息发送方
域名A的代码示例:
import BroadcastMessage from 'broadcast-message'
const bmOpts = {
channelId: 'cross-domain-demo',
/* 必须使用可信域下的broadcast-message页来进行消息的中转,后面会介绍如何自定义可信域页面地址 */
trustedDomainPages: 'https://broadcast-message.anzz.top/dist/pages/broadcast-message.html',
/* 指定接收来自哪个域名发送过来的消息,*表示信任所有域名的消息,生产环境推荐必须指定具体域名 */
// targetOrigin: "https:anzz.top"
targetOrigin: "*"
}
const broadcastMessage = new BroadcastMessage(bmOpts)
/* 注册接收消息事件 */
broadcastMessage.addEventListener("message", event => {
console.log(`[BroadcastMessage-Event]`, event.data)
})
域名B的代码示例:
import BroadcastMessage from 'broadcast-message'
const bmOpts = {
channelId: 'cross-domain-demo',
/* 可信域消息中转页的地址必须和域名A定义的一致 */
trustedDomainPages: 'https://broadcast-message.anzz.top/dist/pages/broadcast-message.html',
}
const broadcastMessage = new BroadcastMessage(bmOpts)
/* 发送消息 */
broadcastMessage.postMessage(`[cross-domain-demo] ${Date.now()}`)
附注:当进行跨域通信时,需先确认域名A和域名B的页面都已加载,且都完成了broadcast-message的初始化工作,才能开始通信,否则会出现消息发送出去了,但接收方还没准备好等情况,导致消息通信失败
对于有跨域通信需求的项目,加载一个可信域的页面作为消息中转页是必不可少的。默认可以使用项目的自带可信域页面:
https://broadcast-message.anzz.top/dist/pages/broadcast-message.html
但出于下面的原因你可能需要自定义可信域页面
- 需要更快更稳定的可信域页面
- 项目对第三方网址有严格的限定
- 封装内部使用的跨域消息通信库
自定义可信域页面代码示例如下:
import BroadcastMessage from 'broadcast-message'
new BroadcastMessage({
/* 只需标识当前脚本运行在可信域页面内即可 */
inTrustedDomainPages: true,
})
将初始化了inTrustedDomainPages
选项的页面发布出去,即可获得一个自定义可信域页面
附注:为了让自定义可信域页面更快地加载,你应该务必不要掺杂其他无关逻辑进去,它只作为消息中转页面存在,并不需要其他额外内容
new BroadcastMessage(opts)
中的opts是一个对象它包含的选项和具体说明如下:
const bmOpts = {
/**
* 指定BroadcastMessage的消息频道id,不同BroadcastMessage实例,只要channelId一致即可通信
* 默认的channelId为"*" , 这意味着在不指定频道id的情况下,各个实例的消息都会发往同一个频道上
*/
channelId: '*',
/**
* 允许既是消息的发送页,也可以是消息的接收页,从而做到同源同页收发消息
* postMessage、BroadcastChannel和storage事件都是必须一个页面发送,另一个页面接收
* 而BroadcastMessage允许同页面收发消息,只需将allowLocalBroadcast设为true即可
*/
allowLocalBroadcast: false,
/**
* 给 broadcastMessage.addEventListener("message", function (event) {}) 事件派发原始消息对象
* 默认情况下,broadcastMessage.postMessage(msg) 中的 msg 等于接收到的 event.data,但其实为了能准确派送消息,
* msg会被包装成有很多辅助参数的对象,如果你需要这些辅助参数信息,可以将emitOriginalMessage设为true
* 则收到的event.data将是如下的对象实体:
* event.data = {
* data: msg, // 消息本体
* type: 'BroadcastMessage', // 消息类型,默认为BroadcastMessage,其他类型,则是自定义消息或内部消息
* origin: '', // 消息来源的域名地址
* targetOrigin: '', // 消息目标源的域名地址
* referrer: '', // 消息来自哪个页面
* timeStamp: '', // 消息发出时的时间戳
* transportType: '', // 中转消息的载体类型,只可能为BroadcastMessage和localStorage
* allowLocalBroadcast: false, // 该消息是否允许被同页面的BroadcastMessage实例接收到
* channelId: '*', // 消息所属的频道id
* instanceId: 'xx_xx', // 消息所属BroadcastMessage实例id
* debug: false // 是否为开启了调试选项的消息
* }
*/
emitOriginalMessage: false,
/**
* 指定消息发送的目标域,规则跟postMessage的targetOrigin一样
* 但不同的是支持定义数组形式的targetOrigin,从而实现批量跨域数据发送
* 当然如果是"*"的话就是给任意运行了本插件的页面发送数据
* 默认为 `location.origin`,即只可以在同域名的页面之间进行通信
*/
targetOrigin: location.origin,
/**
* 指定数据中转传输使用的传输类型,可选值:BroadcastChannel、localStorage
* 不指定的话,优先使用BroadcastChannel,在不兼容BroadcastChannel的浏览器下使用localStorage
* 无特需情况,不建议修改该项配置
*/
transportType: 'BroadcastChannel',
/**
* 标识BroadcastMessage实例是否处于可信域的页面上运行
* 如果是,当前页面作为可信域的中介页嵌入到具体运行环境中
* 该选项只在自定义可信域页面中使用,其他情况不要使用
*/
inTrustedDomainPages: false,
/**
* 指定作为消息中转的可信域页面地址,有需跨域通信需求才会用到该选项
* 官方提供的可信域页面地址为:
* https://broadcast-message.anzz.top/dist/pages/broadcast-message.html
*/
trustedDomainPages: '',
/* 标识是否开启调试选项 */
debug: false,
}
broadcast-message实例包含的方法如下:
- addEventListener
- onMessage
- removeEventListener
- offMessage
- postMessage
- ready
- close
下面对各个函数的使用方式进行逐一说明:
函数参数:
- type {String} 必选,必须为 'message'
- listener {Function} 必选
添加消息事件的侦听器
import BroadcastMessage from 'broadcast-message'
const broadcastMessage = new BroadcastMessage({...opts})
broadcastMessage.addEventListener('message', event => {
console.log(`[BroadcastMessage-Event]`, event.data)
})
函数参数:
- handler {Function} 必选
添加消息处理函数,addEventListener是基于onMessage的,只是为了让BroadcastMessage保持跟BroadcastChannel一致的函数调用方式,所以增加了addEventListener事件
import BroadcastMessage from 'broadcast-message'
const broadcastMessage = new BroadcastMessage({...opts})
broadcastMessage.onMessage(event => {
console.log(`[BroadcastMessage-Event]`, event.data)
})
函数参数:
- type {String} 必选,必须为 'message'
- listener {Function} 必选
移除消息事件的侦听器
import BroadcastMessage from 'broadcast-message'
const broadcastMessage = new BroadcastMessage({...opts})
const listener = event => { console.log(`[BroadcastMessage-Event]`, event.data) }
broadcastMessage.addEventListener('message', listener)
/* 一段时间后移除消息事件侦听器 */
setTimeout(() => { broadcastMessage.removeEventListener('message', listener) }, 3000)
函数参数:
- handler {Function} 必选
移除消息处理函数,removeEventListener是基于onMessage的,只是为了让BroadcastMessage保持跟BroadcastChannel一致的函数调用方式,所以增加了removeEventListener事件
import BroadcastMessage from 'broadcast-message'
const broadcastMessage = new BroadcastMessage({...opts})
const handler = event => { console.log(`[BroadcastMessage-Event]`, event.data) }
broadcastMessage.onMessage(handler)
/* 一段时间后移除消息事件处理函数 */
setTimeout(() => { broadcastMessage.offMessage(listener) }, 3000)
函数参数:
- message {String|Number|Array|Object} 必选,必须为 'message'
- messageType {String} 可选 默认为'BroadcastMessage', 内部消息为:'Internal-BroadcastMessage', 一般不需传入该参数
发送消息
import BroadcastMessage from 'broadcast-message'
const broadcastMessage = new BroadcastMessage({...opts})
broadcastMessage.postMessage('test')
函数参数:
- handler {Function} 必选 BroadcastMessage实例创建就绪后的回调函数
侦听BroadcastMessage实例创建就绪后,再执行响应的回调操作,因为BroadcastMessage初始化会创建消息中转的iframe,需要一定的创建时间,所以并不是拥有了BroadcastMessage实例就可以马上postMessage,而是要等到实例就绪才会将消息传送出去
默认情况下,在执行postMessage前,会自动检测BroadcastMessage实例是否完全就绪,如果没有会将需要发送的消息缓存起来,等BroadcastMessage实例创建就绪之后才将缓存的消息发送出去
import BroadcastMessage from 'broadcast-message'
const broadcastMessage = new BroadcastMessage({...opts})
broadcastMessage.ready(() => {
broadcastMessage.postMessage('test')
})
// 如果没使用ready函数,直接调用postMessage也是没问题的
// 这是因为BroadcastMessage会自动判断实例是否创建就绪
// broadcastMessage.postMessage('test')
函数参数:
- 无
需要销毁BroadcastMessage实例时调用的函数,该函数会将BroadcastMessage中转所需的iframe、BroadcastChannel实例和通过addEventListener或onMessage定义的消息函数销毁掉
import BroadcastMessage from 'broadcast-message'
let broadcastMessage = new BroadcastMessage({...opts})
broadcastMessage.onMessage(event => { console.log(`[BroadcastMessage-Event]`, event.data) })
broadcastMessage.postMessage('test')
/* 一段时间后销毁broadcastMessage实例 */
setTimeout(() => {
broadcastMessage.close()
broadcastMessage = null
}, 3000)