Kommunikationsmodell

Die Interaktion zwischen der Hostanwendung, dem integrierten Verona-Modul, der darunterliegenden Datenbank und dem Endnutzer lassen sich wie folgt beschreiben:

sequenceDiagram
    participant DB as Datenbank
    participant HA as Host-Anwendung

    DB->>HA:Laden Daten
    participant VM as Verona-Modul
    HA->>VM:Initialisierung
    VM->>HA:vopReadyNotification
    HA->>VM:vopStartCommand
    loop
        Actor TP as Person
        VM->TP:Interaktion
        VM->>HA:vopStateChangedNotification
    end
    VM-xTP:Navigation
    HA->>DB:Speichern geänderte Daten
    HA-xVM:Navigation

Initialisierung

Verona-Module werden üblicherweise dynamisch geladen und ausgeführt. Wenn ein bestimmtes Modul erforderlich ist und grundsätzlich im System verfügbar, dann wird es zunächst von der Hostanwendung (Frontend im Browser) in den Hauptspeicher geladen.

Anschließend muss ein <iframe>-Element verfügbar sein. Die Browser unterstützen jetzt alle das Attribut srcdoc, und diesem Attribut wird der Modul-Code zugewiesen. Dies entspricht technisch dem Laden einer Webseite. Dies erfordert eine gewisse (unbekannte) Zeit.

Nachrichten empfangen

Die Kommunikation kann nur erfolgen, wenn die Host-Anwendung Nachrichten an das window-Objekt abfängt. Das Verona-Modul kann nur allgemein an seine Host-Anwendung Nachrichten schicken, nicht gezielt an eine bestimmte Komponente oder ein bestimmtes DOM-Element. Auf höchster Ebene muss also z. B. so etwas einmalig aufgerufen werden:

Listener einrichten bei Start der Hostanwendung
 window.addEventListener('message', (event: MessageEvent) => {
      const msgData = event.data;
      const msgType = msgData.type;
      if ((msgType !== undefined) &&
            (msgType.substr(0, 2) === 'vo')) {
        this.tcs.postMessage$.next(event);
      }
    });

In diesem Beispiel prüft die Funktion, ob der Typ der Nachricht mit ‘vo’ beginnt. In diesem Fall wird angenommen, dass es sich um eine Nachricht von einem Verona-Modul handelt und ein Observable wird auf einen neuen Wert gesetzt. An passenden Stellen im Host kann dann darauf reagiert werden.

Verona-Modul meldet Bereitschaft

Wenn das Modul die eigene Initialisierung abgeschlossen hat, meldet es die Bereitschaft an den Host. Erst ab diesem Zeitpunkt kann man davon ausgehen, dass das Modul Nachrichten empfangen kann. Davor hört es quasi nicht zu.

Im Verona-Kontext liefert das Modul seine Metadaten mit, d. h. der Host kann prüfen, ob das richtige Modul geladen wurde bzw. ob bestimmte Anpassungen in der Kommunikation erfolgen müssen.

Vorsicht bei mehreren Modulen auf einer Seite

Wenn mehrere Module desselben Typs auf einer Seite eingerichtet werden sollen, muss der Host prüfen, von welchem <iframe>-Element die Bereitschaftsmeldung kam (source/target). Anschließend sollte die sessionId verwendet werden, um die Nachrichten bzw. deren Daten zuzuordnen.

Start-Kommando

An das <iframe>-Element wird dann üblicherweise ein Kommando geschickt, das die spezifischen Daten der Unit enthält (Definition, vorherige Antworten, Kodierschema usw.). Damit erstellt bzw. parametrisiert das Modul die erforderlichen Elemente und die Interaktion mit dem User (Testperson, Autorin) kann beginnen.

Beispiel Startkommando für Player
  this.postMessageTarget.postMessage({
        type: 'vopStartCommand',
        sessionId: this.itemPlayerSessionId,
        unitDefinition: pendingUnitDef,
        unitState: {
          dataParts: pendingUnitDataToRestore
        },
        playerConfig: this.tcs.fullPlayerConfig
      }, '*');
`type’ ist Teil des Payloads

Die Konvention sieht vor, dass die Operation-ID als Eigenschaft type stets Teil des Datenobjektes ist. Es gibt keinen separaten Parameter dafür.

Wenn später ein neues Startkommando vom Host geschickt wird, stellt das Modul zunächst den Ausgangszustand zur Initialisierung wieder her und verfährt dann wie oben. Sollten z. B. mehrere Units hintereinander denselben Player anfordern, muss nicht immer neu das gesamte Modul neu initialisiert werden. Der Player kann über ein neues Start-Kommando “nachgenutzt” werden.

Interaktion: Zustandsänderung melden

Während der Interaktion meldet das Verona-Modul Änderungen je nach Typ des Moduls. Ob diese Änderungen gespeichert werden, hängt vom jeweiligen Anwendungsfall ab.

Vorsicht Datenflut

Bei schnellen Tastatureingaben oder ambitionierter Beobachtung des Userverhaltens (Logging) fallen sehr viele Daten an. Beim Player-Modul kann man die Daten aufteilen (sog. dataParts), aber auch sonst könnte es viel werden. Die Hostanwendung sollte einen Puffer einrichten und nicht alles sofort wegspeichern. Bei rxjs gibt es beispielsweise den Operator debounceTime.

Korrekte Zuordnung der Modul-Instanz bzw. der Daten

Bei den Nachrichten gibt es zwei Eigenschaften, die eine korrekte Zuordnung unterstützen:

  • sessionId: Diese ID wird beim Startkommando vom Host vergeben und wird vom Modul bei jeder Nachricht mitgeschickt. Diese ID wird durch das Modul nicht verändert. Der Host kann darüber das Modul-Element identifizieren und daher sicherstellen, dass bei den überwiegend asynchronen Vorgängen im Browser die Daten stets z. B. der richtigen Unit zugeordnet werden.
  • timeStamp: Auch diese Eigenschaft wird stets vom Modul mitgeschickt. Darüber kann sichergestellt werden, dass die Reihenfolge der Nachrichten nicht vertauscht wird. Es ist stets die jeweils letzte Änderung feststellbar.

Beenden: unnötig

Da Änderungen sofort gemeldet werden, ist ein formelles Beenden des Moduls unnötig. Es gibt bei keinem Verona-Modul Bedarf, vor Entfernen des Moduls bestimmte Funktionen aufzurufen. Wenn das Modul nicht mehr benötigt wird (oben dargestellt als Navigation), kann das <iframe>-Element entfernt oder geleert werden (srcdoc="").

Sicherheitshinweis

Über die Verwendung von postMessage() in den MDN Web Docs:

Any window may access this method on any other window, at any time, regardless of the location of the document in the window, to send it a message. Consequently, any event listener used to receive messages must first check the identity of the sender of the message, using the origin and possibly source properties. This cannot be overstated: Failure to check the origin and possibly source properties enables cross-site scripting attacks.

Host-Anwendung und Verona-Modul sollten also alles unternehmen, die Identität des Senders festzustellen. Dazu kann man die Html-Elemente mit IDs versehen und die sessionId verwenden.