New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly define the pushsubscriptionchange event #234
Changes from 6 commits
f2f99fa
9f859b0
1ee1d48
90ad882
c84869a
109d4a9
30cca42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -358,31 +358,81 @@ <h2> | |
with a <a>service worker registration</a> and a <a>service worker registration</a> has at | ||
most one <a>push subscription</a>. | ||
</p> | ||
<p> | ||
A <a>push subscription</a> has internal slots for a P-256 <a>ECDH</a> key pair and an | ||
authentication secret in accordance with [[!WEBPUSH-ENCRYPTION]]. These slots MUST be | ||
populated when creating the <a>push subscription</a>, and MUST remain constant for its | ||
lifetime. | ||
</p> | ||
<p> | ||
When a <a>push subscription</a> is <dfn data-lt= | ||
"deactivate">deactivated</dfn>, both the <a>user agent</a> and the <a>push | ||
service</a> MUST delete any stored copies of its details. Subsequent <a>push messages</a> | ||
for this <a>push subscription</a> MUST NOT be delivered. | ||
</p> | ||
<p> | ||
A <a>push subscription</a> is <a>deactivated</a> when its associated <a>service worker | ||
registration</a> is unregistered, though a <a>push subscription</a> MAY be | ||
<a>deactivated</a> earlier. A <a>push subscription</a> is removed when the <a>clear | ||
registration</a> algorithm is run for the <a>service worker registration</a>. | ||
</p> | ||
<p> | ||
A <a>push subscription</a> has an associated <dfn data-lt= | ||
"push endpoints">push endpoint</dfn>. It MUST be the absolute URL exposed | ||
by the <a>push service</a> where the <a>application server</a> can send <a>push | ||
messages</a> to. A <a>push endpoint</a> MUST uniquely identify the <a>push | ||
subscription</a>. | ||
</p> | ||
<p> | ||
A <a>push subscription</a> has internal slots for a P-256 <a>ECDH</a> key pair and an | ||
authentication secret in accordance with [[!WEBPUSH-ENCRYPTION]]. These slots MUST be | ||
populated when creating the <a>push subscription</a>. | ||
</p> | ||
<p> | ||
If the <a>user agent</a> has to change the keys for any reason, | ||
it MUST <a>fire the pushsubscriptionchange event</a> with the | ||
<a>service worker registration</a> associated with the <a>push subscription</a> as | ||
<var>registration</var>, a <a>PushSubscription</a> instance representing the | ||
<a>push subscription</a> having the old keys as <var>oldSubscription</var> and a | ||
<a>PushSubscription</a> instance representing the <a>push subscription</a> having the new | ||
keys as <var>newSubscription</var>. | ||
</p> | ||
<section> | ||
<h2> | ||
Subscription refreshes | ||
</h2> | ||
<p> | ||
A <a>user agent</a> or <a>push service</a> MAY choose to <dfn>refresh</dfn> a | ||
<a>push subscription</a> at any time, for example because it has reached a certain age. | ||
</p> | ||
<p> | ||
When this happens, the <a>user agent</a> MUST create a new <a>push subscription</a> | ||
with the <a>push service</a> on behalf of the application, using the | ||
<a>PushSubscriptionOptions</a> that were provided for creating the current | ||
<a>push subscription</a>. The new <a>push subscription</a> MUST have a key pair that's | ||
different from the original subscription. | ||
</p> | ||
<p> | ||
When successful, <a>user agent</a> then MUST <a>fire the pushsubscriptionchange event</a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about when the refresh is not successful? I think that we could recommend/suggest that the user agent either retain the old subscription, though it may treat the subscription as broken at its discretion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we require the user agent to retry if the refresh is unsuccessful? (Or fire There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the following paragraph:
I like Kit's suggestion of setting |
||
with the <a>service worker registration</a> associated with the <a>push subscription</a> | ||
as <var>registration</var>, a <a>PushSubscription</a> instance representing the initial | ||
<a>push subscription</a> as <var>oldSubscription</var> and a <a>PushSubscription</a> | ||
instance representing the new <a>push subscription</a> as <var>newSubscription</var>. | ||
</p> | ||
<p> | ||
<a>User agents</a> MAY continue to accept messages for the old <a>push subscription</a> | ||
for a brief amount of time, but MUST stop doing so once the first message for the | ||
refreshed <a>push subscription</a> has been received, as this indicates that the | ||
<a>application server</a> has propagated the subscription change. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That creates an interesting externality: if the application is required to update two data sources with updated subscription details, it has to ensure that one source isn't used before the other is emplaced. I think that's a reasonable global trade-off though. I would restructure this to more directly link the MAY to the reason:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Adopted. |
||
</p> | ||
|
||
<p class="issue"> | ||
The steps for creating a subscription should be separated from the | ||
<a lt="PushManager.subscribe">subscribe</a> method into an algorithm of its own so that | ||
it can be referenced here. Similarly, the <a>PushSubscriptionOptions</a> provided to the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would raise the issue on github. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #235. Removed here. |
||
<a lt="PushManager.subscribe">subscribe</a> method should be associated with the | ||
<a>push subscription</a> so that the refresh can access them. | ||
</p> | ||
</section> | ||
<section> | ||
<h2> | ||
Subscription deactivation | ||
</h2> | ||
<p> | ||
When a <a>push subscription</a> is <dfn data-lt= | ||
"deactivate">deactivated</dfn>, both the <a>user agent</a> and the <a>push | ||
service</a> MUST delete any stored copies of its details. Subsequent | ||
<a>push messages</a> for this <a>push subscription</a> MUST NOT be delivered. | ||
</p> | ||
<p> | ||
A <a>push subscription</a> is <a>deactivated</a> when its associated <a>service worker | ||
registration</a> is unregistered, though a <a>push subscription</a> MAY be | ||
<a>deactivated</a> earlier. A <a>push subscription</a> is removed when the <a>clear | ||
registration</a> algorithm is run for the <a>service worker registration</a>. | ||
</p> | ||
</section> | ||
</section> | ||
<section> | ||
<h2> | ||
|
@@ -423,8 +473,13 @@ <h2> | |
acquiring permission or determining the permission status. | ||
</p> | ||
<p> | ||
When a permission is revoked, all <a>push subscriptions</a> created with that permission | ||
MUST be <a>deactivated</a>. | ||
When a permission is revoked, the <a>user agent</a> MAY | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about if permission is granted again, after being revoked? @beverloo, I think you mentioned Chrome won't fire a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually think we found a case where we need to do this too. Let me file a more elaborate issue on it in a bit. |
||
<a>fire the pushsubscriptionchange event</a> for subscriptions created with that permission, | ||
with the <a>servce worker registration</a> associated with the <a>push subscription</a> as | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: servce There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
<var>registration</var>, a <a>PushSubscription</a> instance representing the | ||
<a>push subscription</a> as <var>oldSubscription</var>, and <code>null</code> as | ||
<var>newSubscription</var>. The <a>user agent</a> MUST, in parallel, <a>deactivate</a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the user agent must deactivate the affected subscriptions in parallel There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
the affected subscriptions. | ||
</p> | ||
<p> | ||
When a <a>service worker registration</a> is unregistered, any associated <a>push | ||
|
@@ -999,26 +1054,29 @@ <h2> | |
<h2> | ||
Events | ||
</h2> | ||
<p> | ||
The Service Worker specification defines a <code>ServiceWorkerGlobalScope</code> interface | ||
[[!SERVICE-WORKERS]], which this specification extends. | ||
</p> | ||
<pre class="idl"> | ||
partial interface ServiceWorkerGlobalScope { | ||
attribute EventHandler onpush; | ||
attribute EventHandler onpushsubscriptionchange; | ||
}; | ||
</pre> | ||
<p> | ||
The <dfn>onpush</dfn> attribute is | ||
an <a>event handler</a> whose corresponding <a>event handler event type</a> is | ||
<code>push</code>. | ||
</p> | ||
<p> | ||
The <dfn>onpushsubscriptionchange</dfn> | ||
attribute is an <a>event handler</a> whose corresponding <a>event handler event type</a> is | ||
<a>pushsubscriptionchange</a>. | ||
</p> | ||
<section data-dfn-for="ServiceWorkerGlobalScope"> | ||
<h2>Extensions to the <a>ServiceWorkerGlobalScope</a> interface</h2> | ||
<p> | ||
The Service Worker specification defines a <code>ServiceWorkerGlobalScope</code> interface | ||
[[!SERVICE-WORKERS]], which this specification extends. | ||
</p> | ||
<pre class="idl"> | ||
partial interface ServiceWorkerGlobalScope { | ||
attribute EventHandler onpush; | ||
attribute EventHandler onpushsubscriptionchange; | ||
}; | ||
</pre> | ||
<p> | ||
The <dfn>onpush</dfn> attribute is | ||
an <a>event handler</a> whose corresponding <a>event handler event type</a> is | ||
<code>push</code>. | ||
</p> | ||
<p> | ||
The <dfn>onpushsubscriptionchange</dfn> | ||
attribute is an <a>event handler</a> whose corresponding <a>event handler event type</a> is | ||
<code>pushsubscriptionchange</code>. | ||
</p> | ||
</section> | ||
<section> | ||
<h2> | ||
<dfn>PushEvent</dfn> interface | ||
|
@@ -1147,59 +1205,81 @@ <h2> | |
</section> | ||
<section> | ||
<h2> | ||
The "<dfn>pushsubscriptionchange</dfn>" event | ||
The <dfn>pushsubscriptionchange</dfn> event | ||
</h2> | ||
<p> | ||
The <a>pushsubscriptionchange</a> event indicates that a <a>push subscription</a> | ||
has been invalidated, or will soon be invalidated. For example, the <a>push service</a> | ||
MAY set an expiration time. A <a>Service Worker</a> SHOULD attempt to resubscribe while | ||
handling this event, in order to continue receiving <a>push messages</a>. | ||
The <a>pushsubscriptionchange</a> event indicates a change in a <a>push subscription</a> | ||
that was triggered outside of the application's control, for example because it has been | ||
refreshed, revoked or lost. | ||
</p> | ||
<p> | ||
When new <a>push subscription</a> information becomes available, the <a>user agent</a> | ||
MUST run the following steps: | ||
To <dfn>fire the pushsubscriptionchange event</dfn> given a | ||
<a>service worker registration</a> of <var>registration</var>, <var>newSubscription</var> | ||
and <var>oldSubscription</var>, the <a>user agent</a> must run the following steps: | ||
</p> | ||
<ol> | ||
<li>Let <var>registration</var> be the <a>service worker registration</a> corresponding | ||
to the push message. | ||
<li>Create a <a>trusted event</a>, <var>event</var>, that uses the | ||
<a>PushSubscriptionChangeEvent</a> interface, with the event type | ||
<code>pushsubscriptionchange</code>, which does not bubble, is not cancelable, and has no | ||
default action. | ||
</li> | ||
<li>If <var>registration</var> is not found, abort these steps. | ||
<li>Set the <code>newSubscription</code> attribute of <var>event</var> to | ||
<var>newSubscription</var>. | ||
</li> | ||
<li>Invoke the <a>Handle Functional Event</a> algorithm with a <a>service worker | ||
registration</a> of <var>registration</var> and <var>callbackSteps</var> set to the | ||
following steps: | ||
<li>Set the <code>oldSubscription</code> attribute of <var>event</var> to | ||
<var>oldSubscription</var>. | ||
</li> | ||
<li>Invoke the <a>Handle Functional Event</a> algorithm with <var>event</var>, | ||
<var>registration</var> and <var>callbackSteps</var> set to the following steps: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list construction implies that all three inputs are set to the following steps. I don't know how to improve that with some more restructuring though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slightly rephrased. |
||
<ol> | ||
<li>Set <var>global</var> to the global object that was provided as an argument. | ||
</li> | ||
<li>Create a <a>trusted event</a>, <var>e</var>, that uses the | ||
<a>ExtendableEvent</a> interface, with the event type | ||
"<a>pushsubscriptionchange</a>", which does not bubble, is not cancelable, and | ||
has no default action. | ||
<li>Set <var>global</var> to the global object associated with the | ||
<var>registration</var>. | ||
</li> | ||
<li>Dispatch <var>e</var> to <var>global</var>. | ||
</li> | ||
<li>If the previous <a>push subscription</a> is still active, perform the following | ||
steps in parallel: | ||
<ol> | ||
<li>Set <var>oldSubscription</var> to the previous <a>push subscription</a>. | ||
</li> | ||
<li>Wait for all of the promises in the <a>extend lifetime promises</a> of <var> | ||
e</var> to either resolve or reject. | ||
</li> | ||
<li>Unsubscribe <var>oldSubscription</var>. | ||
</li> | ||
</ol> | ||
<li>Dispatch <var>event</var> to <var>global</var>. | ||
</li> | ||
</ol> | ||
</li> | ||
</ol> | ||
<p> | ||
This algorithm ensures that the <a>Service Worker</a> is able to react to any | ||
non-destructive change in a <a>push subscription</a>, such as an automatic refresh, | ||
without causing any active <a>push subscription</a> to be terminated prematurely. A | ||
<a>Service Worker</a> can request a new <a>push subscription</a> during this process and | ||
ensure that no <a>push messages</a> are lost. | ||
<p class="note"> | ||
Consider using a more reliable synchronization mechanism such as [[WEB-BACKGROUND-SYNC]] | ||
when sending the details of the new <a>push subscription</a> to your | ||
<a>application server</a>. The user might be subject to unreliable network conditions that | ||
could cause a fetch to fail. | ||
</p> | ||
|
||
<section data-dfn-for="PushSubscriptionChangeEvent"> | ||
<h2> | ||
<dfn>PushSubscriptionChangeEvent</dfn> interface | ||
</h2> | ||
<pre class="idl"> | ||
dictionary PushSubscriptionChangeInit : ExtendableEventInit { | ||
PushSubscription newSubscription = null; | ||
PushSubscription oldSubscription = null; | ||
}; | ||
|
||
[Constructor(DOMString type, optional PushSubscriptionChangeInit eventInitDict), Exposed=ServiceWorker] | ||
interface PushSubscriptionChangeEvent : ExtendableEvent { | ||
readonly attribute PushSubscription? newSubscription; | ||
readonly attribute PushSubscription? oldSubscription; | ||
}; | ||
</pre> | ||
<p> | ||
The <dfn>newSubscription</dfn> attribute contains the details of the | ||
<a>push subscription</a> that is valid per invocation of the <a>pushsubscriptionchange</a> | ||
event. The value will be <code>null</code> when no new <a>push subscription</a> could be | ||
established, for example because the <a>webapp</a> has lost <a>express permission</a>. | ||
</p> | ||
<p> | ||
The <dfn>oldSubscription</dfn> attribute contains the details of the | ||
<a>push subscription</a> that SHOULD NOT be used anymore. The value will be | ||
<code>null</code> when the <a>user agent</a> is not able to provide the full set of | ||
details, for example because of partial database corruption. | ||
</p> | ||
<p class="issue"> | ||
Should we include an attribute that indicates whether the <code>oldSubscription</code> | ||
has been decisively invalidated? How would developers use this information? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that the answer here is no. The intent of having the old subscription respond to push messages is to avoid races. We should not encourage use of subscriptions once they are considered old. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. Allowing the old subscription to briefly receive messages is helpful, but I don't think we want folks depending on it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Thanks - removed. |
||
</p> | ||
</section> | ||
</section> | ||
</section> | ||
<section data-dfn-for="PushPermissionState"> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
title case for titles (throughout)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done