PostMessage per TWA

Sayed El-Abady
Sayed El-Abady

A partire da Chrome 115, le Attività web attendibili (TWA) possono inviare messaggi utilizzando postMessage. Questo documento illustra la configurazione necessaria per la comunicazione tra l'app e il web.

Al termine di questa guida:

  • Scopri come funziona la convalida dei contenuti web e del client.
  • Devi sapere come inizializzare il canale di comunicazione tra il client e i contenuti web.
  • Devi sapere come inviare messaggi a webcontent e ricevere messaggi da webcontent.

Per seguire questa guida avrai bisogno di:

  • Per aggiungere la libreria androidx.browser (min v1.6.0-alpha02) più recente al file build.gradle.
  • Chrome versione 115.0.5790.13 o successive per TWA.

Il metodo window.postMessage() abilita in modo sicuro la comunicazione cross-origin tra oggetti Window. Ad esempio, tra una pagina e un popup che ha generato o tra una pagina e un iframe incorporato al suo interno.

Di solito, gli script su pagine diverse possono accedere l'uno all'altro solo se le pagine provengono dalla stessa origine e condividono lo stesso protocollo, lo stesso numero di porta e lo stesso host (noto anche come criterio della stessa origine). Il metodo window.postMessage() fornisce un meccanismo controllato per comunicare in modo sicuro tra origini diverse. Ciò può essere utile per implementare applicazioni di chat, strumenti di collaborazione e altro ancora. Ad esempio, un'applicazione di chat potrebbe utilizzare postMessage per inviare messaggi tra utenti che si trovano su siti web diversi. L'utilizzo di postMessage in Attività web attendibili (TWA) può essere un po' complicato e questa guida illustra come utilizzare postMessage nel client TWA per inviare e ricevere messaggi dalla pagina web.

Aggiungere l'app alla convalida web

L'API postMessage consente a due origini valide di comunicare tra loro: un'origine e un'origine di destinazione. Affinché l'applicazione Android possa inviare messaggi all'origine di destinazione, deve dichiarare a quale origine di origine è equivalente. Questo può essere fatto con Digital Asset Links (DAL) aggiungendo il nome del pacchetto dell'app nel file assetlinks.json con la relazione use_as_origin, quindi sarà il seguente:

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

Tieni presente che la configurazione dell'origine associata al TWA richiede di fornire un'origine per il campo MessageEvent.origin, ma postMessage può essere utilizzato per comunicare con altri siti che non includono il link agli asset digitali. Ad esempio, se possiedi www.example.com, dovrai dimostrarlo tramite DAL, ma puoi comunicare con qualsiasi altro sito web, ad esempio www.wikipedia.org.

Aggiungi PostMessageService al manifest

Per ricevere comunicazioni postMessage, devi configurare il servizio aggiungendo PostMessageService nel file manifest Android:

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

Ottieni un'istanza CustomTabsSession

Dopo aver aggiunto il servizio al manifest, utilizza la classe CustomTabsClient per associarlo. Una volta connesso, puoi utilizzare il client fornito per creare una nuova sessione come segue. CustomTabsSession è la classe di base per la gestione dell'API postMessage. Il codice seguente mostra come, una volta connesso il servizio, il client viene utilizzato per creare una nuova sessione, che viene utilizzata per 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;
 }
});

Ti stai chiedendo cos'è questa istanza di customTabsCallback, giusto? Lo creeremo nella prossima sezione.

Crea CustomTabsCallback

CustomTabsCallback è una classe di callback per CustomTabsClient per ricevere messaggi relativi agli eventi nelle relative schede personalizzate. Uno di questi eventi è onPostMessage e viene chiamato quando l'app riceve un messaggio dal web. Aggiungi il callback al client per inizializzare il canale postMessage per avviare la comunicazione, come mostrato nel codice seguente.

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

Comunicazione dal web

Ora possiamo inviare e ricevere messaggi dalla nostra app host. Come facciamo a fare lo stesso dal web? La comunicazione deve iniziare dall'app host, quindi la pagina web deve ottenere la porta dal primo messaggio. Questa porta viene utilizzata per la comunicazione di ritorno. Il file JavaScript avrà il seguente aspetto:

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

Puoi trovare un esempio completo qui

Foto di Joanna Kosinska su Unsplash