Skip to content

Commit aeee747

Browse files
authored
Merge pull request #1831 from guardian/live/readme
Add README for live activities
2 parents 7aa81a7 + 4a861de commit aeee747

3 files changed

Lines changed: 121 additions & 7 deletions

File tree

football/README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
# Mobile Goal Notifications
1+
# Mobile Goal Notifications (Push and Broadcast)
22

33
Service that watches current football matches on PA, and when a goal is scored
4-
uses the Guardian Mobile Notifications service to send a push notification to
4+
uses the Guardian Mobile Notifications service to send a push notification or Live Activity Service to send a broadcast update to
55
subscribed users.
66

77
### Testing in CODE
88

99
In order to facilitate testing when there are no matches in progress, you can [force](https://github.com/guardian/mobile-n10n/blob/682289cc9651f4f8475c9efc5eb2a3da35985c15/football/src/main/scala/com/gu/mobile/notifications/football/Lambda.scala#L66-L76) the `CODE` environment to run in the past using [`football-time-machine`](https://github.com/guardian/football-time-machine).
1010

11-
1. Find a match which [recently took place](https://www.theguardian.com/football/results).
12-
1. Install a debug version of the app and start following a relevant team or match.
11+
1. Find a match which [recently took place](https://www.theguardian.com/football/results) - football time machine holds data for the last 3 months. To find kick off time, visit the match info tab and add `.json?dcr` to the url - id and kick off will be under `matchInfo`
12+
1. Install a debug version of the app and start following a relevant team or match - to receive push notifications, the debug app must be installed on a device directly from Xcode. Live activities work on the simulator.
1313
1. Check that the scheduled rule which triggers the Lambda on a regular basis is enabled in `CODE`.
14-
1. Set a date and time for just before the match started. For example, if you want to trigger notifications for a match which kicked off at 8pm on 4th February 2026, you would use: `curl "https://hdjq4n85yi.execute-api.eu-west-1.amazonaws.com/Prod/setDate?startDate=2026-02-04T19:59:00Z"`.
15-
1. Check the [logs](https://logs.gutools.co.uk/s/mobile/goto/cc75dcf9f10d33851bc1bc1e851f96be) to track progress of the match.
14+
1. Set a date and time for just before the match started. For example, if you want to trigger notifications for a match which kicked off at 8pm on 4th February 2026, you would use: `curl "https://hdjq4n85yi.execute-api.eu-west-1.amazonaws.com/Prod/setDate?startDate=2026-02-04T19:59:00Z"`.
15+
- Note: for BST subtract one hour from the local time to get UTC time.
16+
- Note: for live activities, jump to 30min before kick off to ensure a channel is created, and you receive the initial "start-live-activity" push notification 20min before kick off.
1617
1. Your device should receive notifications when significant match events occur (e.g. kick off, goals, half-time etc.)
18+
2. Check the [logs](https://logs.gutools.co.uk/s/mobile/goto/cc75dcf9f10d33851bc1bc1e851f96be) to track progress of the match.
1719

18-
Note that the Lambda keeps track of which PA events it has processed (to avoid sending duplicate notifications), so you will not be able to 'replay' the notifications for the same match more than once.
20+
21+
**Important:** When you are done testing, please _disable_ the scheduled rule in CODE to stop polling time machine events unneccessarily. The Lambda keeps track of which PA events it has processed (to avoid sending duplicate notifications), so you will not be able to 'replay' the notifications for the same match more than once, without manually deleting the sent notifications by UUID from Dyanmo tables:
22+
23+
- `mobile-notifications-football-notifications-CODE` and `mobile-notifications-reports-CODE` for push notifications tables which can be cumbersome.
24+
- `mobile-notifications-liveactivities-payload-CODE` for live activities
25+
26+
You can search logs for "Distinct event" to help identify UUIDs of sent notifications.

liveactivities/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
![architecture.png](architecture.png)
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.

liveactivities/architecture.png

169 KB
Loading

0 commit comments

Comments
 (0)