-
Notifications
You must be signed in to change notification settings - Fork 3k
Notification Service and Push
The Notification Service extension is an app extension that allows Firefox iOS to intercept encrypted payloads from Autopush, decrypting them, and possibly modifying them before displaying them to the user.
This document will cover how the Notification Service is set up, and its interaction with both the Autopush and Firefox Accounts systems to deliver send tabs.
The document will also cover how it's possible to extend the notification service and push to support other, non-Firefox Accounts related push notifications.
To understand why the Notification Service and push work the way they do today, it's important to understand some of the constraints around how Firefox Accounts delivers push notifications.
Firefox Accounts leverages the Autopush infrastructure to deliver push notifications. Because it uses Autopush, Firefox Accounts delivers encrypted payloads to the user, that need to be decrypted before being displayed.
The above requirement thus requires that the application:
- Register with Autopush, and receive a unique URL to pass to Firefox Accounts so Firefox Accounts can send push notifications to the applications.
- Generate a public/private encryption key pair to encrypt the communication from Firefox Account to the application.
- Deliver the public encryption key to Firefox Accounts.
- Persist the private encryption key.
- When Firefox Accounts sends a push notification to Autopush, it will encrypt the message using the public key it received from the application.
- Autopush will send the encrypted push notification to Apple's notification service, which will deliver the encrypted notification to the app.
- The application would then intercept the push notification as delivered by APNS, decrypt it using a private key that was persisted earlier
- The application would then display the notification
At the bottom of this document is a sequence diagram visualizing the above interactions.
Step 7 above is what is done by the Notification Service because Apple requires that if the application would like to modify the content of a push notification before displaying it to the user, the application must use a Notification Service app extension.
There is some additional complexity in Step 7 this document will cover next, mainly that Send Tabs aren't delivered directly through the push notification, instead, the push notification carries the send tab's index - so the notification service must query Firefox Accounts to get the actual tab's URL and title.
Send tabs are a form of Device Commands. At the time of this document, they are the only device command used by Firefox.
Device commands don't carry their payload, instead, they carry an index to where the application can request their payload. For example, for send tab, the push notification (after it's decrypted by push) can look like the following:
{
version: 1,
command: "fxaccounts:command_received",
data: {
"command": "https://identity.mozilla.com/cmd/open-uri",
"sender": "0a4abb5e6f2e378f3aadda7f97482e99",
"url": "https://api.accounts.firefox.com/v1/device/commands?index=42&limit=1"
}
}
The application must then query the url
to receive the content of the actual tab (there is a second layer of encryption there, but that's out of scope for this document).
Due to how device commands work, the Notification Service will need to send a network request to Firefox Accounts to retrieve the content of the tab.
Finally, after the notification service has received the tab it needs to do the following:
- Display the tab's URL to the user as a push notification
- Ensure that once the user opens the app, the URL will be opened
Displaying the tab as a push notification to the user is passing the tab's information to a handler provided by a native iOS API. Ensuring that the app opens the URL is slightly more complicated, as it requires the notification service to pass data to the main process.
In order to retrieve device command data (i.e send tab), the application must:
- Hit the
commands
endpoint on the server - Provide an
index
to which command(s) to retrieve - Provide a
limit
to the number of command(s) to retrieve.
The application persists the index of the command it retrieved lasts and regularly hits the Firefox Accounts endpoint to retrieve any missed tabs. The application will trigger this request if either:
- It has been 24 hours since the last request, or
- The notification service has explicitly indicated that there is a device command to retrieve
Additionally, the application will attempt to trigger the request:
- On every startup
- Every time the app is put in the foreground
- When a notification is received while it is in the foreground
- Every sync
It's important to note that the network request will only truly be triggered if one of the two conditions listed above is true (i.e the 24-hour timeout, or an explicit notification)
Additionally, this request duplicates the request the notification service already made, when the notification service retrieved the tab's information to display to the user.
This is okay, because the request the notification service made the first time, is purely to display the push notification, and not to open the tab, and additionally, the second request the app makes:
- Retrieves all command data that hasn't been applied, (i.e a tab opened)
- Will also include any missed commands (for example, if our push system failed to deliver a previous push notification).
- Will update the persisted index of the last tab retrieved.
The above can be visualized in a diagram below
sequenceDiagram
participant F as Firefox iOS
participant AP as Autopush
participant FA as Firefox Accounts
F->>AP: Subscribe
activate F
activate AP
AP->>F: Push URL, Channel ID
deactivate AP
F->>F: Generate Private/public key pair
F->>F: Persist Private key
F->>FA: Public key
deactivate F
sequenceDiagram
participant FA as Firefox Accounts
participant AP as Autopush
participant APNS
participant F as Firefox iOS
activate FA
FA->>FA: Encrypt notification w/push public key
FA->>AP: Send encrypted push notification
deactivate FA
activate AP
AP->>APNS: Forward push notification
deactivate AP
activate APNS
APNS->>F: Deliver push notification
deactivate APNS
activate F
F->>F: Decrypt push notification using private key
F->>F: Extract tab index from push notification
F->>FA: Get tab using index
activate FA
FA->>F: Tab URL + title
deactivate FA
F->>F: Display Notification
deactivate F
The following happens after the notification is displayed
sequenceDiagram
participant N as Notification Service
participant F as main process
participant FA as Firefox Accounts
activate N
N->>N: Display notification
N->>F: Set pref of timestamp last poll to 0
deactivate N
F->>F: Wake up
activate F
F->>F: Check pref for last poll timestamp
F->>FA: Poll for commands since last retrieved index
activate FA
FA->>F: Tab URL and title
deactivate FA
F->>F: Update index of last retrieved command
F->>F: Open Tab
deactivate F