웹 백그라운드 동기화

초안 커뮤니티 그룹 보고서,

이 버전:
https://wicg.github.io/background-sync/spec/
이슈 추적:
GitHub
편집자:
(Google)
(Google)

초록

이 명세는 웹 애플리케이션이 백그라운드에서 데이터를 동기화할 수 있게 하는 방법을 설명한다.

이 문서의 상태

이 명세는 Web Platform Incubator Community Group에서 발행했다. 이는 W3C 표준이 아니며 W3C 표준화 트랙에 있지도 않다. W3C Community Contributor License Agreement (CLA)에 따라 제한적인 opt-out 및 기타 조건이 적용된다는 점에 유의한다. W3C Community and Business Groups에 대해 더 알아보기.

1. 소개

이 절은 비규범적이다.

Web Applications는 신뢰할 수 없는 네트워크(예: 휴대전화)와 알 수 없는 수명(브라우저가 종료되거나 사용자가 다른 곳으로 이동할 수 있음)을 가진 환경에서 실행되는 경우가 많다. 이로 인해 웹 앱의 클라이언트 데이터(예: 사진 업로드, 문서 변경 또는 작성된 이메일)를 서버와 동기화하기가 어렵다. 브라우저가 닫히거나 사용자가 동기화를 완료하기 전에 다른 곳으로 이동하면, 앱은 사용자가 페이지를 다시 방문할 때까지 기다렸다가 다시 시도해야 한다. 이 명세는 처음 요청된 시점의 불리한 조건에도 불구하고 동기화 시도를 계속할 수 있도록 service worker에서 백그라운드에서 발생할 수 있는 새로운 onsync 이벤트를 제공한다. 이 API는 콘텐츠 생성과 서버와의 콘텐츠 동기화 사이의 시간을 줄이기 위한 것이다.

이 API는 service workers에 의존하므로, 이 API가 제공하는 기능은 secure context에서만 사용할 수 있다.

browsing context에서 백그라운드 동기화 기회를 요청하기:
function sendChatMessage(message) {
  return addChatMessageToOutbox(message).then(() => {
    // scoped service worker registration이 active 상태의
    // service worker를 얻을 때까지 기다린다
    return navigator.serviceWorker.ready;
  }).then(reg => {
    return reg.sync.register('send-chats');
  }).then(() => {
    console.log('Sync registered!');
  }).catch(() => {
    console.log('Sync registration failed :(');
  });
}

위 예제에서 addChatMessageToOutbox는 개발자가 정의한 함수이다.

service worker 내에서 sync 이벤트에 반응하기:

self.addEventListener('sync', event => {
  if (event.tag == 'send-chats') {
    event.waitUntil(
      getMessagesFromOutbox().then(messages => {
        // 메시지를 서버로 게시한다
        return fetch('/send', {
          method: 'POST',
          body: JSON.stringify(messages),
          headers: { 'Content-Type': 'application/json' }
        }).then(() => {
          // 성공! outbox에서 제거한다
          return removeMessagesFromOutbox(messages);
        });
      }).then(() => {
        // 성공 사실을 페이지에 알려 UI를 갱신할 수 있게 한다
        return clients.matchAll({ includeUncontrolled: true });
      }).then(clients => {
        clients.forEach(client => client.postMessage('outbox-processed'))
      })
    );
  }
});

위 예제에서 getMessagesFromOutboxremoveMessagesFromOutbox는 개발자가 정의한 함수이다.

2. 개념

해당 service worker registration의 origin에 대해 frame type이 top-level 또는 auxiliary인 service worker clients가 존재하지 않는 경우, sync 이벤트는 백그라운드에서 실행되는 것으로 간주된다.

사용자 에이전트가 네트워크 연결을 설정한 경우 사용자 에이전트는 online인 것으로 간주된다. 사용자 에이전트는 online에 대해 더 엄격한 정의를 사용할 수 있다(MAY). 이러한 더 엄격한 정의는 service worker 또는 sync registration이 연결된 특정 origin을 고려할 수 있다(MAY).

3. 구성 요소

service worker registration은 element type이 sync registration인 연관된 sync registrations 목록을 가진다.

sync registrationtagstate로 구성된 tuple이다.

sync registration은 DOMString인 연관된 tag를 가진다.

sync registration은 연관된 registration state를 가지며, 이는 pending, waiting, firing, 또는 reregisteredWhileFiring 중 하나이다. 이는 처음에 pending으로 설정된다.

sync registration은 연관된 service worker registration을 가진다. 이는 처음에 null로 설정된다.

하나의 sync registrations 목록 안에서 각 sync registration은 고유한 tag를 가져야 한다(MUST).

