푸시 API

W3C 작업 초안

이 문서에 대한 추가 정보
이 버전:
https://www.w3.org/TR/2025/WD-push-api-20250806/
최신 공개 버전:
https://www.w3.org/TR/push-api/
최신 편집자 초안:
https://w3c.github.io/push-api/
역사:
https://www.w3.org/standards/history/push-api/
커밋 히스토리
편집자:
Peter Beverloo (Google)
Martin Thomson (Mozilla 재단)
Marcos Caceres (Apple Inc.)
이전 편집자:
Bryan Sullivan (AT&T) - 까지
Eduardo Fullea (Telefonica) - 까지
Michaël van Ouwerkerk (Google) - 까지
피드백:
GitHub w3c/push-api (풀 리퀘스트, 새 이슈, 오픈 이슈)

요약

푸시 API푸시 메시지푸시 서비스를 통해 웹 애플리케이션에 전송할 수 있도록 합니다. 애플리케이션 서버는 웹 애플리케이션이나 사용자 에이전트가 비활성 상태일 때에도 언제든지 푸시 메시지를 보낼 수 있습니다. 푸시 서비스사용자 에이전트에 안정적이고 효율적으로 메시지를 전달합니다. 푸시 메시지는 웹 애플리케이션의 오리진에서 실행되는 서비스 워커에 전달되며, 서비스 워커는 메시지의 정보를 활용해 로컬 상태를 업데이트하거나 사용자에게 알림을 표시할 수 있습니다.

이 명세는 웹 푸시 프로토콜과 함께 사용하도록 설계되었습니다. 이 프로토콜은 애플리케이션 서버 또는 사용자 에이전트푸시 서비스와 어떻게 상호작용하는지 설명합니다.

문서 상태

이 섹션은 이 문서가 출판될 당시의 상태를 설명합니다. 최신 W3C 출판물과 이 기술 보고서의 최신 개정본은 W3C 표준 및 초안 색인에서 확인할 수 있습니다.

이 문서는 웹 애플리케이션 작업 그룹권고안 경로를 이용하여 작업 초안으로 발행한 것입니다.

작업 초안으로 출판되었다고 해서 W3C와 회원들의 승인이나 지지를 의미하지는 않습니다.

이 문서는 초안이며 언제든지 업데이트, 교체 또는 폐기될 수 있습니다. 진행 중인 작업 외의 목적으로 인용하는 것은 적절하지 않습니다.

이 문서는 W3C 특허 정책에 따라 운영되는 그룹에 의해 작성되었습니다. W3C공개된 특허 명세 목록을 유지 관리하며, 해당 그룹의 산출물과 관련하여 제출된 특허 공개 내역을 포함합니다. 또한 특허 공개 방법에 대한 안내도 포함되어 있습니다. 어떤 사람이 본인이 알고 있는 특허가 필수 청구항을 포함하고 있다고 판단할 경우, W3C 특허 정책 6장에 따라 정보를 공개해야 합니다.

이 문서는 2023년 11월 3일 W3C 프로세스 문서에 따라 관리됩니다.

1. 소개

이 섹션은 규범적이지 않습니다.

푸시 API는 웹 애플리케이션이 사용자 에이전트와 비동기적으로 통신할 수 있도록 해줍니다. 이를 통해 애플리케이션 서버사용자 에이전트에 시기적절한 정보를 제공할 수 있으며, 사용자가 웹 애플리케이션을 열 때까지 기다릴 필요가 없습니다.

여기 정의된 대로, 푸시 서비스는 언제든지 푸시 메시지의 전달을 지원합니다.

특히, 푸시 메시지는 웹 애플리케이션이 현재 브라우저 창에서 활성화되어 있지 않더라도 전달됩니다. 이는 사용자가 웹 애플리케이션을 닫더라도 푸시 메시지를 받으면 웹 애플리케이션이 다시 시작되어 이점을 누릴 수 있는 사용 사례와 관련이 있습니다. 예를 들어, 푸시 메시지는 사용자가 다가오는 WebRTC 통화가 있음을 알리는 데 사용할 수 있습니다.

푸시 메시지사용자 에이전트가 일시적으로 오프라인일 때에도 전송될 수 있습니다. 이를 지원하기 위해 푸시 서비스사용자 에이전트를 위해 메시지를 저장하다가, 사용자 에이전트가 다시 사용 가능해지면 메시지를 전달합니다. 이는 사용자가 오프라인일 때 웹 애플리케이션이 변화를 인지하고, 사용자 에이전트에 시기적절한 정보를 제공할 수 있도록 지원합니다. 푸시 메시지푸시 서비스에 저장되어 있다가 사용자 에이전트가 연결 가능해지면 전달됩니다.

푸시 API는 사용자 에이전트가 웹 애플리케이션을 적극적으로 사용하는 동안에도 푸시 메시지를 안정적으로 전달하도록 보장합니다. 예를 들어, 사용자가 웹 애플리케이션을 적극적으로 사용하거나, 웹 애플리케이션이 활성 워커, 프레임 또는 백그라운드 창을 통해 애플리케이션 서버와 적극적으로 통신할 때 등입니다. 이는 푸시 API의 주요 사용 사례는 아닙니다. 웹 애플리케이션은 애플리케이션 서버와 지속적인 통신을 유지할 필요 없이, 드물게 메시지를 받을 때 푸시 API를 선택할 수 있습니다.

푸시 메시징은 사용자 에이전트와 웹 애플리케이션 사이에 이미 활성화된 통신 채널이 없는 경우에 가장 적합합니다. 푸시 메시지를 보내는 것은 Fetch API 또는 [WebSockets]과 같은 더 직접적인 통신 방법에 비해 훨씬 더 많은 리소스를 필요로 합니다. 푸시 메시지는 일반적으로 직접 통신보다 지연이 더 크고, 사용에 제한이 있을 수 있습니다. 대부분의 푸시 서비스는 전송할 수 있는 푸시 메시지의 크기와 수량을 제한합니다.

2. 의존성

웹 푸시 프로토콜 [RFC8030]은 사용자 에이전트 또는 애플리케이션 서버푸시 서비스 사이의 통신을 가능하게 하는 프로토콜을 설명합니다. 대체 프로토콜을 사용할 수도 있지만, 이 명세는 해당 프로토콜의 사용을 가정하며, 대체 프로토콜들은 호환 가능한 의미론을 제공해야 합니다.

Content-Encoding HTTP 헤더는 [RFC7231]의 3.1.2.2절에서 설명되며, 푸시 메시지의 페이로드에 적용된 콘텐츠 코딩을 나타냅니다.

3. 개념

3.1 애플리케이션 서버

애플리케이션 서버는 웹 애플리케이션의 서버 측 구성 요소를 의미합니다.

3.2 푸시 메시지

푸시 메시지애플리케이션 서버에서 웹 애플리케이션으로 전송되는 데이터입니다.

푸시 메시지는 해당 메시지가 제출된 활성 워커와 연결된 푸시 구독에 전달됩니다. 서비스 워커가 현재 실행 중이 아니면, 전달을 위해 워커가 시작됩니다.

3.3 선언적 푸시 메시지

선언적 푸시 메시지푸시 메시지의 데이터가 사용자 에이전트가 이해할 수 있는 JSON 문서인 경우를 말합니다. 사용자 에이전트는 각 수신되는 푸시 메시지선언적 푸시 메시지 파서를 사용해 선언적 푸시 메시지인지 판단하기 위해 기회가 있을 때마다 구문 분석합니다.

선언적 푸시 메시지는 서비스 워커의 개입 없이 알림을 생성하고 표시할 수 있습니다. 하지만, 애플리케이션 서버에서 원하는 경우 서비스 워커가 관여할 수도 있습니다. 이런 경우, 푸시 메시지의 선언적 특성은 예를 들어 저장 공간 압력으로 서비스 워커가 제거된 경우 백업 역할을 하며, 알림 데이터 전송에 더 객체 지향적인 접근 방식을 제공합니다.

