diff --git a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift index 4440021229..b5799c082b 100644 --- a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift +++ b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift @@ -44,6 +44,10 @@ extension UNNotificationContent { @objc var roomID: String? { userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String } + + @objc var eventID: String? { + userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String + } } extension UNMutableNotificationContent { @@ -64,6 +68,15 @@ extension UNMutableNotificationContent { userInfo[NotificationConstants.UserInfoKey.roomIdentifier] = newValue } } + + override var eventID: String? { + get { + userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String + } + set { + userInfo[NotificationConstants.UserInfoKey.eventIdentifier] = newValue + } + } func addMediaAttachment(using mediaProvider: MediaProviderProtocol?, mediaSource: MediaSourceProxy) async -> UNMutableNotificationContent { diff --git a/NSE/Sources/NotificationContentBuilder.swift b/NSE/Sources/NotificationContentBuilder.swift index 569d90852f..54cc72137f 100644 --- a/NSE/Sources/NotificationContentBuilder.swift +++ b/NSE/Sources/NotificationContentBuilder.swift @@ -63,6 +63,10 @@ struct NotificationContentBuilder { let notification = UNMutableNotificationContent() notification.receiverID = notificationItem.receiverID notification.roomID = notificationItem.roomID + notification.eventID = switch notificationItem.event { + case .timeline(let event): event.eventId() + case .invite, .none: nil + } notification.sound = notificationItem.isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil // So that the UI groups notification that are received for the same room but also for the same user // Removing the @ fixes an iOS bug where the notification crashes if the mute button is tapped diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 2164ff6ee0..a2f24a354e 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -59,8 +59,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { guard !DataProtectionManager.isDeviceLockedAfterReboot(containerURL: URL.appGroupContainerDirectory), - let roomId = request.roomId, - let eventId = request.eventId, + let roomID = request.roomID, + let eventID = request.eventID, let clientID = request.pusherNotificationClientIdentifier, let credentials = keychainController.restorationTokens().first(where: { $0.restorationToken.pusherNotificationClientIdentifier == clientID }) else { // We cannot process this notification, it might be due to one of these: @@ -103,8 +103,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { Task { await run(with: credentials, - roomId: roomId, - eventId: eventId, + roomID: roomID, + eventID: eventID, unreadCount: request.unreadCount) } } @@ -117,10 +117,10 @@ class NotificationServiceExtension: UNNotificationServiceExtension { } private func run(with credentials: KeychainCredentials, - roomId: String, - eventId: String, + roomID: String, + eventID: String, unreadCount: Int?) async { - MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)") + MXLog.info("\(tag) run with roomId: \(roomID), eventId: \(eventID)") guard let userSession = Self.userSession else { MXLog.error("Invalid NSE User Session, discarding.") @@ -128,7 +128,7 @@ class NotificationServiceExtension: UNNotificationServiceExtension { } do { - guard let itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId) else { + guard let itemProxy = await userSession.notificationItemProxy(roomID: roomID, eventID: eventID) else { MXLog.info("\(tag) no notification for the event, discard") return discard(unreadCount: unreadCount) } @@ -137,6 +137,10 @@ class NotificationServiceExtension: UNNotificationServiceExtension { return discard(unreadCount: unreadCount) } + if await handleRedactionNotification(itemProxy) { + return discard(unreadCount: unreadCount) + } + // After the first processing, update the modified content modifiedContent = try await notificationContentBuilder.content(for: itemProxy, mediaProvider: nil) @@ -246,6 +250,29 @@ class NotificationServiceExtension: UNNotificationServiceExtension { return false } + + /// Handles a notification for an `m.room.redaction` event. + /// - Returns: A boolean indicating whether the notification was handled. + private func handleRedactionNotification(_ itemProxy: NotificationItemProxyProtocol) async -> Bool { + guard case let .timeline(event) = itemProxy.event, + case let .messageLike(content) = try? event.eventType(), + case let .roomRedaction(redactedEventID, _) = content else { + return false + } + + guard let redactedEventID else { + MXLog.error("Unable to redact notification due to missing event ID.") + return true // Return true as there's no point showing this notification. + } + + let deliveredNotifications = await UNUserNotificationCenter.current().deliveredNotifications() + + if let targetNotification = deliveredNotifications.first(where: { $0.request.content.eventID == redactedEventID }) { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [targetNotification.request.identifier]) + } + + return true + } } // https://stackoverflow.com/a/77300959/730924 diff --git a/NSE/Sources/Other/UNNotificationRequest.swift b/NSE/Sources/Other/UNNotificationRequest.swift index 24c7116bb0..de7a191d27 100644 --- a/NSE/Sources/Other/UNNotificationRequest.swift +++ b/NSE/Sources/Other/UNNotificationRequest.swift @@ -18,11 +18,11 @@ import Foundation import UserNotifications extension UNNotificationRequest { - var roomId: String? { + var roomID: String? { content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String } - var eventId: String? { + var eventID: String? { content.userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String }