網頁版 Google Play 的新功能'

自去年推出可信任網路活動後,Chrome 團隊會繼續努力開發,讓 Bubblewrap 更易於使用,進而新增多項功能,例如即將推出的 Google Play 帳款服務整合,並使這項功能能在 ChromeOS 等更多平台上運作。本文將總結受信任網路活動的最新消息和近期更新。

全新「Bubblewrap」和「受信任網路活動」功能

Bubblewrap 可讓您建立應用程式,以便在受信任的網路活動中啟動 PWA,而無需瞭解平台專用工具。

簡化設定流程

過去使用 Bubblewrap 須手動設定 Java Development Kit 和 Android SDK,兩者都很容易出錯。這項工具現在會在首次執行時,提供自動下載外部依附元件的選項。

您仍然可以選擇使用現有的安裝依附元件,也可以這樣做,而新的 doctor 指令可協助您發現問題,並提供設定修正建議。您現在可以透過指令列使用 updateConfig 指令更新這些設定。

改良的精靈

使用 init 建立專案時,Bubblewrap 需要資訊才能產生 Android 應用程式。該工具會從網頁應用程式資訊清單擷取值,並盡可能提供預設值。

您可以在建立新專案時變更這些值,但之前各個欄位的含義不明確。初始化對話方塊經過重新建構,為每個輸入欄位提供更好的說明和驗證。

顯示:支援全螢幕和螢幕方向

在某些情況下,您可能希望應用程式盡可能使用最大螢幕空間,而且在建構 PWA 時,將 display 欄位從網頁應用程式資訊清單設為 fullscreen 即可實作。

當 Bubblewrap 在網頁應用程式資訊清單中偵測到全螢幕選項時,會將 Android 應用程式設定為也以 Android 專屬條款 (或沉浸模式) 啟動。

網頁應用程式資訊清單的 orientation 欄位會定義應用程式應以直向模式、橫向模式,還是裝置目前的螢幕方向啟動。Bubblewrap 現在會讀取「網頁應用程式資訊清單」欄位,並在建立 Android 應用程式時將其設為預設。

您可以在 bubblewrap init 流程中自訂這兩項設定。

AppBundle 輸出內容

應用程式套件是一種應用程式發布格式,會委派最終 APK 產生及簽署 Play。實務上,這可讓從商店下載應用程式時,向使用者提供較小的檔案。

Bubblewrap 現在會將應用程式封裝為應用程式套件,放在名為 app-release-bundle.aab 的檔案中。將應用程式發布到 Play 商店時,建議您採用這個格式,因為自 2021 下半年起,商店必須強制採用此格式

地理位置委派

使用者希望安裝在裝置上的應用程式不管採用什麼技術,都能以一致的方式運作。在受信任的網路活動中使用時,可將 GeoLocation 權限委派給作業系統。啟用後,使用者會看到與使用 Kotlin 或 Java 建構的應用程式相同的對話方塊,且會在同一處尋找可管理權限的控制項。

這項功能可透過 Bubblewrap 新增,且由於它會為 Android 專案新增額外的依附元件,因此只有在網頁應用程式使用地理位置權限時,才啟用該功能。

最佳化二進位檔

儲存空間有限的裝置在全球某些地區很常見,而這些裝置的擁有者通常偏好小型應用程式。使用受信任網路活動的應用程式會產生小型二進位檔,移除這些使用者的一些焦慮。

Bubblewrap 改善了 Bubblewrap 的最佳化機制,縮減了所需的 Android 程式庫清單,進而產生小於 80 萬的二進位檔。實務上,這比先前版本產生的平均大小還不到一半。如要利用較小的二進位檔,您只需使用最新版的 Bubblewrap 更新應用程式。

如何更新現有應用程式

Bubblewrap 產生的應用程式是由網頁應用程式和開啟 PWA 的輕量 Android 包裝函式所組成。即使在受信任網路活動中開啟的 PWA,與任何網頁應用程式的更新週期相同,原生包裝函式仍可以更新,且也應更新。

您應更新應用程式,確保應用程式使用最新版本的包裝函式,並有最新的錯誤修正和功能。安裝最新版 Bubblewrap 後,update 指令會將最新版本的包裝函式套用至現有專案:

npm update -g @bubblewrap/cli
bubblewrap update
bubblewrap build

更新這些應用程式的另一個好處是,確保網路資訊清單的變更會套用至應用程式。請使用新的 merge 指令來執行這項操作:

bubblewrap merge
bubblewrap update
bubblewrap build

品質標準更新

Chrome 第 86 版導入了「受信任網路活動品質」準則,詳情請參閱「使用受信任網路活動 PWA 的品質標準變更」一文。

總而言之,您應確保應用程式能處理以下情境,以免應用程式當機:

  • 無法在應用程式啟動時驗證數位資產連結
  • 離線網路資源要求無法傳回 HTTP 200
  • 在應用程式中傳回 HTTP 404 或 5xx 錯誤。

除了確保應用程式通過 Digital Asset Links 驗證之外,服務工作站也能處理其餘情境:

