微前端插件:获取iframe发送postMessage数据

2,689 阅读3分钟

问题描述

在主页面内部点击按钮,通过Iframe打开另一个的页面,两个页面完全是不一样的工程,主页面使用的框架时React,弹窗嵌入页面使用的框架时Vue,两这个通过PostMessage进行消息传递。因此在实际开发过程中,如果想要在本地调试嵌入式页面,需要在控制台中,使用window.postMessage向页面发送信息或者在页面使用mock数据,才能正常调试。但是在排查问题和本地调试,尤其是真线问题的时候,无法准确的获得主页面使用postMessage发送的具体数据,进行本地测试,如果通过接口自己mock数据,可能会出现偏差,导致本地没有问题,但是发上去就有问题,严重影响开发效率和测试回归效率。

目前的浏览器插件,只能支持监听主页面的也就是顶层window的postMessage数据,不能支持监听签入iframe页面的window对象的postMessage数据,针对此问题,我们开发了一个浏览器插件 ,可以获取到内部iframe页面发送的postMessage数据,可以准确获取到mock数据,帮助本地调试。

基础知识

🚀PostMessage

父窗口想通过postMessage向子窗口发送数据,需要获取到iframe的window对象

子窗口想通过postMessage向父窗口发送数据,需要获取到iframe的window对象

upStarOrGuarantee = (item, isUpStar, renewal, guaranteeCode) => {  
  setTimeout(() => {  
    const iFrame = document.getElementById("inlineFrameExample");  
    iFrame?.contentWindow?.postMessage?.(params, "*");  
    this.setState({  
      loadingfalse,  
    });  
  }, 2000);  
};
<iframe>
  id="inlineFrameExample"  
  title="Inline Frame Example"  
  width="980"  
  height="560"  
  src={`${window.envHref.middle}/luban/guarantee/component`}
/>
window.addEventListener('message'(event) => {  
    if (event.origin === 'https://yourdomain.com') {  
      console.log('Received:', event.data);  
    } else {  
      console.warn('Untrusted origin:', event.origin);  
    }  
});

🚀浏览器插件

核心配置:manifest.json,插件运行在独立的环境中,与网页环境隔离开来,可以通过content.js与页面交互。

{  
  "manifest_version"2,  
  "name""Sample Extension",  
  "version""1.0",  
  "description""A simple browser extension example.",  
  "icons": {  
    "16""icons/icon16.png",  
    "48""icons/icon48.png",  
    "128""icons/icon128.png"  
  },  
  //插件需要的权限,例如访问特定域名、使用浏览器的某些功能(如tabs、cookies等)。  
  "permissions": [  
    "" ,  
    "https://*.example.com/*"  
  ],  
  "browser_action": {  
    "default_icon": {  
      "16""icons/icon16.png",  
      "48""icons/icon48.png",  
      "128""icons/icon128.png"  
    },  
    "default_popup""popup.html",  
    "default_title""Sample Extension"  
  },  
  //后台脚本,这些脚本在插件加载时运行,通常用于处理长期任务或者事件监听  
  "background": {  
    "scripts": ["background.js"],  
    "persistent"false  
  },  
  //配置内容脚本,这些脚本注入到匹配的网页中,并可以与网页内容交互  
  "content_scripts": [  
    {  
      //字段指定匹配的URL模式,控制注入的页面  
      "matches": ["https://*.example.com/*"],  
      //js调本  
      "js": ["content.js"],  
      //css脚本  
      "css": ["styles.css"],  
      //运行的时机  
      "run_at""document_idle",  
      //是否注入所有的iframe  
      "all_frames"true  
    }  
  ],  
  //声明插件中的哪些资源(如图像、脚本等)可以被网页访问。  
  "web_accessible_resources": [  
    "images/*"  
  ],  
  //配置插件的选项页面,用户可以在此页面中配置插件的设置。  
  "options_page""options.html"  
}

解 决方案

🤔️方案1:获取到iframe的contentwinow监听message方法,在控制台打印数据获取到的数据

const iFrame = document.getElementById("inlineFrameExample");  
iFrame?.contentWindow?.addEventListener("message"(e) => {  
  console.log(e);  
});

❌结果:不可行,父窗口不打开弹框的情况下,获取不到Iframe对象,无法监听postMessage,触发函数也是异步获取的。在控制台,异步监听也会报错,会出现同源问题,如下所示。

setTimeout(() => {  
  const iFrame = document.getElementById("inlineFrameExample");  
  iFrame?.contentWindow?.addEventListener("message"(e) => {  
    console.log(e);  
  });  
}, 15000);  
  
// VM218:3 Uncaught DOMException: Failed to read a named property 'addEventListener' from 'Window': Blocked a frame with origin "https://agreement.test.zcygov.cn" from accessing a cross-origin frame.  
// at :3:24  
// (anonymous)    @    VM218:3

🤔️方案2:使用浏览器插件,通过浏览器插件,将配置内容脚本注入到所有的Iframe中,通过window对象监听postEvent事件,并打印数据。✅

📃创建【manifest.json】,确保插件有足够的权限,访问目标域名,并配置内容脚本到所有的iframe中。

{  
  "manifest_version"2,  
  "name""Print Iframe Scripts and Variables",  
  "version""1.0",  
  "permissions": [  
    ""   
  ],  
  "content_scripts": [  
    {  
      "matches": [""],  
      "js": ["content.js"],  
      "all_frames"true,  
      "run_at""document_idle"  
    }  
  ]  
}

📃创建【content.js】,在内容脚本中注入javaScript,并监听iframe的postMessage事件

/** @format */  
  
(function () {  
  // Check if the script is running in an iframe  
  if (window !== window.top) {  
    try {  
      console.log('window is not the top window'window);  
      window.addEventListener('message'e => {  
          console.log('window.event', e);  
          console.log('window.data', e.data.data);  
          console.log('window.msgType', e.data.msgType);  
      });  
    } catch (error) {  
      console.error('Error accessing iframe content:', error);  
    }  
  }  
})();