PostMessage für TWA

Sayed El-Abady
Sayed El-Abady

Ab Chrome 115 können vertrauenswürdige Web-Aktivitäten mithilfe von postMessage Nachrichten senden. In diesem Dokument wird die Einrichtung beschrieben, die für die Kommunikation zwischen Ihrer App und dem Web erforderlich ist.

Am Ende dieses Leitfadens können Sie:

  • Verstehen, wie die Validierung von Client- und Webinhalten funktioniert.
  • Sie müssen wissen, wie Sie den Kommunikationskanal zwischen Client und Webcontent initialisieren.
  • Sie wissen, wie Sie Nachrichten an Webcontent senden und von Webcontent empfangen.

Für diese Anleitung benötigen Sie Folgendes:

  • Fügen Sie Ihrer build.gradle-Datei die neueste androidx.browser-Bibliothek (mind. Version 1.6.0-alpha02) hinzu.
  • Chrome-Version 115.0.5790.13 oder höher für TWA

Die Methode window.postMessage() ermöglicht eine sichere plattformübergreifende Kommunikation zwischen Window-Objekten. Beispielsweise zwischen einer Seite und einem Pop-up, das von ihr gestartet wurde, oder zwischen einer Seite und einem darin eingebetteten Iframe.

Normalerweise dürfen Scripts auf verschiedenen Seiten nur dann auf andere zugreifen, wenn die Seiten aus demselben Ursprung stammen und dasselbe Protokoll, dieselbe Portnummer und denselben Host haben (auch als Same-Origin-Richtlinie bezeichnet). Die window.postMessage()-Methode bietet einen kontrollierten Mechanismus für die sichere Kommunikation zwischen verschiedenen Ursprüngen. Das kann für die Implementierung von Chatanwendungen, Tools für die Zusammenarbeit und anderen Anwendungen nützlich sein. Eine Chat-Anwendung könnte beispielsweise postMessage verwenden, um Nachrichten zwischen Nutzern zu senden, die sich auf verschiedenen Websites befinden. Die Verwendung von postMessage in vertrauenswürdigen Web-Aktivitäten (Trusted Web Activities, TWA) kann etwas schwierig sein. In diesem Leitfaden wird beschrieben, wie Sie mit postMessage im TWA-Client Nachrichten an die Webseite senden und von ihr empfangen.

App zur Webüberprüfung hinzufügen

Die postMessage API ermöglicht es zwei gültigen Ursprüngen, miteinander zu kommunizieren: einer Quelle und einem Zielursprung. Damit die Android-Anwendung Nachrichten an den Zielursprung senden kann, muss sie angeben, mit welchem Quellursprung sie übereinstimmt. Das ist mit Digital Asset Links (DAL) möglich. Fügen Sie dazu den Paketnamen der App in die Datei assetlinks.json mit der Beziehung use_as_origin ein. Das sieht dann so aus:

[{
  "relation": ["delegate_permission/common.use_as_origin"],
  "target" : { "namespace": "android_app", "package_name": "com.example.app", "sha256_cert_fingerprints": [""] }
}]

Bei der Einrichtung auf dem mit der TWA verknüpften Ursprung muss ein Ursprung für das Feld MessageEvent.origin angegeben werden. postMessage kann aber verwendet werden, um mit anderen Websites zu kommunizieren, die keinen Digital Assets Link enthalten. Wenn Sie beispielsweise Inhaber von www.example.com sind, müssen Sie dies über DAL nachweisen. Sie können aber mit anderen Websites kommunizieren, z. B. mit www.wikipedia.org.

PostMessageService zum Manifest hinzufügen

Wenn Sie postMessage-Kommunikation erhalten möchten, müssen Sie den Dienst einrichten. Fügen Sie dazu PostMessageService in Ihr Android-Manifest ein:

<service android:name="androidx.browser.customtabs.PostMessageService"
android:exported="true"/>

CustomTabsSession-Instanz abrufen

Nachdem Sie den Dienst dem Manifest hinzugefügt haben, verwenden Sie die Klasse CustomTabsClient, um den Dienst zu binden. Sobald die Verbindung hergestellt ist, können Sie den bereitgestellten Client verwenden, um eine neue Sitzung zu erstellen: CustomTabsSession ist die Kernklasse für die Verarbeitung der postMessage API. Der folgende Code zeigt, wie, sobald der Dienst verbunden ist, der Client verwendet wird, um eine neue Sitzung zu erstellen, und diese Sitzung für postMessage verwendet wird:

private CustomTabsClient mClient;
private CustomTabsSession mSession;

