PostMessage para TWA

Sayed El-Abady
Sayed El-Abady

No Chrome 115, as atividades confiáveis na Web (TWA) podem enviar mensagens usando postMessage. Este documento explica a configuração necessária para a comunicação entre o app e a Web.

Ao final deste guia, você vai:

  • Entenda como a validação de conteúdo da Web e do cliente funciona.
  • Saiba como inicializar o canal de comunicação entre o cliente e o conteúdo da Web.
  • Saiba como enviar e receber mensagens de conteúdo da Web.

Para seguir este guia, você vai precisar de:

  • Para adicionar a biblioteca androidx.browser (mínima v1.6.0-alpha02) mais recente ao arquivo build.gradle.
  • Chrome versão 115.0.5790.13 ou mais recente para TWA.

O método window.postMessage() permite com segurança a comunicação entre origens diferentes entre objetos Window. Por exemplo, entre uma página e um pop-up que ela gerou ou entre uma página e um iframe incorporado nela.

Normalmente, os scripts em páginas diferentes só podem acessar uns aos outros se as páginas tiverem a mesma origem, compartilharem o mesmo protocolo, número de porta e host (também conhecido como política de mesma origem). O método window.postMessage() fornece um mecanismo controlado para comunicação segura entre diferentes origens. Isso pode ser útil para implementar aplicativos de chat, ferramentas colaborativas e outras. Por exemplo, um aplicativo de chat pode usar postMessage para enviar mensagens entre usuários que estão em sites diferentes. O uso de postMessage em Atividades confiáveis na Web (TWA) pode ser um pouco complicado. Este guia ensina como usar postMessage no cliente TWA para enviar e receber mensagens da página da Web.

Adicionar o app à validação da Web

A API postMessage permite que duas origens válidas se comuniquem, uma de origem e outra de destino. Para que o app Android possa enviar mensagens para a origem de destino, ele precisa declarar a origem de origem equivalente. Isso pode ser feito com Links de recursos digitais (DAL, na sigla em inglês) adicionando o nome do pacote do app no arquivo assetlinks.json com a relação use_as_origin. Vai ficar assim:

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

A configuração na origem associada ao TWA exige que você forneça uma origem para o campo MessageEvent.origin, mas o postMessage pode ser usado para se comunicar com outros sites que não incluem o link de recursos digitais. Por exemplo, se você for proprietário de www.example.com, vai precisar provar isso usando o DAL, mas poderá se comunicar com qualquer outro site, como www.wikipedia.org.

Adicionar o PostMessageService ao manifesto

Para receber a comunicação postMessage, você precisa configurar o serviço. Para isso, adicione PostMessageService ao manifesto do Android:

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

Acessar uma instância do CustomTabsSession

Depois de adicionar o serviço ao manifesto, use a classe CustomTabsClient para vincular o serviço. Depois de conectado, use o cliente fornecido para criar uma nova sessão da seguinte maneira. CustomTabsSession é a classe principal para processar a API postMessage. O código a seguir mostra como, depois que o serviço é conectado, o cliente é usado para criar uma nova sessão, que é usada 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;
 }
});

Você deve estar se perguntando o que é essa instância customTabsCallback, certo? Vamos criar isso na próxima seção.

Criar CustomTabsCallback

CustomTabsCallback é uma classe de callback para que o CustomTabsClient receba mensagens sobre eventos nas guias personalizadas. Um desses eventos é onPostMessage, que é chamado quando o app recebe uma mensagem da Web. Adicione o callback ao cliente para inicializar o canal postMessage e iniciar a comunicação, conforme mostrado no código abaixo.

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

Como se comunicar pela Web

Agora podemos enviar e receber mensagens do app host. Como fazer o mesmo pela Web? A comunicação precisa começar no app host, e a página da Web precisa receber a porta da primeira mensagem. Essa porta é usada para responder. Seu arquivo JavaScript vai ficar parecido com este exemplo:

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

Confira um exemplo completo aqui.

Foto de Joanna Kosinska no Unsplash