CloudServices/Push/Legacy/0

From MozillaWiki
Jump to: navigation, search

Push Version 0

The Web, as a platform, needs the ability to push messages to users, across multiple contexts, with nothing more than a simple API call. iOS and Android both support cloud-based push notifications to devices, but that generally requires installing native apps to enable that type of interaction. We can do better.

The primary use case for push notifications in practice is short text messages, often associated with an action (View, Reply, Check In, etc). In the Web case, this action will generally be a URL. On iOS 3&4 this is an alert dialog, on Android it is a system notification, and in Firefox this will most likely be a Home Tab feature, but we can experiment with many different user experience options.

Obvious Use Cases

Travel App

A travel site that tracks itineraries and flights, and notifies users of flight delays and changes. While a user is on the site, the site asks for a notification token and host. If the user grants permission, the app provides the appropriate data. When there is a flight delay, the app uses the Web API to send the user a message, and the client will receive the message on whatever devices are available. The app can set notifications to expire after the new scheduled departure so the notifications are only sent/shown while relevant.

Email App

An webmail wants to be able to tell users about new emails when the user isn't online, without disclosing contents to the server. The site requests a token and encryption keys from the browser, and the user is prompted for permission. The site can then encrypt and send messages via the server to the user, and the client will decrypt the messages and display those to the user.

Requirements

v1 requirements

  • Pseudonymous by default, meaning per-site tokens for senders
  • Users can revoke tokens to stop receiving messages
  • Simple content API for obtaining permission+tokens
  • Simple POST+JSON API for sending messages
  • Crypto as an optional component, allowing developers to send encrypted messages
  • The service should work on any device capable of supporting the protocol

Potential future requirements

  • Email + JSON delivery option, with email forwarding as a backup/additional option
  • Android/iOS support to allow native notifications without requiring native apps (may be mutually exclusive to crypto, TBD)
  • Location-based notifications, i.e. only show Foursquare notifications if you're within half a mile of the check-in
  • Additional notification types beyond straightforward messaging

Proposed APIs for Developers

Javascript content API

We will expose a content API to retrieve a token, hostname, and optional encryption keys.

 

/*
@params app_name
   may be displayed in addition to the domain (or eTLD+1)
@params account
   used to differentiate between different notifications from the same
      domain/app
@callback token
      a unique token tied to a specific user
   hostname used to define the domain where 
      notifications should be sent (see Web API section)
   encryptionKey [optional]
   hmacKey [optional]
      These will be used to encrypt and verify messages, if the site 
      chooses to support it
*/

navigator.notifications.getToken (
  {"app_name": "My Awesome App",
   "account": “myUsername”}, 
   function callback({token, hostname, encryptionKey, hmacKey}) {
    ...
   }
  }
};

Calling this in Firefox would show a message similar to the geolocation prompt “example.com wants to send you notifications [Allow] [Reject]” If the user grants permission, the callback is invoked with appropriate values by the client. If there is an existing token for that domain+account, the client will provide the same token/hostname. The site can then use this data to send the user notifications via the web API.


Web API

All requests are required to be over HTTPS, and consist of a single POST request containing a JSON-formatted UTF8 payload.

Construction of the URL follows the following pattern:

https:// + $hostname + /notify/ + $token
i.e. https://notifications.example.com/notify/f9q82u5h8fqjar32

POST body format

{ 
"body": "{ 
        "timestamp": ########, // UTC Timestamp in seconds (OPTIONAL)
        "ttl": ######## (in seconds), // Time To Live in seconds (OPTIONAL defaults to 72hrs)
        "plaintext": <payload goes here>, // if not encrypted, see payload format below
        "ciphertext": "BASE64==", // if encrypted, see payload format below
        "IV": "BASE64==" // Initialization vector to use in decrypting ciphertext
       }",
"HMAC": "BASE64=="  // HMAC verification code, only used if encrypted
}

NOTE: the body element contains a serialized JSON object.

POST content description

timestamp - UTC timestamp of creation. This is an optional field used to determine the expiration time. Future timestamps are reduced to the current time.

ttl - Time to live for the message. If not specified, we use the server configured default time (notifserver.max_ttl_seconds which defaults to 72 hours)

plaintext - The unencrypted message payload. This is usually serialized JSON stored as a string value. NOTE: including both plaintext and ciphertext will cause an error and the message will not be stored. See Payload for expected format.

