Add APNs Live Activity broadcast channel support - #46
Add APNs Live Activity broadcast channel support#46mithileshchellappan wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — This PR adds database-backed APNs Live Activity broadcast channels while preserving the existing direct-token delivery path.
- Add APNs channel management and broadcast delivery —
Clientnow creates and deletes no-storage channels, publishes update and end events through Apple’s broadcast endpoint, and supplies channel or update-token inputs for capable remote starts. - Persist channel mappings and client capability — Migration 16,
PostgresStore, and service logic store activity-to-topic channel mappings, track capable APNs start tokens, reconcile concurrent provisioning, and retire stale channels safely. - Integrate mixed-cohort fanout — Live Activity jobs carry matching channel IDs through the existing worker pipelines, count one channel task alongside direct APNs and FCM targets, and isolate channel failures from token invalidation.
- Expose and document channel workflows — New HTTP routes, OpenAPI schemas, setup guidance, and end-to-end documentation cover provisioning, lookup, deletion, client registration, remote starts, and update delivery.
- Extend regression coverage — APNs, server, service, storage, and worker tests exercise wire payloads, persistence ambiguity, races, capability propagation, dispatch accounting, and partial failures.
GPT Sol | 𝕏
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 679a2a75af
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| switch action { | ||
| case string(model.LiveActivityActionStart): | ||
| if err := s.FailLAJobIfActive(ctx, jobID); err != nil && !errors.Is(err, Errors.NotFound) { | ||
| return fmt.Errorf("error failing empty LA start job: %w", err) | ||
| } | ||
| case string(model.LiveActivityActionEnd): |
There was a problem hiding this comment.
Restore failure handling for empty start dispatches
When a start dispatch finds no eligible tokens, CompleteLADispatchEnqueue reaches this switch with a zero total, but the removed start case now leaves the job ACTIVE while marking its only dispatch completed. No device received the start, and later attempts using the same activityId are returned as already_started by CreateOrGetLAStartJob, so registering a token and retrying cannot recover the activity. Continue failing an empty start job rather than leaving an unusable active record.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7bcb9393b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return nil, fmt.Errorf("failed to get JWT: %w", err) | ||
| } | ||
| endpoint := fmt.Sprintf( | ||
| "%s/1/apps/%s/channels", |
There was a problem hiding this comment.
Address the channel resource in DELETE requests
For every channel deletion, this builds the collection URL /1/apps/{bundle}/channels and supplies the channel ID only as an apns-channel-id header. APNs channel management deletes /1/apps/{bundle}/channels/{channelID}, so DELETE /v1/live-activity/channels/{activityID} receives a provider error and retains the local mapping instead of deleting the channel; append the escaped channel ID to the URL for DELETE requests.
Useful? React with 👍 / 👎.
| if req.TopicID != "" { | ||
| channel, _, err := s.ProvisionLAChannel(ctx, req.ActivityID, req.TopicID) |
There was a problem hiding this comment.
Avoid provisioning a channel for an already-started activity
When the original topic-scoped start continued token-only after channel provisioning failed, retrying the same start after APNs recovers reaches this call before CreateOrGetLAStartJob. EnsureLAChannel sees the matching existing job, creates and stores a new channel, and then the start returns already_started without enqueueing another start containing input-push-channel; consequently no existing activity subscribes to the channel, while later updates send a redundant broadcast to it and the mapping consumes a remote channel until manually deleted.
Useful? React with 👍 / 👎.

Summary
Testing