背景
最近在做的一个中台系统有这样一个需求:在顶部导航栏增加一个tab,点击后渲染一个外部生产地址,并且当前系统登录后才可以正常开始加载。在前端开发中,这种跨窗口通信是一个常见的需求,HTML5提供的postMessage
API为我们提供了安全可靠的跨窗口通信解决方案。
所以,使用iframe加载url,并且通过postMessage透传当前系统的token这种方式是比较合适的,借此机会也深入探讨下postMessage
的透传原理及其在实际开发中的应用场景。
1 postMessage基本原理
1.1 核心概念
postMessage
是HTML5提供的一个安全的跨域通信机制,主要用于不同窗口iframe
之间的通信。其基本语法如下:
// 发送消息
window.postMessage(message, targetOrigin);
// 接收消息
window.addEventListener('message', (event) => {
// event.data: 消息内容
// event.origin: 消息来源
// event.source: 发送消息的窗口引用
});
1.2 透传原理
1.2.1 消息传递过程
- 序列化阶段:
- 发送方将消息对象序列化为字符串,然后通过浏览器的消息队列进行传递,最后接收方将消息反序列化回对象
- 安全机制:防止跨域攻击
targetOrigin
参数控制消息接收方event.origin
验证消息来源
- 消息队列:
- 消息按顺序处理
- 异步传递
- 不阻塞主线程
1.2.2 数据透传特性
- 支持传递复杂对象、数组等
- 自动序列化和反序列化
- 不能传递函数、DOM节点等不可序列化对象
2 实际应用场景
2.1 基础通信功能
// 父窗口发送消息给iframe
iframe.contentWindow.postMessage('你好,iframe!', '目标域名');
// iframe接收消息
window.addEventListener('message', (event) => {
if (event.origin !== '父窗口域名') return;
console.log('收到消息:', event.data);
});
2.2 跨域数据传递
// 父窗口发送数据
const data = {
type: 'userInfo',
content: {
name: '张三',
age: 25
}
};
iframe.contentWindow.postMessage(data, '目标域名');
2.3 跨域表单提交
// iframe中处理表单提交
window.addEventListener('message', (event) => {
if (event.data.type === 'submitForm') {
const formData = event.data.formData;
// 处理表单数据
}
});
2.4 跨域文件上传
// 父窗口发送文件
const file = new File(['content'], 'test.txt');
iframe.contentWindow.postMessage({
type: 'uploadFile',
file: file
}, '目标域名');
2.5 跨域状态同步
// 父窗口和iframe之间的状态同步
window.addEventListener('message', (event) => {
if (event.data.type === 'stateChange') {
updateUI(event.data.newState);
}
});
2.6 安全最佳实践
// 发送方
const targetOrigin = 'https://trusted-domain.com';
iframe.contentWindow.postMessage({
type: 'sensitiveData',
data: sensitiveData
}, targetOrigin);
// 接收方
window.addEventListener('message', (event) => {
// 验证来源
if (event.origin !== 'https://trusted-domain.com') {
return;
}
// 验证消息类型
if (event.data.type !== 'sensitiveData') {
return;
}
// 处理数据
handleData(event.data.data);
});
2.7 TypeScript支持
interface MessageData {
type: string;
payload: any;
}
// 类型安全的处理
window.addEventListener('message', (event: MessageEvent<MessageData>) => {
if (event.origin !== 'https://trusted-domain.com') return;
switch (event.data.type) {
case 'updateData':
handleUpdate(event.data.payload);
break;
case 'syncState':
handleSync(event.data.payload);
break;
}
});
在实际开发中,我们需要根据具体场景选择合适的通信方式,并注意其安全性(即始终验证event.origin
)。