iframe与主窗口之间的通信协议

开发人员有时需要在多个浏览器选项卡之间建立通信,以便能够将消息从一个选项卡发送到另一个选项卡并接收响应。 在某些时候,我们也面临着这种需求。


已经存在一些解决方案(例如,BroadcastChannel API)。 但是,它对浏览器的支持还有很多不足 ,因此我们决定使用自己的库。 库准备就绪时,不再需要该功能。 尽管如此,另一个任务出现了:iframe和主窗口之间的通信。


经过仔细检查,结果发现不需要更改三分之二的库-仅需要一些代码重构。 该库是可以与文本数据一起使用的通信协议。 它可以应用于所有传输文本的情况,例如iframe,window.open,worker,浏览器标签或WebSocket。


运作方式


当前,该协议具有两个功能:发送消息和事件订阅。 协议中的任何消息都是数据对象。 对我们来说,该对象的主要字段是type ,它告诉我们它是哪种消息。 类型字段是具有以下值的枚举


  • 0-发送消息
  • 1-发送请求
  • 2-收到回应。

传送讯息


发送消息并不意味着响应。 要发送消息,我们创建一个具有以下字段的对象:


  • 类型 -事件类型0
  • 名称 -用户事件名称
  • 数据 -用户数据(类似于JSON)。

在另一侧收到类型为 field = 0的消息时 ,我们知道这是一个事件,具有现有的事件名称和数据。 我们要做的就是广播事件(几乎是标准的EventEmitter模式)。


它如何在简单模式中工作:



发送请求


发送请求意味着在库中创建了一个请求ID ,并且该库将等待ID为的响应。 成功收到响应后,将从中删除所有辅助字段,并将响应返回给用户。 另外,可以设置最大响应时间。



至于请求,这有点复杂。 要响应请求,您需要宣布我们协议中可用的方法。 这是通过registerRequestHandler方法完成的。 它接受响应请求的名称和返回响应的函数。 要创建一个请求,我们需要一个id ,我们基本上可以使用timestamp ,但是调整起来不是很方便。 因此,这是一个类ID,用于发送响应+响应编号+字符串文字。 现在,我们创建一个具有以下字段的对象: idtype = 1name作为请求名称, data作为用户数据(类似于JSON)。


收到请求后,我们将检查是否有用于响应此请求的API,否则,我们将返回错误消息。 如果我们有一个API,我们将通过registerRequestHandler返回具有相应请求名称的函数执行结果。


对于响应,我们创建一个具有以下字段的对象: type = 2, id为我们响应的消息的ID, status为表明该响应是否为错误的字段(如果我们没有API,或处理程序发生错误,或者用户返回了被拒绝的承诺,或者发生了另一个错误(序列化)),并将内容作为响应数据。


因此,我们已经描述了协议的操作,该协议执行Bus类,但是没有说明发送和接收消息的过程。 为此,我们需要具有三种方法的类适配器。


  • send是一种基本上负责发送消息的方法
  • addListener是一种订阅事件的方法
  • destroy是删除Bus时删除订阅的方法。

适配器。 协议的执行


要启动该协议,当前,仅准备好用于iframe /窗口的适配器。 它使用postMessageaddEventListener 。 这非常简单:您需要使用正确的来源将消息发送到postMessage ,并在“ message”事件上通过addEventListener侦听消息。


我们遇到了一些细微差别:


  • 您应该始终在您的窗口上收听回复,并在其他窗口(iframe,opener,parent,worker等)上发送回复。 如果您尝试在OTHER窗口上收听一条消息,并且其来源与当前消息不同,则会发生错误。
  • 接收到消息后,请确保已将消息定向到您:该窗口可以容纳许多分析消息,WebStorm(如果使用)和其他iframe,因此您需要确保该事件在协议中并针对您。
  • 您不能使用Window副本返回诺言 ,因为诺言在返回结果时将尝试检查结果是否具有then方法。 如果您无权访问该窗口(例如,具有其他来源的窗口),则会发生错误(尽管并非在所有浏览器中)。 为避免此问题,将窗口包装在对象中并将对象放入具有链接到正确窗口的Promise中就足够了。

用法示例:


该库在NPM中可用,您可以通过软件包管理器轻松安装它- @ wave / wave-browser-bus


要与iframe建立双向通信,只需使用以下代码即可:


import { Bus, WindowAdapter } from '@waves/waves-browser-bus'; const url = 'https://some-iframe-content-url.com'; const iframe = document.createElement('iframe'); WindowAdapter.createSimpleWindowAdapter(iframe).then(adapter => { const bus = new Bus(adapter); bus.once('ready', () => { // A message from iframe received }); }); iframe.src = url; // It's preferable to assign a url after calling WindowAdapter.createSimpleWindowAdapter document.body.appendChild(iframe); 

在iframe内部:


 import { Bus, WindowAdapter } from '@waves/waves-browser-bus'; WindowAdapter.createSimpleWindowAdapter().then(adapter => { const bus = new Bus(adapter); bus.dispatchEvent('ready', null); // A message has been sent to the parent window }); 

接下来是什么?


我们有一个灵活,通用的协议,可以在任何情况下使用。 接下来,我计划将适配器与协议分开,并将它们放入单独的npm软件包中,并为工作程序和浏览器选项卡添加适配器。 我希望编写用于其他目的的执行协议的适配器尽可能地容易。 如果您想参与开发过程,或者对库的功能有任何想法,欢迎与您联系。

Source: https://habr.com/ru/post/zh-CN456016/


All Articles