Protocole de communication entre iframe et la fenĂȘtre principale du navigateur

De nombreux développeurs doivent périodiquement établir une communication entre plusieurs onglets du navigateur: la possibilité d'envoyer des messages de l'un à l'autre et de recevoir une réponse. Une telle tùche s'est présentée à nous.


Il existe des solutions standard comme BroadcastChannel, mais la prise en charge du navigateur laisse dĂ©sormais beaucoup Ă  dĂ©sirer , nous avons donc dĂ©cidĂ© d'implĂ©menter notre bibliothĂšque. Lorsque la bibliothĂšque Ă©tait prĂȘte, il s'est avĂ©rĂ© qu'une telle fonctionnalitĂ© n'Ă©tait plus nĂ©cessaire, mais une autre tĂąche est apparue: il fallait communiquer entre l'iframe et la fenĂȘtre principale.


En y regardant de plus prĂšs, il s'est avĂ©rĂ© que les deux tiers de la bibliothĂšque ne peuvent pas ĂȘtre modifiĂ©s en mĂȘme temps, il vous suffit de refactoriser un peu le code. La bibliothĂšque est plutĂŽt un PROTOCOLE de communication qui peut fonctionner avec des donnĂ©es texte. Il peut ĂȘtre utilisĂ© dans tous les cas s'il est possible de transfĂ©rer du texte (iframe, window.open, travailleur, onglets du navigateur, WebSocket).


Comment ça marche


À l'heure actuelle, le protocole a deux fonctions: l'envoi d'un message et l'inscription aux Ă©vĂ©nements. Tout message du protocole est un objet contenant des donnĂ©es. Le champ principal de cet objet est le champ type , qui nous indique de quel type de message il s'agit. Le champ type est Ă©numĂ©rĂ© avec des valeurs:


  • 0 - envoyer un message
  • 1 - envoi de la demande
  • 2 - recevoir une rĂ©ponse.

Envoi de message


L'envoi d'un message n'implique pas de réponse. Pour envoyer un événement, nous construisons un objet avec des champs:


  • type - type d' Ă©vĂ©nement 0
  • name - nom de l'Ă©vĂ©nement utilisateur
  • data - donnĂ©es utilisateur (de type JSON).

Lorsque nous recevons un message de l'autre cÎté avec le champ type = 0, nous savons qu'il s'agit d'un événement et qu'il existe un nom et des données d'événement. Il ne reste plus qu'à déclencher l'événement (un modÚle EventEmitter presque régulier).


Schéma de travail avec les événements:



Demande de soumission


L'envoi d'une demande implique qu'un ID de demande est généré à l'intérieur de la bibliothÚque, la bibliothÚque attendra une réponse avec cet ID, et aprÚs une réponse réussie, les champs de service en seront supprimés et la réponse sera retournée à l'utilisateur. De plus, vous pouvez définir le temps de réponse maximum.



Avec la demande, tout est un peu plus compliquĂ©. Pour rĂ©pondre Ă  une demande, vous devez dĂ©clarer les mĂ©thodes disponibles dans notre protocole. Cela se fait Ă  l'aide de la mĂ©thode registerRequestHandler . Il accepte le nom de la demande Ă  laquelle il rĂ©pondra et la fonction qui renvoie la rĂ©ponse. Pour crĂ©er une demande, nous avons besoin d'un identifiant , et en gĂ©nĂ©ral, vous pouvez utiliser l' horodatage , mais il est trĂšs gĂȘnant de dĂ©boguer. Par consĂ©quent, il s'agit de l' id de la classe qui envoie la requĂȘte + le numĂ©ro de sĂ©rie de la requĂȘte + la constante de chaĂźne. Ensuite, nous construisons un objet avec les champs id , tapez - avec une valeur de 1, nom - le nom de la demande, donnĂ©es - donnĂ©es utilisateur (de type JSON).


DĂšs rĂ©ception de la demande, nous vĂ©rifions si nous avons une API pour rĂ©pondre Ă  cette demande, s'il n'y a pas d'API, nous renvoyons une erreur. S'il existe une API, nous renvoyons le rĂ©sultat de la fonction de registerRequestHandler , avec le nom de requĂȘte correspondant.


