|
| 1 | +# Live Activities |
| 2 | + |
| 3 | +Live Activities is an iOS feature that provides a richer notification experience using Apple's APNS Live Activity API. It offers an enhanced UI, reduces individual notification alerts, and delegates device delivery to Apple — we send a single payload to Apple, which broadcasts it to all subscribed iOS devices. |
| 4 | + |
| 5 | +Currently live in Production for Football notifications only, though the infrastructure is designed to be extensible. |
| 6 | + |
| 7 | +Live Activities requires iOS app version 13.34. Users on older versions continue to receive standard push notifications. The existing n10n push notification server handles an initial live activity subscription flow. Android receives visual parity via regular push notifications, but the service itself is iOS-only. |
| 8 | + |
| 9 | + |
| 10 | +## Architecture Overview |
| 11 | + |
| 12 | +The service is powered by a set of lambdas and a DynamoDB table (provisioned in the n10n repo), connected via an EventBridge event bus. |
| 13 | + |
| 14 | +1. [The Football Notification Lambda](https://github.com/guardian/mobile-n10n/tree/main/football) polls PA Football API once per minute through a Guardian caching layer, builds live activity payloads using MatchStatusLiveActivityPayloadBuilder, and publishes them to the Live Activities event bus. Duplicate payloads and unsupported competitions are filtered out. This lambda also serves normal footbal push notifications for older Android and iOS app versions. |
| 15 | +2. EventBridge rules (defined in the Live Activities CDK) route payloads to either the Channel Manager or Broadcast lambda. |
| 16 | + - Channel Manager lambda receives a "create-channel" event two hours before activity start (eg. kick off) to create a new Apple channel and store that channel mapping in the DynamoDB table. It then sends an initial update to the Broadcast lambda. An activity is considered live at this point. |
| 17 | + - Broadcast lambda receives "broadcast-update" events payloads. Each payload is sent once to Apple with the destination channel id, and Apple then pushes the payload to all individual devices subscribed to the channel. The lambda also handles "broadcast-end" payloads to signal the live activity has ended, trigger the final update with a timestamp for auto-dismissal from screens after 1hr. |
| 18 | +3. A DynamoDB table (`mobile-notifications-liveactivities-[STAGE]`) mapping Live Activity IDs (eg. PA football match id) to Apple Channel IDs is shared between the Live Activities Service and MAPI to support the feature on iOS. These activity mappings have a TTL of 14 days. |
| 19 | + - `isActive=true` means a channel exists in Apple APNS. `False` means the clean up lambda has deleted the channel from APNS. |
| 20 | + - `isLive=true` means we have started broadcasting updates for an activity. When a "broadcast-end" event is processed, this is set to false." |
| 21 | +4. MAPI `mobile-sport` reads from the dynamo table to return a channel id on a match info response if a football activity is active, is live, and has a lastEventId. This allows users to tune in directly to a live channel from a device. |
| 22 | +5. Once a day, the CleanUp Lambda runs and deletes channels from Apple APNS that are not live and were created more than 24hrs ago. In Apple APNS, we should never have more than today's, and yesterday's channels. |
| 23 | +6. Users on iOS app versions that support live activities will subscribe to notifications on liveactivity-specific topic strings. This allows us to send a special "start-live-activity" push notification alert via the existing push notification service to individual devices encouraging them to "Tap to enable live updates" to start the live activity on their device. |
| 24 | + |
| 25 | + |
| 26 | +#### Shared Models: |
| 27 | +- [Eventbridge payloads](https://github.com/guardian/mobile-n10n/blob/main/api-models/src/main/scala/com/gu/mobile.notifications.client/models/liveActitivites/LiveActivityEventBridgePayloads.scala) between football lambda and live activity service |
| 28 | +- [Apple APNS boadcast ContentState model](https://github.com/guardian/mobile-n10n/blob/main/api-models/src/main/scala/com/gu/mobile.notifications.client/models/liveActitivites/BroadcastContentState.scala) for football matches |
| 29 | + |
| 30 | +#### Live Activity Models: |
| 31 | +- [Apple APNS Live Activity Broadcast API payload models](https://github.com/guardian/mobile-n10n/blob/main/liveactivities/src/main/scala/com/gu/liveactivities/models/BroadcastPayloads.scala) |
| 32 | + - [DynamoDB Channel Mapping models](https://github.com/guardian/mobile-n10n/blob/main/liveactivities/src/main/scala/com/gu/liveactivities/models/LiveActivityChannelMappings.scala) |
| 33 | + |
| 34 | + |
| 35 | +### Architecture Diagram |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +________ |
| 40 | + |
| 41 | +## Getting Started |
| 42 | + |
| 43 | +### Apple APNS |
| 44 | + |
| 45 | +It may be necessary to create and Apple developer account and have the [iOS Platform team](https://mail.google.com/mail/u/0/#chat/space/AAAAp1rAJUw) grant access/permissions. You will also need to know the bundleId for the debug app. |
| 46 | + |
| 47 | +You can view active channels for the debug (CODE) and live app in the[ Apple developer dashboard](https://icloud.developer.apple.com/dashboard/notifications). |
| 48 | + |
| 49 | + |
| 50 | +### CODE development |
| 51 | + |
| 52 | +The easiest way to test changes is in CODE via the debug app on a simulator. |
| 53 | + |
| 54 | +Follow instructions to test [football live activiites here](https://github.com/guardian/mobile-n10n/tree/main/football#readme) |
| 55 | + |
| 56 | +You can also find an activity channel mapping in the `mobile-notifications-liveactivities-CODE` |
| 57 | + table that is active and live, and tune in directly to that channel from the debug app via the live activity debug menu. |
| 58 | + |
| 59 | +You can track live activity payloads sent via the logs and the `mobile-notifications-liveactivities-payload-CODE` - use the TTL to sort them chronologically. |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | +### Local development with CODE environment and debug app. |
| 64 | + |
| 65 | +A `LambdaLocalRun` file exists which reads the mock eventbridge payloads in `liveactivities/src/main/resources`. |
| 66 | + |
| 67 | +1. Get Janus 'mobile' credentials |
| 68 | +2. Create a local conf file with CODE credentials available in parameter store. |
| 69 | +3. Run `sbt project liveactivities run` and select the number for the lambda you want to run. |
| 70 | + |
| 71 | +``` |
| 72 | +apns { |
| 73 | + keyId = "<APNS_KEY_ID>" |
| 74 | + teamId = "<APNS_TEAM_ID>" |
| 75 | + bundleId = "<APNS_BUNDLE_ID>" |
| 76 | + certificate = "<APNS_CERTIFICATE>" |
| 77 | + sendingToProdServer = false |
| 78 | +} |
| 79 | +isEnabled = true |
| 80 | +stage = "CODE" |
| 81 | +region = "eu-west-1" |
| 82 | +``` |
| 83 | + |
| 84 | +## Logs |
| 85 | + |
| 86 | +- [LiveActivity Service Logs PROD](https://logs.gutools.co.uk/s/mobile/app/r/s/oVfGy) |
| 87 | +- [LiveActivity Service Logs CODE](https://logs.gutools.co.uk/s/mobile/app/r/s/MdIbH) |
| 88 | + |
| 89 | +LiveActivity logs are structured. You can filter by lambda via these cloudWatchLogGroups: |
| 90 | +``` |
| 91 | +/aws/lambda/liveactivities-channel-cleaner-[STAGE] |
| 92 | +/aws/lambda/liveactivities-channel-manager-[STAGE] |
| 93 | +/aws/lambda/liveactivities-broadcast-[STAGE] |
| 94 | +``` |
| 95 | + |
| 96 | +## Monitoring |
| 97 | + |
| 98 | +Apple provide limited broadcast notification delivery metrics via[ Apple developer dashboard](https://icloud.developer.apple.com/dashboard/notifications). |
| 99 | + |
| 100 | +## Alarms |
| 101 | + |
| 102 | +Alerts are set up if the lambda dead letter queues have >1 message. |
| 103 | + |
| 104 | +The lambdas are asynchronously triggered by Eventbridge events - if a lambda execution fails, a message is retried up to 3 times then sent to the respective dead-letter-queue. Failing messages can be polled and interrogated in the DLQs. |
| 105 | + |
| 106 | +**Football:** We expect the occasional `Out of Order` error message due to Eventbridge not guaranteeing delivery order - this can happen if two goals/penalties are scored within a minute or just as the whistle is blown, meaning two events are processed within the same polling cycle. |
0 commit comments