From 56c5432a10d995b62303a4c2eef69be7fcbe7470 Mon Sep 17 00:00:00 2001
From: Anne van Kesteren
The Push API enables sending of a push message to a web application via
@@ -164,6 +165,555 @@
service worker is not currently running, the worker is started to enable delivery.
+ A declarative push message is a [=push message=] whose data is a JSON document + that is understood by the user agent. A user agent opportunistically parses each incoming + [=push message=] to determine if it is a [=declarative push message=] using the + [=declarative push message parser=]. +
++ A [=declarative push message=] allows for the creation and display of a notification + without the involvement of a service worker. Nevertheless, a service worker can still be + involved if desired by the [=application server=]. In such a scenario the declarative + nature of the [=push message=] serves as a backup in case the service worker was evicted + due to storage pressure, for instance. And also provides a more object-oriented approach + to transmitting notification data. +
++ { + "web_push": 8030, + "notification": { + "title": "Watch the new season of Return of the Example now!", + "lang": "en-US", + "dir": "ltr", + "body": "The examples are going to great lengths once more.", + "url": "https://tv.example/return" + } + } ++
+ A [=declarative push message=] has the following members: +
+web_push
(required)
+ + An integer that must be 8030. Used to disambiguate a [=declarative push message=] + from other JSON documents. +
+notification
(required)
+
+ A JSON object consisting of the following members, all analogous to Notifications
+ API features, though sometimes with a slightly stricter type. Apart from
+ title
all members are derived from the {{NotificationOptions}}
+ dictionary and to be maintained in tandem. [[NOTIFICATIONS]]
+
title
(required)
+ + A string. +
+dir
+
+ "auto
", "ltr
", or "rtl
".
+
lang
+ + A string that holds a language tag. +
+body
+ + A string. +
+navigate
(required)
+ + A string that holds a URL. +
+tag
+ + A string. +
+image
+ + A string that holds a URL. +
+icon
+ + A string that holds a URL. +
+badge
+ + A string that holds a URL. +
+vibrate
+ + An array of [=/32-bit unsigned integers=]. +
+timestamp
+ + A [=/64-bit unsigned integer=]. +
+renotify
+ + A boolean. +
+silent
+ + A boolean. +
+require_interaction
+ + A boolean. +
+data
+ + Any JSON value. +
+actions
+ + An array of JSON objects consisting of the following members, all derived from + the {{NotificationAction}} dictionary and to be maintained in tandem. +
+action
(required)
+ + A string. +
+title
(required)
+ + A string. +
+navigate
(required)
+ + A string that holds a URL. +
+icon
+ + A string that holds a URL. +
+app_badge
+ + A [=/64-bit unsigned integer=]. +
++ Platform conventions are likely to impose a lower limit with regards to what is + displayed to the end user. [[BADGING]] +
+mutable
+
+ A boolean. When true causes a push
event to be dispatched to a service
+ worker (if any) containing the {{Notification}} object and app badge number (if
+ any) described by the declarative push message.
+
+ A declarative push message parser result is a [=/tuple=] consisting of a + notification (a + [=/notification=]), an app + badge (null or an integer), and a mutable (a boolean). +
++ The declarative push message parser given a [=/byte sequence=] + bytes, [=/origin=] origin, [=/URL=] baseURL, and + {{EpochTimeStamp}} fallbackTimestamp runs these steps. They return failure + or a [=/declarative push message parser result=]. +
++ Let message be the result of [=parse JSON bytes to an Infra + value|parsing JSON bytes to an Infra value=] given bytes. If that throws + an exception, then return failure. +
++ If message is not a [=/map=], then return failure. +
++ If message["`web_push`"] does not [=map/exist=] or is not 8030, then + return failure. +
++ If message["`notification`"] does not [=map/exist=], then return + failure. +
++ Let notificationInput be message["`notification`"]. +
++ If notificationInput is not a [=/map=], then return failure. +
++ If notificationInput["`title`"] does not [=map/exist=] or is not a + string, then return failure. +
++ If notificationInput["`navigate`"] does not [=map/exist=] or is not a + string, then return failure. +
++ Let notificationTitle be notificationInput["`title`"]. +
++ Let notificationOptions be a {{NotificationOptions}} dictionary. +
++ If notificationInput["`dir`"] [=map/exists=] and is "`auto`", "`ltr`", + or "`rtl`", then set notificationOptions["{{NotificationOptions/dir}}"] + to notificationInput["`dir`"]. +
++ If notificationInput["`lang`"] [=map/exists=] and is a string, then set + notificationOptions["{{NotificationOptions/lang}}"] to + notificationInput["`lang`"]. +
++ If notificationInput["`body`"] [=map/exists=] and is a string, then set + notificationOptions["{{NotificationOptions/body}}"] to + notificationInput["`body`"]. +
++ Set notificationOptions["{{NotificationOptions/navigate}}"] to + notificationInput["`navigate`"], [=string/converted=]. +
++ If notificationInput["`tag`"] [=map/exists=] and is a string, then set + notificationOptions["{{NotificationOptions/tag}}"] to + notificationInput["`tag`"]. +
++ If notificationInput["`image`"] [=map/exists=] and is a string, then set + notificationOptions["{{NotificationOptions/image}}"] to + notificationInput["`image`"], [=string/converted=]. +
++ If notificationInput["`icon`"] [=map/exists=] and is a string, then set + notificationOptions["{{NotificationOptions/icon}}"] to + notificationInput["`icon`"], [=string/converted=]. +
++ If notificationInput["`badge`"] [=map/exists=] and is a string, then set + notificationOptions["{{NotificationOptions/badge}}"] to + notificationInput["`badge`"], [=string/converted=]. +
++ If notificationInput["`vibrate`"] [=map/exists=] and is a [=/list=] of + which each [=list/item=] is a [=/32-bit unsigned integer=], then set + notificationOptions["{{NotificationOptions/vibrate}}"] to + notificationInput["`vibrate`"]. +
++ If notificationInput["`timestamp`"] [=map/exists=] and is a [=/64-bit + unsigned integer=], then set + notificationOptions["{{NotificationOptions/timestamp}}"] to + notificationInput["`timestamp`"]. +
++ If notificationInput["`renotify`"] [=map/exists=] and is a boolean, then + set notificationOptions["{{NotificationOptions/renotify}}"] to + notificationInput["`renotify`"]. +
++ If notificationInput["`silent`"] [=map/exists=] and is a boolean, then + set notificationOptions["{{NotificationOptions/silent}}"] to + notificationInput["`silent`"]. +
++ If notificationInput["`require_interaction`"] [=map/exists=] and is a + boolean, then set + notificationOptions["{{NotificationOptions/requireInteraction}}"] to + notificationInput["`require_interaction`"]. +
++ If notificationInput["`data`"] [=map/exists=], then set + notificationOptions["{{NotificationOptions/data}}"] to the result of + running convert an Infra value to a JSON-compatible JavaScript value given + notificationInput["`data`"]. +
++ If notificationInput["`actions`"] [=map/exists=] and is a [=/list=]: +
++ Let notificationActions be « ». +
++ [=list/For each=] actionInput of + notificationInput["`actions`"]: +
++ If actionInput["`action`"] does not [=map/exist=] or is not a + string, then [=iteration/continue=]. +
++ If actionInput["`title`"] does not [=map/exist=] or is not a + string, then [=iteration/continue=]. +
++ If actionInput["`navigate`"] does not [=map/exist=] or is not a + string, then [=iteration/continue=]. +
++ Let actionNavigate be actionInput["`navigate`"], + [=string/converted=]. +
++ Let notificationAction be the {{NotificationAction}} dictionary + «[ "{{NotificationAction/action}}" → actionInput["`action`"], + "{{NotificationAction/title}}" → actionInput["`title`"], + "{{NotificationAction/navigate}}" → actionNavigate ]». +
++ If actionInput["`icon`"] [=map/exists=] and is a string, then + set notificationAction["{{NotificationAction/icon}}"] to + actionInput["`icon`"], [=string/converted=]. +
++ [=list/Append=] notificationAction to + notificationActions. +
++ Set notificationOptions["{{NotificationOptions/actions}}"] to + notificationActions. +
++ Let notification be the result of creating a notification given + notificationTitle, notificationOptions, origin, + baseURL, and fallbackTimestamp. If this throws an exception, + then return failure. +
++ If notification's [=notification/navigation URL=] is null, then return + failure. +
++ If the [=notification action/navigation URL=] of any [=/notification action=] of + notification's [=notification/actions=] is null, then return failure. +
++ Let appBadge be null. +
++ If message["`app_badge`"] [=map/exists=] and + message["`app_badge`"] is a [=/64-bit unsigned integer=], then set + appBadge to message["`app_badge`"]. +
++ Let mutable be false. +
++ If message["`mutable`"] [=map/exists=] and + message["`mutable`"] is a boolean, then set mutable to + message["`mutable`"]. +
++ Return (notification, appBadge, mutable). +
+- {{PushMessageData}} objects have an associated bytes (a [=byte sequence=]), - which is set on creation. + {{PushMessageData}} objects have an associated bytes (a [=byte sequence=]), which is set on creation.
The arrayBuffer() method steps are to return an {{ArrayBuffer}} whose contents @@ -1063,12 +1613,22 @@
+[Exposed=ServiceWorker, SecureContext] interface PushEvent : ExtendableEvent { constructor(DOMString type, optional PushEventInit eventInitDict = {}); readonly attribute PushMessageData? data; + readonly attribute Notification? notification; + readonly attribute unsigned long long? appBadge; }; + + dictionary PushEventInit : ExtendableEventInit { + PushMessageDataInit? data = null; + Notification? notification = null; + unsigned long long? appBadge = null; + }; + + typedef (BufferSource or USVString) PushMessageDataInit;When a constructor of the PushEvent interface, or of an interface that @@ -1082,29 +1642,18 @@
- The data, when getting, returns the value it was initialized with. + The data attribute must return the value it was initialized with. +
++ The notification attribute must return the value it was initialized with.
-- typedef (BufferSource or USVString) PushMessageDataInit; - - dictionary PushEventInit : ExtendableEventInit { - PushMessageDataInit data; - }; -
- The data member contains the data included in the push message when - included and the user agent verified its authenticity. The value will be set to - `null` in all other cases. + The appBadge attribute must return the value it was initialized with.
+ If the push message payload could not be decrypted for any reason, then + [=acknowledge a push message|acknowledge=] the push message and abort + these steps. +
++ A `push` event is not fired for a push message that was not successfully + decrypted using the key pair associated with the push subscription. +
++ If |bytes| is non-null: +
++ Let |baseURL| be |registration|'s [=service worker registration/scope URL=]. +
++ Let |origin| be |baseURL|'s [=url/origin=]. +
++ Let |fallbackTimestamp| be [=current coarsened wall time=]. +
++ Let |declarativeResult| be the result of running the [=/declarative push message + parser=] given |bytes|, |origin|, |baseURL|, and |fallbackTimestamp|. +
++ If |declarativeResult| is not failure: +
+ Let |notification| be |declarativeResult|'s [=declarative push message parser + result/notification=]. +
++ Set |notification|'s [=notification/service worker registration=] to + |registration|. +
+ Let |notificationShown| be false. +
++ Let |appBadgeSet| be false. +
++ If |declarativeResult|'s [=declarative push message parser result/mutable=] + is true: +
++ Let |result| be the result of [=fire a push event|firing a push event=] + given |registration|, null, a new {{Notification}} object representing + |notification|, and |declarativeResult|'s [=declarative push message + parser result/app badge=]. +
++ If |result| is not failure, then set |notificationShown| to |result|'s + [=push event result/notification shown=] and |appBadgeSet| to |result|'s + [=push event result/app badge set=]. +
++ If |notificationShown| is false, then run the [=notification show steps=] + given |notification|. +
++ If |appBadgeSet| is false, then w3c/badging #111... +
++ [=acknowledge a push message|Acknowledge=] the push message and abort + these steps. +
- A `push` event will not be fired for a push message that was not - successfully decrypted using the key pair associated with the push - subscription. -
+ Let |data| be a new {{PushMessageData}} object whose [=PushMessageData/bytes=] is + |bytes| if |bytes| is non-null; otherwise null. +
++ Let |result| be the result of [=fire a push event|firing a push event=] given + |registration|, |data|, null, and null. +
++ If result is failure and the same push message has been delivered + to a service worker registration multiple times unsuccessfully, then + [=acknowledge a push message|acknowledge=] the push message. +
++ If result is not failure, then [=acknowledge a push message|acknowledge=] + the push message. +
++ A push event result is a [=/tuple=] consisting of a notification shown (a [=/boolean=]) and a app badge set (a [=/boolean=]). +
++ To fire a push event given a [=/service worker registration=] |registration|, + {{PushMessageData}} object or null |data|, a [=/notification=] or null |notification|, + and an integer or null |appBadge|, run these steps. They return failure or a [=/push + event result=]. +
++ Let |notificationResult| be null. +
++ Let |appBadgeResult| be null. +
+Fire a functional event named "`push`" using PushEvent on @@ -1156,44 +1846,92 @@
Then run the following steps in parallel, with |dispatchedEvent|:
+ Wait for all of the promises in the [=ExtendableEvent/extend lifetime promises=] + of |dispatchedEvent| to resolve. +
- If the same push message has been delivered to a service worker - registration multiple times unsuccessfully, acknowledge the receipt of the - push message according to [[RFC8030]]. + If they do not resolve successfully, then set |notificationResult| and + |appBadgeResult| to failure and abort these steps.
+- Acknowledging the push message causes the push service to stop - delivering the message and to report success to the application server. - This prevents the same push message from being retried by the push - service indefinitely. + Set |notificationResult| to true if + {{ServiceWorkerRegistration/showNotification()}} has been invoked; otherwise + false.
+- Acknowledging also means that an application server could incorrectly - receive a delivery receipt indicating successful delivery of the push - message. Therefore, multiple rejections SHOULD be permitted before - acknowledging; allowing at least three attempts is recommended. + Set |appBadgeResult| to true if {{NavigatorBadge/setAppBadge()}} has been + invoked; otherwise false.
+ Wait for |notificationResult| and |appBadgeResult| to be non-null. +
++ If |notificationResult| is failure, then return failure. +
++ [=/Assert=]: |notificationResult| and |appBadgeResult| are [=/booleans=]. +
++ Return (|notificationResult|, |appBadgeResult|). +
++ To acknowledge a push message given a push message + pushMessage means to acknowledge the receipt of pushMessage + according to [[RFC8030]]. +
++ Acknowledging the push message causes the push service to stop delivering + the message and to report success to the application server. This prevents the + same push message from being retried by the push service indefinitely. +
++ Acknowledging also means that an application server could incorrectly receive a + delivery receipt indicating successful delivery of the push message. Therefore, + multiple rejections SHOULD be permitted before acknowledging; allowing at least three + attempts is recommended. +