PostMessage per TWA

Sayed El-Abady
Sayed El-Abady

Dalle funzionalità Attività web attendibili (TWA) di Chrome 115 puoi inviare messaggi utilizzando postMessage. Questo documento illustra la configurazione necessaria per comunicare tra la tua app e il web.

Al termine della guida:

  • Capire come funziona la convalida dei contenuti web e del client.
  • Sapere come inizializzare il canale di comunicazione tra client e contenuti web.
  • Scopri come inviare e ricevere messaggi da contenuti web.

Per seguire questa guida ti serviranno:

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

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

In genere, gli script su pagine diverse possono accedere l'uno all'altro solo se le pagine hanno la stessa origine, 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 visitano siti web diversi. L'uso di postMessage in Attività web attendibili può essere un po' complicato; questa guida ti illustrerà come usare postMessage nel client TWA per inviare e ricevere messaggi dalla pagina web.

Aggiungi 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 per Android possa inviare messaggi all'origine di destinazione, deve dichiarare a quale origine è equivalente. Puoi farlo 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 sull'origine associata al TWA deve fornire un'origine per il campo MessageEvent.origin, ma è possibile utilizzare postMessage per comunicare con altri siti che non includono il link agli asset digitali. Ad esempio, se sei il proprietario di www.example.com, dovrai dimostrarlo tramite DAL, ma puoi comunicare con qualsiasi altro sito web, ad esempio www.wikipedia.org.

Aggiungi PostMessageService al tuo manifest

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

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

Recupera un'istanza CustomTabsSession

Dopo aver aggiunto il servizio al manifest, utilizza la classe CustomTabsClient per associare il servizio. Una volta stabilita la connessione, puoi utilizzare il client fornito per creare una nuova sessione, come indicato di seguito. 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. Questa sessione 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 cosa sia questa istanza di customTabsCallback, giusto. Lo creeremo nella prossima sezione.

Crea callback di schede personalizzate

CustomTabsCallback è una classe di callback che permette a CustomTabsClient di ricevere messaggi relativi agli eventi nelle sue 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 e 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.
    }
};

Comunicare dal web

Ora possiamo inviare e ricevere messaggi dall'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 comunicare con gli altri. Il file JavaScript sarà simile al seguente esempio:

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 completo qui.

Foto di Joanna Kosinska su Unsplash