// We use this helper method to return the preferred package to use for
// Custom Tabs.
String packageName = CustomTabsClient.getPackageName(this, null);

// Binding the service to (packageName).
CustomTabsClient.bindCustomTabsService(this, packageName, new CustomTabsServiceConnection() {
 @Override
 public void onCustomTabsServiceConnected(@NonNull ComponentName name,
     @NonNull CustomTabsClient client) {
   mClient = client;

   // Note: validateRelationship requires warmup to have been called.
   client.warmup(0L);

   mSession = mClient.newSession(customTabsCallback);
 }

 @Override
 public void onServiceDisconnected(ComponentName componentName) {
   mClient = null;
 }
});

Sie fragen sich jetzt, was diese customTabsCallback-Instanz ist. Das erstellen wir im nächsten Abschnitt.

CustomTabsCallback erstellen

CustomTabsCallback ist eine Callback-Klasse für CustomTabsClient, über die Nachrichten zu Ereignissen auf benutzerdefinierten Tabs empfangen werden. Eines dieser Ereignisse ist onPostMessage. Es wird aufgerufen, wenn die App eine Nachricht aus dem Web empfängt. Fügen Sie dem Client den Callback hinzu, um den postMessage-Kanal zu initialisieren und die Kommunikation zu starten, wie im folgenden Code gezeigt.

private final String TAG = "TWA/CCT-PostMessageDemo";

// The origin the TWA is equivalent to, where the Digital Asset Links file
// was created with the "use_as_origin" relationship.
private Uri SOURCE_ORIGIN = Uri.parse("https://source-origin.example.com");

// The origin the TWA will communicate with. In most cases, SOURCE_ORIGIN and
// TARGET_ORIGIN will be the same.
private Uri TARGET_ORIGIN = Uri.parse("https://target-origin.example.com");

// It stores the validation result so you can check on it before requesting
// postMessage channel, since without successful validation it is not possible
// to use postMessage.
boolean mValidated;

CustomTabsCallback customTabsCallback = new CustomTabsCallback() {

    // Listens for the validation result, you can use this for any kind of
    // logging purposes.
    @Override
    public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin,
        boolean result, @Nullable Bundle extras) {
        // If this fails:
        // - Have you called warmup?
        // - Have you set up Digital Asset Links correctly?
        // - Double check what browser you're using.
        Log.d(TAG, "Relationship result: " + result);
        mValidated = result;
    }

    // Listens for any navigation happens, it waits until the navigation finishes
    // then requests post message channel using
    // CustomTabsSession#requestPostMessageChannel(sourceUri, targetUri, extrasBundle)

    // The targetOrigin in requestPostMessageChannel means that you can be certain their messages are delivered only to the website you expect.
    @Override
    public void onNavigationEvent(int navigationEvent, @Nullable Bundle extras) {
        if (navigationEvent != NAVIGATION_FINISHED) {
            return;
        }

        if (!mValidated) {
            Log.d(TAG, "Not starting PostMessage as validation didn't succeed.");
        }

        // If this fails:
        // - Have you included PostMessageService in your AndroidManifest.xml ?
        boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN, new Bundle());
        Log.d(TAG, "Requested Post Message Channel: " + result);
    }

    // This gets called when the channel we requested is ready for sending/receiving messages.
    @Override
    public void onMessageChannelReady(@Nullable Bundle extras) {
        Log.d(TAG, "Message channel ready.");

        int result = mSession.postMessage("First message", null);
        Log.d(TAG, "postMessage returned: " + result);
    }

    // Listens for upcoming messages from Web.
    @Override
    public void onPostMessage(@NonNull String message, @Nullable Bundle extras) {
        super.onPostMessage(message, extras);
        // Handle the received message.
    }
};

Kommunikation über das Web

Wir können jetzt Nachrichten über unsere Host-App senden und empfangen. Wie geht das über das Web? Die Kommunikation muss von der Host-App ausgehen. Die Webseite muss dann den Port aus der ersten Nachricht abrufen. Über diesen Port erfolgt die Rückkommunikation. Ihre JavaScript-Datei sollte in etwa so aussehen:

window.addEventListener("message", function (event) {
  // We are receiveing messages from any origin, you can check of the origin by
  // using event.origin

  // get the port then use it for communication.
  var port = event.ports[0];
  if (typeof port === 'undefined') return;

  // Post message on this port.
  port.postMessage("Test")

  // Receive upcoming messages on this port.
  port.onmessage = function(event) {
    console.log("[PostMessage1] Got message" + event.data);
  };
});

Ein vollständiges Beispiel finden Sie hier.

Foto von Joanna Kosinska auf Unsplash