self.addEventListener('fetch', event => {
  event.respondWith((async () => {
    try {
      return await fetchAndHandleError(event.request);
    } catch {
      // Failed to load from the network. User is offline or the response
      // has a status code that triggers the Quality Criteria.
      // Try loading from cache.
      const cachedResponse = await caches.match(event.request);
      if (cachedResponse) {
        return cachedResponse;
      }
      // Response was not found on the cache. Send the error / offline
      // page. OFFLINE_PAGE should be pre-cached when the service worker
      // is activated.
      return await caches.match(OFFLINE_PAGE);
    }
  })());
});

async function fetchAndHandleError(request) {
  const cache = await caches.open(RUNTIME_CACHE);
  const response = await fetch(request);

  // Throw an error if the response returns one of the status
  // that trigger the Quality Criteria.
  if (response.status === 404 ||
      response.status >= 500 && response.status < 600) {
    throw new Error(`Server responded with status: ${response.status}`);
  }

  // Cache the response if the request is successful.
  cache.put(request, response.clone());
  return response;
}

Workbox 遵循最佳做法,可在使用 Service Worker 時移除樣板。或者,您也可以考慮使用 Workbox 外掛程式來處理下列情境:

export class FallbackOnErrorPlugin {
  constructor(offlineFallbackUrl, notFoundFallbackUrl, serverErrorFallbackUrl) {
    this.notFoundFallbackUrl = notFoundFallbackUrl;
    this.offlineFallbackUrl = offlineFallbackUrl;
    this.serverErrorFallbackUrl = serverErrorFallbackUrl;
  }

  checkTrustedWebActivityCrash(response) {
    if (response.status === 404 || response.status >= 500 && response.status <= 600) {
      const type = response.status === 404 ? 'E_NOT_FOUND' : 'E_SERVER_ERROR';
      const error = new Error(`Invalid response status (${response.status})`);
      error.type = type;
      throw error;
    }
  }

  // This is called whenever there's a network response,
  // but we want special behavior for 404 and 5**.
  fetchDidSucceed({response}) {
    // Cause a crash if this is a Trusted Web Activity crash.
    this.checkTrustedWebActivityCrash(response);

    // If it's a good response, it can be used as-is.
    return response;
  }

  // This callback is new in Workbox v6, and is triggered whenever
  // an error (including a NetworkError) is thrown when a handler runs.
  handlerDidError(details) {
    let fallbackURL;
    switch (details.error.details.error.type) {
      case 'E_NOT_FOUND': fallbackURL = this.notFoundFallbackUrl; break;
      case 'E_SERVER_ERROR': fallbackURL = this.serverErrorFallbackUrl; break;
      default: fallbackURL = this.offlineFallbackUrl;
    }

    return caches.match(fallbackURL, {
      // Use ignoreSearch as a shortcut to work with precached URLs
      // that have _WB_REVISION parameters.
      ignoreSearch: true,
    });
  }
}

Google Play 帳款服務

除了讓您的應用程式在 Play 商店販售數位商品和訂閱項目之外,Google Play 帳款服務也提供多項工具,可用於管理目錄、價格和訂閱項目、實用報表,以及由使用者熟悉的 Play 商店提供的結帳流程。此外,凡是在 Play 商店發布數位商品的應用程式,也必須符合這項規定。

Chrome 88 將在 Android 上推出來源試用,整合 Trusted Web ActivitiesPayment Request APIDigital Goods API,透過 Google Play 帳款服務實作購買流程。這項來源試用功能也應適用於 89 以上版本的 ChromeOS。

重要事項:Google Play Billing API 有專屬的術語,包括用戶端和後端元件。本節僅介紹 API 的一小部分,專門用於數位商品 API 和受信任網路活動。在整合到正式版應用程式之前,請務必詳閱 Google Play 帳款服務說明文件並瞭解其概念。

基本流程

Play 管理中心選單

如要透過 Play 商店提供數位商品,您需要在 Play 商店中設定目錄,並將 Play 商店連結至 PWA 的付款方式。

準備好設定目錄時,請先在 Play 管理中心的左側選單中找到「產品」部分:

這裡會提供查看現有應用程式內商品和訂閱項目的選項, 您也可以使用「建立」按鈕新增商品。

應用程式內產品

產品詳細資料

如要建立新的應用程式內商品,您需要提供產品 ID、名稱、說明和價格。請務必建立有意義的好記的產品 ID,稍後會用到,而且 ID 建立後即無法變更。

建立訂閱項目時,您也必須指定帳單週期。您可以選擇列出訂閱福利,並新增功能,例如是否提供免費試用期、新用戶優惠、寬限期和重新訂閱選項。

建立每項產品後,請啟用產品,讓使用者在應用程式中使用。

如有需要,你也可以透過 Play Developers API 新增產品。

目錄設定完畢後,下一步就是透過 PWA 設定結帳程序。為此,您需要搭配使用 Digital Goods APIPayment Request API

使用 Digital Goods API 擷取產品價格

