Please note, I wrote this while very tired and lacking sleep. It will receive updates over the next few days.
Right now, we live in a similar yet divided world. Home Assistant Companion (hereinafter HAC) for iOS came first and had many years to mature before HAC Android was even a glint in our eyes. Due to that time and somewhat limited thought for the future, iOS made some decisions that don't work well in the Android world. Android is starting to make decisions that will also affect iOS. We should try to cut down on as many differences between the two apps as possible. This will allow us to simplify our documentation but more importantly make it easier for users in households with both Androids and iPhones, in addition to making config sharing easier.
Notifications
The first place to start with is notifications. Right now, both apps support different functionalities. Some functionalities exist because of work developers have done, some will never exist due to OS specific limitations. As much as possible though, we should make it possible for a user to send a notification and 90% of the time it will appear similar across platform. The way that we do this is deriving a schema that both apps can hew to while also providing allowances for platform specific functionality that users want.
As a first step towards that goal, I have come up with this:
{
"push_token": "",
"registration_info": {
"app_id": "io.robbie.HomeAssistant.beta",
"app_version": "2.0.0 (68)",
"os_name": "iOS",
"os_version": "13.1.3"
},
"id": "uuid",
"notification": {
"message": "The front door is opened",
"title": "Front Door",
"badge": 5,
"image": "https://github.com/home-assistant/home-assistant-assets/blob/master/logo-round-192x192.png?raw=true",
"tag": "one",
"click_action": "",
"actions": [
{
"title": "Action 1",
"id": "action_one"
}
],
"android": {
"priority": "high",
"ttl": "3.5s",
"sticky": true,
"visibility": "VISIBILITY_UNSPECIFIED",
"local_only": true,
"icon": "ics_launcher",
"color": "#000000",
"channel_id": "default",
"ticker": "Hello World",
"event_time": "2020-02-14T07:30:21Z",
"notification_priority": "PRIORITY_DEFAULT",
"default_sound": true,
"default_vibrate_timings": true,
"default_light_settings": true,
"vibrate_timings": [
"1s",
"3.5s"
],
"light_settings": {
"color": {
"red": 1,
"green": 1,
"blue": 1,
"alpha": 0
},
"light_on_duration": "3.5s",
"light_off_duration": "3.5s"
}
},
"ios": {
"image": {
"content_type": "image/png",
"hide_thumbnail": true
},
"subtitle": "TwelveTwelve",
"sound": {
"name": "default",
"critical": true,
"volume": 1
}
}
}
}
As you can see, we have generic fields at the top level (under the notification
dictionary) and OS specific fields in their own dictionaries. I also made a spreadsheet that shows common features and their availability across both platforms. We should conform both apps to accept this schema. It will be a pain for users to adapt to one time but will have other benefits to be explained later. The change will be opt in for existing users, at least on iOS. We also have added a UUID to the notification payload to provide lifecycle tracking. The UUID should be generated and stored by Home Assistant Core and used in all follow up events and actions. The actions are now also defined in the notification, whereas previously iOS required defining all possible actions in advance. Actions are now registered with iOS just-in-time before displaying the notification.
Action Events
The next place the apps deviate significantly to the detriment of the user is Notification Action Events. On iOS, this is the event named ios.notification_action_fired
. On Android it is mobile_app_notification_action
. To start, iOS should adapt the more generic mobile_app_notification_action
event name. However, the payloads sent in the events are also very different between platforms and deserve to be unified into one
My proposal is this:
{
"event_type": "mobile_app_notification_action",
"data": {
"action_chosen": "action_one",
"action_data": {},
"device_name": "Robbie's iPhone",
"id": "uuid"
},
"origin": "REMOTE",
"time_fired": "2020-02-02T04:45:05.550251+00:00",
"context": {
"id": "abc123",
"parent_id": null,
"user_id": "123abc"
}
}
This provides everything we need to let users determine where the event came from, as well as provides them flexibility to customize by providing the action_data
dictionary, a totally user controlled space. They can fill the action_data
dict when sending the notification. The id
is the same one from the notification example above.
Providing turnkey automation support
Great, so notifications and events are well defined and traceable. That will allow us to implement a new type of automation block which will allow pausing an automation until such time as a user responds to a notification by tapping an action. Users will no longer have to worry about setting up two automations (one to send the notification and one to catch the response event), it will just be handled seamlessly by Home Assistant Core. @balloob came up with this idea and can expand more on it.
Notification Lifecycle Tracking
Now that notifications have unique IDs attached to them, we can monitor them through their entire lifecycle. I propose adding the following new events:
- Notification received by device
- Notification tapped
- Notification dismissed
In addition, I propose that Home Assistant Core start storing these notifications and providing a API to mobile_app
implementors to allow viewing notification history.
End to end encrypted notifications
As somewhat of a carrot to get iOS users to opt in to the new notification and event formats, we will begin offering end to end encryption of push notifications on both platforms. On iOS, this is made possible by UNNotificationServiceExtension which will decrypt and map the notification payload before displaying the notification to the user. On Android, we already are in a position where the app is the one processing and displaying the notification, not the OS. mobile_app
will be modified to encrypt outgoing notifications using libsodium with the same webhook secret the apps already have. A very minimal forwarder has already been developed and deployed and is available for review here. Here's everything that the forwarder receives:
{
"encrypted": true,
"encrypted_data": "j6lwIesVlc+1rYltzw1iFgZZgir3QcZzs+erYXIB6TphTl6bY6xQUYYqhXQ6tGjFAWvMetre/FC89Z85OLExlm3AwX4TzEbbmpLLAoD+zuiG+FugGk756fju58ImTA+Ci5echwgyMNwehKC/RYsbS0V0YieMU34RG/2DT6SDAh++/DmktPBpealTBLnEHRx4/PuNMeqdC7kB5nfkr1gYaBBEXON0R/yLc0sR4qk42RcIyOMd2VrG1apRBSxJizhQVyBrwUWe4z8UdhlXuwLDUhBtieB/xRJSJg1B74ywGvfEtsVJVLlFii8BSsgP/wp/RlcRMLRy8ixa/WPNHLj9YSXLCNYrIvjhTwlxhjAEZncO6fHy0jOuAWbiCrWPgju2q7zWwUxtNO13rGV5pXju++sjiuGn0gKvDBsaw9ZWnVZmR50NeBVha7e4XgUPpS5+Wx78DukpCcuRU/s3N9Q4yCQHkiPtFKszEg6A5FDYb9gGnzD5aSBfKINbNDmh6oBQvN6G2HBsO8E8AdA1cOupMWEFE6b6Y7taHxgNAaX3ik1zYKGe+zDSUSv0SWqIsx8MbbgG+qkSvdNqHswvOw==",
"push_token": "",
"registration_info": {
"app_id": "io.robbie.HomeAssistant.beta",
"app_version": "2.0.0 (68)",
"os_version": "13.1.3"
}
}
and what it sends to the device:
{
"apns": {
"payload": {
"aps": {
"alert": {
"title": "Encrypted notification",
"body": "If you're seeing this, something has gone wrong with encryption"
},
"mutable-content": 1
}
}
},
"data": {
"encrypted_data": "j6lwIesVlc+1rYltzw1iFgZZgir3QcZzs+erYXIB6TphTl6bY6xQUYYqhXQ6tGjFAWvMetre/FC89Z85OLExlm3AwX4TzEbbmpLLAoD+zuiG+FugGk756fju58ImTA+Ci5echwgyMNwehKC/RYsbS0V0YieMU34RG/2DT6SDAh++/DmktPBpealTBLnEHRx4/PuNMeqdC7kB5nfkr1gYaBBEXON0R/yLc0sR4qk42RcIyOMd2VrG1apRBSxJizhQVyBrwUWe4z8UdhlXuwLDUhBtieB/xRJSJg1B74ywGvfEtsVJVLlFii8BSsgP/wp/RlcRMLRy8ixa/WPNHLj9YSXLCNYrIvjhTwlxhjAEZncO6fHy0jOuAWbiCrWPgju2q7zWwUxtNO13rGV5pXju++sjiuGn0gKvDBsaw9ZWnVZmR50NeBVha7e4XgUPpS5+Wx78DukpCcuRU/s3N9Q4yCQHkiPtFKszEg6A5FDYb9gGnzD5aSBfKINbNDmh6oBQvN6G2HBsO8E8AdA1cOupMWEFE6b6Y7taHxgNAaX3ik1zYKGe+zDSUSv0SWqIsx8MbbgG+qkSvdNqHswvOw==",
"encrypted": "true",
"registration_info": "{\"app_id\":\"io.robbie.HomeAssistant.beta\",\"app_version\":\"2.0.0 (68)\",\"os_version\":\"13.1.3\"}"
},
"token": ""
}
Obviously our servers are unable to break the encryption and the secret is only ever exchanged directly via app and Home Assistant Core. iOS requires alert and mutable-content to be set to engage our UNNotificationServiceExtension. The user should never actually see that text except if decryption fails for some reason.
The identified downsides to e2e encryption are the following:
- There is no support for Android's
ttl
or priority
fields or APNS headers such as apns-collapse-id
or apns-priority
. Modifications could be made to notify.mobile_app
to allow sending certain whitelisted parameters in the clear.
- All notification translation logic is shipped client side instead of on the server. Therefore, we would either need to commit to only ever adding fields and not removing/modifying existing ones, lest we break older apps, or we version the schema somehow.
- Could make one on one debugging with users harder.
I'm excited to hear the communities thoughts on this proposal. Thanks for reading.