NotificationService.swift (4993B)
1 // 2 // NotificationService.swift 3 // DamusNotificationService 4 // 5 // Created by Daniel D’Aquino on 2023-11-10. 6 // 7 8 import UserNotifications 9 import Foundation 10 11 class NotificationService: UNNotificationServiceExtension { 12 13 var contentHandler: ((UNNotificationContent) -> Void)? 14 var bestAttemptContent: UNMutableNotificationContent? 15 16 override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 17 self.contentHandler = contentHandler 18 19 guard let nostr_event_json = request.content.userInfo["nostr_event"] as? String, 20 let nostr_event = NdbNote.owned_from_json(json: nostr_event_json) 21 else { 22 // No nostr event detected. Just display the original notification 23 contentHandler(request.content) 24 return; 25 } 26 27 // Log that we got a push notification 28 Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex()) 29 30 guard let state = NotificationExtensionState() else { 31 Log.debug("Failed to open nostrdb", for: .push_notifications) 32 33 // Something failed to initialize so let's go for the next best thing 34 guard let improved_content = NotificationFormatter.shared.format_message(event: nostr_event) else { 35 // We cannot format this nostr event. Suppress notification. 36 contentHandler(UNNotificationContent()) 37 return 38 } 39 contentHandler(improved_content) 40 return 41 } 42 43 let txn = state.ndb.lookup_profile(nostr_event.pubkey) 44 let profile = txn?.unsafeUnownedValue?.profile 45 let name = Profile.displayName(profile: profile, pubkey: nostr_event.pubkey).displayName 46 47 // Don't show notification details that match mute list. 48 // TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block 49 if state.mutelist_manager.is_event_muted(nostr_event) { 50 // We cannot really suppress muted notifications until we have the notification supression entitlement. 51 // The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details 52 let content = UNMutableNotificationContent() 53 content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted") 54 content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it") 55 content.sound = UNNotificationSound.default 56 contentHandler(content) 57 return 58 } 59 60 guard should_display_notification(state: state, event: nostr_event, mode: .push) else { 61 Log.debug("should_display_notification failed", for: .push_notifications) 62 // We should not display notification for this event. Suppress notification. 63 // contentHandler(UNNotificationContent()) 64 // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification 65 contentHandler(request.content) 66 return 67 } 68 69 guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else { 70 Log.debug("generate_local_notification_object failed", for: .push_notifications) 71 // We could not process this notification. Probably an unsupported nostr event kind. Suppress. 72 // contentHandler(UNNotificationContent()) 73 // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification 74 contentHandler(request.content) 75 return 76 } 77 78 Task { 79 guard let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: name, notify: notification_object, state: state) else { 80 81 Log.debug("NotificationFormatter.format_message failed", for: .push_notifications) 82 return 83 } 84 85 contentHandler(improvedContent) 86 } 87 } 88 89 override func serviceExtensionTimeWillExpire() { 90 // Called just before the extension will be terminated by the system. 91 // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 92 if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { 93 contentHandler(bestAttemptContent) 94 } 95 } 96 97 }