ciphertext - The encrypted message payload. This is usually a URL safe, B64 encoded encrypted block. NOTE: including both plaintext and ciphertext will cause an error and the message will not be stored. See Payload for expected format.

IV - Initialization vector for decrypting the ciphertext (e.g. Public Key)

The message may contain either plaintext or ciphertext, but not both. Encrypted Content cannot be read or deciphered on the server. The "HMAC" field is included to verify that the message has not been tampered with by a third party. It is calculated by signing the contents of the "body" field using the standard HMAC algorithm with the SHA-256 hash function. It is only included if the message is encrypted.

Payload

The payload comprises the plain or ciphertext content of the POST Body and is a short message format containing enough information for the user to take useful action. All fields should be considered plain text since there is no guarantee that there may be any advanced rendering support on display. In addition, the content of this message is used by the display client. While additional fields can be injected, only the following fields will be used by all services.

The total payload content length is limited to less than 4096 bytes.

{ 
"type": "notification",
"title": "You've got mail!",
"body": "There are current 2 messages in your inbox.",
"url": "http://mail.google.com"
}


type - The type of message being delivered. This may trigger different behaviors by the display client. Most clients will expect this to be set to "notification". Unknown or unexpected payload types are ignored.

title - OPTIONAL Short title for message.

body - message content.

url - OPTIONAL actionable URL associated with your message.

Process Flow

Initial Request

  • A site wishes to expose a Notification feature to prospective users, so it includes a javascript snippet on the page advertising the feature.
  • A user, interested in receiving the notifications, starts the notification registration process (e.g. by clicking a "Notify Me" button.) This invokes
navigator.pushNotifications.requestPermissions (
   {"app_name": "Application Name"},
   confirmationCallbackFunction);
  • The user agent fetches a new channel ID from the server by calling:
POST /1.0/new_subscription/{usertoken}

{ /* No body required */ }
    • If the user agent is not logged in, the server requests an auth. (Note, this element is in progress pending BrowserID single sign on).
    • The server generates a new subscription queue and returns the following object back to the user agent
{"queue_id": "256bit random string",
 "host": "Host to send notifications to",
 "port": "Port (if not '80') to use to connect to 'host' "
}
  • The user receives the new subscription queue info, adds the optional encryption and HMAC keys and returns to the site by calling the confirmation callback function
function( {"token": "queue_id from notification server",
   "encryption_key": "optional encryption keypair",
   "hmac_key": "private HMAC key",
   "server_url": "Fully qualified URL for the server to send notifications to",
   "interval": "expected publication interval in seconds (e.g. 60)"
   } )

The site may then take additional action as appropriate (e.g. POST information to their backend server for storage and processing)

Notification send

  • The remote server sends a Notification post to the server_url (from the confirmation callback data)
POST /1.0/notify/token from confirmation callback
   POST Body
  • The following response codes may be returned:
   200 - Success
   401 - The user has blocked or removed the subscription. The site should no longer attempt to send the notification.
   404 - The token is invalid.
   500 - A server error prevented the notification from being recieved

Client Message Poll

  • The user agent uses a chrome level worker thread to intermittently poll the notification URL for pending content. The user agent polls
GET /1.0/feed/usertoken

Username is currently not well defined (pending single sign on auth). Currently, it's supposed that it is the BrowserID user email.

this will return a Javascript Array containing all pending messages.

[
 {"body": "body content",
  "HMAC": "Optional HMAC"},
 ...
]

Note that the body and HMAC are the values passed during the POST. Messages that have expired or for which the subscription has been deleted are not included.

Note: in the case of using browserid, we may wish to include the assertion in the get request. Further investigation is required.

Prototype Server

A prototype server is available on [github]

Server Internals

APIs

  • Public facing calls:
    • POST /VER/notify/usertoken

{notification} send a notification to the user

    • GET /VER/feed/usertoken

(requires auth) retrieve messages for a user token

  • client API
    • POST /VER/new_queue

{} (requires auth) Create a new queue token, or return the existing token for the user

    • POST /VER/new_subscription

{"token": "subscription token"} (requires auth) Creates a new subscription for the user for a given token (token is created by client JS).

    • POST /VER/remove_subscription

{"token": "subscription token"} (requires auth) Removes a subscription for the user.

    • POST /VER/broadcast

{notification} (requires auth) send a notification to the user (currently not used)