Open beidson opened 1 year ago
(preface: I have not read this very long message in its entirety yet)
I'm supportive of this general thrust, but I want to ensure that we do this well. Revisions to IETF protocols probably need to happen in the IETF. I also think that there is an opportunity to fix some issues with the cryptography used by Web Push at the time that we look at new content types.
Tentatively, I think the only IETF level change required here is to encourage push requests to have a Content-Type and specify that JSON content types have special meaning.
Defining the format for application/notification+json
and registering the content type can be done in W3C space I think, since the IETF protocol is otherwise payload-agnostic. The proposed changes to Push API and Notification API would go here and in https://github.com/whatwg/notifications respectively. In due course issues should be raised in all three places but this seems like a good starting point for reviewing the overall plan before taking that step.
It is absolutely not secure to change the content type, which is not authenticated, and reinterpret the content of a message (which is raw binary) differently as a result. That creates what could turn into attacks on sites.
That is why I think that we will have to make at least some revisions to the format of messages.
Would PushManager
also be exposed in a WorkerNavigator
context?
The current NotificationOptions
use camelCase (like requireInteraction
). This proposal should probably align.
The current NotificationOptions
already include badge
. There's a potential confusion with your proposed appBadge
(camelCased your original app_badge
, as per my suggestion in https://github.com/w3c/push-api/issues/360#issuecomment-1721039328). I do reckon that appBadge
is a perfect name given the intended use case, though, so the potential for confusion might just be something to live with.
Similar to your open question…
Do we allow a notification payload whose only member is
"app_badge"
, skipping the notification but allowing efficient updating of the application badge?
…should this allow notification payloads with just "data"
? The use case would be apps that keep settings like, say, the chosen app language purely on the client. For example, for an airline travel app, this would allow the server to send a notification with a "data"
object like…
{
"data": {
"type": "gate_change",
"details": {
"old": 12,
"new": 25
}
}
}
…that the app could then dynamically expand to a notification like "There's a gate change for your flight, the old gate was 12, the new gate is now 25" when the app language is set to English, and translations thereof for other languages. This of course requires allowing script execution, so may be out of scope slightly.
Would
PushManager
also be exposed in aWorkerNavigator
context?
That would introduce a situation where - in a SW context - you have two ways to get to the PushManager. Which is... not necessarily problematic but... is weird.
The use case isn't as compelling, but it could be?
The current
NotificationOptions
already includebadge
. There's a potential confusion with your proposedappBadge
(camelCased your originalapp_badge
, as per my suggestion in #360 (comment)). I do reckon thatappBadge
is a perfect name given the intended use case, though, so the potential for confusion might just be something to live with.
We discussed this.
Putting appBadge
on NotificationOptions is necessary, and we can't change the old badge
for backwards compatibility.
There is potential for confusion, but the typing (string vs. uint64_t) and docs will help.
…should this allow notification payloads with just
"data"
? The use case would be apps that keep settings like, say, the chosen app language purely on the client. For example, for an airline travel app, this would allow the server to send a notification with a"data"
object like…
I don't think so.
Because as you mention:
This of course requires allowing script execution, so may be out of scope slightly.
The primary goal here is that a payload always represents a user visible action the platform or user agent can take without javascript.
A "data only" message that's immutable would, of course, be a no-op. A "data only" message that's mutable could be processed by SW javascript, but doesn't have the user visible fallback that is required by the proposal. e.g. if the JS failed to display a notification, the UA wouldn't have the default message to display.
A "data only message that absolutely requires JS to display the notification" is actually a precise description of existing web push, which lives side-by-side with the declarative model.
The current
NotificationOptions
use camelCase (likerequireInteraction
). This proposal should probably align.
Between when this explainer was written and when I landed our first implementation, I believe I made this change already.
Will be updating the explainer sometime soon.
Thanks for answering the other questions, I'm all 👍
with your answers. Also agree for the "data"
-only use case to not work.
That would introduce a situation where - in a SW context - you have two ways to get to the PushManager. Which is... not necessarily problematic but... is weird.
From the Explainer prose:
Push subscriptions are interchang[e]able. An existing push subscription that was made via a ServiceWorkerRegistration whose scope happens to match the security origin of a window object will be visible to that window.navigator.pushManager
Conversely, a new push subscription made via window.navigator.pushManager will be visible to a ServiceWorkerRegistration whose scope matches that security origin. Removing the subscription from one will be reflected in the other.
There's some interchangeability built into the proposal already, so having two ways of accessing the push manager doesn't seem overly surprising to me at least.
The use case isn't as compelling, but it could be?
It's mostly just a question I had. Not sure if there would be user demand.
The concept of “persistent notification” needs to change from “a notification with an associated service worker registration” to a notification with an associated service worker registration or associated push subscription”. Or some other language that clarifies automatically created Notifications from a notification push payload also qualify as “persistent”
Can this be "a notification with default action URL"? Such notification would have an action to do regardless of SW or push subscription, and thus can exist in system notification center even after the browser exits (meaning, "persistent"), right?
Edit: Now this is being done https://github.com/whatwg/notifications/pull/213
And with that I wonder we can introduce a persistent notification API that can be used regardless of push and SW, and then we can try this proposal with two steps: handling push and handling notification click, each without triggering SW.
It could be enough to add a default action URL option to existing new Notification()
. Thoughts?
Status update:
push
event modifications (to support the mutable
field for which I further substantiated the use cases in https://github.com/w3c/push-api/issues/391) are being standardized here (I consider this one to be ready enough): https://github.com/w3c/push-api/pull/385
PushManager
available in a window environment is here (I consider this one to be ready): https://github.com/w3c/push-api/pull/393Notification
objects to specify a navigable URL is standardized here (I consider this one to be ready): https://github.com/whatwg/notifications/pull/213We will be updating our implementation in WebKit to reflect the above.
I have a few general questions about this, mostly around the payload element:
1) Would proposed JSON Payload remain fully e2e encrypted or would there be elements of that payload that would not be encrypted (e.g. to allow for the "default" displayable message or action URL in case of encryption failures)? If so, how do we ensure message privacy? (e.g. Could a company that provides Push be subpoenaed to report all user information for messages related to a given "default URL"?)
2) Would the default 4K message size limit be maintained with allowances for the cryptographic overhead, or would the message size be increased to handle the additional required fields?
3) Companies like Mozilla rely on using proprietary messaging systems like Android Firebase Cloud Messaging (FCM) and Apple's Push Notification system (APNs) to deliver push messages to mobile platforms. These also have existing message size limits, which may impact the total available size of an encrypted message payload. How would those companies deal with this sort of issue (ideally, without implementing a "TCP over FCM/APNs" type solution)?
Thank you, those answers do address most of my initial concerns.
For what it's worth, we generally see fairly large payloads (although I suspect that's mostly due to padding). I can see how the structured form could impact the amount of data being exchanged, but I also expect that it might address some other security concerns.
I also wonder how or if this modification would see adoption unless there's a concerted effort to either promote it or some other incentive for publishers to adopt it? FWIW, even 8 years after the release of RFC 8030, and three years after a personal effort to have a majority of Push libraries change their default, half our subscription load still uses the older "aesgcm" encoding, indicating that A LOT of publishers never update their code. Hopefully, the "You don't need to copy/paste your service worker code", will provide enough incentive for those publishers, but I suspect that problem is out of scope of this proposal.
I think there's two main factors to drive adoption:
I think there's also some benefit that if you do enable service workers (through "mutable": true
) you get a push
event that contains much more structured data allowing you to simplify your client logic.
All three major PRs in https://github.com/w3c/push-api/issues/360#issuecomment-2435186319 are ready now. All that remains is some better integration with the Badging API and defining more precisely what signal user agents can use to determine whether to display the declarative notification/badge when mutable is used as requested by mt. Now would be a good moment to give feedback if any remains.
Declarative Web Push:
More efficient and privacy-preserving push notifications
By: Brady Eidson Technical review by: Marcos Caceres and Anne van Kesteren.
This explainer proposes various changes to existing web standards so that, in the majority of cases, push-initiated notifications can be presented by the platform or user agent without involving a Service Worker.
The primary mechanism to enable this is by making the push message payload a declarative description of the notification to be displayed.
Our proposed model doesn’t require a Service Worker work to get a PushSubscription. Even if a Service Worker is registered, push notification messages bypass it by default. This makes push notifications from the web more privacy-preserving as sites aren’t given the opportunity to execute any JavaScript until a user explicitly interacts with a notification. It also makes web push notifications more efficient by skipping the CPU and battery cost required to launch a Service Worker and execute its code.
We also propose a more flexible event handling model that developers can opt into to transform a push notification. This new model still depends on Service Workers, but provides the privacy-preserving guarantee that there is always a fallback notification to be shown, so pushes cannot trigger silent background runtime.
This explainer describes what we have implemented internally to prototype or what we plan to implement next. Implementation experience and feedback from the standards community might change some details.
What are the drawbacks of current push notifications?
The W3C’s Push API and related specifications work great for maximizing user engagement, but suffer from notable drawbacks:
Learning from platform experience
Push notifications that come in to an OS platform are usually displayed by the platform itself, without any app code executing. It is also fairly standard practice for apps to optionally get a small amount of CPU time to transform an incoming push notification - specially where changes since the last push message matter. Some examples of tasks performed during these allocated time slices include decrypting push data, updating the badge count, or updating based on score from an in-progress game. If the app fails to complete a transformation task in their allotted time - due to coding error or other factors outside their control - then the original notification is displayed to a user. This automatic recovery model avoids disrupting the user experience, and ensures that push notifications don’t become a vector for untrusted silent background runtime.
We have deep experience with this flow and it is the base model for our proposed enhancements to Web Push. Most existing web app that rely on the existing Service Workers driven-model can easily adapt to the more efficient and privacy preserving model; the web application can transform the received notification payload as needed, but the payload itself describes the primary notification. If a web app fails to complete the transformation in its allotted time, or there is a script error, a notification is still always presented to the end user. When existing web apps simply include the full notification itself in push message JSON and have their service worker display it as-is, adopting the 100% streamlined model is trivial, and they can remove their push-related Service Worker code.
We detail how this model works in the sections below.
Goals
Plan of action: amendments to existing specifications
The current set of specifications that govern the delivery and presentation of push notifications serve as a solid foundation to build on. They’ve served us well for a number of years, proving their robustness at Web scale.
To help with the standardization process and re-use existing push-related primitives and algorithms, we need to amend existing specs to support the new model we propose in this document.
The standards community would need to coordinate on small-to-moderate changes to RFC 8030, Push API, Notifications API, and possibly the Badging API specification. Our goal is to retain full backwards compatibility with the current Push API. Any amendments will live along side it (or integrate directly into the appropriate sections).
The spec changes proposed below are the ones we deemed to be the necessary starting points, but are by no means exhaustive. We are under no illusion that once we begin diving deeper more changes will be required.
RFC 8030 - Generic Event Delivery Using HTTP Push
RFC 8030 defines the mechanism by which push servers send messages to user agents. “Section 5 - Requesting Push Message Delivery” describes how a server requests that a push service send a push message payload to the user agent.
As far as HTTP is concerned, the push message data is just a blob posted in the HTTP request. But the HTTP request itself is where we can determine if the push message is “legacy” or “new” with an HTTP request header.
We propose that - even though it hasn’t been standard practice so far - RFC 8030 (or a follow-on spec) suggest push requests include a Content-Type. Most content type values will be ignored, with the payload understood to be a legacy push payload. A specific content type value will flag a message as containing a notification payload.
Later in this document we’ll detail a standard JSON format describing a visible notification payload, so one might think application/json makes sense. It also happens that legacy push message payloads are usually also JSON. And in our experience many legacy push message payloads are already sent with a Content-Type: application/json header.
To eliminate confusion between “legacy push JSON” and “declarative web push JSON”, we propose registering a new application/notification+json type.
Push messages received without that content-type are considered to have “legacy” disposition. Push messages received with that content-type are considered to have a “notification” disposition, and the meaning of that will be covered later in this document.
Push API - Push Subscription
The Push API does a great job at describing a Push Subscription. However, as currently specified, it is intrinsically tied to a "Service Worker Registration". For one example:
Since a stated goal of this proposal is to work without a service worker, Push Subscription needs to be generalized and not bound to a service worker. We propose defining a push subscription owner, which can be either a “service worker registration” or security origin bound.
After that’s established, appropriately replace all references of “service worker registration as owner of a push subscription” with that of “push subscription owner”.
We also expose a navigator.pushManager on window.navigator, so that PushManager instances can be reached without needing a service worker registration.
Push subscriptions are interchangable. An existing push subscription that was made via a ServiceWorkerRegistration whose scope happens to match the security origin of a window object will be visible to that window.navigator.pushManager
Conversely, a new push subscription made via window.navigator.pushManager will be visible to a ServiceWorkerRegistration whose scope matches that security origin. Removing the subscription from one will be reflected in the other.
Push messages sent to that subscription can other have the legacy disposition and require a Service Worker to handle them with a push event, or have the notification disposition to allow for automatic handling, or an optional pass through a pushnotification event handler (described below)
Push API - subscribe() and related methods
For push subscriptions to be owned by something other than service worker registrations, and for a PushManager instance to be useful without having a service worker registration, PushManager.subscribe() will require an overhaul:
Push API - Receiving a push message
10.4 Receiving a Push Message will need a significant rewrite:
We’re considering various ways that pushnotification events should specify their replacement Notification. Currently, calling showNotification like in the legacy case seems appropriate, but it’s a bit more complicated than it seems on the surface. We’ll share more thoughts once we figure them out.
In the notification disposition cases, the push message data is parsed as JSON and validated with certain requirements, such as providing a title, a default action, and the optional NotificationsOptions details. If the JSON doesn’t represent a well defined Notification object, it’s dropped on the floor (perhaps with a developer console warning).
To further enable the goal of avoiding the Service Worker when possible, we are also considering stricter requirements on service workers used for push event handling by keeping track of what event handlers are installed simply as a result of evaluating the Service Worker source code. For example, if synchronous evaluation of a Service Worker source doesn’t result in a pushnotification event handler being installed, then we would remember that and never consider firing a pushnotification event to it even if the notification payload JSON opts in.
We believe dynamically adding push event handlers to a Service Worker after initial evaluation is a developer error, and should not hurt platform performance, and making this mistake should result in a developer console warning, and not reduced performance or privacy characteristics for the user.
Activating a notification
In legacy Web Push, when a notification is activated, the user agent handles it by dispatching a notificationclick event at a Service Worker instance. Usually that event handler verifies a window client exists at an app specified URL to send it a message, or opens a new window client to an app-specified URL.
Navigating the user agent to HTTP URLs is the native language of the web platform, and opening a URL is the most common result of processing a notificationclick event. So our proposed model uses URLs as a declarative means to serve the same purpose.
Therefore a requirement of the notification payload JSON is to specify a “default action URL”, and NotificationAction will be extended to also have an “action URL”. Any NotificationActions specified in the NotificationOptions JSON will be required to specify an action URL.
We propose extending the JavaScript API for creating a persistent notification - ServiceWorkerRegistration.showNotification() - to allow for optionally specifying action URLs. Both the default action URL, and the NotificationOptions NotificationAction action URLs.
If a persistent notification is activated with an action that has an associated action URL, notificationclick event dispatch can be skipped and the URL is opened directly.
Therefore, persistent notifications created from a notification disposition payload will aways skip the notificationclick handler, and legacy push implementations that create notifications with Service Workers can optionally skip the notificationclick handler.
Notifications API
Legacy Push API gives the entire push message data to the web application, to be used however it sees fit. As established earlier, our proposal leverages a standardized JSON structure to represent that notification and related data.
The Notifications API needs a few tweaks to reflect all desired new behaviors:
In our current proof of concept implementation, we implement JSON structure illustrated by the following example:
The top level “title” member is required, and represents the title that would be passed in to e.g. the Notification constructor. The “options” member is optional, and represents the NotificationOptions dictionary that would be passed in to e.g. the Notification constructor.
The inclusion of badging raises a few as-of-yet unanswered questions that we don’t have strong thoughts about quite yet, such as:
These questions do need answers, but we’re not letting them hold up progress in implementing the rest of the proposal.
In this description of the JSON fields, we’ve mentioned some optional and some required fields. We’ve also mentioned some acceptable value ranges for certain fields.
Requiring validation of the the incoming JSON to the extent that it needed to fully describe a Notification that the platform or user agent can handle directly is one of the goals of declarative web push. If the JSON fails to meet those requirements, then it will be ignored, and likely an error message shown to the developer console.
Finally, since concepts and algothrim steps from Notifications API are being borrowed and repurposed to support declarative, automatic handling by the User Agent, as opposed to imperative API calls, we might want to rename the Notifications API to just “Notifications”
Badging API specification
In the above description of the JSON payload we mention executing steps in the Badging API spec.
The algorithms of the badging specification need to be generalized so, where possible, they are not tied to the API. The solution we come up with as part of this process needs to work with the existing values and model used by the badging specification. As such, we would probably want to generalize and rename the Badging API to just “Badging”.