Add iOS Live Activity webhook handlers to mobile_app#166072
Add iOS Live Activity webhook handlers to mobile_app#166072rwarner wants to merge 23 commits intohome-assistant:devfrom
Conversation
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There was a problem hiding this comment.
Pull request overview
Adds Home Assistant Core support in the mobile_app integration for iOS Live Activities by introducing webhook handlers that store and clear per-activity APNs push tokens and emit lifecycle events for automations.
Changes:
- Extend
SCHEMA_APP_DATAand constants to support Live Activities capability flags and push-to-start registration fields. - Add
update_live_activity_tokenandlive_activity_dismissedwebhooks that manage an in-memory token store and fire remote-origin bus events. - Add a
supports_live_activities()helper and webhook tests covering token storage, defaults, and cleanup.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
homeassistant/components/mobile_app/const.py |
Adds Live Activity-related constants/events and extends registration SCHEMA_APP_DATA. |
homeassistant/components/mobile_app/__init__.py |
Initializes a new in-memory DATA_LIVE_ACTIVITY_TOKENS store under hass.data[DOMAIN]. |
homeassistant/components/mobile_app/webhook.py |
Implements the new Live Activity webhook handlers and fires lifecycle events. |
homeassistant/components/mobile_app/util.py |
Adds supports_live_activities() helper based on stored app_data. |
tests/components/mobile_app/test_webhook.py |
Adds tests for storing tokens, default env behavior, and dismiss cleanup/event firing. |
Comments suppressed due to low confidence (1)
tests/components/mobile_app/test_webhook.py:1398
- This test only asserts the HTTP status. To fully validate the contract of
live_activity_dismissed(which returnsempty_okay_response()), also assert the JSON body is{}(and optionally that no tokens were removed when none existed) to prevent regressions in response shape/side effects.
resp = await webhook_client.post(
f"/api/webhook/{webhook_id}",
json={
"type": "live_activity_dismissed",
"data": {
"tag": "nonexistent_activity",
},
},
)
assert resp.status == HTTPStatus.OK
- Define EVENT_LIVE_ACTIVITY_TOKEN_UPDATED and EVENT_LIVE_ACTIVITY_DISMISSED constants in const.py instead of inline f-strings - Add ATTR_APNS_ENVIRONMENT constant for schema and data access - Add EventOrigin.remote to async_fire calls, matching webhook_fire_event pattern - Use DATA_LIVE_ACTIVITY_TOKENS constant in tests instead of string literals - Import event constants in tests for consistency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Make push-to-start token and environment vol.Inclusive so they must be provided together — a token without an environment is ambiguous since sandbox tokens are rejected by the production APNs endpoint - Clean up DATA_LIVE_ACTIVITY_TOKENS for the webhook_id in async_unload_entry to prevent stale tokens accumulating in memory when devices are removed or re-added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a notification contains live_activity: true and a tag, the notify service now routes it through the dedicated APNs relay endpoint instead of FCM. This completes the direct APNs delivery path: 1. Per-activity token — if the iOS app has registered a push token for the given tag (via update_live_activity_token webhook), use that token and its stored push_url to deliver directly to the running activity. 2. Push-to-start fallback — if no per-activity token exists but the device has a push-to-start token in app_data (iOS 17.2+), use that to start a new activity remotely without the app being open. 3. Normal FCM — if live_activity is not set, or no tag is provided, the notification flows through the existing FCM path unchanged. The apns_environment (sandbox/production) is included in registration_info so the relay server can route to the correct APNs endpoint. Adds 4 tests: stored token routing, push-to-start fallback, no-tag fallthrough, and normal notification ignoring stored tokens. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FCM v1 API natively supports Live Activities via apns.liveActivityToken. This simplifies the core integration: - notify.py: instead of routing to a separate relay URL, sends both the FCM token (push_token) and Live Activity APNs token (live_activity_token) to the SAME relay endpoint. The relay server places it in the FCM message's apns.liveActivityToken field, and FCM handles APNs delivery. - webhook.py: update_live_activity_token schema simplified — removed push_url and apns_environment (FCM handles routing automatically) - const.py: removed ATTR_APNS_ENVIRONMENT (no longer needed) - Tests updated to match simplified token storage and routing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- notify.py: guard against non-string tag values in notification payload to avoid runtime errors when used as dict key - webhook.py: use ATTR_DEVICE_ID and CONF_WEBHOOK_ID constants in event data instead of string literals for consistency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- webhook.py: reject empty push tokens with vol.Length(min=1) in update_live_activity_token schema - notify.py: use `is not True` for live_activity flag to prevent truthy non-bool values like string "false" from triggering Live Activity routing - const.py: reject empty push-to-start tokens with vol.Length(min=1) in SCHEMA_APP_DATA Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ssed tag, add unload test - Replace CONF_WEBHOOK_ID with ATTR_WEBHOOK_ID as the key in EVENT_LIVE_ACTIVITY_TOKEN_UPDATED and EVENT_LIVE_ACTIVITY_DISMISSED payloads to keep runtime event data semantically separate from config constants - Require non-empty ATTR_LIVE_ACTIVITY_TAG in the live_activity_dismissed webhook schema (vol.Length(min=1)) to match the update handler and prevent stale token store entries from empty tags - Add test_unload_removes_live_activity_tokens to verify live activity tokens are purged from hass.data when a config entry is unloaded Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ivities Unifies the iOS and Android notification data field: live_update: true now triggers Live Activity routing on iOS, matching the field Android already uses. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y _get_live_activity_token signature - Drop EVENT_LIVE_ACTIVITY_TOKEN_UPDATED and EVENT_LIVE_ACTIVITY_DISMISSED — nothing consumes these events in any of the three repos (no automation triggers, no iOS listener, no relay usage), so they add noise without value - Remove supports_live_activities() from util.py — defined but never called - Pass app_data directly into _get_live_activity_token instead of the full registration dict (edenhaus review feedback) - Group Live Activity push constants with other PUSH attrs in const.py - Update tests to remove event-capture assertions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The generic 'tag' field name collides with the notification tag used elsewhere in mobile_app. Using 'live_activity_tag' makes the webhook contract unambiguous. notify.py continues to read 'tag' from the notification payload (user YAML) unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Core registered 'update_live_activity_token' and 'live_activity_dismissed' but the iOS app sends 'mobile_app_live_activity_token' and 'mobile_app_live_activity_dismissed', matching the mobile_app_ prefix convention used elsewhere in the integration. Rename core handlers to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
live_update is the cross-platform YAML key shared with Android; on iOS it maps to ActivityKit Live Activities, on Android to a different mechanism. The live_activity naming in webhook handlers and token storage is intentionally iOS-specific. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused ATTR_SUPPORTS_LIVE_ACTIVITIES and ATTR_SUPPORTS_LIVE_ACTIVITIES_FREQUENT_UPDATES constants and schema entries - Add ATTR_LIVE_UPDATE, ATTR_LIVE_ACTIVITY_TOKEN, and ATTR_PUSH_TAG constants; replace hardcoded strings - Simplify tag check: remove redundant isinstance(tag, str) guard - Use walrus operator for live activity token assignment in notify.py - Store live activity push token as a plain string instead of a dict - Remove comment before webhook registration already described in docstring - Simplify push-to-start token return to app_data.get(...) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop vol.Length(min=1) from the tag and push token fields — consistent with webhook_scan_tag which uses plain cv.string. APNs tokens are explicitly variable length per Apple docs; hardcoding a minimum is both inconsistent and fragile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lution Two Live Activity test docstrings lost their indentation when resolving rebase conflicts, causing a syntax error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consistent with all other webhook types in this integration which use short names (scan_tag, update_location, etc.) without a mobile_app_ prefix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused AsyncGenerator import from notify.py - Sort ATTR_PUSH_TAG import alphabetically (after ATTR_PUSH_RATE_LIMITS_SUCCESSFUL) - Wrap long line in webhook_update_live_activity_token Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Store per-activity tokens as {"push_token": <token>} dict in
DATA_LIVE_ACTIVITY_TOKENS so the structure matches test expectations
and leaves room for additional fields without a schema change
- Update _get_live_activity_token to read the push_token key from the dict
- Update test_notify.py setups to use dict format
- Fix stale "mobile_app_live_activity_token" type name in test_init.py
(should be "live_activity_token" after the prefix-drop rename)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9cf6a0e to
eb478d4
Compare
|
Please don't rebase after a review have started as this means a reviewer needs to review again the whole PR instead of the changes. Keep this in mind and also instruct claude to do it |
|
Previously requested changes are not implemented. Drafting the PR. Not sure if the AI has reintroduced them or if they still pending to implement but due the rebase each commit is marked as new and I will not check each commit. Please make sure all previous requested changes are implemented correctly before marking the PR as ready for review. and again we require the human in the loop and it feels from the comments that the are written completely by the AI |
edenhaus
left a comment
There was a problem hiding this comment.
PR description not in sync with the changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Okay addressing some of the things here. This has unfortunately gotten to be a mess
Yes, I did use AI for some responses that I felt like it could explain more sufficiently than I could. But I am personally involved in all of this. I apologize, as I was unaware this was against policy with OHF
Updated the description to match with current fork branch changes
Sorry, again I was unaware this was a bad thing and now understand. It was 100+commits behind and I was simply doing this to try to fix CI solely. Items that were tended to, as some things were lost during the rebase and I thought they were still in there:
I have "unresolved" the comments that were lost during the rebase so they can be re-verified. Would it be better for you @edenhaus if I just started a new PR at this point? |
Proposed change
Adds server-side support for iOS Live Activities in the `mobile_app` integration. This is the HA core companion to the iOS companion app PRs and the relay server PR.
Live Activities let Home Assistant automations push real-time state to the iOS Lock Screen and Dynamic Island. The iOS app handles the ActivityKit lifecycle; this PR adds the webhook handlers and notification routing that HA core needs.
How it works: When a notification contains `live_update: true` and a `tag`, the notify service looks up the stored APNs Live Activity token for that tag and includes it alongside the normal FCM registration token in the relay request. The relay places it in the FCM message's `apns.liveActivityToken` field — no separate APNs endpoint or credentials needed. If no per-activity token exists, it falls back to the device's push-to-start token (iOS 17.2+) to start a new activity remotely.
What this adds:
Type of change
Additional information