Protokoll für die Kommunikation zwischen iframe und Hauptbrowserfenster

Viele Entwickler müssen regelmäßig die Kommunikation zwischen mehreren Browser-Registerkarten herstellen: die Möglichkeit, Nachrichten von einem zum anderen zu senden und eine Antwort zu erhalten. Eine solche Aufgabe stellte sich vor uns.


Es gibt Standardlösungen wie BroadcastChannel, aber die Browserunterstützung lässt jetzt zu wünschen übrig . Deshalb haben wir uns entschlossen, unsere Bibliothek zu implementieren. Als die Bibliothek fertig war, stellte sich heraus, dass diese Funktionalität nicht mehr benötigt wurde, aber eine andere Aufgabe erschien: Es war notwendig, zwischen dem Iframe und dem Hauptfenster zu kommunizieren.


Bei näherer Betrachtung stellte sich heraus, dass zwei Drittel der Bibliothek nicht gleichzeitig geändert werden können. Sie müssen den Code nur ein wenig umgestalten. Die Bibliothek ist eher ein Kommunikationsprotokoll, das mit Textdaten arbeiten kann. Es kann in allen Fällen verwendet werden, wenn es möglich ist, Text zu übertragen (iframe, window.open, worker, Browser-Registerkarten, WebSocket).


Wie funktioniert es?


Derzeit hat das Protokoll zwei Funktionen: Senden einer Nachricht und Abonnieren von Ereignissen. Jede Nachricht im Protokoll ist ein Objekt mit Daten. Das Hauptfeld dieses Objekts ist das Typfeld , das uns sagt, um welche Art von Nachricht es sich handelt. Das Typfeld ist eine Aufzählung mit Werten:


  • 0 - Nachricht senden
  • 1 - Anfrage senden
  • 2 - eine Antwort erhalten.

Nachrichten senden


Das Senden einer Nachricht bedeutet keine Antwort. Um ein Ereignis zu senden, erstellen wir ein Objekt mit Feldern:


  • Typ - Ereignistyp 0
  • name - Name des Benutzerereignisses
  • Daten - Benutzerdaten (JSON-ähnlich).

Wenn wir auf der anderen Seite eine Nachricht mit dem Feld Typ = 0 erhalten, wissen wir, dass dies ein Ereignis ist und dass es einen Ereignisnamen und Daten gibt. Alles, was bleibt, ist das Auslösen des Ereignisses (ein fast reguläres EventEmitter- Muster).


Schema der Arbeit mit Veranstaltungen:



Einreichung anfordern


Das Senden einer Anforderung impliziert, dass eine Anforderungs-ID in der Bibliothek generiert wird. Die Bibliothek wartet auf eine Antwort mit dieser ID. Nach einem erfolgreichen Antwortdienst werden Felder daraus gelöscht und die Antwort an den Benutzer zurückgegeben. Außerdem können Sie die maximale Antwortzeit festlegen.



Mit der Anfrage ist alles etwas komplizierter. Um auf eine Anfrage zu antworten, müssen Sie Methoden deklarieren, die in unserem Protokoll verfügbar sind. Dies erfolgt mit der Methode registerRequestHandler . Es akzeptiert den Namen der Anfrage, auf die es antworten wird, und die Funktion, die die Antwort zurückgibt. Zum Erstellen einer Anforderung benötigen wir eine ID . Im Allgemeinen können Sie einen Zeitstempel verwenden . Das Debuggen ist jedoch sehr unpraktisch. Daher ist dies die ID der Klasse, die die Anforderung + Seriennummer der Anforderung + Zeichenfolgenkonstante sendet. Als nächstes konstruieren wir ein Objekt mit den Feldern id , type - mit dem Wert 1, name - dem Namen der Anfrage, data - user data (JSON-like).


