前端跨窗口通信:Iframe - postMessage的透传原理及应用

152 阅读2分钟

背景

最近在做的一个中台系统有这样一个需求:在顶部导航栏增加一个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 消息传递过程

  1. 序列化阶段
    • 发送方将消息对象序列化为字符串,然后通过浏览器的消息队列进行传递,最后接收方将消息反序列化回对象
  2. 安全机制:防止跨域攻击
    • targetOrigin参数控制消息接收方
    • event.origin验证消息来源
  3. 消息队列
    • 消息按顺序处理
    • 异步传递
    • 不阻塞主线程

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)。