{
  "web_push": 8030,
  "notification": {
    "title": "Ada emailed ‘London’",
    "lang": "en-US",
    "dir": "ltr",
    "body": "Did you hear about the tube strikes?",
    "navigate": "https://email.example/message/12"
  }
}

3.3.1 멤버

선언적 푸시 메시지는 다음 멤버를 가집니다:

web_push (필수)

반드시 8030이어야 하는 정수입니다. 선언적 푸시 메시지와 다른 JSON 문서를 구분하는 데 사용됩니다.

notification (필수)

다음과 같은 멤버로 구성된 JSON 객체로, 모두 Notifications API 기능과 유사하지만, 경우에 따라 타입이 더 엄격할 수 있습니다. title을 제외한 모든 멤버는 NotificationOptions 딕셔너리에서 파생되며, 함께 관리되어야 합니다. [NOTIFICATIONS]

title (필수)

문자열입니다.

dir

"auto", "ltr", 또는 "rtl".

lang

언어 태그를 담는 문자열입니다.

body

문자열입니다.

navigate (필수)

URL을 담는 문자열입니다.

tag

문자열입니다.

image

URL을 담는 문자열입니다.

icon

URL을 담는 문자열입니다.

badge

URL을 담는 문자열입니다.

vibrate

32비트 부호 없는 정수의 배열입니다.

timestamp

64비트 부호 없는 정수입니다.

renotify

불리언 값입니다.

silent

불리언 값입니다.

requireInteraction

불리언 값입니다.

참고

NotificationOptions 딕셔너리와의 일관성을 위해 require_interaction이 아닌 이름을 사용합니다.

data

모든 JSON 값.

actions

다음 멤버로 구성된 JSON 객체 배열로, 모두 NotificationAction 딕셔너리에서 파생되며, 함께 관리되어야 합니다.

action (필수)

문자열입니다.

title (필수)

문자열입니다.

navigate (필수)

URL을 담는 문자열입니다.

icon

URL을 담는 문자열입니다.

mutable

불리언 값입니다. true일 경우 Notification 객체가 포함된 push 이벤트가 서비스 워커(있는 경우)로 디스패치됩니다. 이 객체는 선언적 푸시 메시지에 의해 기술됩니다.

3.3.2 파서

선언적 푸시 메시지 파서 결과튜플로, notification(알림)과 mutable(불리언 값)로 구성됩니다.

선언적 푸시 메시지 파서byte sequence bytes, origin origin, URL baseURL, 그리고 EpochTimeStamp fallbackTimestamp를 인자로 받아 다음 절차를 실행합니다. 실패 또는 선언적 푸시 메시지 파서 결과 중 하나를 반환합니다.

  1. messageparse JSON bytes to an Infra valuebytes에 대해 실행한 결과로 한다. 예외가 발생하면 실패를 반환한다.

  2. messagemap이 아니면 실패를 반환한다.

  3. message["web_push"]가 존재하지 않거나 8030이 아니면 실패를 반환한다.

  4. message["notification"]가 존재하지 않으면 실패를 반환한다.

  5. notificationInputmessage["notification"]로 한다.

  6. notificationInputmap이 아니면 실패를 반환한다.

  7. notificationInput["title"]가 존재하지 않거나 문자열이 아니면 실패를 반환한다.

  8. notificationInput["navigate"]가 존재하지 않거나 문자열이 아니면 실패를 반환한다.

  9. notificationTitlenotificationInput["title"]로 한다.

  10. notificationOptionsNotificationOptions 딕셔너리로 한다.

  11. notificationInput["dir"]가 존재하고 값이 "auto", "ltr", "rtl" 중 하나라면, notificationOptions["dir"] 에 notificationInput["dir"] 값을 설정한다.

  12. notificationInput["lang"]가 존재하고 문자열이면, notificationOptions["lang"] 에 notificationInput["lang"] 값을 설정한다.

  13. notificationInput["body"]가 존재하고 문자열이면, notificationOptions["body"] 에 notificationInput["body"] 값을 설정한다.

  14. notificationOptions["navigate"] 에 notificationInput["navigate"]를 변환하여 설정한다.

  15. notificationInput["tag"]가 존재하고 문자열이면, notificationOptions["tag"] 에 notificationInput["tag"] 값을 설정한다.

  16. notificationInput["image"]가 존재하고 문자열이면, notificationOptions["image"] 에 notificationInput["image"]를 변환하여 설정한다.

  17. notificationInput["icon"]가 존재하고 문자열이면, notificationOptions["icon"] 에 notificationInput["icon"]를 변환하여 설정한다.

  18. notificationInput["badge"]가 존재하고 문자열이면, notificationOptions["badge"] 에 notificationInput["badge"]를 변환하여 설정한다.

  19. notificationInput["vibrate"]가 존재하고 list이며, 각 item32비트 부호 없는 정수라면, notificationOptions["vibrate"] 에 notificationInput["vibrate"]를 설정한다.

  20. notificationInput["timestamp"]가 존재하고 64비트 부호 없는 정수이면, notificationOptions["timestamp"] 에 notificationInput["timestamp"]를 설정한다.

  21. notificationInput["renotify"]가 존재하고 불리언 값이면, notificationOptions["renotify"] 에 notificationInput["renotify"]를 설정한다.

  22. notificationInput["silent"]가 존재하고 불리언 값이면, notificationOptions["silent"] 에 notificationInput["silent"]를 설정한다.

  23. notificationInput["requireInteraction"]가 존재하고 불리언 값이면, notificationOptions["requireInteraction"] 에 notificationInput["requireInteraction"]를 설정한다.

  24. notificationInput["data"]가 존재하면 notificationOptions["data"] 에 convert an Infra value to a JSON-compatible JavaScript valuenotificationInput["data"]에 대해 실행한 결과를 설정한다.

  25. notificationInput["actions"]가 존재하고 list라면:

    1. notificationActions를 « »로 한다.

    2. actionInput에 대해 notificationInput["actions"]를 순회한다:

      1. actionInput["action"]가 존재하지 않거나 문자열이 아니면 continue.

      2. actionInput["title"]가 존재하지 않거나 문자열이 아니면 continue.

      3. actionInput["navigate"]가 존재하지 않거나 문자열이 아니면 continue.

      4. actionNavigateactionInput["navigate"]를 변환한 값으로 한다.

      5. notificationActionNotificationAction 딕셔너리 «[ "action" → actionInput["action"], "title" → actionInput["title"], "navigate" → actionNavigate ]»로 한다.

      6. actionInput["icon"]가 존재하고 문자열이면 notificationAction["icon"] 에 actionInput["icon"]를 변환하여 설정한다.

      7. Append notificationActionnotificationActions에 추가한다.

    3. notificationOptions["actions"] 에 notificationActions를 설정한다.

  26. notificationcreate a notificationnotificationTitle, notificationOptions, origin, baseURL, fallbackTimestamp에 대해 실행한 결과로 한다. 예외가 발생하면 실패를 반환한다.

  27. notificationnavigation URL이 null이면 실패를 반환한다.

  28. notificationactions에 있는 notification actionnavigation URL이 null이면 실패를 반환한다.

  29. mutable을 false로 한다.

  30. message["mutable"]가 존재하고 불리언 값이면 mutablemessage["mutable"] 값으로 설정한다.

  31. (notification, mutable)를 반환한다.

3.4 푸시 구독

푸시 구독은 웹 애플리케이션을 대신하여 사용자 에이전트푸시 서비스 사이에 설정되는 메시지 전달 컨텍스트입니다. 각 푸시 구독서비스 워커 등록과 연결되어 있으며, 하나의 서비스 워커 등록은 최대 하나의 푸시 구독만 가질 수 있습니다.

푸시 구독에는 푸시 엔드포인트가 연결되어 있습니다. 이는 푸시 서비스가 노출하는 절대 URL이어야 하며, 애플리케이션 서버푸시 메시지를 전송할 수 있는 위치입니다. 푸시 엔드포인트푸시 구독을 고유하게 식별해야 합니다.

