하위 앱

비공식 제안 초안,

이 버전:
https://WICG.github.io/sub-apps
이슈 추적:
GitHub
명세 내 인라인
편집자:
(Google)

초록

하위 앱 API를 사용하면 상위 애플리케이션 컨텍스트가 상위 앱의 출처, 저장소 및 수명 주기 범위를 공유하는 보조 애플리케이션을 프로그래밍 방식으로 설치하고, 나열하고, 제거할 수 있으며, 운영 체제에는 고유한 이름, 아이콘 및 창 ID를 표시할 수 있다.

이 문서의 상태

이 명세는 웹 플랫폼 인큐베이터 커뮤니티 그룹에서 발행했다. 이 문서는 W3C 표준이 아니며 W3C 표준화 절차에도 포함되지 않는다. 다음 사항에 유의해야 한다. W3C 커뮤니티 기여자 라이선스 계약 (CLA)에 따라 제한적인 탈퇴 선택권이 있으며 기타 조건이 적용된다. W3C 커뮤니티 및 비즈니스 그룹에 대해 자세히 알아본다.

1. 소개

하위 앱 API를 사용하면 상위 애플리케이션이 다음과 같은 보조 애플리케이션(하위 앱)을 프로그래밍 방식으로 설치하고, 나열하고, 제거할 수 있다.

  1. 운영 체제와 사용자에게 완전히 별개의 애플리케이션으로 표시된다(별도의 런처 아이콘, 고유한 작업 표시줄/선반 창 및 개별 OS 통합).

  2. 상위 애플리케이션의 기반 리소스, 출처, 저장소, 권한 및 업데이트 수명 주기를 공유한다.

보안과 데이터 무결성을 보장하기 위해 이 API는 격리된 컨텍스트로 제한된다.

2. 개념

설치된 웹 애플리케이션에는 연결된 상위 앱이 있으며, 이는 null이거나 설치된 웹 애플리케이션이다.

설치된 웹 애플리케이션에는 연결된 하위 앱 집합이 있으며, 이는 집합 형태의 설치된 웹 애플리케이션들이다.

Document에는 연결된 설치된 웹 애플리케이션이 있으며, 이는 해당 Document가 일부로 표시되는 설치된 웹 애플리케이션이다. 이 연결에 대한 정확한 메커니즘은 구현 정의이다.

Document연결된 설치된 웹 애플리케이션이 있고 그 연결된 설치된 웹 애플리케이션상위 앱이 null이 아니면 하위 앱 문서이다.

3. Window 인터페이스의 확장

[Exposed=Window, SecureContext, IsolatedContext]
partial interface Window {
  [SameObject] readonly attribute SubApps subApps;
};

3.1. subApps 속성

Window 객체에는 연결된 subApps가 있으며, 이는 해당 Window와 함께 생성된 SubApps 인스턴스이다.

subApps getter 단계는 다음과 같다.
  1. thissubApps를 반환한다.

4. SubApps 인터페이스

// https://w3c.github.io/manifest/#id-member를 나타낸다
typedef USVString ManifestId;