Nach Erhalt der Anfrage prüfen wir, ob wir eine API haben, um auf diese Anfrage zu antworten. Wenn keine API vorhanden ist, geben wir einen Fehler zurück. Wenn es eine API gibt, geben wir das Ergebnis der Funktion von registerRequestHandler mit dem entsprechenden Anforderungsnamen zurück.


Für die Antwort wird ein Objekt mit Typfeldern gebildet - mit dem Wert 2, ID - ID der Nachricht, auf die wir antworten, Status - ein Feld, das angibt, ob diese Antwort ein Fehler ist (wenn keine API vorhanden ist oder beim Benutzerexit ein Fehler aufgetreten ist oder der Benutzer das abgelehnte Versprechen zurückgegeben hat). andere Fehler (serialisieren)), Inhalt - Antwortdaten.


Daher haben wir die Funktionsweise des Protokolls selbst beschrieben, das die Busklasse implementiert, aber nicht beschrieben, wie Nachrichten tatsächlich gesendet und empfangen werden. Dafür benötigen wir Adapter - eine Klasse mit 3 Methoden:


  • send - eine Methode, die tatsächlich für das Senden einer Nachricht verantwortlich ist
  • addListener - eine Methode zum Abonnieren von Ereignissen
  • zerstören - um Abonnements zu zerstören, wenn Bus zerstört wird.

Adapter Protokollimplementierung.


Zu Beginn ist derzeit nur der Adapter für die Arbeit mit iframe / window bereit. Es funktioniert mit postMessage und addEventListener . Hier ist alles ganz einfach: Sie müssen eine Nachricht mit dem richtigen Ursprung an postMessage senden und Nachrichten über addEventListener für das Ereignis "message" abhören.


Kleine Feinheiten, auf die wir gestoßen sind:


  • Sie sollten immer auf die Antworten in IHREM Fenster hören und sie an eine andere Person senden (Iframe, Opener, Eltern, Arbeiter, ...).
    Tatsache ist, dass beim Versuch, eine Nachricht im Fenster einer anderen Person abzuhören, ein Fehler auftritt, wenn der Ursprung vom aktuellen abweicht.
  • Wenn Sie eine Nachricht erhalten, stellen Sie sicher, dass sie an Sie gesendet wird (eine Reihe von Nachrichten aus der Analyse werden im Fenster ausgelöst).
    WebStrom (falls Sie es verwenden), fremde Iframes, daher sollten Sie sicherstellen, dass das Ereignis in unserem Protokoll und für uns enthalten ist.
  • Sie können Promise nicht mit einer Instanz von Window zurückgeben , da Promise bei der Rückgabe des Ergebnisses versucht zu überprüfen, ob das Ergebnis eine then- Methode hat. Wenn Sie keinen Zugriff auf das Fenster haben (z. B. ein Fenster mit einem anderen Ursprung), tritt ein Fehler auf (obwohl dies nicht in allen Browsern der Fall ist) ) Um dieses Problem zu vermeiden, wickeln Sie das Fenster einfach in ein Objekt ein und fügen Sie das Promise- Objekt ein, in dem sich ein Link zum gewünschten Fenster befindet.

Anwendungsbeispiele:


Die Bibliothek kann mit Ihrem bevorzugten Paketmanager installiert werden - @ wells / wave-browser-bus


Um eine bidirektionale Kommunikation mit einem Iframe herzustellen, schreiben Sie einfach den 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); 

Und im Iframe:


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

Was weiter?


Es stellte sich ein flexibles und universelles Protokoll heraus, das in jeder Situation verwendet werden kann.
Jetzt plane ich, die Adapter vom Protokoll zu trennen und sie in separate npm-Pakete zu packen. Fügen Sie Adapter für die Arbeit mit Worker- und Browser-Registerkarten hinzu. Ich möchte Adapter schreiben, die das Protokoll für alle anderen Anforderungen implementieren, es war so einfach wie möglich.


Wenn Sie sich der Entwicklung oder den Ideen zur Funktionalität der Bibliothek anschließen möchten, sind Sie im Repository willkommen.

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


All Articles