푸시 구독구독 만료 시간이 연결될 수 있습니다. 설정된 경우에는 UTC 기준 1970년 1월 1일 00:00:00 이후의 밀리초 단위 시간으로, 해당 시점에 구독이 비활성화되는 시간이어야 합니다. 사용자 에이전트는 구독이 만료되기 전에 갱신을 시도해야 합니다.

푸시 구독은 RFC8291에 따라 P-256 ECDH 키 쌍과 인증 비밀을 위한 내부 슬롯을 가집니다. 이 슬롯들은 푸시 구독 생성 시 반드시 채워져야 합니다.

사용자 에이전트가 어떤 이유로든 키를 변경해야 하는 경우, 반드시 해당 서비스 워커 등록과 연결된 푸시 구독registration, 이전 키를 가진 PushSubscription 인스턴스를 oldSubscription, 새 키를 가진 PushSubscription 인스턴스를 newSubscription으로 하여 "pushsubscriptionchange" 이벤트를 반드시 발생시켜야 합니다.

푸시 구독 생성PushSubscriptionOptionsInit optionsDictionary가 주어졌을 때 다음과 같이 수행됩니다:

  1. subscription을 새 PushSubscription으로 생성합니다.
  2. options를 새롭게 PushSubscriptionOptions 객체로 생성하며, 해당 객체의 속성들을 optionsDictionary의 멤버 및 값으로 초기화합니다.
  3. subscriptionoptions 속성을 options으로 설정합니다.
  4. 새 P-256 ECDH 키 쌍을 생성합니다. 개인 키는 subscription의 내부 슬롯에 저장되며, 이 값은 애플리케이션에 노출되지 않습니다. 공개 키 역시 내부 슬롯에 저장되며, getKey() 메서드에서 "p256dh" 인자를 사용해 조회할 수 있습니다.
  5. 새 인증 비밀을 생성합니다. 이는 RFC8291에 정의된 옥텟 시퀀스입니다. 인증 비밀은 subscription의 내부 슬롯에 저장되며, getKey() 메서드에서 "auth" 인자를 사용해 조회할 수 있습니다.
  6. 푸시 구독을 요청합니다. applicationServerKey 속성이 설정되어 있다면 해당 값을 포함합니다. 예외가 발생하면 예외를 다시 던집니다.
  7. 푸시 구독 요청이 성공적으로 완료되면:
    1. subscriptionendpoint 속성을 푸시 구독푸시 엔드포인트로 설정합니다.
    2. 푸시 구독에서 제공되는 경우, subscriptionexpirationTime을 설정합니다.
  8. subscription을 반환합니다.

3.4.1 구독 갱신

사용자 에이전트푸시 서비스는 언제든지 갱신을 선택할 수 있습니다. 예를 들어, 일정 기간이 경과한 경우 등입니다.

이 경우 사용자 에이전트는 현재 푸시 구독을 생성할 때 사용된 PushSubscriptionOptions를 사용하여 반드시 푸시 구독 생성 단계를 실행해야 합니다. 새 푸시 구독은 반드시 원래 구독과 다른 키 쌍을 가져야 합니다.

성공하면 사용자 에이전트는 반드시 해당 서비스 워커 등록과 연결된 푸시 구독registration, 초기 푸시 구독oldSubscription, 새 푸시 구독newSubscription으로 하여 "pushsubscriptionchange" 이벤트를 반드시 발생시켜야 합니다.

애플리케이션 서버에 변경 사항이 전파될 시간을 허용하기 위해 사용자 에이전트는 갱신 후 잠시 동안 이전 푸시 구독에 대한 메시지를 계속 받을 수 있습니다. 갱신된 푸시 구독에 대한 메시지를 수신하면, 모든 이전 푸시 구독은 반드시 비활성화되어야 합니다.

사용자 에이전트푸시 구독을 갱신할 수 없는 경우, 주기적으로 갱신을 재시도해야 합니다. 푸시 구독을 더 이상 사용할 수 없는 경우(예: 만료된 경우), 사용자 에이전트는 반드시 해당 서비스 워커 등록과 연결된 푸시 구독registration, 비활성화되는 PushSubscription 인스턴스를 oldSubscription, nullnewSubscription으로 하여 "pushsubscriptionchange" 이벤트를 반드시 발생시켜야 합니다.

3.4.2 구독 비활성화

푸시 구독비활성화되면 사용자 에이전트푸시 서비스는 해당 정보의 저장된 복사본을 반드시 삭제해야 합니다. 이후 이 푸시 구독에 대한 푸시 메시지는 반드시 전달되지 않아야 합니다.

푸시 구독은 연결된 서비스 워커 등록이 등록 해제될 때 비활성화되지만, 푸시 구독은 더 일찍 비활성화될 수도 있습니다.

참고

푸시 구독서비스 워커 등록이 삭제될 때 함께 제거됩니다.

3.5 푸시 서비스

푸시 서비스애플리케이션 서버가 웹 애플리케이션에 푸시 메시지를 전송할 수 있도록 하는 시스템을 의미합니다. 푸시 서비스는 자신이 서비스하는 푸시 구독을 위한 푸시 엔드포인트 또는 엔드포인트를 제공합니다.

사용자 에이전트푸시 서비스에 연결하여 푸시 구독을 생성합니다. 사용자 에이전트는 사용 가능한 푸시 서비스의 선택을 제한할 수 있습니다. 그 이유로는 성능 관련 문제(특정 국가 또는 직장 네트워크 등에서 서비스가 방화벽에 의해 차단되는지 여부 포함), 신뢰성, 배터리 수명에 대한 영향, 특정 푸시 서비스로 메타데이터를 유도하거나 회피하는 합의 등이 있습니다.

3.6 권한

푸시 API는 강력한 기능으로, 이름 "push"로 식별됩니다.

Permissions 명세와의 통합을 위해, 본 명세는 PushPermissionDescriptor 권한 디스크립터 타입을 정의합니다.

WebIDLdictionary PushPermissionDescriptor : PermissionDescriptor {
  boolean userVisibleOnly = false;
};

userVisibleOnlyuserVisibleOnly와 동일한 의미를 가집니다.

{name: "push", userVisibleOnly: false}{name: "push", userVisibleOnly: true}보다 더 강한 권한입니다.

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

푸시 메시지의 내용은 암호화됩니다 [RFC8291] 그러나 푸시 서비스애플리케이션 서버사용자 에이전트에게 푸시 구독을 통해 메시지를 보낼 때 메시지의 메타데이터에 노출됩니다. 여기에는 메시지의 타이밍, 빈도, 크기가 포함됩니다. 푸시 서비스를 변경하는 것(사용자 에이전트가 이를 허용하지 않을 수 있음)을 제외하면, 알려진 유일한 대응 방법은 패딩을 통해 메시지 크기를 인위적으로 늘리는 것입니다.

푸시 메시지가 웹 애플리케이션과 동일한 오리진을 가진 애플리케이션 서버에서 전송되었다는 보장은 없습니다. 애플리케이션 서버푸시 구독을 사용하는 데 필요한 정보를 제3자와 자유롭게 공유할 수 있습니다.

다음 요구사항은 가능한 한 사용자의 프라이버시와 보안을 보호하기 위한 것이며, 해당 목표를 달성하는 범위 내에서 애플리케이션 서버와 사용자의 통신의 무결성도 보호하기 위한 것입니다.

사용자 에이전트는 사용자의 명시적 허락 없이 웹 애플리케이션에 Push API 접근을 제공해서는 안 됩니다. 사용자 에이전트subscribe() 메서드의 각 호출에 대해 사용자 인터페이스를 통해 반드시 허가를 받아야 하며, 이전에 허가가 저장되었거나 사전 신뢰관계가 적용되는 경우는 예외입니다. 현재 브라우징 세션을 넘어서 보존되는 권한은 반드시 취소 가능해야 합니다.