4. Permissions 통합

Web Background Synchronization API는 default powerful feature이며, name "background-sync"로 식별된다.

5. 개인정보 고려 사항

5.1. 권한

사용자 에이전트는 사용자가 background sync를 비활성화할 수 있는 방법을 제공할 수 있다(MAY).

참고: Background sync는 기본적으로 활성화되어야 한다(SHOULD). 권한이 거부되는 것은 예외적인 경우로 간주된다.

5.2. 위치 추적

백그라운드에서 실행되는 동안 onsync 이벤트 내의 fetch requests는 사용자가 페이지를 떠난 뒤에 클라이언트의 IP 주소를 서버에 노출할 수 있다. 사용자 에이전트는 재시도 횟수와 sync 이벤트의 지속 시간을 제한하여 추적을 제한해야 한다(SHOULD).

5.3. 방문 기록 누출

백그라운드에서 실행되는 동안 onsync 이벤트 내의 fetch requests는 수동적 도청자에게 클라이언트의 navigation history에 관한 무언가를 드러낼 수 있다. 예를 들어, 클라이언트가 https://example.com 사이트를 방문하고, 이 사이트가 sync 이벤트를 등록하지만, 사용자가 페이지를 벗어나 네트워크를 변경한 뒤에야 이벤트가 발생할 수 있다. 새 네트워크의 수동적 도청자는 onsync 이벤트가 만드는 fetch requests를 볼 수 있다. fetch requests는 HTTPS이므로 요청 내용은 누출되지 않지만 domain은 (DNS 조회와 요청의 IP 주소를 통해) 노출될 수 있다.

6. API 설명

6.1. ServiceWorkerRegistration 인터페이스에 대한 확장

ServiceWorkerRegistration/sync

현재 엔진 하나에만 있음.

Firefox없음Safari없음Chrome49+
Opera36+Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView49+Samsung Internet4.0+Opera Mobile36+
partial interface ServiceWorkerRegistration {
  readonly attribute SyncManager sync;
};

sync 속성은 SyncManager를 노출하며, 이는 해당 속성이 노출되는 ServiceWorkerRegistration로 표현되는 연관된 service worker registration을 가진다.

6.2. SyncManager 인터페이스

SyncManager

현재 엔진 하나에만 있음.

Firefox없음Safari없음Chrome49+
Opera없음Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView49+Samsung Internet5.0+Opera Mobile없음
[Exposed=(Window,Worker)]
interface SyncManager {
  Promise<undefined> register(DOMString tag);
  Promise<sequence<DOMString>> getTags();
};

SyncManager/register

현재 엔진 하나에만 있음.

Firefox없음Safari없음Chrome49+
Opera없음Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView49+Samsung Internet5.0+Opera Mobile없음

register(tag) 메서드는 호출되면 새 promise promise를 반환하고 다음 단계를 병렬로 실행해야 한다(MUST):

  1. serviceWorkerRegistrationSyncManager의 연관된 service worker registration으로 둔다.
  2. serviceWorkerRegistrationactive worker가 null이면, promiseInvalidStateErrorreject하고 이 단계들을 abort한다.
  3. 사용자가 background sync를 비활성화했으면, promiseNotAllowedErrorreject하고 이 단계들을 abort한다.
  4. isBackground를 true로 둔다.
  5. serviceWorkerRegistration의 origin에 대한 service worker clients의 각 client에 대해:
    1. clientframe type이 top-level 또는 auxiliary이면, isBackground를 false로 설정한다.
  6. isBackground가 true이면, promiseInvalidAccessErrorreject하고 이 단계들을 abort한다.
  7. currentRegistration을, 존재한다면 serviceWorkerRegistrationsync registrations 목록에서 tagtag와 같은 registration으로 두고, 그렇지 않으면 null로 둔다.
  8. currentRegistration이 null이 아니면:
    1. currentRegistrationregistration statewaiting이면, currentRegistrationregistration statepending으로 설정한다.
    2. currentRegistrationregistration statefiring이면, currentRegistrationregistration statereregisteredWhileFiring으로 설정한다.
    3. promiseResolve한다.
    4. 사용자 에이전트가 현재 online이고 currentRegistrationregistration statepending이면, currentRegistration에 대해 sync 이벤트를 발생시킨다.
  9. 그렇지 않으면:
    1. newRegistration을 새 sync registration으로 둔다.
    2. newRegistration의 연관된 tagtag로 설정한다.
    3. newRegistration의 연관된 service worker registrationserviceWorkerRegistration로 설정한다.
    4. newRegistrationserviceWorkerRegistrationsync registrations 목록에 추가한다.
    5. promiseResolve한다.
    6. 사용자 에이전트가 현재 online이면, newRegistration에 대해 sync 이벤트를 발생시킨다.

