PostMessage pour TWA

Sayed El-Abady
Sayed El-Abady

À partir de Chrome 115, les activités Web fiables peuvent envoyer des messages à l'aide de postMessage. Ce document explique la configuration nécessaire pour communiquer entre votre application et le Web.

À la fin de ce guide, vous saurez:

  • Comprendre le fonctionnement de la validation du client et du contenu Web
  • Savoir comment initialiser le canal de communication entre le client et le contenu Web
  • Savoir envoyer des messages à des contenus Web et en recevoir

Pour suivre ce guide, vous avez besoin des éléments suivants:

  • Pour ajouter la dernière bibliothèque androidx.browser (version minimale v1.6.0-alpha02) à votre fichier build.gradle :
  • Chrome 115.0.5790.13 ou version ultérieure pour TWA

La méthode window.postMessage() permet de manière sécurisée la communication inter-origine entre les objets Window. Par exemple, entre une page et un pop-up qu'elle a généré, ou entre une page et un iframe intégré.

En règle générale, les scripts situés sur des pages différentes ne sont autorisés à accéder les uns aux autres que si les pages qui proviennent de la même origine et partagent le même protocole, numéro de port et hôte (également appelé règle d'origine identique). La méthode window.postMessage() fournit un mécanisme contrôlé pour communiquer de manière sécurisée entre différentes origines. Cela peut être utile pour implémenter des applications de chat, des outils collaboratifs, etc. Par exemple, une application de chat peut utiliser postMessage pour envoyer des messages entre des utilisateurs qui se trouvent sur différents sites Web. L'utilisation de postMessage dans les activités Web fiables peut s'avérer un peu délicate. Ce guide vous explique comment utiliser postMessage dans le client TWA pour envoyer des messages à la page Web et en recevoir.

Ajouter l'application à la validation Web

L'API postMessage permet à deux origines valides de communiquer entre elles, une origine source et une origine cible. Pour que l'application Android puisse envoyer des messages à l'origine cible, elle doit déclarer l'origine source à laquelle elle est équivalente. Pour ce faire, utilisez des liens d'éléments numériques (DAL) en ajoutant le nom de package de l'application dans votre fichier assetlinks.json avec la relation use_as_origin. Le fichier se présente alors comme suit:

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

Notez que pour la configuration de l'origine associée à la TWA, vous devez fournir une origine pour le champ MessageEvent.origin, mais postMessage peut être utilisé pour communiquer avec d'autres sites qui n'incluent pas le lien vers les ressources numériques. Par exemple, si vous êtes propriétaire de www.example.com, vous devrez le prouver via DAL, mais vous pouvez communiquer avec d'autres sites Web, comme www.wikipedia.org.

Ajouter PostMessageService à votre fichier manifeste

Pour recevoir des communications postMessage, vous devez configurer le service. Pour ce faire, ajoutez PostMessageService dans votre fichier manifeste Android:

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

Obtenir une instance CustomTabsSession

Après avoir ajouté le service au fichier manifeste, utilisez la classe CustomTabsClient pour le lier. Une fois connecté, vous pouvez utiliser le client fourni pour créer une session comme suit. CustomTabsSession est la classe de base pour gérer l'API postMessage. Le code suivant montre qu'une fois le service connecté, le client est utilisé pour créer une session, cette session est utilisée pour postMessage:

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

Vous vous demandez peut-être à quoi sert cette instance customTabsCallback. Nous allons le créer dans la section suivante.

Créer un CustomTabsCallback

CustomTabsCallback est une classe de rappel permettant à CustomTabsClient de recevoir des messages concernant les événements dans ses onglets personnalisés. L'un de ces événements est onPostMessage, qui est appelé lorsque l'application reçoit un message du Web. Ajoutez le rappel au client pour initialiser le canal postMessage et démarrer la communication, comme indiqué dans le code suivant.

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.
    }
};

Communiquer depuis le Web

Nous pouvons maintenant envoyer et recevoir des messages depuis notre application hôte. Comment faire de même depuis le Web ? La communication doit commencer à partir de l'application hôte, puis la page Web doit obtenir le port à partir du premier message. Ce port est utilisé pour la communication en retour. Votre fichier JavaScript ressemblera à l'exemple suivant:

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

Vous trouverez un exemple complet ici.

Photo de Joanna Kosinska sur Unsplash