使用 Google Play 帳款服務時,您可能會想確保向使用者顯示的價格與商店資訊中的價格一致。手動將價格資料保持同步是不可能的任務,因此 Digital Goods API 可讓網頁應用程式向相關付款服務供應商查詢價格:

// The SKU for the product, as defined in the Play Store interface
async function populatePrice(sku) {
  try {
    // Check if the Digital Goods API is supported by the browser.
    if (window.getDigitalGoodsService) {
      // The Digital Goods API can be supported by other Payments provider.
      // In this case, we're retrieving the Google Play Billing provider.
      const service =
          await window.getDigitalGoodsService("https://play.google.com/billing");

      // Fetch product details using the `getDetails()` method.
      const details = await service.getDetails([sku]);

      if (details.length === 0) {
        console.log(`Could not get SKU: "${sku}".`);
        return false;
      }

      // The details will contain both the price and the currenncy.
      item = details[0];
      const value = item.price.value;
      const currency = item.price.currency;

      const formattedPrice = new Intl.NumberFormat(navigator.language, {
        style: 'currency', currency: currency }).format(value);

      // Display the price to the user.
      document.getElementById("price").innerHTML = formattedPrice;
    } else {
      console.error("Could not get price for SKU \"" + sku + "\".");
    }
  } catch (error) {
    console.log(error);
  }
  return false;
}

如要偵測 Digital Goods API 是否支援,請檢查 window 物件是否提供 getDigitalGoodsService()

接著,使用 Google Play 帳款服務 ID 做為參數呼叫 window.getDigitalGoodsService()。 這項操作會傳回 Google Play 帳款服務的服務執行個體,其他廠商則可實作 Digital Goods API 的支援功能,且會有不同的 ID。

最後,請針對 Google Play 帳款服務物件參照呼叫 getDetails(),將該項目的 SKU 做為參數傳遞。此方法將傳回詳細資料物件,其中包含可以向使用者顯示的商品價格和貨幣。

啟動購買流程

Payment Request API 可啟用網路上的購買流程,並用於 Google Play 帳款服務整合作業。請參閱 Payment Request API 的運作方式,進一步瞭解您第一次使用 PaymentRequest API。

如要將 API 與 Google Play 帳款服務搭配使用,您必須新增名為 https://play.google.com/billing 的支援相容性的付款方式,並將 SKU 新增為付款方式資料的一部分:

const supportedInstruments = [{
  supportedMethods: "https://play.google.com/billing",
  data: {
    sku: sku
  }
}];

接著,照常建構 PaymentRequest 物件,並照常使用 API

const request = new PaymentRequest(supportedInstruments, details);

確認購買交易

交易完成後,您需要使用 Digital Goods API 確認付款。PaymentRequest 的回應物件會包含用於確認交易的權杖:

const response = await request.show();
const token = response.details.token;
const service =
          await window.getDigitalGoodsService("https://play.google.com/billing");
await service.acknowledge(token, 'onetime');

Digital Goods API 和 Payment Request API 不會瞭解使用者身分。因此,您可以在後端將購買交易與使用者建立關聯,並確保使用者有權存取購買的商品。為使用者建立購買交易關聯時,請記得儲存購買憑證,因為您可能需要驗證購買憑證是否已取消或退款,或是訂閱項目是否仍然有效。查看 Real Time Developer Notifications APIGoogle Play Developer API,因為這些 API 提供的端點可在後端處理這些記錄。

檢查現有授權

使用者可能已兌換促銷代碼,或是已有產品訂閱。如要驗證使用者是否擁有適當的授權,您可以在數位商品服務上呼叫 listPurchases() 指令。這項操作會傳回客戶在您應用程式中進行的所有購買交易。您也可以在這個位置確認任何未確認的購買交易,確保使用者能正確兌換授權。

const purchases = await itemService.listPurchases();
for (p of purchases) {
  if (!p.acknowledged) {
    await itemService.acknowledge(p.purchaseToken, 'onetime');
  }
}

上傳至 ChromeOS Play 商店

自 Chrome 第 85 版起,ChromeOS Play 商店也會開始提供「Trusted Web Activities」。在 ChromeOS 中列出應用程式的程序與 ChromeOS 相同。

建立應用程式套件後,Play 管理中心會引導您完成必要步驟,在 Play 商店列出應用程式。您可以參閱 Play 管理中心說明文件,瞭解如何建立應用程式資訊、管理 APK 檔案和其他設定,以及測試和安全發布應用程式的相關說明。

如果只要將應用程式限制為 Chromebook,請在 Bubblewrap 中初始化應用程式時新增 --chromeosonly 標記:

bubblewrap init --manifest="https://example.com/manifest.json" --chromeosonly

在不使用 Bubblewrap 的情況下手動建構應用程式時,請在 Android 資訊清單中新增 uses-feature 標記:

<uses-feature  android:name="org.chromium.arc" android:required="true"/>

如果您的資訊與 Android 應用程式分享,則 ChromeOS 專屬套件版本一律必須高於 Android 應用程式套件版本。您可以將 ChromeOS 套裝組合版本設為高於 Android 版本的值,這樣就不必在每個版本更新時一併更新這兩個版本。