From 558a77525beb23bab8a55df1f21b376e4b68559d Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 17 Apr 2025 12:21:47 +0300 Subject: [PATCH] Refactor the NSE so that the original notification content is preserved between all the different processing steps - building brand new content is probably interfering with how iOS syncs up delivered and seen notifications between devices - simplify all the various steps and make the flows easier to follow --- ElementX.xcodeproj/project.pbxproj | 4 - .../Extensions/UNNotificationContent.swift | 30 ++- .../Proxy/NotificationItemProxyProtocol.swift | 28 --- NSE/Sources/NotificationContentBuilder.swift | 233 +++++++++--------- .../NotificationServiceExtension.swift | 91 +++---- NSE/Sources/Other/UNNotificationRequest.swift | 27 -- 6 files changed, 168 insertions(+), 245 deletions(-) delete mode 100644 NSE/Sources/Other/UNNotificationRequest.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 96fb41a8b..ba8a486ca 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -425,7 +425,6 @@ 510C4EDF826CA9C6CEEC6C95 /* ManageRoomMemberSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34D9BCA1A7D9A56E1EAF1D /* ManageRoomMemberSheetViewModel.swift */; }; 5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; }; 513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */; }; - 518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; }; 51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; }; 523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */; }; 52473A4D7B1FBD4CD1E770C8 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; }; @@ -1747,7 +1746,6 @@ 49ABAB186CF00B15C5521D04 /* MenuSheetLabelStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSheetLabelStyle.swift; sourceTree = ""; }; 49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = ""; }; - 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = ""; }; 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = ""; }; 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = ""; }; 4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = ""; }; @@ -3955,7 +3953,6 @@ children = ( 4959CECEC984B3995616F427 /* DataProtectionManager.swift */, EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */, - 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */, ); path = Other; sourceTree = ""; @@ -6733,7 +6730,6 @@ DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */, 45D6DC594816288983627484 /* UITestsScreenIdentifier.swift in Sources */, 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */, - 518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */, 06B55882911B4BF5B14E9851 /* URL.swift in Sources */, D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */, 34357B287357BC0B9715DD51 /* UserAgentBuilder.swift in Sources */, diff --git a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift index a15be7028..04fe9003d 100644 --- a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift +++ b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift @@ -39,6 +39,10 @@ extension UNNotificationContent { @objc var eventID: String? { userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String } + + @objc var pusherNotificationClientIdentifier: String? { + userInfo[NotificationConstants.UserInfoKey.pusherNotificationClientIdentifier] as? String + } } extension UNMutableNotificationContent { @@ -68,19 +72,21 @@ extension UNMutableNotificationContent { userInfo[NotificationConstants.UserInfoKey.eventIdentifier] = newValue } } + + var unreadCount: Int? { + userInfo[NotificationConstants.UserInfoKey.unreadCount] as? Int + } - func addMediaAttachment(using mediaProvider: MediaProviderProtocol?, - mediaSource: MediaSourceProxy) async -> UNMutableNotificationContent { - guard let mediaProvider else { - return self - } + func addMediaAttachment(using mediaProvider: MediaProviderProtocol, + mediaSource: MediaSourceProxy) async { switch await mediaProvider.loadFileFromSource(mediaSource) { case .success(let file): do { guard let url = file.url else { MXLog.error("Couldn't add media attachment: URL is nil") - return self + return } + let identifier = ProcessInfo.processInfo.globallyUniqueString let newURL = try FileManager.default.copyFileToTemporaryDirectory(file: url, with: "\(identifier).\(url.pathExtension)") let attachment = try UNNotificationAttachment(identifier: identifier, @@ -89,24 +95,21 @@ extension UNMutableNotificationContent { attachments.append(attachment) } catch { MXLog.error("Couldn't add media attachment:: \(error)") - return self + return } case .failure(let error): MXLog.error("Couldn't load the file for media attachment: \(error)") } - - return self } - func addSenderIcon(using mediaProvider: MediaProviderProtocol?, - senderID: String, + func addSenderIcon(senderID: String, senderName: String, icon: NotificationIcon, - forcePlaceholder: Bool = false) async throws -> UNMutableNotificationContent { + forcePlaceholder: Bool = false, + mediaProvider: MediaProviderProtocol) async throws -> UNMutableNotificationContent { var fetchedImage: INImage? let image: INImage if !forcePlaceholder, - let mediaProvider, let mediaSource = icon.mediaSource { switch await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) { case .success(let data): @@ -164,6 +167,7 @@ extension UNMutableNotificationContent { // Donate the interaction before updating notification content. try await interaction.donate() + // Update notification content before displaying the // communication notification. let updatedContent = try updating(from: intent) diff --git a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift index 63952f27a..0f0f7214c 100644 --- a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift +++ b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift @@ -41,32 +41,4 @@ extension NotificationItemProxyProtocol { var isDM: Bool { isRoomDirect && roomJoinedMembers <= 2 } - - var hasMedia: Bool { - if (isDM && senderAvatarMediaSource != nil) || - (!isDM && roomAvatarMediaSource != nil) { - return true - } - switch event { - case .invite, .none: - return false - case .timeline(let event): - switch try? event.eventType() { - case .state, .none: - return false - case let .messageLike(content): - switch content { - case let .roomMessage(messageType, _): - switch messageType { - case .image, .video, .audio: - return true - default: - return false - } - default: - return false - } - } - } - } } diff --git a/NSE/Sources/NotificationContentBuilder.swift b/NSE/Sources/NotificationContentBuilder.swift index cb0e2608e..bda862262 100644 --- a/NSE/Sources/NotificationContentBuilder.swift +++ b/NSE/Sources/NotificationContentBuilder.swift @@ -18,70 +18,78 @@ struct NotificationContentBuilder { /// - notificationItem: The notification item /// - mediaProvider: Media provider to process also media. May be passed nil to ignore media operations. /// - Returns: A notification content object if the notification should be displayed. Otherwise nil. - func content(for notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { + func process(notificationContent: UNMutableNotificationContent, + notificationItem: NotificationItemProxyProtocol, + mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent { + notificationContent.receiverID = notificationItem.receiverID + notificationContent.roomID = notificationItem.roomID + + switch notificationItem.event { + case .timeline(let event): + notificationContent.eventID = event.eventId() + case .invite, .none: + notificationContent.eventID = 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 + notificationContent.threadIdentifier = "\(notificationItem.receiverID)\(notificationItem.roomID)".replacingOccurrences(of: "@", with: "") + + MXLog.info("isNoisy: \(notificationItem.isNoisy)") + notificationContent.sound = notificationItem.isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil + switch notificationItem.event { case .none: - return processEmpty(notificationItem: notificationItem) + return processEmpty(notificationContent) case .invite: - return try await processInvited(notificationItem: notificationItem, mediaProvider: mediaProvider) + return await processInvited(notificationContent: notificationContent, + notificationItem: notificationItem, + mediaProvider: mediaProvider) case .timeline(let event): - guard let eventType = try? event.eventType() else { - return processEmpty(notificationItem: notificationItem) + guard let eventType = try? event.eventType(), + case let .messageLike(content) = eventType else { + return processEmpty(notificationContent) } - - switch eventType { - case let .messageLike(content): - switch content { - case .roomMessage(let messageType, _): - return try await processRoomMessage(notificationItem: notificationItem, messageType: messageType, mediaProvider: mediaProvider) - case .poll(let question): - return try await processPollStartEvent(notificationItem: notificationItem, pollQuestion: question, mediaProvider: mediaProvider) - case .callInvite: - return try await processCallInviteEvent(notificationItem: notificationItem, mediaProvider: mediaProvider) - case .callNotify: - return try await processCallNotifyEvent(notificationItem: notificationItem, mediaProvider: mediaProvider) - default: - return processEmpty(notificationItem: notificationItem) - } - case .state: - return processEmpty(notificationItem: notificationItem) + + let notificationContent = await processMessageLike(notificationContent: notificationContent, + notificationItem: notificationItem, + mediaProvider: mediaProvider) + + switch content { + case .roomMessage(let messageType, _): + return await processRoomMessage(notificationContent: notificationContent, + notificationItem: notificationItem, + messageType: messageType, + mediaProvider: mediaProvider) + case .poll(let question): + notificationContent.body = L10n.commonPollSummary(question) + return notificationContent + case .callInvite: + notificationContent.body = L10n.commonUnsupportedCall + return notificationContent + case .callNotify: + notificationContent.body = L10n.notificationIncomingCall + return notificationContent + default: + return processEmpty(notificationContent) } } } // MARK: - Private - func baseMutableContent(for notificationItem: NotificationItemProxyProtocol) -> UNMutableNotificationContent { - let notification = UNMutableNotificationContent() + private func processEmpty(_ notificationContent: UNMutableNotificationContent) -> UNMutableNotificationContent { + notificationContent.title = InfoPlistReader(bundle: .app).bundleDisplayName + notificationContent.body = L10n.notification + notificationContent.categoryIdentifier = NotificationConstants.Category.message - notification.receiverID = notificationItem.receiverID - notification.roomID = notificationItem.roomID - notification.eventID = switch notificationItem.event { - case .timeline(let event): event.eventId() - case .invite, .none: 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 - notification.threadIdentifier = "\(notificationItem.receiverID)\(notificationItem.roomID)".replacingOccurrences(of: "@", with: "") - - MXLog.info("isNoisy: \(notificationItem.isNoisy)") - notification.sound = notificationItem.isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil - - return notification - } - - private func processEmpty(notificationItem: NotificationItemProxyProtocol) -> UNMutableNotificationContent { - let notification = baseMutableContent(for: notificationItem) - notification.title = InfoPlistReader(bundle: .app).bundleDisplayName - notification.body = L10n.notification - notification.categoryIdentifier = NotificationConstants.Category.message - return notification + return notificationContent } - private func processInvited(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { - var notification = baseMutableContent(for: notificationItem) - - notification.categoryIdentifier = NotificationConstants.Category.invite + private func processInvited(notificationContent: UNMutableNotificationContent, + notificationItem: NotificationItemProxyProtocol, + mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent { + notificationContent.categoryIdentifier = NotificationConstants.Category.invite let body: String if !notificationItem.isDM { @@ -89,83 +97,43 @@ struct NotificationContentBuilder { } else { body = L10n.notificationInviteBody } - - notification = try await notification.addSenderIcon(using: mediaProvider, - senderID: notificationItem.senderID, - senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName, - icon: icon(for: notificationItem), - forcePlaceholder: settings.hideInviteAvatars) - notification.body = body - return notification - } - - private func processRoomMessage(notificationItem: NotificationItemProxyProtocol, messageType: MessageType, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { - var notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider) + notificationContent.body = body - let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName - notification.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters) - - guard settings.timelineMediaVisibility == .always || - (settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate) - else { return notification } - - switch messageType { - case .image(content: let content): - notification = await notification.addMediaAttachment(using: mediaProvider, - mediaSource: .init(source: content.source, - mimeType: content.info?.mimetype)) - case .audio(content: let content): - notification = await notification.addMediaAttachment(using: mediaProvider, - mediaSource: .init(source: content.source, - mimeType: content.info?.mimetype)) - case .video(content: let content): - notification = await notification.addMediaAttachment(using: mediaProvider, - mediaSource: .init(source: content.source, - mimeType: content.info?.mimetype)) - default: - break + do { + return try await notificationContent.addSenderIcon(senderID: notificationItem.senderID, + senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName, + icon: icon(for: notificationItem), + forcePlaceholder: settings.hideInviteAvatars, + mediaProvider: mediaProvider) + } catch { + return notificationContent } - - return notification - } - - private func processPollStartEvent(notificationItem: NotificationItemProxyProtocol, pollQuestion: String, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { - let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider) - notification.body = L10n.commonPollSummary(pollQuestion) - return notification } - private func processCallInviteEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { - let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider) - notification.body = L10n.commonUnsupportedCall - return notification - } - - private func processCallNotifyEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { - let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider) - notification.body = L10n.notificationIncomingCall - return notification - } - - private func processCommonRoomMessage(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { - var notification = baseMutableContent(for: notificationItem) - notification.title = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName - if notification.title != notificationItem.roomDisplayName { - notification.subtitle = notificationItem.roomDisplayName + private func processMessageLike(notificationContent: UNMutableNotificationContent, + notificationItem: NotificationItemProxyProtocol, + mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent { + notificationContent.title = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName + if notificationContent.title != notificationItem.roomDisplayName { + notificationContent.subtitle = notificationItem.roomDisplayName } - notification.categoryIdentifier = NotificationConstants.Category.message - + notificationContent.categoryIdentifier = NotificationConstants.Category.message + let senderName = if let displayName = notificationItem.senderDisplayName { notificationItem.hasMention ? L10n.notificationSenderMentionReply(displayName) : displayName } else { notificationItem.roomDisplayName } - notification = try await notification.addSenderIcon(using: mediaProvider, - senderID: notificationItem.senderID, - senderName: senderName, - icon: icon(for: notificationItem)) - return notification + + do { + return try await notificationContent.addSenderIcon(senderID: notificationItem.senderID, + senderName: senderName, + icon: icon(for: notificationItem), + mediaProvider: mediaProvider) + } catch { + return notificationContent + } } func icon(for notificationItem: NotificationItemProxyProtocol) -> NotificationIcon { @@ -176,4 +144,37 @@ struct NotificationContentBuilder { groupInfo: .init(name: notificationItem.roomDisplayName, id: notificationItem.roomID)) } } + + private func processRoomMessage(notificationContent: UNMutableNotificationContent, + notificationItem: NotificationItemProxyProtocol, + messageType: MessageType, + mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent { + let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName + notificationContent.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters) + + guard settings.timelineMediaVisibility == .always || + (settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate) + else { + return notificationContent + } + + switch messageType { + case .image(content: let content): + await notificationContent.addMediaAttachment(using: mediaProvider, + mediaSource: .init(source: content.source, + mimeType: content.info?.mimetype)) + case .audio(content: let content): + await notificationContent.addMediaAttachment(using: mediaProvider, + mediaSource: .init(source: content.source, + mimeType: content.info?.mimetype)) + case .video(content: let content): + await notificationContent.addMediaAttachment(using: mediaProvider, + mediaSource: .init(source: content.source, + mimeType: content.info?.mimetype)) + default: + break + } + + return notificationContent + } } diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 4c211046c..f1fe61ae6 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -41,8 +41,8 @@ private let notificationContentBuilder = NotificationContentBuilder(messageEvent settings: settings) class NotificationServiceExtension: UNNotificationServiceExtension { - private var handler: ((UNNotificationContent) -> Void)? - private var modifiedContent: UNMutableNotificationContent? + private var contentHandler: ((UNNotificationContent) -> Void)! + private var notificationContent: UNMutableNotificationContent! private let appHooks = AppHooks() @@ -55,9 +55,9 @@ 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 clientID = request.pusherNotificationClientIdentifier, + let roomID = request.content.roomID, + let eventID = request.content.eventID, + let clientID = request.content.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: // - Device rebooted and locked @@ -67,10 +67,14 @@ class NotificationServiceExtension: UNNotificationServiceExtension { return contentHandler(request.content) } + guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else { + return contentHandler(request.content) + } + Target.nse.configure(logLevel: settings.logLevel, traceLogPacks: settings.traceLogPacks) - handler = contentHandler - modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent + self.contentHandler = contentHandler + notificationContent = mutableContent MXLog.info("\(tag) #########################################") @@ -90,7 +94,6 @@ class NotificationServiceExtension: UNNotificationServiceExtension { await processEvent(eventID, roomID: roomID, - unreadCount: request.unreadCount, userSession: userSession) } catch { MXLog.error("Failed creating user session with error: \(error)") @@ -102,77 +105,51 @@ class NotificationServiceExtension: UNNotificationServiceExtension { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. MXLog.warning("\(tag) Extension time will expire") - notify(unreadCount: nil) + deliverNotification() } // MARK: - Private private func processEvent(_ eventID: String, roomID: String, - unreadCount: Int?, userSession: NSEUserSession) async { MXLog.info("\(tag) Processing event: \(eventID) in room: \(roomID)") - do { - guard let itemProxy = await userSession.notificationItemProxy(roomID: roomID, eventID: eventID) else { - MXLog.error("\(tag) Failed retrieving notification item") - return discard(unreadCount: unreadCount) - } - - switch await preprocessNotification(itemProxy) { - case .processedShouldDiscard, .unsupportedShouldDiscard: - return discard(unreadCount: unreadCount) - case .shouldDisplay: - break - } + guard let notificationItemProxy = await userSession.notificationItemProxy(roomID: roomID, eventID: eventID) else { + MXLog.error("\(tag) Failed retrieving notification item") + discardNotification() + return + } + + switch await preprocessNotification(notificationItemProxy) { + case .processedShouldDiscard, .unsupportedShouldDiscard: + discardNotification() + case .shouldDisplay: + notificationContent = await notificationContentBuilder.process(notificationContent: notificationContent, + notificationItem: notificationItemProxy, + mediaProvider: userSession.mediaProvider) - modifiedContent = try await notificationContentBuilder.content(for: itemProxy, mediaProvider: nil) - - guard itemProxy.hasMedia else { - MXLog.info("\(tag) Notification item doesn't contain media") - return notify(unreadCount: unreadCount) - } - - MXLog.info("\(tag) Processing media") - if let latestContent = try? await notificationContentBuilder.content(for: itemProxy, mediaProvider: userSession.mediaProvider) { - modifiedContent = latestContent - } else { - MXLog.error("\(tag) Failed processing notification media") - } - - return notify(unreadCount: unreadCount) - } catch { - MXLog.error("Failed processing with error: \(error)") - return discard(unreadCount: unreadCount) + deliverNotification() } } - private func notify(unreadCount: Int?) { - guard let modifiedContent else { - MXLog.error("\(tag) Notification modified content invalid") - return discard(unreadCount: unreadCount) - } - + private func deliverNotification() { MXLog.info("\(tag) Displaying notification") - if let unreadCount { - modifiedContent.badge = NSNumber(value: unreadCount) - } - - handler?(modifiedContent) + contentHandler(notificationContent) cleanUp() } - private func discard(unreadCount: Int?) { + private func discardNotification() { MXLog.info("\(tag) Discarding notification") let content = UNMutableNotificationContent() - if let unreadCount { + if let unreadCount = notificationContent.unreadCount { content.badge = NSNumber(value: unreadCount) } - - handler?(content) + + contentHandler(content) cleanUp() } @@ -181,8 +158,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { } private func cleanUp() { - handler = nil - modifiedContent = nil + contentHandler = nil + notificationContent = nil } private func preprocessNotification(_ itemProxy: NotificationItemProxyProtocol) async -> NotificationProcessingResult { diff --git a/NSE/Sources/Other/UNNotificationRequest.swift b/NSE/Sources/Other/UNNotificationRequest.swift deleted file mode 100644 index 190689218..000000000 --- a/NSE/Sources/Other/UNNotificationRequest.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright 2022-2024 New Vector Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. -// - -import Foundation -import UserNotifications - -extension UNNotificationRequest { - var roomID: String? { - content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String - } - - var eventID: String? { - content.userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String - } - - var unreadCount: Int? { - content.userInfo[NotificationConstants.UserInfoKey.unreadCount] as? Int - } - - var pusherNotificationClientIdentifier: String? { - content.userInfo[NotificationConstants.UserInfoKey.pusherNotificationClientIdentifier] as? String - } -}