Web In Play의 새로운 기능

작년에 신뢰할 수 있는 웹 활동이 도입된 이후 Chrome팀은 제품 개발을 위해 계속 노력하여 Bubblewrap을 더욱 간편하게 사용할 수 있도록 하고, 예정된 Google Play 결제 통합과 같은 새로운 기능을 추가하며, ChromeOS와 같은 더 많은 플랫폼에서 작동할 수 있도록 지원하고 있습니다. 이 도움말에서는 신뢰할 수 있는 웹 활동에 관한 최신 및 향후 업데이트를 요약합니다.

새로운 Bubblewrap 및 신뢰할 수 있는 웹 활동 기능

Bubblewrap을 사용하면 플랫폼별 도구에 관한 지식 없이도 신뢰할 수 있는 웹 활동 내에서 PWA를 실행하는 앱을 만들 수 있습니다.

간소화된 설정 흐름

이전에는 버블랩을 사용하려면 자바 개발 키트와 Android SDK를 수동으로 설정해야 했으며 둘 다 오류가 발생하기 쉬웠습니다. 이제 도구를 처음 실행할 때 외부 종속 항목을 자동으로 다운로드할 수 있습니다.

원하는 경우 기존 종속 항목 설치를 사용할 수도 있으며, 새 doctor 명령어를 사용하면 문제를 발견하고 구성 수정을 제안하는 데 도움이 됩니다. 이제 명령줄에서 updateConfig 명령어를 사용하여 업데이트할 수 있습니다.

마법사 개선

init로 프로젝트를 만들 때 Bubblewrap에는 Android 앱을 생성하기 위한 정보가 필요합니다. 이 도구는 웹 앱 매니페스트에서 값을 추출하고 가능한 경우 기본값을 제공합니다.

새 프로젝트를 만들 때 이러한 값을 변경할 수 있지만 이전에는 각 필드의 의미가 명확하지 않았습니다. 각 입력란에 대한 더 나은 설명 및 유효성 검사로 초기화 대화상자가 다시 빌드되었습니다.

디스플레이: 전체 화면 및 방향 지원

경우에 따라 애플리케이션에서 화면을 최대한 많이 사용하고 PWA를 빌드할 때 이를 구현할 수 있습니다. 웹 앱 매니페스트의 display 필드를 fullscreen로 설정하면 됩니다.

Bubblewrap은 웹 앱 매니페스트에서 전체 화면 옵션을 감지하면 Android 관련 용어로 전체 화면 또는 몰입형 모드로도 실행되도록 Android 애플리케이션을 구성합니다.

웹 앱 매니페스트의 orientation 필드는 애플리케이션을 세로 모드, 가로 모드 또는 기기에서 현재 사용 중인 방향으로 시작할지 여부를 정의합니다. 이제 버블랩은 웹 앱 매니페스트 필드를 읽고 Android 앱을 만들 때 이를 기본값으로 사용합니다.

두 구성 모두 bubblewrap init 흐름의 일부로 맞춤설정할 수 있습니다.

AppBundle 출력

App Bundle은 최종 APK 생성 및 서명을 Play에 위임하는 앱의 게시 형식입니다. 실제로 스토어에서 앱을 다운로드할 때 사용자에게 더 작은 파일을 제공할 수 있습니다.

이제 Bubblewrap은 애플리케이션을 app-release-bundle.aab라는 파일에 App Bundle로 패키징합니다. Play 스토어에 앱을 게시할 때는 이 형식을 사용하는 것이 좋습니다. 2021년 하반기부터 스토어에 요구되기 때문입니다.

위치정보 위임

사용자는 기기에 설치된 애플리케이션이 기술과 관계없이 일관되게 작동하기를 기대합니다. 신뢰할 수 있는 웹 활동 내에서 사용할 경우 이제 GeoLocation 권한을 운영체제에 위임할 수 있습니다. 이 권한을 사용 설정하면 Kotlin 또는 Java로 빌드된 앱과 동일한 대화상자가 사용자에게 표시되고 같은 위치에서 권한을 관리하는 컨트롤을 찾습니다.

이 기능은 버블랩을 통해 추가할 수 있으며, Android 프로젝트에 추가 종속 항목을 추가하므로 웹 앱에서 위치정보 권한을 사용할 때만 사용 설정해야 합니다.

최적화된 바이너리

저장용량이 제한된 기기는 세계의 특정 지역에서는 일반적이며 이러한 기기 소유자는 작은 애플리케이션을 선호하는 경우가 많습니다. 신뢰할 수 있는 웹 활동을 사용하는 애플리케이션은 작은 바이너리를 생성하여 사용자의 불안감을 일부 제거합니다.