SyncManager/getTags

현재 엔진 하나에만 있음.

Firefox없음Safari없음Chrome49+
Opera없음Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView49+Samsung Internet5.0+Opera Mobile없음

getTags() 메서드는 호출되면 새 promise promise를 반환하고 다음 단계를 병렬로 실행해야 한다(MUST):

  1. serviceWorkerRegistrationSyncManager의 연관된 service worker registration으로 둔다.
  2. currentTags를 새 sequence로 둔다.
  3. serviceWorkerRegistrationsync registrations 목록의 각 registration에 대해, registration의 연관된 tagcurrentTags에 추가한다.
  4. promisecurrentTagsResolve한다.

6.3. sync 이벤트

SyncEvent

현재 엔진 하나에만 있음.

Firefox없음Safari없음Chrome49+
Opera?Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView없음Samsung Internet5.0+Opera Mobile?

ServiceWorkerGlobalScope/onsync

모든 현재 엔진에 있음.

Firefox44+Safari11.1+Chrome49+
Opera24+Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox44+iOS Safari11.3+Android용 Chrome49+Android WebView49+Samsung Internet5.0+Opera Mobile24+

SyncEvent/SyncEvent

현재 엔진 하나에만 있음.

Firefox없음Safari없음Chrome49+
Opera?Edge79+
Edge (Legacy)없음IE없음
Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView없음Samsung Internet5.0+Opera Mobile?
partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onsync;
};

[Exposed=ServiceWorker]
interface SyncEvent : ExtendableEvent {
  constructor(DOMString type, SyncEventInit init);
  readonly attribute DOMString tag;
  readonly attribute boolean lastChance;
};

dictionary SyncEventInit : ExtendableEventInit {
  required DOMString tag;
  boolean lastChance = false;
};

참고: SyncEvent 인터페이스는 firing sync registration을 나타낸다. 이벤트를 등록한 page(또는 worker)가 실행 중이면, 사용자 에이전트는 network connectivity가 사용 가능해지는 즉시 sync 이벤트를 발생시킨다. 그렇지 않으면, 사용자 에이전트는 가능한 가장 빠른 편리한 시점에 이벤트를 실행해야 한다. sync 이벤트가 실패하면, 사용자 에이전트는 자신이 선택한 시점에 이를 재시도하기로 결정할 수 있다. lastChance 속성은 사용자 에이전트가 현재 시도 이후 이 sync를 더 이상 시도하지 않을 경우 true이다.

lastChance에 반응하기:
self.addEventListener('sync', event => {
  if (event.tag == 'important-thing') {
    event.waitUntil(
      doImportantThing().catch(err => {
        if (event.lastChance) {
          self.registration.showNotification("Important thing failed");
        }
        throw err;
      })
    );
  }
});

위 예제는 사용자에게 notification을 표시함으로써 lastChance에 반응한다. 이를 위해서는 origin이 notifications를 표시할 권한을 가져야 한다.

위 예제에서 doImportantThing은 개발자가 정의한 함수이다.

사용자 에이전트가 online으로 변경될 때마다, 사용자 에이전트는 registration statepending인 각 sync registration에 대해 sync 이벤트를 발생시켜야 한다(SHOULD). 이벤트들은 어떤 순서로든 발생할 수 있다.