Push API는 서비스 워커 등록과 연결된 Service Worker를 깨워서 개발자가 제공한 이벤트 핸들러를 실행해야 할 수도 있습니다. 이로 인해 네트워크 트래픽 등 리소스 사용이 발생할 수 있으며, 사용자 에이전트는 이러한 사용을 해당 푸시 구독을 생성한 웹 애플리케이션에 귀속시켜야 합니다.

사용자 에이전트는 권한을 획득하거나 권한 상태를 결정할 때 PushSubscriptionOptions를 고려할 수 있습니다.

권한이 취소될 경우 사용자 에이전트는 해당 권한으로 생성된 구독에 대해 "pushsubscriptionchange" 이벤트를 발생시킬 수 있으며, 해당 서비스 워커 등록과 연결된 푸시 구독registration, PushSubscription 인스턴스를 oldSubscription, nullnewSubscription으로 사용합니다. 사용자 에이전트는 영향을 받는 구독들을 반드시 병렬로 비활성화해야 합니다.

서비스 워커 등록이 등록 해제되면, 연결된 모든 푸시 구독은 반드시 비활성화되어야 합니다.

푸시 엔드포인트푸시 서비스 외의 행위자가 사용자의 디바이스, 신원, 위치 등의 정보를 유추할 수 있도록 정보를 노출해서는 안 됩니다. 정확한 요건은 RFC8030의 Privacy Considerations를 참고하세요.

비활성화푸시 구독푸시 엔드포인트는 새 푸시 구독에 재사용되어서는 안 됩니다. 이는 사용자가 삭제할 수 없는 영구 식별자의 생성을 방지하며, 한 푸시 구독의 정보를 재사용해 다른 푸시 구독에 푸시 메시지를 보내는 것도 방지합니다.

사용자 에이전트는 Push API를 반드시 보안 컨텍스트에서만 사용할 수 있도록 구현해야 합니다. 이는 푸시 구독 데이터를 얻으려는 중간자 공격으로부터 사용자를 더 잘 보호합니다. 브라우저는 개발 목적에 한해 이 규칙을 무시할 수 있습니다.

5. 푸시 프레임워크

이 섹션은 규범적이지 않습니다.

푸시 메시지애플리케이션 서버에서 웹 애플리케이션으로 다음과 같이 전송됩니다:

이 전체 프레임워크는 애플리케이션 서버의 이벤트에 대응하여 서비스 워커를 활성화할 수 있도록 합니다. 해당 이벤트에 대한 정보는 푸시 메시지에 포함될 수 있어, 웹 애플리케이션이 그 이벤트에 적절히 반응할 수 있도록 하며, 네트워크 요청을 시작하지 않아도 될 수도 있습니다.

아래의 코드와 다이어그램은 푸시 API의 가상 사용 예시를 보여줍니다.

5.1 예시

이 섹션은 규범적이지 않습니다.

// https://example.com/serviceworker.js
this.onpush = event => {
  console.log(event.data);
  // 여기서 데이터를 IndexedDB에 저장하거나, 열린 윈도우에 전송하거나, 알림을 표시하는 등 다양한 작업을 수행할 수 있습니다.
}

// https://example.com/webapp.js
// async 함수 내부...
try {
  const serviceWorkerRegistration = await navigator.serviceWorker.register(
    "serviceworker.js"
  );
  const pushSubscription = await serviceWorkerRegistration.pushManager.subscribe();
  // 애플리케이션 서버에 필요한 푸시 구독 정보가 이제 확보되어,
  // 예를 들어 XMLHttpRequest 등을 통해 서버로 전송할 수 있습니다.
  console.log(pushSubscription.endpoint);
  console.log(pushSubscription.getKey("p256dh"));
  console.log(pushSubscription.getKey("auth"));
} catch (err) {
  // 운영 환경에서는 오류 정보를
  // 애플리케이션 서버로 전송하는 것이 바람직할 수 있습니다.
  console.log(error);
}

5.2 시퀀스 다이어그램

이 섹션은 규범적이지 않습니다.

구독, 푸시 메시지 전달 및 구독 해지에 대한 예시 이벤트 흐름
그림 1 구독, 푸시 메시지 전달 및 구독 해지에 대한 예시 이벤트 흐름

5.3 푸시 서비스 사용

PushSubscription에 포함된 필드는 애플리케이션 서버푸시 메시지를 전송하는 데 필요한 모든 정보를 담고 있습니다. Push API와 호환되는 푸시 서비스는 웹 푸시 프로토콜을 준수하는 푸시 엔드포인트를 제공합니다. 주요 파라미터 및 속성에는 다음이 포함됩니다:

6. ServiceWorkerRegistration 인터페이스 확장

Service Worker 명세는 ServiceWorkerRegistration 인터페이스를 정의합니다 [SERVICE-WORKERS]. 이 명세는 해당 인터페이스를 확장합니다.

WebIDL[SecureContext]
partial interface ServiceWorkerRegistration {
  readonly attribute PushManager pushManager;
};

pushManager 속성은 PushManager를 노출하며, 해당 속성은 서비스 워커 등록과 연결되어 있습니다. 이 서비스 워커 등록은 ServiceWorkerRegistration 객체로 표현됩니다.

7. PushManager 인터페이스

PushManager 인터페이스는 푸시 서비스 접근에 필요한 동작을 정의합니다.

WebIDL[Exposed=(Window,Worker), SecureContext]
interface PushManager {
  [SameObject] static readonly attribute FrozenArray<DOMString> supportedContentEncodings;

  Promise<PushSubscription> subscribe(optional PushSubscriptionOptionsInit options = {});
  Promise<PushSubscription?> getSubscription();
  Promise<PermissionState> permissionState(optional PushSubscriptionOptionsInit options = {});
};

supportedContentEncodings 속성은 푸시 메시지의 페이로드를 암호화할 때 사용할 수 있는 지원되는 콘텐츠 코딩 시퀀스를 노출합니다. 콘텐츠 코딩은 푸시 서비스푸시 메시지 전송을 요청할 때 Content-Encoding 헤더 필드로 표시됩니다.

사용자 에이전트는 [RFC8291]에 정의된 aes128gcm 콘텐츠 코딩을 반드시 지원해야 하며, 이전 버전 초안에서 정의된 콘텐츠 코딩을 호환성 목적으로 지원할 수도 있습니다.

7.1 subscribe() 메서드

subscribe() 메서드는 호출되었을 때 반드시 다음 단계들을 실행해야 합니다:

  1. promise새로운 promise로 한다.
  2. globalthis관련 글로벌 객체로 한다.
  3. promise를 반환하고, 이후 단계는 병렬로 계속한다.
    참고: 검증 순서는 사용자 에이전트마다 다를 수 있음
  4. options 인자에 userVisibleOnly 값이 false로 설정되어 있고, 사용자 에이전트가 true를 요구하는 경우, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "NotAllowedError" DOMException를 반환한다.
  5. options 인자에 applicationServerKey 멤버의 값이 null이 아닌 값으로 포함되어 있지 않고, 푸시 서비스가 해당 값을 요구하는 경우, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "NotSupportedError" DOMException를 반환한다.
  6. options 인자에 applicationServerKey 속성의 값이 null이 아닌 경우, 다음 하위 단계를 수행한다:
    1. optionsapplicationServerKey 값이 DOMString이면, 해당 값을 base64url 디코딩하여 얻은 옥텟 시퀀스를 담은 ArrayBuffer로 변환한다 [RFC7515].
    2. 디코딩에 실패하면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "InvalidCharacterError" DOMException를 반환하고 해당 단계를 종료한다.
    3. optionsapplicationServerKey 값이 P-256 곡선 위의 올바른 점을 표현하는지 확인한다. 값이 유효하지 않으면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "InvalidAccessError" DOMException를 반환하고 해당 단계를 종료한다.
  7. registrationthis의 연결된 서비스 워커 등록으로 한다.
  8. registration활성 워커가 null이면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "InvalidStateError" DOMException를 반환하고 해당 단계를 종료한다.
  9. permission"push" 사용 권한 요청의 결과로 한다.
  10. permission이 "denied"이면, 글로벌 태스크를 큐에 넣어 사용자 인터랙션 태스크 소스에서 global을 사용해 promise를 reject하고 "NotAllowedError" DOMException를 반환하고 해당 단계를 종료한다.
  11. registration푸시 구독이 있는 경우:
    1. subscriptionregistration푸시 구독을 얻는 결과로 한다. 오류가 있으면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "AbortError" DOMException를 반환하고 해당 단계를 종료한다.
    2. options 인자와 subscriptionoptions 속성을 비교한다. BufferSource 값의 내용은 참조가 아닌 값의 동일성으로 비교한다.
    3. options의 속성 중 하나라도 subscription에 저장된 값과 다르면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 "InvalidStateError" DOMException를 반환하고 해당 단계를 종료한다.
    4. 요청이 완료되면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 resolve하고 subscription을 반환하며 해당 단계를 종료한다.
  12. subscription푸시 구독 생성으로 options을 사용해 시도한다. 구독 생성 과정에서 예외가 발생하면, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 reject하고 해당 예외를 반환하며 해당 단계를 종료한다.
  13. 그 밖의 경우, 글로벌 태스크를 큐에 넣어 네트워킹 태스크 소스에서 global을 사용해 promise를 resolve하고 PushSubscription에 새 subscription의 정보를 담아 반환한다.