버블랩은 필요한 Android 라이브러리의 목록을 줄여 최적화되었고, 그 결과 800k 더 작은 바이너리가 생성됩니다. 실제로 이는 이전 버전에서 생성된 평균 크기의 절반 미만입니다. 더 작은 바이너리를 활용하려면 최신 버전의 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 오류를 반환합니다.

애플리케이션이 디지털 애셋 링크 유효성 검사를 통과하는지 확인하는 것 외에도 나머지 시나리오는 서비스 워커가 처리할 수 있습니다.

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는 권장사항에 따라 베이킹하고 서비스 워커를 사용할 때 상용구를 삭제합니다. 또는 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은 Google Play 결제를 통해 구매 흐름을 구현할 수 있도록 신뢰할 수 있는 웹 활동, Payment Request API, Digital Goods API를 통합할 수 있는 Android 오리진 트라이얼과 함께 출시됩니다. 이 오리진 트라이얼은 ChromeOS 버전 89에서도 제공될 예정입니다

중요: Google Play Billing API에는 자체 용어가 있으며 클라이언트 및 백엔드 구성요소를 포함합니다. 이 섹션에서는 Digital Goods API 및 신뢰할 수 있는 웹 활동 사용과 관련된 API의 일부분만 다룹니다. 프로덕션 애플리케이션에 통합하기 전에 Google Play 결제 문서를 읽고 개념을 숙지하시기 바랍니다.

기본 흐름

Play Console 메뉴

Play 스토어를 통해 디지털 상품을 제공하려면 Play 스토어에서 카탈로그를 구성하고 PWA의 결제 수단으로 Play 스토어를 연결해야 합니다.

카탈로그를 구성할 준비가 되면 먼저 Play Console의 왼쪽 메뉴에서 제품 섹션을 찾습니다.

여기에서 기존 인앱 상품 및 정기 결제를 볼 수 있는 옵션과 새 인앱 상품 및 정기 결제를 추가할 수 있는 만들기 버튼도 있습니다.

인앱 상품

제품 세부정보

새 인앱 상품을 만들려면 제품 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;
}

window 객체에서 getDigitalGoodsService()를 사용할 수 있는지 확인하여 Digital Goods API에 대한 지원을 감지할 수 있습니다.

그런 다음 Google Play 결제 식별자를 매개변수로 사용하여 window.getDigitalGoodsService()를 호출합니다. 이렇게 하면 Google Play 결제의 서비스 인스턴스가 반환되고 다른 공급업체가 Digital Goods API에 관한 지원을 구현할 수 있으며 서로 다른 식별자를 갖습니다.

마지막으로 항목의 SKU를 매개변수로 전달하는 Google Play 결제 객체 참조에서 getDetails()를 호출합니다. 이 메서드는 사용자에게 표시될 수 있는 항목의 가격과 통화를 모두 포함하는 세부정보 객체를 반환합니다.

구매 절차를 시작합니다.

Payment Request API는 웹에서 구매 흐름을 지원하며 Google Play 결제 통합에도 사용됩니다. Payment Request API를 처음 사용하는 경우 Payment Request API 작동 방식에서 자세히 알아보세요.

Google Play 결제에서 API를 사용하려면 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는 사용자의 ID를 알지 못합니다. 따라서 구매를 백엔드의 사용자와 연결하고 사용자가 구매한 항목에 액세스할 수 있는지 확인하는 것은 개발자의 몫입니다. 구매를 사용자와 연결할 때는 구매가 취소 또는 환불되었는지 또는 정기 결제가 아직 활성 상태인지 확인하는 데 필요할 수 있으므로 구매 토큰을 저장해야 합니다. 백엔드에서 이러한 케이스를 처리하기 위한 엔드포인트를 제공하는 Real Time Developer Notifications APIGoogle Play Developer 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 스토어에서 신뢰할 수 있는 웹 활동도 사용할 수 있습니다. 스토어에 앱을 나열하는 프로세스는 Android와 ChromeOS에서 동일합니다.

App Bundle을 만들면 Play Console에서 Play 스토어에 앱을 등록하는 데 필요한 단계를 안내합니다. Play Console 문서에서 앱 등록정보 만들기, APK 파일 및 기타 설정 관리, 테스트 및 안전하게 앱 출시에 관한 안내를 확인할 수 있습니다.

애플리케이션을 Chromebook으로만 제한하려면 Bubblewrap에서 애플리케이션을 초기화할 때 --chromeosonly 플래그를 추가합니다.

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

버블랩 없이 수동으로 애플리케이션을 빌드할 때는 다음과 같이 Android 매니페스트에 uses-feature 플래그를 추가합니다.

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

등록정보를 Android 앱과 공유하는 경우 ChromeOS 전용 패키지 버전은 항상 Android 앱 패키지 버전보다 높아야 합니다. ChromeOS 번들 버전을 Android 버전보다 훨씬 높은 수로 설정할 수 있으므로 각 출시마다 두 버전을 모두 업데이트할 필요가 없습니다.