# BlueskyNSE BlueskyNSE is an iOS Notification Service Extension that processes push notifications before they are displayed to the user. NSE stands for "Notification Service Extension", a native iOS app extension type. ## What It Does This extension intercepts incoming push notifications and performs processing before displaying them: 1. Manages badge counts for app icon 2. Applies custom notification sounds based on user preferences 3. Enables notification customization without requiring the main app to be running ## How It Works When a push notification arrives on iOS, the system can invoke this extension to modify the notification content before displaying it. The extension runs in a separate process from the main app and has strict time limits (approximately 30 seconds) to complete its work. ### Architecture The extension uses shared UserDefaults (via App Groups) to access preferences set by the main app: - **App Group**: `group.app.bsky` allows data sharing between the main app and the extension - **Shared Preferences**: Stored in UserDefaults suite accessible by both processes - **Thread Safety**: Uses a dedicated serial DispatchQueue (`NSEPrefsQueue`) to prevent race conditions when multiple notifications arrive simultaneously ### Notification Processing Flow 1. System receives push notification 2. `NotificationService.didReceive()` is called 3. Extension creates mutable copy of notification content 4. Based on notification type (determined by `reason` field): - **Chat messages** (`reason == "chat-message"`): Applies custom DM sound if user preference `playSoundChat` is enabled - **Other notifications**: Increments and applies badge count 5. Extension delivers modified notification to system via `contentHandler` ### Badge Count Management Badge counts are managed centrally by the extension: - Each non-chat notification increments the badge count - Count is synchronized across notification instances using the serial queue - Main app can reset the count via the `expo-background-notification-handler` module ### Notification Sounds Two sound types are supported: - **Default system sound**: Standard iOS notification sound - **DM sound**: Custom `dm.aiff` sound file for chat messages DM sound only plays if the user has enabled the `playSoundChat` preference in the main app's chat settings. ## Key Files | File | Purpose | |------|---------| | `NotificationService.swift` | Main service extension implementation | | `BlueskyNSE.entitlements` | iOS entitlements configuration for App Group access | | `Info.plist` | Extension metadata and configuration | ### NotificationService.swift Contains two main classes: **NotificationService**: The main extension class that implements `UNNotificationServiceExtension` - `didReceive(_:withContentHandler:)`: Processes incoming notifications - `serviceExtensionTimeWillExpire()`: Handles timeout scenarios - Mutation methods for modifying notification content **NSEUtil**: Singleton utility class for shared state management - Provides shared `UserDefaults` instance for the App Group - Manages serial queue for thread-safe preference access - Helper methods for notification content manipulation ## Configuration ### App Group Setup The extension requires the `group.app.bsky` App Group to be configured in: 1. Main app target capabilities 2. Extension target capabilities (defined in `BlueskyNSE.entitlements`) ### Shared Preferences The following preferences are shared between the main app and extension: | Preference Key | Type | Purpose | |----------------|------|---------| | `badgeCount` | Int | Current badge count for app icon | | `playSoundChat` | Bool | Whether to play sound for chat notifications | These are managed by the `expo-background-notification-handler` module in the main app. ### Sound Files The custom DM sound file (`dm.aiff`) must be included in the extension's bundle. The iOS project configuration handles copying this resource during the build. ## Platform Support - **iOS**: Fully supported (primary platform for this extension) - **Android**: Not applicable (Android uses different notification handling mechanisms) - **Web**: Not applicable (web notifications are handled by browser APIs) ## Integration with Main App The extension coordinates with the main app through: 1. **expo-background-notification-handler** module: Provides JavaScript API for managing shared preferences 2. **App Group shared storage**: Enables data synchronization between processes 3. **Push notification payload**: Must include `reason` field to determine notification type ### Setting User Preferences Users can control notification sounds via the Chat Settings screen (`src/screens/Messages/Settings.tsx`): ```typescript import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' const {preferences, setPref} = useBackgroundNotificationPreferences() setPref('playSoundChat', true) // Enable DM sounds ``` ## Limitations 1. **Time constraints**: Extension must complete processing within ~30 seconds or the system will terminate it 2. **Process isolation**: Runs in separate process with limited memory and resources 3. **iOS only**: Notification Service Extensions are an iOS-specific feature 4. **Concurrent processing**: Multiple notifications may arrive simultaneously, requiring careful state management ## Best Practices When modifying this extension: 1. Keep processing fast and synchronous when possible 2. Use the shared serial queue for any UserDefaults mutations 3. Avoid network requests that could cause timeouts 4. Always call `contentHandler` with modified content, even on errors 5. Test with multiple concurrent notifications to verify thread safety