TWA 的 PostMessage

Sayed El-Abady
Sayed El-Abady

從 Chrome 115 版開始,Trusted Web Activities (TWA) 可以使用 postMessages 傳送訊息。本文將逐步介紹在應用程式和網頁之間進行通訊所需的設定。

完成本指南後,您將瞭解: - 瞭解用戶端和網頁內容驗證的運作方式。 - 瞭解如何初始化用戶端和網頁內容之間的通訊管道。 - 瞭解如何傳送訊息,以及如何從網頁內容接收訊息。

你必須符合下列條件,才能按照本指南的說明操作:

  • 將最新的 androidx.browser (min v1.6.0-alpha02) 程式庫新增至 build.gradle 檔案中。
  • TWA 適用的 Chrome 115.0.5790.13 以上版本。

window.postMessage() 方法可安全地啟用 Window 物件之間的跨來源通訊。例如,介於某個網頁和衍生的彈出式視窗之間,或是網頁和內嵌的 iframe 之間。

一般來說,不同頁面中的指令碼只有在網頁來源相同、採用相同的通訊協定、通訊埠編號和主機 (又稱相同來源政策) 時,才能存取彼此的指令碼。window.postMessage() 方法提供經過控管的機制,可在不同來源之間安全地通訊。在實作即時通訊應用程式、協作工具和其他元件時,這項功能非常實用。舉例來說,即時通訊應用程式可使用 postMessage,在位於不同網站的使用者之間傳送訊息。 在Trusted Web Activities (TWA) 中使用 postMessage 可能不太容易,本指南將引導您瞭解如何在 TWA 用戶端中使用 postMessage,從網頁傳送訊息。

將應用程式加入網路驗證

網站與啟動此網站的「受信任網路活動」應用程式之間必須建立有效關係,PostMessage 可正常運作。方法是使用 Digital Asset Links (DAL),在 assetlinks.json 檔案中加入應用程式的套件名稱,並將關係設為 use_as_origin,如下所示:

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

請注意,在與 TWA 相關聯的來源上設定時,您必須為 MessageEvent.origin 欄位提供來源,但 postMessage 可用來與其他不含 Digital Assets Link 的網站進行通訊。舉例來說,如果您擁有 www.example.com,就必須透過 DAL 證明這一點,但您可以與任何其他網站 (例如 www.wikipedia.org) 通訊。

將 PostMessageService 加入資訊清單

如要接收「postMessage」通訊,您必須在 Android 資訊清單中新增 PostMessageService,完成此設定:

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

取得 CustomTabsSession 執行個體

將服務新增至資訊清單後,請使用 CustomTabsClient 類別來繫結服務。連線後,您可以使用提供的用戶端建立新工作階段,如下所示。 CustomTabsSession 是處理 postMessage API 的核心類別。以下程式碼顯示服務連線後,如何使用用戶端建立新的工作階段,此工作階段則用於 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;
 }
});

您想知道這個 customTabsCallback 例項是什麼嗎?我們將在下一節建立這個項目。

建立自訂分頁回呼

CustomTabsCallback 是 CustomTabsClient 的回呼類別,用於接收自訂分頁中的事件相關訊息。其中一個事件為 onPostMessage,當應用程式收到來自網路的訊息時,就會呼叫此事件。將回呼新增至用戶端,以初始化 postMessage 管道以開始通訊,如以下程式碼所示。

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.

 }
};

透過網頁通訊

既然我們可以從主機應用程式收發訊息,又該如何從網頁進行同樣的操作呢?通訊必須從主機應用程式啟動,而網頁必須從第一則訊息取得通訊埠。這個通訊埠的用途是彼此通訊。您的 JavaScript 檔案看起來會像這樣:

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

您可以前往這裡查看完整範例

相片來源:Joanna KosinskaUnsplash 網站上