PostMessage para TWA

Sayed El-Abady
Sayed El-Abady

A partir de Chrome 115, las Actividades web de confianza (TWA) pueden enviar mensajes con postMessages. En este artículo, se explica la configuración necesaria para establecer la comunicación entre tu app y la Web.

Al final de esta guía, aprenderás lo siguiente: - Comprenderás cómo funciona la validación de cliente y de contenido web. - Conocer cómo inicializar el canal de comunicación entre el cliente y el contenido web. - Sepa cómo enviar y recibir mensajes de contenido web.

Para seguir esta guía, necesitarás lo siguiente:

  • Para agregar la biblioteca androidx.browser más reciente (v1.6.0-alpha02 como mínimo) a tu archivo build.gradle,
  • Chrome versión 115.0.5790.13 o posterior para TWA.

El método window.postMessage() habilita de forma segura la comunicación de origen cruzado entre objetos Window. Por ejemplo, entre una página y una ventana emergente que generó, o entre una página y un iframe incorporado en ella.

Por lo general, las secuencias de comandos de diferentes páginas solo pueden acceder unas a otras si las páginas donde se originan en el mismo origen comparten el mismo protocolo, número de puerto y host (también conocida como política del mismo origen). El método window.postMessage() proporciona un mecanismo controlado para comunicarse de forma segura entre diferentes orígenes. Esto puede ser útil para implementar aplicaciones de chat, herramientas de colaboración y mucho más. Por ejemplo, una aplicación de chat podría usar postMessage para enviar mensajes entre usuarios que estén en sitios web diferentes. El uso de postMessage en Trusted Web Activities (TWA) puede ser un poco complicado. En esta guía, se explica cómo usar postMessage en el cliente TWA para enviar y recibir mensajes de la página web.

Agrega la app a la validación web

Para que postMessage funcione, se requiere una relación válida entre un sitio web y la app de Trusted Web Activity que lanza este sitio. Esto se puede hacer con Vínculos de recursos digitales (DAL). Para ello, agrega el nombre del paquete de la app en tu archivo assetlinks.json con una relación como use_as_origin, de modo que se vea de la siguiente manera:

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

Ten en cuenta que la configuración en el origen asociado con el TWA es obligatorio proporcionar un origen para el campo MessageEvent.origin, pero se puede usar postMessage para comunicarse con otros sitios que no incluyen el Vínculo de recursos digitales. Por ejemplo, si eres el propietario de www.example.com, deberás probarlo a través de DAL, pero podrás comunicarte con cualquier otro sitio web (por ejemplo, www.wikipedia.org).

Cómo agregar el PostMessageService a tu manifiesto

Para recibir comunicación con postMessage, debes configurar el servicio agregando PostMessageService en tu manifiesto de Android:

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

Cómo obtener una instancia CustomTabsSession

Después de agregar el servicio al manifiesto, usa la clase CustomTabsClient para vincular el servicio. Una vez que te conectes, puedes usar el cliente proporcionado para crear una sesión nueva de la siguiente manera. CustomTabsSession es la clase principal para controlar la API de postMessage. En el siguiente código, se muestra cómo una vez que se conecta el servicio, se usa el cliente a fin de crear una sesión nueva. Esta sesión se usa para 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;
 }
});

Te preguntas qué es esta instancia de customTabsCallback. Crearemos esto en la próxima sección.

Crear CustomTabsCallback

CustomTabsCallback es una clase de devolución de llamada para CustomTabsClient a fin de recibir mensajes sobre eventos en sus pestañas personalizadas. Uno de estos eventos es onPostMessage, y se llama a él cuando la app recibe un mensaje de la Web. Agrega la devolución de llamada al cliente para inicializar el canal postMessage y comenzar la comunicación, como se muestra en el siguiente código.

private final String TAG = "TWA/CCT-PostMessageDemo";
private Uri SOURCE_ORIGIN = Uri.parse("my-app-origin-uri");
private Uri TARGET_ORIGIN = Uri.parse("website-you-are-communicating-with");

// It stores the validation result so you can check on it before requesting postMessage channel, since without successful validation it is not posible 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.

 }
};

Cómo comunicarte desde la Web

Ahora podemos enviar y recibir mensajes desde nuestra app host, ¿cómo hacemos lo mismo desde la Web? La comunicación debe comenzar desde la app host y, luego, la página web debe obtener el puerto del primer mensaje. Este puerto se usa para establecer la comunicación. El archivo JavaScript tendrá un aspecto similar al siguiente ejemplo:

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

Puedes encontrar una muestra completa aquí.

Foto de Joanna Kosinska en Unsplash