This is a notification service that modifies incoming notifications.
Currently it support communication notifications for ios. Adding company logo as avatar.
It also supports e2e encryption for notifications.
Just add ttmobile-notification-service as a config plugin in your app.json. Then send payload to apns like this:
sender: {
id,
avatarUrl,
displayName
}
The system implements end-to-end encryption for push notifications across iOS and Android platforms using a hybrid encryption scheme combining RSA and AES.
- Each device generates 2048-bit RSA key pair during initialization
- Private keys stored securely in platform keychain/keystore
- Public keys transmitted to server and stored with device tokens
- Keys are accessible only after first device unlock (iOS) or with similar Android restrictions
- Prepares notification payload with title, message and data
- Generates random AES-256 key and IV for GCM encryption
- Encrypts payload using AES-GCM producing ciphertext and auth tag
- Encrypts AES key with device's public RSA key (OAEP-SHA1 padding)
- Packages encrypted components into notification:
- Encrypted AES key
- Ciphertext
- Nonce (IV)
- Authentication tag
- Server sends encrypted package through APNS/FCM
- Includes fallback message for legacy app versions
- Handles token invalidation and cleanup
- Device receives notification in background service
- Retrieves private RSA key from secure storage
- Decrypts AES key using RSA private key
- Uses AES key to verify and decrypt payload
- Updates notification content with decrypted data
- Industry standard algorithms (RSA-2048, AES-256-GCM)
- Keys never leave secure hardware
- Per-notification unique encryption keys
- Authenticated encryption prevents tampering
- Graceful fallback for pre-unlock state and legacy versions
- Uses platform-specific secure storage (Keychain/Keystore) with appropriate access controls
- Implements OAEP padding for RSA for enhanced security
- Handles key rotation and device token management
- Provides consistent cross-platform behavior
- Includes error handling and logging
An ActivityKit push-to-start notification wakes the app in the background "to
download assets that the Live Activity needs" (Apple). During that wake,
LiveActivityEnricher (started by LiveActivityEnrichmentAppDelegateSubscriber
on every launch, with no JS dependency) enriches push-started Live Activities
with E2E-encrypted PII entirely natively:
- Observes
Activity<LiveActivityAttributes>.activities+.activityUpdates. The attributes struct is a shape-identical duplicate of expo-widgets' internal one — ActivityKit matches activities by unqualified type name, the same string the push payload carries inattributes-type. - Reads the content-state
propsJSON and extractsencrypted_data— aMobileNotifications::PayloadEncryptorblob{encrypted_key, cipher_text, nonce, tag}(object or JSON string), encrypted against this device's registered public key. - Decrypts via
CryptoUtils.hybridDecryptinto a JSON object of PII fields, e.g.{"candidateName": "...", "avatarUrl": "https://..."}. - Downloads
avatarUrlinto<app group>/ExpoWidgets/la-avatar-<key>(the directory expo-widgets shares with the widget extension) and rewrites the field to the localfile://URL.<key>=props.meetingEventId. - Stages the enrichment in app-group
UserDefaultsunder__tt_la_enrichment_<key>— the widget extension merges it into props at render time, so a later APNs channel update that replaces the content-state cannot permanently wipe the enrichment (those updates don't wake the app). - Updates the activity with the enriched props merged in (preserving
staleDate/relevanceScore) to re-render immediately.
The decryption key is kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: a
wake between reboot and first unlock leaves the activity un-enriched (the
widget falls back to its non-PII layout) and the next launch retries.
Debug: log collect --device, subsystem = app bundle id, category LA-enrich.