Un objet est formé pour la réponse avec des champs de type - avec une valeur de 2, id - id du message auquel nous répondons, status - un champ qui indique si cette réponse est une erreur (s'il n'y a pas d'API, ou une erreur s'est produite dans la sortie de l'utilisateur, ou si l'utilisateur a renvoyé la promesse rejetée, autres erreurs (sérialiser)), contenu - données de réponse.


Ainsi, nous avons dĂ©crit le fonctionnement du protocole lui-mĂȘme, qui implĂ©mente la classe Bus , mais n'a pas dĂ©crit comment rĂ©ellement envoyer et recevoir des messages. Pour cela, nous avons besoin d'adaptateurs - une classe avec 3 mĂ©thodes:


  • send - une mĂ©thode qui est rĂ©ellement responsable de l'envoi d'un message
  • addListener - une mĂ©thode pour s'abonner aux Ă©vĂ©nements
  • destroy - pour dĂ©truire les abonnements lors de la destruction de Bus.

Adaptateurs Mise en Ɠuvre du protocole.


Pour commencer tout cela, pour le moment, seul l'adaptateur pour travailler avec iframe / window est prĂȘt. Il fonctionne sur postMessage et addEventListener . Tout est assez simple ici: vous devez envoyer un message Ă  postMessage avec l' origine correcte et Ă©couter les messages via addEventListener sur l'Ă©vĂ©nement "message".


Petites subtilités que nous avons rencontrées:


  • Vous devriez toujours Ă©couter les rĂ©ponses sur VOTRE fenĂȘtre et les envoyer Ă  quelqu'un d'autre (iframe, ouvreur, parent, travailleur, ...).
    Le fait est que lorsque vous essayez d'Ă©couter un message sur la fenĂȘtre de quelqu'un d'autre, si l'origine diffĂšre de la fenĂȘtre actuelle, une erreur se produit.
  • Lorsque vous recevez un message, assurez-vous qu'il vous est envoyĂ© (un tas de messages d'analyse sont dĂ©clenchĂ©s sur la fenĂȘtre,
    WebStrom (si vous l'utilisez), iframes étrangers, vous devez donc vous assurer que l'événement est dans notre protocole et pour nous).
  • Vous ne pouvez pas retourner Promise avec une instance de Window , car Promise, lors du retour du rĂ©sultat, essaie de vĂ©rifier si le rĂ©sultat a une mĂ©thode then , et si vous n'avez pas accĂšs Ă  la fenĂȘtre (une fenĂȘtre avec une origine diffĂ©rente, par exemple), une erreur se produira (mais pas dans tous les navigateurs ) Pour Ă©viter ce problĂšme, enveloppez simplement la fenĂȘtre dans un objet et placez l'objet Promise dans lequel il existe un lien vers la fenĂȘtre souhaitĂ©e.

Exemples d'utilisation:


La bibliothĂšque peut ĂȘtre installĂ©e Ă  l'aide de votre gestionnaire de paquets prĂ©fĂ©rĂ© - @ vagues / vagues-navigateur-bus


Pour établir une communication bidirectionnelle avec un iframe, écrivez simplement le code:


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

Et à l'intérieur de l'iframe:


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

Et ensuite?


Il s'est avĂ©rĂ© un protocole flexible et universel qui peut ĂȘtre utilisĂ© dans n'importe quelle situation.
Maintenant, je prévois de séparer les adaptateurs du protocole et de les placer dans des packages npm séparés, d'ajouter des adaptateurs pour travailler avec les onglets de travail et de navigateur. Je voudrais écrire des adaptateurs qui implémentent le protocole pour tout autre besoin, c'était aussi simple que possible.


Si vous souhaitez rejoindre le dĂ©veloppement ou des idĂ©es sur la fonctionnalitĂ© de la bibliothĂšque - vous ĂȘtes les bienvenus dans le rĂ©fĂ©rentiel .

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


All Articles