Protocolo para la comunicación entre el iframe y la ventana principal del navegador

Muchos desarrolladores necesitan periódicamente establecer comunicación entre varias pestañas del navegador: la capacidad de enviar mensajes de uno a otro y recibir una respuesta. Tal tarea surgió ante nosotros.


Existen soluciones estándar como BroadcastChannel, pero el soporte del navegador ahora deja mucho que desear , por lo que decidimos implementar nuestra biblioteca. Cuando la biblioteca estuvo lista, resultó que tal funcionalidad ya no era necesaria, pero apareció otra tarea: era necesario comunicarse entre el iframe y la ventana principal.


Tras una inspección más cercana, resultó que dos tercios de la biblioteca no se pueden cambiar al mismo tiempo, solo necesita refactorizar un poco el código. La biblioteca es más bien un PROTOCOLO de comunicación que puede trabajar con datos de texto. Se puede usar en todos los casos si es posible transferir texto (iframe, window.open, trabajador, pestañas del navegador, WebSocket).


Como funciona


Por el momento, el protocolo tiene dos funciones: enviar un mensaje y suscribirse a eventos. Cualquier mensaje en el protocolo es un objeto con datos. El campo principal de este objeto es el campo de tipo , que nos dice qué tipo de mensaje es. El campo de tipo es enum con valores:


  • 0 - enviar mensaje
  • 1 - solicitud de envío
  • 2 - recibiendo una respuesta.

Envío de mensajes


Enviar un mensaje no implica una respuesta. Para enviar un evento, construimos un objeto con campos:


  • tipo - tipo de evento 0
  • nombre : nombre del evento del usuario
  • datos : datos de usuario (tipo JSON).

Cuando recibimos un mensaje en el otro lado con el campo type = 0, sabemos que este es un evento y que hay un nombre y datos del evento. Todo lo que queda es activar el evento (un patrón EventEmitter casi regular).


Esquema de trabajo con eventos:



Solicitud de envío


Enviar una solicitud implica que se genera una ID de solicitud dentro de la biblioteca, la biblioteca esperará una respuesta con esta ID, y después de que un servicio de respuesta exitoso se elimine de ella, y la respuesta se devolverá al usuario. Además, puede establecer el tiempo de respuesta máximo.



Con la solicitud, todo es algo más complicado. Para responder a una solicitud, debe declarar los métodos que están disponibles en nuestro protocolo. Esto se hace usando el método registerRequestHandler . Acepta el nombre de la solicitud a la que responderá y la función que devuelve la respuesta. Para crear una solicitud, necesitamos una identificación y, en general, puede usar la marca de tiempo , pero es muy inconveniente depurar. Por lo tanto, esta es la identificación de la clase que envía la solicitud + número de serie de la solicitud + constante de cadena. A continuación, construimos un objeto con campos id , tipo - con un valor de 1, nombre - el nombre de la solicitud, datos - datos de usuario (tipo JSON).


Al recibir la solicitud, verificamos si tenemos una API para responder a esta solicitud, si no hay una API, devolvemos un error. Si hay una API, devolvemos el resultado de la función de registerRequestHandler , con el nombre de solicitud correspondiente.


Se forma un objeto para la respuesta con campos de tipo - con un valor de 2, id - el id del mensaje al que estamos respondiendo, estado - un campo que dice si esta respuesta es un error (si no hay API, o se produjo un error en la salida del usuario, o el usuario devolvió la Promesa rechazada, otros errores (serializar)), contenido - datos de respuesta.


Por lo tanto, describimos el funcionamiento del protocolo en sí, que implementa la clase Bus , pero no describimos cómo enviar y recibir mensajes. Para esto necesitamos adaptadores, una clase con 3 métodos:


  • enviar : un método que es realmente responsable de enviar un mensaje
  • addListener : un método para suscribirse a eventos
  • Destruir : para destruir las suscripciones al destruir el autobús.

Adaptadores Implementación de protocolo.


Para comenzar todo esto, por el momento solo el adaptador para trabajar con iframe / window está listo. Funciona en postMessage y addEventListener . Aquí todo es bastante simple: debe enviar un mensaje a postMessage con el origen correcto y escuchar los mensajes a través de addEventListener en el "mensaje" del evento.


Pequeñas sutilezas que encontramos:


  • Siempre debe escuchar las respuestas en SU ​​ventana y enviárselas a otra persona (iframe, abridor, padre, trabajador, ...).
    El hecho es que cuando intenta escuchar un mensaje en la ventana de otra persona, si el origen difiere del actual, se producirá un error.
  • Cuando reciba un mensaje, asegúrese de que se lo envíen (se activan un montón de mensajes de análisis en la ventana,
    WebStrom (si lo usa), iframes extranjeros, por lo que debe asegurarse de que el evento esté en nuestro protocolo y para nosotros).
  • No puede devolver Promise con una instancia de Window , ya que Promise, al devolver el resultado, intenta verificar si el resultado tiene un método de entonces , y si no tiene acceso a la ventana (una ventana con un origen diferente, por ejemplo), se producirá un error (aunque no en todos los navegadores) ) Para evitar este problema, simplemente envuelva la ventana en un objeto y coloque el objeto Promise en el que hay un enlace a la ventana deseada.

Ejemplos de uso:


La biblioteca se puede instalar usando su administrador de paquetes favorito - @ waves / waves-browser-bus


Para establecer una comunicación bidireccional con un iframe, solo escriba el código:


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', () => { //    iframe }); }); iframe.src = url; //   url   WindowAdapter.createSimpleWindowAdapter document.body.appendChild(iframe); 

Y dentro del iframe:


 import { Bus, WindowAdapter } from '@waves/waves-browser-bus'; WindowAdapter.createSimpleWindowAdapter().then(adapter => { const bus = new Bus(adapter); bus.dispatchEvent('ready', null); //      }); 

Que sigue


Resultó un protocolo flexible y universal que se puede utilizar en cualquier situación.
Ahora planeo separar los adaptadores del protocolo y ponerlos en paquetes npm separados, agregar adaptadores para trabajar con las pestañas de trabajador y navegador. Me gustaría escribir adaptadores que implementen el protocolo para cualquier otra necesidad, fue lo más simple posible.


Si desea unirse al desarrollo o las ideas sobre la funcionalidad de la biblioteca, puede acceder al repositorio .

Source: https://habr.com/ru/post/455942/


All Articles