getSubscription 메서드는 호출 시 반드시 다음 단계들을 실행해야 합니다:

  1. promise새로운 promise로 한다.
  2. promise를 반환하고 이후 단계는 비동기로 계속한다.
  3. Service Worker가 구독되어 있지 않으면 promise를 null로 resolve한다.
  4. 푸시 구독을 해당 Service Worker와 연결된 것으로 조회한다.
  5. 오류가 있으면 promise를 이름이 "AbortError"인 DOMException으로 reject하고 해당 단계를 종료한다.
  6. 요청이 완료되면 promisePushSubscription에 조회된 푸시 구독 정보를 담아 resolve한다.

permissionState() 메서드는 호출 시 반드시 다음 단계들을 실행해야 합니다:

  1. promise새로운 promise로 한다.
  2. promise를 반환하고 이후 단계는 비동기로 계속한다.
  3. descriptor를 새로운 PermissionDescriptor 객체로 생성하며, name을 "push"로 초기화한다.
  4. statedescriptor권한 상태로 한다.
  5. promisestate로 resolve한다.

푸시 서비스 사용 권한은 영속적일 수 있으며, 유효한 권한이 있으면 이후 구독에서도 재확인할 필요가 없습니다.

권한 요청이 필요하면 subscribe() 메서드를 호출해야 합니다.

7.2 PushSubscriptionOptions 인터페이스

WebIDL[Exposed=(Window,Worker), SecureContext]
interface PushSubscriptionOptions {
  readonly attribute boolean userVisibleOnly;
  [SameObject] readonly attribute ArrayBuffer? applicationServerKey;
};

userVisibleOnly 속성은 get할 때 초기화된 값을 반환합니다.

applicationServerKey 속성은 get할 때 초기화된 값을 반환합니다.

존재하는 경우, applicationServerKey의 값은 반드시 P-256 타원 곡선 위의 점을 포함해야 하며 [DSS] Annex A에서 설명된 비압축 형식(즉, 0x04로 시작하는 65 바이트)으로 인코딩되어야 합니다. DOMString으로 제공되는 경우, 반드시 base64url 인코딩되어야 합니다 [RFC7515].

applicationServerKey가 없고 푸시 서비스가 운영상 요구하는 경우 사용자 에이전트는 구독 시도를 거부할 수 있습니다.

applicationServerKey는 반드시 메시지 암호화에 사용되는 값과 달라야 합니다 [RFC8291].

7.3 PushSubscriptionOptionsInit 딕셔너리

WebIDLdictionary PushSubscriptionOptionsInit {
  boolean userVisibleOnly = false;
  (BufferSource or DOMString)? applicationServerKey = null;
};

userVisibleOnly 멤버가 true로 설정된 경우, 푸시 구독은 예를 들어 웹 알림을 표시하는 등 사용자에게 효과가 가시적으로 드러나는 푸시 메시지에만 사용됨을 나타냅니다. [NOTIFICATIONS]

PushSubscriptionOptionsInit푸시 구독과 연관된 추가 옵션을 나타냅니다. 사용자 에이전트는 이러한 옵션을 사용자의 명시적 허락을 요청할 때 고려할 수 있습니다. 옵션이 고려되는 경우, 사용자 에이전트는 반드시 해당 옵션을 수신되는 푸시 메시지에 적용해야 합니다.

이러한 옵션은 선택사항이며, 사용자 에이전트는 이들 중 일부만 지원할 수 있습니다. 사용자 에이전트는 지원하지 않는 옵션을 노출해서는 안 됩니다.

한 번 설정된 푸시 구독의 옵션은 변경될 수 없습니다. 기존 푸시 구독unsubscribe를 통해 구독 해지 후 새 옵션으로 푸시 구독을 생성할 수 있습니다.

applicationServerKey 멤버는 사용자 에이전트푸시 서비스푸시 구독을 설정할 때 사용됩니다. applicationServerKey 옵션에는 애플리케이션 서버의 타원 곡선 공개키가 포함됩니다. 이 키는 애플리케이션 서버가 해당 푸시 구독푸시 메시지를 보낼 때 자신을 인증하는 데 사용되며, RFC8292에서 정의되어 있습니다. 푸시 서비스는 해당 개인키로 인증 토큰을 생성하지 않으면 푸시 메시지를 거부합니다.

8. PushSubscription 인터페이스

PushSubscription 객체는 푸시 구독을 나타냅니다.

WebIDL[Exposed=(Window,Worker), SecureContext]
interface PushSubscription {
  readonly attribute USVString endpoint;
  readonly attribute EpochTimeStamp? expirationTime;
  [SameObject] readonly attribute PushSubscriptionOptions options;
  ArrayBuffer? getKey(PushEncryptionKeyName name);
  Promise<boolean> unsubscribe();

  PushSubscriptionJSON toJSON();
};

dictionary PushSubscriptionJSON {
  USVString endpoint;
  EpochTimeStamp? expirationTime = null;
  record<DOMString, USVString> keys;
};

endpoint 속성을 get할 때, 사용자 에이전트는 반드시 해당 푸시 구독과 연결된 푸시 엔드포인트를 반환해야 합니다. 사용자 에이전트는 입력값에 따라 분기하지 않는(즉, 상수 시간) 직렬화 방법을 반드시 사용해야 합니다.

expirationTime 속성을 get할 때, 사용자 에이전트는 반드시 해당 푸시 구독과 연결된 구독 만료 시간을 반환해야 하며, 없으면 null을 반환해야 합니다.

options 속성을 get할 때, 사용자 에이전트는 반드시 해당 푸시 구독과 연결된 PushSubscriptionOptions 객체를 반환해야 합니다.

getKey() 메서드는 메시지 암호화 및 인증에 사용할 수 있는 키 자료를 조회합니다. getKey()가 호출되면 다음 절차를 따릅니다:

  1. name 인자로 지정된 키에 해당하는 내부 슬롯을 찾습니다.
  2. 슬롯을 찾지 못하면 null을 반환합니다.
  3. ArrayBuffer 인스턴스로 key 변수를 초기화합니다.
  4. 내부 슬롯에 비대칭 키 쌍이 있으면, key의 내용을 키 쌍의 공개키 직렬화 값으로 설정합니다. 직렬화 포맷은 키 이름을 정의하는 명세에 따릅니다. 예를 들어, [RFC8291]에서는 "p256dh" 공개키가 Annex A에 정의된 비압축 형식(65 바이트, 0x04로 시작)으로 인코딩됨을 명시합니다.
  5. 그렇지 않고 내부 슬롯에 대칭키가 있으면, key의 내용을 해당 슬롯의 값을 복사합니다. 예를 들어, auth 파라미터는 사용자 에이전트애플리케이션 서버가 보낸 메시지를 인증하는 데 사용하는 옥텟 시퀀스입니다.
  6. key를 반환합니다.