sync registration registration에 대해 sync 이벤트를 발생시키려면, 사용자 에이전트는 다음 단계를 실행해야 한다(MUST):

  1. Assert: registrationregistration statepending이다.
  2. serviceWorkerRegistrationregistration과 연관된 service worker registration으로 둔다.
  3. Assert: registrationserviceWorkerRegistration과 연관된 sync registrations 목록에 존재한다.
  4. registrationregistration statefiring으로 설정한다.
  5. serviceWorkerRegistration에서 다음 속성을 사용하여 SyncEvent로 "sync" Fire Functional Event한다:

    SyncEvent/tag

    현재 엔진 하나에만 있음.

    Firefox없음Safari없음Chrome49+
    Opera?Edge79+
    Edge (Legacy)없음IE없음
    Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView없음Samsung Internet5.0+Opera Mobile?
    tag
    registration과 연관된 tag

    SyncEvent/lastChance

    현재 엔진 하나에만 있음.

    Firefox없음Safari없음Chrome49+
    Opera?Edge79+
    Edge (Legacy)없음IE없음
    Android용 Firefox없음iOS Safari없음Android용 Chrome49+Android WebView없음Samsung Internet5.0+Opera Mobile?
    lastChance
    사용자 에이전트가 이 sync 이벤트가 실패하면 재시도할 경우 false, 또는 현재 시도 이후 더 이상의 시도가 이루어지지 않을 경우 true.

    그런 다음 dispatchedEvent로 다음 단계를 실행한다:

    1. waitUntilPromisedispatchedEventextended lifetime promises 모두에 대해 waiting for all한 결과로 둔다.
    2. waitUntilPromisefulfillment 시, 다음 단계를 원자적으로 수행한다:
      1. registration의 state가 reregisteredWhileFiring이면:
        1. registration의 state를 pending으로 설정한다.
        2. 사용자 에이전트가 현재 online이면, registration에 대해 sync 이벤트를 발생시킨다.
        3. 이 단계들의 나머지를 abort한다.
      2. Assert: registrationregistration statefiring이다.
      3. registrationserviceWorkerRegistrationsync registration 목록에서 제거한다.
    3. waitUntilPromiserejection 시, 또는 service workertermination으로 인해 script가 abort된 경우, 다음 단계를 원자적으로 수행한다:
      1. registration의 state가 reregisteredWhileFiring이면:
        1. registration의 state를 pending으로 설정한다.
        2. 사용자 에이전트가 현재 online이면, registration에 대해 sync 이벤트를 발생시킨다.
        3. 이 단계들의 나머지를 abort한다.
      2. dispatchedEventlastChance 속성이 false이면, registrationregistration statewaiting으로 설정하고, 다음 단계를 병렬로 수행한다:
        1. 사용자 에이전트가 정의한 시간 동안 기다린다.
        2. registrationregistration statewaiting이 아니면, 이 하위 단계를 abort한다.
        3. registrationregistration statepending으로 설정한다.
        4. 사용자 에이전트가 현재 online이면, registration에 대해 sync 이벤트를 발생시킨다.
      3. 그렇지 않으면 registrationserviceWorkerRegistrationsync registrations 목록에서 제거한다.

사용자 에이전트는 SyncEvent의 lifetime extension 및 execution time에 대해 일반적인 ExtendableEvent에 부과되는 제한보다 더 엄격한 시간 제한을 부과할 수 있다(MAY). 특히 lastChance가 true인 이벤트는 상당히 단축된 시간 제한을 가질 수 있다(MAY).

사용자 에이전트는 사용자 에이전트가 정의한 어떤 heuristics에 따라 sync 이벤트재시도한다.

적합성

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

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

이 명세의 예제는 “for example”이라는 단어로 도입되거나 다음처럼 class="example"으로 규범적 텍스트와 분리된다:

이는 informative example의 예이다.

정보성 참고는 “Note”라는 단어로 시작하며 다음처럼 class="note"로 규범적 텍스트와 분리된다:

참고, 이는 정보성 참고이다.

색인

이 명세에서 정의하는 용어

참조에서 정의한 용어

참조

규범적 참조

[ECMASCRIPT]
ECMAScript 언어 명세. URL: https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren; et al. HTML 표준. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[PERMISSIONS]
Marcos Caceres; Mike Taylor; Jeffrey Yasskin. Permissions. 2021년 10월 23일. WD. URL: https://www.w3.org/TR/permissions/
[PROMISES-GUIDE]
Promise를 사용하는 명세 작성하기. 2015년 7월 24일. W3C TAG의 Finding. URL: https://www.w3.org/2001/tag/doc/promises-guide
[RFC2119]
S. Bradner. 요구 수준을 나타내기 위해 RFC에서 사용하는 핵심 단어. 1997년 3월. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SECURE-CONTEXTS]
Mike West. Secure Contexts. 2021년 9월 18일. CR. URL: https://www.w3.org/TR/secure-contexts/
[SERVICE-WORKERS-1]
Alex Russell; et al. Service Workers 1. 2019년 11월 19일. CR. URL: https://www.w3.org/TR/service-workers-1/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 표준. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL 색인

partial interface ServiceWorkerRegistration {
  readonly attribute SyncManager sync;
};

[Exposed=(Window,Worker)]
interface SyncManager {
  Promise<undefined> register(DOMString tag);
  Promise<sequence<DOMString>> getTags();
};

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onsync;
};

[Exposed=ServiceWorker]
interface SyncEvent : ExtendableEvent {
  constructor(DOMString type, SyncEventInit init);
  readonly attribute DOMString tag;
  readonly attribute boolean lastChance;
};

dictionary SyncEventInit : ExtendableEventInit {
  required DOMString tag;
  boolean lastChance = false;
};