dictionary SubAppsAddResponse {
  record<USVString, ManifestId> installedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsRemoveResponse {
  sequence<ManifestId> removedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsListResult {
  required DOMString appName;
};

[
  Exposed=Window,
  SecureContext,
  IsolatedContext
] interface SubApps {
  Promise<SubAppsAddResponse> add(sequence<USVString> install_paths);
  Promise<SubAppsRemoveResponse> remove(sequence<ManifestId> manifest_ids);
  Promise<record<USVString, SubAppsListResult>> list();
};

다음 조건을 모두 충족하는 경우 문자열 pathDocument document에 대한 유효한 상대 경로이다.

  1. path는 유효한 절대 URL이 아니다.

  2. path"/"로 시작한다.

  3. path는 비어 있지 않다.

  4. path"//"로 시작하지 않는다.

  5. document문서 기준 URL을 기준 URL로 사용하여 path구문 분석한 결과가 실패가 아니다.

4.1. add() 메서드

add(install_paths) 메서드 단계는 다음과 같다.

install_paths 인수는 하위 앱의 시작 HTML 페이지를 가리키는 상대 경로 목록이다.

  1. promise새 promise로 둔다.

  2. document관련 전역 객체연결된 Document로 둔다.

  3. document에 "sub-apps"라는 이름의 정책 제어 기능사용할 권한이 없다면, promise를 "SecurityError" DOMException으로 거부하고 promise를 반환한다.

  4. document하위 앱 문서라면, promise를 "NotSupportedError" DOMException으로 거부하고 promise를 반환한다.

  5. parsedUrls를 빈 목록으로 둔다.

  6. install_paths의 각 installPath에 대해 다음을 수행한다:

    1. installPathdocument에 대한 유효한 상대 경로가 아니라면, promise를 "TypeError" DOMException으로 거부하고 promise를 반환한다.

    2. absoluteUrldocument문서 기준 URL을 기준 URL로 사용하여 installPath파싱한 결과로 둔다.

    3. absoluteUrl이 실패라면, promise를 "TypeError" DOMException으로 거부하고 promise를 반환한다.

    4. absoluteUrlparsedUrls에 추가한다.

  7. parentAppdocument연결된 설치된 웹 애플리케이션으로 둔다.

  8. currentSubAppsCountparentApp하위 앱 집합크기로 둔다.

  9. install_paths크기가 20보다 크다면, promise를 "QuotaExceededError" DOMException으로 거부하고 promise를 반환한다.

  10. currentSubAppsCount + install_paths크기가 50보다 크다면, promise를 "QuotaExceededError" DOMException으로 거부하고 promise를 반환한다.

  11. subAppsthis로 둔다.

  12. 다음 단계를 병렬로 실행한다:

    1. userConsentinstall_paths의 하위 앱 설치에 대한 사용자 동의를 요청한 결과로 둔다 (예: 통합 설치 대화 상자를 표시하는 방식).

    2. userConsent가 거부되었다면, subApps관련 전역 객체에서 promise를 "NotAllowedError" DOMException으로 거부하는 전역 작업을 큐에 넣고, 이 단계들을 중단한다.

    3. installedApps를 빈 맵으로 둔다.

    4. failedApps를 빈 맵으로 둔다.

    5. parsedUrls의 각 absoluteUrl에 대해 다음을 수행한다:

      1. installPathabsoluteUrl경로로 둔다.

      2. manifestabsoluteUrl이 주어졌을 때 매니페스트를 가져와 처리한 결과로 둔다.

      3. manifest가 실패라면, 다음 단계를 수행한다:

        1. failedApps[installPath]를 새 "DataError" DOMException으로 설정한다.

        2. 계속한다.

      4. manifestIdmanifestid로 둔다. 이것이 정의되지 않았다면, manifeststart_url을 대신 사용한다(참조/해시 조각 제외).

      5. parentAppmanifestId를 사용하는 하위 앱이 이미 설치되어 있다면, 다음 단계를 수행한다:

        1. failedApps[installPath]를 새 "InvalidStateError" DOMException으로 설정한다.

        2. 계속한다.

      6. parentApp매니페스트범위manifest범위접두사이거나, manifest범위parentApp하위 앱 집합에 현재 설치된 하위 앱 중 하나의 범위접두사이거나, parentApp하위 앱 집합에 현재 설치된 하위 앱 중 하나의 범위manifest범위접두사이거나, absoluteUrlparentApp 자체의 매니페스트를 가리킨다면, 다음 단계를 수행한다:

        1. failedApps[installPath]를 새 "ConstraintError" DOMException으로 설정한다.

        2. 계속한다.

      7. 플랫폼의 애플리케이션 실행기에 하위 앱 설치를 시도한다.

      8. 시스템 또는 데이터베이스 오류로 인해 설치에 실패한다면:

        1. failedApps[installPath]를 새 "OperationError" DOMException으로 설정한다.

        2. 계속한다.

      9. installedApps[installPath]를 manifestId설정한다.

    6. response를 다음 항목이 포함된 새 SubAppsAddResponse 사전으로 둔다:

    7. subApps관련 전역 객체에서 promiseresponse이행하는 전역 작업을 큐에 넣는다.

  13. promise를 반환한다.

4.2. remove() 메서드

manifest_ids 인수는 제거할 하위 앱의 id 목록이다.

remove(manifest_ids) 메서드 단계는 다음과 같다.

  1. promise새 promise로 둔다.

  2. document관련 전역 객체연결된 Document로 둔다.

  3. document가 "sub-apps"라는 이름의 정책 제어 기능사용하도록 허용되지 않았다면, promise를 "SecurityError" DOMException으로 거부하고 promise를 반환한다.

  4. document하위 앱 문서라면, promise를 "NotSupportedError" DOMException으로 거부하고 promise를 반환한다.

  5. parentAppdocument연결된 설치된 웹 애플리케이션으로 둔다.

  6. parsedManifestIds를 빈 목록으로 둔다.

  7. manifest_ids의 각 manifestId에 대해 다음을 수행한다:

    1. manifestIddocument에 대한 유효한 상대 경로가 아니라면, promise를 "TypeError" DOMException으로 거부하고 promise를 반환한다.

    2. parsedUrldocument문서 기준 URL을 기준 URL로 사용하여 manifestId파싱한 결과로 둔다.

    3. parsedUrl이 실패라면, promise를 "TypeError" DOMException으로 거부하고 promise를 반환한다.

    4. parsedUrlparsedManifestIds에 추가한다.

  8. subAppsthis로 둔다.

  9. 다음 단계를 병렬로 실행한다:

    1. removedApps를 빈 시퀀스로 둔다.

    2. failedApps를 빈 맵으로 둔다.

    3. parsedManifestIds의 각 parsedUrl에 대해 다음을 수행한다:

      1. manifestIdparsedUrl경로로 둔다.

      2. parentApp하위 앱 집합idmanifestId설치된 웹 애플리케이션이 없다면, 다음 단계를 수행한다:

        1. failedApps[manifestId]를 새 "NotFoundError" DOMException으로 설정한다.

        2. 계속한다.

      3. 시스템 실행기와 레지스트리에서 ID가 manifestId인 하위 앱의 제거를 시도한다.

      4. 시스템 오류로 인해 제거에 실패한다면, 다음 단계를 수행한다:

        1. failedApps[manifestId]를 새 "OperationError" DOMException으로 설정한다.

        2. 계속한다.

      5. manifestIdremovedApps에 추가한다.

    4. response를 다음 항목이 포함된 새 SubAppsRemoveResponse 사전으로 둔다:

    5. subApps관련 전역 객체에서 promiseresponse이행하도록 전역 작업을 큐에 넣는다.

  10. promise를 반환한다.

4.3. list() 메서드

list() 메서드 단계는 다음과 같다.

  1. promise새 promise로 둔다.

  2. document관련 전역 객체연결된 Document로 둔다.

  3. document가 "sub-apps"라는 이름의 정책 제어 기능사용하도록 허용되지 않았다면, promise를 "SecurityError" DOMException으로 거부하고 promise를 반환한다.

  4. document하위 앱 문서라면, promise를 "NotSupportedError" DOMException으로 거부하고 promise를 반환한다.

  5. parentAppdocument연결된 설치된 웹 애플리케이션으로 둔다.

  6. subAppsthis로 둔다.

  7. 다음 단계를 병렬로 실행한다:

    1. listResult를 빈 맵으로 둔다.

    2. 플랫폼 레지스트리에서 parentApp에 현재 설치된 모든 하위 앱의 목록을 가져온다.

    3. 플랫폼 오류로 인해 목록을 가져오지 못했다면, 전역 작업을 큐에 넣는다. subApps관련 전역 객체에서 promise를 "OperationError" DOMException으로 거부하도록 하고, 이 단계들을 중단한다.

    4. 설치된 각 하위 앱 subApp에 대해 다음을 수행한다:

      1. manifestIdsubAppid로 둔다.

      2. appName을 하위 앱의 웹 매니페스트에서 추출한 이름으로 둔다.

      3. resultEntrySubAppsListResult 사전의 새 인스턴스로 두고, appNameappName으로 설정한다.

      4. listResult[manifestId]를 resultEntry설정한다.

    5. 전역 작업을 큐에 넣는다. subApps관련 전역 객체에서 promiselistResult이행하도록 한다.

  8. promise를 반환한다.

4.4. 매니페스트 가져오기 및 처리

"매니페스트를 가져와 처리" 알고리즘을 작성한다. [이슈 #2]

url(URL)이 주어졌을 때 매니페스트를 가져와 처리하려면 다음 단계를 실행한다.

  1. 실패를 반환한다.

5. 보안 및 개인정보 보호 고려 사항

보조 애플리케이션을 설치하고 관리하는 것은 강력한 기능이다. 사용자 에이전트는 웹 애플리케이션이 명시적 권한 없이 하위 앱을 설치하거나 관리하도록 허용해서는 안 된다.

이 절에서는 고려되는 위협과 이를 완화하기 위한 사용자 에이전트의 규범적 요구 사항을 설명한다.

5.1. 공유 출처 ID

하위 앱은 별도의 보안 출처를 갖지 않는다. 하위 앱은 상위 앱과 정확히 동일한 출처 및 로컬 데이터 저장소(예: 쿠키, IndexedDB, LocalStorage, Cache Storage)를 공유한다. 표준 웹 보안 경계(예: 동일 출처 정책)는 상위 앱과 모든 하위 앱을 하나의 엔터티로 취급한다.

5.2. 권한 상속

모든 권한은 상위 앱과 하위 앱 간에 공유된다. 하위 앱에 권한(예: 카메라, 파일 시스템 접근, USB)을 부여하면 상위 앱에도 자동으로 부여되며, 그 반대도 마찬가지이다.

하위 앱 API에 접근하려면 상위 앱의 문서에서 권한 정책 sub-apps를 명시적으로 선언해야 한다. 하위 앱에 선언된 권한 정책은 아무런 효과가 없다.

특정 출처에 대해 사용자 동의를 얻어야 한다. add()가 호출되면 사용자 에이전트는 요청된 모든 하위 앱을 표시하는 통합 설치 대화 상자를 사용자에게 표시해야 한다. 여러 하위 앱을 한 번에 추가하는 경우 대화 상자 스팸을 방지하기 위해 하나의 프롬프트 안에 표시하는 것이 좋다.

사용자 에이전트는 어느 출처가 접근을 요청하는지 명확하게 나타내고 사용자가 충분한 정보를 바탕으로 결정을 내릴 수 있도록 충분한 정보(예: 설치되는 하위 앱의 이름과 아이콘)를 제공하는 권한 프롬프트를 표시해야 한다.

5.4. ID 스푸핑 위험

개발자가 하위 앱의 이름과 아이콘을 사용자 지정할 수 있으므로, 악성 애플리케이션이 시스템 대화 상자나 신뢰할 수 있는 타사 애플리케이션을 모방하는 하위 앱을 만들 수 있는 위험이 있다. 이 위험을 완화하기 위해 하위 앱 API는 무결성과 서명 검증을 보장하는 격리된 컨텍스트로 제한된다.

5.5. OS 통합 확장 위험

하위 앱은 자체 OS 통합(예: 프로토콜 처리기 또는 파일 형식 연결)을 등록할 수 있다. 이는 애플리케이션이 상위 앱의 기본 매니페스트에 선언된 범위를 훨씬 넘어 OS까지 영향을 확장할 수 있음을 의미한다. 대부분의 운영 체제 통합은 활성화되기 전에 명시적인 사용자 승인(예: 특정 파일 형식의 기본 애플리케이션으로 하위 앱 선택)을 요구하므로 이 위험이 완화된다.

5.6. 할당량 및 제한

호스트 운영 체제와 사용자의 애플리케이션 실행기가 잠재적으로 리소스를 소진하거나 악용되는 것을 방지하기 위해, 플랫폼은 다음 두 가지 제한을 적용한다:
  1. 상위 애플리케이션당 설치된 하위 앱 50개의 엄격한 제한.

  2. 단일 권한 프롬프트당 설치할 수 있는 하위 앱 20개의 제한.

일괄 설치 호출이 플랫폼 제한을 초과하면, 전체 add() 호출이 "QuotaExceededError" DOMException으로 거부된다.

6. 통합

6.1. 권한 정책

이 명세는 Window 객체의 subApps 속성에서 노출하는 메서드를 사용할 수 있는지를 제어하는 기능을 정의한다.

이 기능의 이름은 "sub-apps"이다.

이 기능의 기본 허용 목록'none'이다. 사용자 에이전트는 특정 출처에 대해 이를 'self'로 재정의할 수 있다(예: 사용자의 결정에 따라).

적합성

문서 규칙

적합성 요구 사항은 설명적 주장과 RFC 2119 용어의 조합으로 표현된다. 이 문서의 규범적 부분에서 사용하는 핵심어 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” 및 “OPTIONAL”은 RFC 2119에 설명된 대로 해석해야 한다. 다만 가독성을 위해 이 명세에서는 이러한 단어가 모두 대문자로 표시되지 않는다.

명시적으로 비규범적이라고 표시된 절, 예제 및 참고 사항을 제외한 이 명세의 모든 텍스트는 규범적이다. [RFC2119]

이 명세의 예제는 “예를 들어”라는 단어로 시작하거나 다음과 같이 class="example"을 사용하여 규범적 텍스트와 구분된다.

정보 제공용 예제의 예이다.

정보 제공용 참고 사항은 “참고”라는 단어로 시작하며 다음과 같이 class="note"를 사용하여 규범적 텍스트와 구분된다.

참고: 이것은 정보 제공용 참고 사항이다.

테스트

이 명세의 내용과 관련된 테스트는 이와 같은 “테스트” 블록에 문서화할 수 있다. 이러한 블록은 모두 비규범적이다.


색인

이 명세에서 정의하는 용어

참조로 정의되는 용어

참고 문헌

규범적 참고 문헌

[APPMANIFEST]
Marcos Caceres; Daniel Murphy; Christian Liebel. 웹 애플리케이션 매니페스트. URL: https://w3c.github.io/manifest/
[DOM]
Anne van Kesteren. DOM 표준. 현행 표준. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; 외. HTML 표준. 현행 표준. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 표준. 현행 표준. URL: https://infra.spec.whatwg.org/
[ISOLATED-CONTEXTS]
격리된 컨텍스트. 커뮤니티 그룹 보고서 초안. URL: https://wicg.github.io/isolated-web-apps/isolated-contexts.html
[PERMISSIONS]
Marcos Caceres; Mike Taylor. 권한. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. 권한 정책. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. 요구 사항 수준을 나타내기 위해 RFC에서 사용하는 핵심어. 1997년 3월. 현재의 최선 관행. URL: https://datatracker.ietf.org/doc/html/rfc2119
[URL]
Anne van Kesteren. URL 표준. 현행 표준. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 표준. 현행 표준. URL: https://webidl.spec.whatwg.org/

IDL 색인

[Exposed=Window, SecureContext, IsolatedContext]
partial interface Window {
  [SameObject] readonly attribute SubApps subApps;
};

// https://w3c.github.io/manifest/#id-member를 나타낸다
typedef USVString ManifestId;

dictionary SubAppsAddResponse {
  record<USVString, ManifestId> installedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsRemoveResponse {
  sequence<ManifestId> removedApps;
  record<USVString, DOMException> failedApps;
};

dictionary SubAppsListResult {
  required DOMString appName;
};

[
  Exposed=Window,
  SecureContext,
  IsolatedContext
] interface SubApps {
  Promise<SubAppsAddResponse> add(sequence<USVString> install_paths);
  Promise<SubAppsRemoveResponse> remove(sequence<ManifestId> manifest_ids);
  Promise<record<USVString, SubAppsListResult>> list();
};

이슈 색인

"매니페스트 가져오기 및 처리" 알고리즘을 작성한다. [이슈 #2]