"p256dh"와 "auth" 키는 반드시 지원되어야 하며, 값은 [RFC8291]에 따라 사용자 에이전트가 수신 푸시 메시지를 복호화하는 데 필요한 값이어야 합니다.

unsubscribe() 메서드는 호출 시 반드시 다음 단계들을 실행해야 합니다:

  1. promise새로운 promise로 한다.
  2. promise를 반환하고, 이후 단계는 비동기로 계속한다.
  3. 푸시 구독이 이미 비활성화되어 있다면, promisefalse로 resolve하고 단계를 종료한다.
  4. 다음 단계를 병렬로 실행한다:
    1. 푸시 구독을 비활성화한다. 사용자 에이전트는 해당 푸시 구독에 대해 추가 푸시 메시지를 전달하지 않아야 합니다.

      사용자 에이전트푸시 서비스비활성화 요청을 실패한 경우(예: 네트워크 오류 등), 일정 시간 동안 재시도를 해야 합니다.

  5. promisetrue로 resolve한다.

toJSON() 메서드는 호출 시 반드시 다음 단계들을 실행해야 합니다:

  1. json을 새로운 PushSubscriptionJSON 딕셔너리로 한다.
  2. json["endpoint"]를 endpoint 속성 get의 결과로 설정한다.
  3. json["expirationTime"]를 expirationTime 속성 get의 결과로 설정한다.
  4. keysrecord<DOMString, USVString>의 새로운 빈 인스턴스로 한다.
  5. 내부 슬롯의 모든 키 식별자 i에 대해 이름순으로 다음을 실행:
    1. 내부 슬롯이 비대칭 키 쌍이면, b를 키 이름 i에 해당하는 공개키 값을 인코딩한 값(키 이름 명세에 정의된 인코딩, getKey() 참고)으로 한다.
    2. 그렇지 않으면 bgetKey가 반환한 값으로 한다.
    3. sbUSVString로 URL-safe base64(패딩 없음)로 인코딩한 값으로 한다 [RFC4648]. 사용자 에이전트b 값에 따라 분기하지 않는 직렬화 방법을 사용해야 합니다.
    4. keys[i]를 s로 설정한다.
  6. json["keys"]를 keys로 설정한다.
  7. json을 반환한다.

PushSubscriptionJSON 딕셔너리는 JSON 타입PushSubscription을 나타냅니다. ECMAScript에서는 JSON.stringify() 함수로 JSON 문자열로 변환할 수 있습니다.

keys 레코드는 지원되는 모든 PushEncryptionKeyName 항목의 값을 URL-safe base64로 인코딩한 표현을 담습니다 [RFC4648].

PushSubscription의 옵션은 직렬화되지 않음을 유의하세요.

8.1 PushEncryptionKeyName 열거형

푸시 메시지 암호화에 사용되는 키는 getKey() 메서드나 PushSubscription 직렬화기를 통해 웹 애플리케이션에 제공됩니다. 각 키는 PushEncryptionKeyName 열거형 값으로 이름이 지정됩니다.

WebIDLenum PushEncryptionKeyName {
  "p256dh",
  "auth"
};

p256dh 값은 [RFC8291]에서 설명된 P-256 ECDH 디피-헬만 공개키를 조회하는 데 사용합니다.

auth 값은 [RFC8291]에서 설명된 인증 비밀을 조회하는 데 사용합니다.

9. PushMessageData 인터페이스

WebIDL[Exposed=ServiceWorker, SecureContext]
interface PushMessageData {
  ArrayBuffer arrayBuffer();
  Blob blob();
  Uint8Array bytes();
  any json();
  USVString text();
};

PushMessageData 객체는 생성 시 설정되는 bytes (즉, 바이트 시퀀스)를 갖습니다.

arrayBuffer() 메서드는 ArrayBuffer 객체를 반환하며, 해당 객체의 내용은 thisbytes입니다. ArrayBuffer 객체 생성 중 발생한 예외는 다시 throw됩니다.

blob() 메서드는 Blob 객체를 새로 생성해 반환하며, 그 내용은 thisbytes입니다.

bytes() 메서드는 ArrayBuffer를 backing으로 하는 새 Uint8Array를 반환하며, 해당 ArrayBuffer의 내용은 thisbytes입니다. ArrayBuffer 객체 생성 중 발생한 예외는 다시 throw됩니다.

json() 메서드는 parse JSON bytes to a JavaScript valuethisbytes에 대해 실행한 결과를 반환합니다.

text() 메서드는 UTF-8 decodethisbytes에 대해 실행한 결과를 반환합니다.

object에서 바이트 시퀀스 추출을 하려면 다음 단계를 수행합니다:

  1. bytes를 빈 바이트 시퀀스로 한다.
  2. object의 타입에 따라 분기:
    BufferSource
    bytesobject의 내용을 복사한다.
    USVString
    bytesutf-8 encodeobject에 대해 실행한 결과를 설정한다.
  3. bytes를 반환한다.

10. 이벤트

10.1 ServiceWorkerGlobalScope 인터페이스 확장

Service Worker 명세는 ServiceWorkerGlobalScope 인터페이스를 정의합니다 [SERVICE-WORKERS] 이 명세는 해당 인터페이스를 확장합니다.

WebIDL[Exposed=ServiceWorker, SecureContext]
partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onpush;
  attribute EventHandler onpushsubscriptionchange;
};

onpush 속성은 이벤트 핸들러 IDL 속성이며 해당 이벤트 핸들러 이벤트 타입은 "push"입니다. "push" 이벤트는 푸시 메시지푸시 구독에 대해 수신되었음을 나타냅니다.

onpushsubscriptionchange 속성은 이벤트 핸들러 IDL 속성이며 해당 이벤트 핸들러 이벤트 타입은 "pushsubscriptionchange"입니다.

10.2 PushEvent 인터페이스

WebIDL[Exposed=ServiceWorker, SecureContext]
interface PushEvent : ExtendableEvent {
  constructor(DOMString type, optional PushEventInit eventInitDict = {});
  readonly attribute PushMessageData? data;
  readonly attribute Notification? notification;
};

dictionary PushEventInit : ExtendableEventInit {
  PushMessageDataInit? data = null;
  Notification? notification = null;
};

typedef (BufferSource or USVString) PushMessageDataInit;

constructorPushEvent 인터페이스 또는 해당 인터페이스를 상속한 인터페이스에서 호출될 때, 일반적인 이벤트 생성 단계에 다음 단계들이 추가됩니다:

  1. eventInitDictdata 멤버가 없으면, 이벤트의 data 속성을 null로 설정하고 단계를 종료합니다.
  2. b바이트 시퀀스 추출eventInitDict의 "data" 멤버에 대해 실행한 결과로 한다.
  3. 이벤트의 data 속성을 PushMessageData 인스턴스(해당 bytesb인 객체)로 설정한다.

data 속성은 초기화된 값을 반환해야 합니다.

notification 속성은 초기화된 값을 반환해야 합니다.

10.3 푸시 메시지 수신

