Skip to main content
This flow is legacy and scheduled for significant refactoring. The current implementation documented below will change. Refer to this page for understanding the existing behavior, but expect breaking changes.

How it works

Device registration is the foundation of the push notification system. Without a registered device token, the backend cannot send any notifications — neither standard alerts nor Live Activity updates.

Initial registration

On first app launch (or after a fresh install), the app requests notification permissions from the user. If granted, iOS registers with Apple’s APNs and returns a device push token. The app sends this token to POST /devices along with metadata (platform, app version). The backend stores it in the cricket-devices table with a composite key of (userId, deviceToken).
Device tokens can change. iOS may assign a new token after an OS update, app reinstall, or restore from backup. The app re-registers on every launch to keep tokens current.

Live Activity tokens

Live Activity push tokens are separate from the standard device token. Each Live Activity instance gets its own unique token from Apple. When the app starts a Live Activity (e.g., when following a live match), it observes the pushTokenUpdates async stream on the Activity object. Once the token arrives, it’s sent to the backend. Push-to-start tokens are a special capability that allows the backend to start a Live Activity remotely, without the user having the app open. The app registers these via POST /devices/push-to-start-token.
Push-to-start tokens are registered once and remain valid until the app is uninstalled or the token is explicitly revoked. They are separate from per-activity tokens.

Signout cleanup

When a user signs out, the app calls DELETE /devices/:token to unregister the device. This prevents ghost notifications to a device that is no longer associated with an active user session.

Failure modes

Token invalidation: If APNs returns a 410 Gone response when the backend tries to push, the token is stale. The backend should remove it from cricket-devices immediately. Continuing to send to invalid tokens can cause APNs to throttle the app.
ScenarioBehavior
User denies notification permissionsNo token is generated; app works but without push
Token changes after OS updateApp re-registers on next launch; old token eventually pruned
Multiple devices per userEach device gets its own record; pushes are sent to all
App uninstalledToken becomes invalid; APNs returns 410 on next push attempt

Key tables

TableKey SchemaPurpose
cricket-devicesPK: userId, SK: deviceTokenMaps users to their registered device tokens

Key endpoints

MethodPathPurpose
POST/devicesRegister a device token
POST/devices/push-to-start-tokenRegister a push-to-start token
DELETE/devices/:tokenUnregister a device token

Areas of improvement

No server-side cleanup of APNs-invalidated tokens. The device registration doc mentions that APNs 410 Gone responses should trigger token removal, but the prematch notification job does not pass an onInvalidToken callback when calling sendPushToStartBatch or sendAlertPushBatch. The apnsService.ts has the plumbing (DeactivateDeviceCallback, shouldDeactivate flag), but the prematch job never wires it up. Stale tokens persist in cricket-devices until the user re-launches the app or signs out.
Race condition in single-device enforcement. In deviceStore.ts registerDevice (lines 70-91), the function queries existing records, deletes stale ones, then upserts the new device. These are separate DynamoDB operations with no transaction. If two devices register simultaneously for the same user, both could read zero stale records and both upsert, leaving two device records. The getDeviceForUser query uses Limit: 1, so only one would be returned for pushes, but the other lingers as an orphan.
identifierForVendor resets on app reinstall. The iOS NotificationService.swift uses UIDevice.current.identifierForVendor as the deviceToken (the DynamoDB sort key). This UUID changes when the user deletes and reinstalls the app. The old device record (with the previous vendor ID) becomes orphaned. The backend’s single-device cleanup in registerDevice does handle this for signed-in users — but if a user never signs back in after reinstalling, the old record persists until its push token goes stale.
Push-to-start token endpoint returns 200 even when no device exists. The POST /devices/push-to-start-token endpoint in devices.ts returns { updated: 0 } with a 200 status when getDeviceForUser finds no device. The iOS client receives a success response but the token was silently dropped. A 404 or 409 would make the failure visible to the client so it could retry after registering the base device first.
No periodic token freshness check. Devices update their lastSeenAt timestamp on each registerDevice call, but there is no background job that prunes devices that have not been seen in a long time (e.g., 90+ days). Over time, the cricket-devices table will accumulate records for users who uninstalled the app without signing out. Consider a TTL attribute or a periodic cleanup job.
No retry on failed device registration from iOS. In NotificationService.swift registerDevice(), if the POST /devices call fails (network error, 5xx), the error is logged but there is no retry. The next opportunity to register is the next app launch. If the user launches the app on poor connectivity, they could go an entire session without push notifications.