-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add iOS Live Activity webhook handlers to mobile_app #166072
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
base: dev
Are you sure you want to change the base?
Changes from all commits
7d4713a
e377da7
14a6987
3d7ea81
3f6346d
d1163a5
d299519
b76e405
d9df34f
ecbb296
a16c8c9
336c64b
023065f
23ff061
61a609b
d5e8477
1337547
df217bd
9d9ef58
a1a6db3
d44727e
eb478d4
978d802
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 |
|---|---|---|
|
|
@@ -67,10 +67,12 @@ | |
| ATTR_DEVICE_NAME, | ||
| ATTR_EVENT_DATA, | ||
| ATTR_EVENT_TYPE, | ||
| ATTR_LIVE_ACTIVITY_TAG, | ||
| ATTR_MANUFACTURER, | ||
| ATTR_MODEL, | ||
| ATTR_NO_LEGACY_ENCRYPTION, | ||
| ATTR_OS_VERSION, | ||
| ATTR_PUSH_TOKEN, | ||
| ATTR_SENSOR_ATTRIBUTES, | ||
| ATTR_SENSOR_DEVICE_CLASS, | ||
| ATTR_SENSOR_DISABLED, | ||
|
|
@@ -99,6 +101,7 @@ | |
| DATA_CONFIG_ENTRIES, | ||
| DATA_DELETED_IDS, | ||
| DATA_DEVICES, | ||
| DATA_LIVE_ACTIVITY_TOKENS, | ||
| DATA_PENDING_UPDATES, | ||
| DOMAIN, | ||
| ERR_ENCRYPTION_ALREADY_ENABLED, | ||
|
|
@@ -798,3 +801,48 @@ async def webhook_scan_tag( | |
| registration_context(config_entry.data), | ||
| ) | ||
| return empty_okay_response() | ||
|
|
||
|
|
||
| @WEBHOOK_COMMANDS.register("live_activity_token") | ||
| @validate_schema( | ||
| { | ||
| vol.Required(ATTR_LIVE_ACTIVITY_TAG): cv.string, | ||
| vol.Required(ATTR_PUSH_TOKEN): cv.string, | ||
| } | ||
| ) | ||
| async def webhook_update_live_activity_token( | ||
| hass: HomeAssistant, config_entry: ConfigEntry, data: dict[str, Any] | ||
| ) -> Response: | ||
| """Store a Live Activity APNs token sent by the iOS app.""" | ||
| webhook_id = config_entry.data[CONF_WEBHOOK_ID] | ||
| activity_tag = data[ATTR_LIVE_ACTIVITY_TAG] | ||
|
|
||
| live_activity_tokens = hass.data[DOMAIN][DATA_LIVE_ACTIVITY_TOKENS] | ||
| live_activity_tokens.setdefault(webhook_id, {})[activity_tag] = { | ||
| ATTR_PUSH_TOKEN: data[ATTR_PUSH_TOKEN] | ||
| } | ||
|
|
||
| return empty_okay_response() | ||
|
|
||
|
|
||
| @WEBHOOK_COMMANDS.register("live_activity_dismissed") | ||
| @validate_schema( | ||
| { | ||
| vol.Required(ATTR_LIVE_ACTIVITY_TAG): cv.string, | ||
| } | ||
| ) | ||
| async def webhook_live_activity_dismissed( | ||
| hass: HomeAssistant, config_entry: ConfigEntry, data: dict[str, str] | ||
| ) -> Response: | ||
| """Remove a stored Live Activity token when the activity ends on device.""" | ||
| webhook_id = config_entry.data[CONF_WEBHOOK_ID] | ||
| activity_tag = data[ATTR_LIVE_ACTIVITY_TAG] | ||
|
|
||
| live_activity_tokens = hass.data[DOMAIN][DATA_LIVE_ACTIVITY_TOKENS] | ||
| if webhook_id in live_activity_tokens: | ||
| live_activity_tokens[webhook_id].pop(activity_tag, None) | ||
| # Clean up the device key if no activities remain. | ||
| if not live_activity_tokens[webhook_id]: | ||
| del live_activity_tokens[webhook_id] | ||
|
Comment on lines
+843
to
+846
Member
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. Can the dismissed be called multiple time for a activitiy tag?
Author
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. Dismissed is only called once per tag in normal flow, but since
Member
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. So just double checking... And if it is correct, is there a technical limitation, why we don't allow live activity to be survive a restart?
Author
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. Yes, that's correct. HA restart loses all in-memory Live Activity tokens. The existing activity continues showing the last known state on the device (iOS keeps it alive for up to 8 hours), but HA can no longer send updates to it. The iOS app does handle recovery automatically in one case: if the iOS companion app is also restarted (or even just brought to foreground after reconnecting), it calls reattach() at launch, re-observes Activity.pushTokenUpdates, and immediately re-sends the current token to HA. So a full device/app restart recovers cleanly. The specific gap is HA restarting while the iOS app stays running. The app has no trigger to proactively re-push existing tokens on HA reconnect. We could persist tokens to the config entry, but a stored token from before the restart may have rotated in the interim, and pushing to a stale token returns BadDeviceToken from APN, which we don't currently handle. For now this is a known limitation. A future improvement could be a "request token re-registration" webhook that HA calls on startup to prompt the iOS app to re-send all active tokens. Open to suggestions or concerns, or marking down the code with a comment about this
Member
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.
Why aren't we handling it as it could always happen that a token is invalid? We could just simply use a store to store, together with the actual time, so we know after 8h, maybe for safety use a longer timeframe, we can clean them up The comments sound like written directly by AI, which is not allowed. Please write them in your own words. Using AI for help is fine but the human must stay in the loop
Author
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.
Sorry, yes that was used in that instance because I thought it would do a better job explaining the overall scope. My apologies. |
||
|
|
||
| return empty_okay_response() | ||
Uh oh!
There was an error while loading. Please reload this page.