사용자 에이전트푸시 서비스로부터 푸시 메시지를 수신하면, 반드시 다음 단계들을 실행합니다.

  1. registration서비스 워커 등록으로 한다. 이는 푸시 메시지에 대응합니다.
  2. registration을 찾지 못하면, 해당 단계들을 중단합니다.
  3. subscriptionregistration의 활성 푸시 구독으로 한다.
  4. bytes를 null로 한다.
  5. 푸시 메시지에 페이로드가 포함되어 있으면:
    1. 푸시 메시지의 페이로드를 subscription과 연관된 키 쌍의 개인키 및 RFC8291에 기술된 절차를 이용해 복호화합니다. bytes를 그 결과 바이트 시퀀스로 설정합니다.
    2. 푸시 메시지 페이로드를 어떤 이유로든 복호화할 수 없다면, 푸시 메시지 확인을 하고 해당 단계들을 중단합니다.

      참고

      푸시 구독과 연관된 키 쌍으로 성공적으로 복호화되지 않은 푸시 메시지에는 push 이벤트가 발생하지 않습니다.

  6. bytes가 null이 아니면:

    1. baseURLregistrationscope URL로 한다.

    2. originbaseURLorigin으로 한다.

    3. fallbackTimestamp현재 벽시계 시간으로 한다.

    4. declarativeResult선언적 푸시 메시지 파서bytes, origin, baseURL, fallbackTimestamp에 대해 실행한 결과로 한다.

    5. declarativeResult가 실패가 아니면:

      1. notificationdeclarativeResultnotification으로 한다.

      2. notification서비스 워커 등록registration으로 설정한다.

      3. notificationShown을 false로 한다.

      4. declarativeResultmutable 값이 true면:

        1. result푸시 이벤트 발생registration, null, 새 Notification 객체(즉, notification를 나타내는 객체)로 실행한 결과로 한다.

        2. result가 실패가 아니면, notificationShownresultnotification shown 값으로 한다.

      5. notificationShown이 false면, 알림 표시 단계notification에 대해 실행한다.

      6. 푸시 메시지 확인푸시 메시지에 대해 실행하고 해당 단계들을 중단한다.

  7. dataPushMessageData 객체로 생성하고, bytesbytes로 한다(null이면 null).

  8. result푸시 이벤트 발생registration, data, null로 실행한 결과로 한다.

  9. result가 실패이고 동일한 푸시 메시지가 서비스 워커 등록에 여러 번 실패한 경우 푸시 메시지 확인을 실행한다.

  10. result가 실패가 아니면 푸시 메시지 확인을 실행한다.

푸시 이벤트 결과튜플로, notification shown (즉, 불리언)이 포함됩니다.

푸시 이벤트 발생서비스 워커 등록 registration, PushMessageData 또는 null data, notification 또는 null notification이 주어졌을 때 다음 단계들을 실행합니다. 이 함수는 실패 또는 푸시 이벤트 결과를 반환합니다.

  1. notificationResult를 null로 한다.

  2. registrationshowNotification() 성공 여부를 false로 설정한다.

  3. 기능 이벤트 발생을 "push" 이름으로 PushEventregistration에서 다음 속성으로 발생시킨다:

    data
    data
    notification
    notification

    그 후 dispatchedEvent에 대해 다음 단계를 병렬로 실행한다:

    1. dispatchedEventextend lifetime promises가 모두 resolve될 때까지 대기한다.

    2. 모두 성공적으로 resolve되지 않으면, notificationResult를 실패로 설정한다.

    3. 그렇지 않으면 notificationResultregistrationshowNotification() 성공 여부로 설정한다.

  4. notificationResult가 null이 아닐 때까지 대기한다.

  5. notificationResult가 실패면, 실패를 반환한다.

  6. Assert: notificationResult불리언이다.

  7. (notificationResult)를 반환한다.

푸시 메시지 확인푸시 메시지 pushMessage가 주어졌을 때 RFC8030에 따라 수신을 확인하는 것을 의미합니다.

푸시 메시지를 확인하면, 푸시 서비스가 해당 메시지 전달을 중단하고 애플리케이션 서버에 성공을 보고합니다. 이는 동일한 푸시 메시지가 푸시 서비스에 의해 무한 반복 전달되는 것을 방지합니다.

확인은 또한 애플리케이션 서버푸시 메시지의 성공적인 전달 영수증을 잘못 받을 수 있음을 의미합니다. 따라서 여러 번 거부가 허용되어야 하며, 최소 3회 반복 시도가 권장됩니다.

10.4 pushsubscriptionchange 이벤트

pushsubscriptionchange 이벤트는 푸시 구독에 대한 변경(예: 갱신, 취소, 손실 등)이 애플리케이션 외부에서 발생했음을 나타냅니다.

"pushsubscriptionchange" 이벤트 발생서비스 워커 등록 registration, newSubscription, oldSubscription이 주어졌을 때, 사용자 에이전트가 반드시 기능 이벤트 발생을 "pushsubscriptionchange" 이름으로 PushSubscriptionChangeEventregistration에서 다음 속성으로 발생시켜야 합니다:

newSubscription
newSubscription
oldSubscription
oldSubscription
참고

새로운 푸시 구독 정보를 애플리케이션 서버로 전송할 때는 [WEB-BACKGROUND-SYNC]과 같은 더 신뢰성 높은 동기화 메커니즘을 사용하는 것이 좋습니다. 네트워크 환경이 불안정할 수 있으며 fetch가 실패할 수 있습니다.

10.4.1 PushSubscriptionChangeEvent 인터페이스

WebIDL[Exposed=ServiceWorker, SecureContext]
interface PushSubscriptionChangeEvent : ExtendableEvent {
  constructor(DOMString type, optional PushSubscriptionChangeEventInit eventInitDict = {});
  readonly attribute PushSubscription? newSubscription;
  readonly attribute PushSubscription? oldSubscription;
};

newSubscription 속성은 get 시 초기화된 값을 반환합니다.

oldSubscription 속성은 get 시 초기화된 값을 반환합니다.

10.4.2 PushSubscriptionChangeEventInit 인터페이스

WebIDLdictionary PushSubscriptionChangeEventInit : ExtendableEventInit {
  PushSubscription newSubscription = null;
  PushSubscription oldSubscription = null;
};

newSubscription 멤버는 푸시 구독pushsubscriptionchange 이벤트 발생 시점에 유효한 정보를 나타냅니다. 새 푸시 구독을 생성할 수 없는 경우(예: 명시적 허락 상실 등)는 값이 null입니다.

oldSubscription 멤버는 더 이상 사용해서는 안 되는 푸시 구독 정보를 나타냅니다. 사용자 에이전트가 전체 정보를 제공할 수 없는 경우(예: 데이터베이스 손상 등)는 값이 null입니다.

11. 접근성

Push API푸시 서비스로부터 받은 데이터를 직접적으로 표시하는 수단을 제공하지 않습니다. 대신 Push API는 다른 API, 주로 Notifications API Standard에 의존하여 수신 정보를 최종 사용자에게 보여줍니다. 따라서 Push API 자체에는 접근성 요구사항이 없습니다. 그러나 [NOTIFICATIONS]와 같은 명세에서는 알림을 접근성 있게 표시하는 방법에 대한 가이드를 제공합니다.

또한 접근성 있는 인터페이스를 제공하려면 푸시 메시지가 전달할 수 있는 정보보다 더 많은 정보를 전송해야 할 수도 있습니다. 푸시 메시지는 소량의 콘텐츠나 식별자를 전달하는 데 가장 적합합니다. 더 큰 리소스는 서버에서 별도로 가져와야 합니다.

12. 적합성

비규범적(non-normative)으로 표시된 섹션뿐만 아니라, 이 명세의 모든 저작 가이드라인, 다이어그램, 예시, 노트 또한 비규범적입니다. 그 외의 모든 내용은 규범적입니다.

이 문서에서 MAY, MUST, MUST NOT, SHOULD, SHOULD NOT 키워드는 BCP 14 [RFC2119] [RFC8174] 에서 정의된 대로, 그리고 여기에서 보여지는 것처럼 모두 대문자로 표시된 경우에만 해당 의미로 해석되어야 합니다.

이 명세는 하나의 제품, 즉 본 명세에 포함된 인터페이스를 구현하는 사용자 에이전트에 적용되는 적합성 기준을 정의합니다.

A. IDL 색인

WebIDLdictionary PushPermissionDescriptor : PermissionDescriptor {
  boolean userVisibleOnly = false;
};

[SecureContext]
partial interface ServiceWorkerRegistration {
  readonly attribute PushManager pushManager;
};

[Exposed=(Window,Worker), SecureContext]
interface PushManager {
  [SameObject] static readonly attribute FrozenArray<DOMString> supportedContentEncodings;

  Promise<PushSubscription> subscribe(optional PushSubscriptionOptionsInit options = {});
  Promise<PushSubscription?> getSubscription();
  Promise<PermissionState> permissionState(optional PushSubscriptionOptionsInit options = {});
};

[Exposed=(Window,Worker), SecureContext]
interface PushSubscriptionOptions {
  readonly attribute boolean userVisibleOnly;
  [SameObject] readonly attribute ArrayBuffer? applicationServerKey;
};

dictionary PushSubscriptionOptionsInit {
  boolean userVisibleOnly = false;
  (BufferSource or DOMString)? applicationServerKey = null;
};

[Exposed=(Window,Worker), SecureContext]
interface PushSubscription {
  readonly attribute USVString endpoint;
  readonly attribute EpochTimeStamp? expirationTime;
  [SameObject] readonly attribute PushSubscriptionOptions options;
  ArrayBuffer? getKey(PushEncryptionKeyName name);
  Promise<boolean> unsubscribe();

  PushSubscriptionJSON toJSON();
};

dictionary PushSubscriptionJSON {
  USVString endpoint;
  EpochTimeStamp? expirationTime = null;
  record<DOMString, USVString> keys;
};

enum PushEncryptionKeyName {
  "p256dh",
  "auth"
};

[Exposed=ServiceWorker, SecureContext]
interface PushMessageData {
  ArrayBuffer arrayBuffer();
  Blob blob();
  Uint8Array bytes();
  any json();
  USVString text();
};

[Exposed=ServiceWorker, SecureContext]
partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onpush;
  attribute EventHandler onpushsubscriptionchange;
};

[Exposed=ServiceWorker, SecureContext]
interface PushEvent : ExtendableEvent {
  constructor(DOMString type, optional PushEventInit eventInitDict = {});
  readonly attribute PushMessageData? data;
  readonly attribute Notification? notification;
};

dictionary PushEventInit : ExtendableEventInit {
  PushMessageDataInit? data = null;
  Notification? notification = null;
};

typedef (BufferSource or USVString) PushMessageDataInit;

[Exposed=ServiceWorker, SecureContext]
interface PushSubscriptionChangeEvent : ExtendableEvent {
  constructor(DOMString type, optional PushSubscriptionChangeEventInit eventInitDict = {});
  readonly attribute PushSubscription? newSubscription;
  readonly attribute PushSubscription? oldSubscription;
};

dictionary PushSubscriptionChangeEventInit : ExtendableEventInit {
  PushSubscription newSubscription = null;
  PushSubscription oldSubscription = null;
};

B. 감사의 글

편집자들은 Firefox OS Push 메시지 솔루션을 구현한 Mozilla와 Telefónica Digital 팀, 그리고 본 문서에 중요한 기술적 기여를 해주신 다음 분들께 깊은 감사를 표하고자 합니다: Antonio Amaya, Miguel García Arribas, Ben Bangert, Kit Cambridge, José Manuel Cantera, JR Conlin, Albert Crespell, Matt Gaunt, Phil Jenvey, Guillermo López, Nikhil Marathe, John Mellor, Pınar Özlen, Fernando R. Sela, Shijun Sun, Doug Turner.

C. 참고문헌

C.1 규범적 참고문헌

[ANSI-X9-62]
금융 서비스 산업용 공개키 암호, 타원곡선 디지털 서명 알고리즘 (ECDSA). ANSI. 2005. URL: https://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.62%3a2005
[dom]
DOM 표준. Anne van Kesteren. WHATWG. 현행 표준. URL: https://dom.spec.whatwg.org/
[DSS]
FIPS PUB 186-5: 디지털 서명 표준 (DSS). 미국 상무부/국립표준기술연구소(NIST). 2023년 2월 3일. 국가 표준. URL: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
[ecmascript]
ECMAScript 언어 명세. Ecma International. URL: https://tc39.es/ecma262/multipage/
[encoding]
인코딩 표준. Anne van Kesteren. WHATWG. 현행 표준. URL: https://encoding.spec.whatwg.org/
[fileapi]
File API. Marijn Kruisselbrink. W3C. 2024년 12월 4일. W3C 초안. URL: https://www.w3.org/TR/FileAPI/
[hr-time]
고해상도 시간(High Resolution Time). Yoav Weiss. W3C. 2024년 11월 7일. W3C 초안. URL: https://www.w3.org/TR/hr-time-3/
[html]
HTML 표준. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 현행 표준. URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra 표준. Anne van Kesteren; Domenic Denicola. WHATWG. 현행 표준. URL: https://infra.spec.whatwg.org/
[NOTIFICATIONS]
Notifications API 표준. Anne van Kesteren. WHATWG. 현행 표준. URL: https://notifications.spec.whatwg.org/
[Permissions]
Permissions. Marcos Caceres; Mike Taylor. W3C. 2025년 6월 24일. W3C 초안. URL: https://www.w3.org/TR/permissions/
[RFC2119]
RFC에서 요구 수준을 나타내는 키워드. S. Bradner. IETF. 1997년 3월. 현행 권고규약. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC4648]
Base16, Base32, Base64 데이터 인코딩. S. Josefsson. IETF. 2006년 10월. 초안 표준. URL: https://www.rfc-editor.org/rfc/rfc4648
[RFC7231]
Hypertext Transfer Protocol (HTTP/1.1): 의미 및 콘텐츠. R. Fielding, Ed.; J. Reschke, Ed. IETF. 2014년 6월. 초안 표준. URL: https://httpwg.org/specs/rfc7231.html
[RFC7515]
JSON Web Signature (JWS). M. Jones; J. Bradley; N. Sakimura. IETF. 2015년 5월. 초안 표준. URL: https://www.rfc-editor.org/rfc/rfc7515
[RFC8030]
HTTP 푸시를 이용한 일반 이벤트 전달. M. Thomson; E. Damaggio; B. Raymor, Ed. IETF. 2016년 12월. 초안 표준. URL: https://www.rfc-editor.org/rfc/rfc8030
[RFC8174]
RFC 2119 키워드의 대문자/소문자 모호성. B. Leiba. IETF. 2017년 5월. 현행 권고규약. URL: https://www.rfc-editor.org/rfc/rfc8174
[RFC8291]
웹 푸시를 위한 메시지 암호화. M. Thomson. IETF. 2017년 11월. 초안 표준. URL: https://www.rfc-editor.org/rfc/rfc8291
[RFC8292]
웹 푸시를 위한 VAPID(자발적 애플리케이션 서버 식별). M. Thomson; P. Beverloo. IETF. 2017년 11월. 초안 표준. URL: https://www.rfc-editor.org/rfc/rfc8292
[SERVICE-WORKERS]
Service Workers. Yoshisato Yanagisawa; Monica CHINTALA. W3C. 2025년 3월 6일. CRD. URL: https://www.w3.org/TR/service-workers/
[url]
URL 표준. Anne van Kesteren. WHATWG. 현행 표준. URL: https://url.spec.whatwg.org/
[WEBIDL]
Web IDL 표준. Edgar Chen; Timothy Gu. WHATWG. 현행 표준. URL: https://webidl.spec.whatwg.org/

C.2 비규범적 참고문헌

[Fetch]
Fetch 표준. Anne van Kesteren. WHATWG. 현행 표준. URL: https://fetch.spec.whatwg.org/
[WEB-BACKGROUND-SYNC]
웹 백그라운드 동기화(Web Background Synchronization). W3C. 커뮤니티 그룹 초안 보고서. URL: https://wicg.github.io/background-sync/spec/
[WebSockets]
WebSockets 표준. Adam Rice. WHATWG. 현행 표준. URL: https://websockets.spec.whatwg.org/