mirror of
https://github.com/ProtonMail/ios-mail.git
synced 2026-05-15 09:50:39 +00:00
127 lines
4.5 KiB
Swift
127 lines
4.5 KiB
Swift
// Copyright (c) 2025 Proton Technologies AG
|
|
//
|
|
// This file is part of Proton Mail.
|
|
//
|
|
// Proton Mail is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
|
|
|
import InboxCore
|
|
import InboxKeychain
|
|
import proton_app_uniffi
|
|
import UserNotifications
|
|
|
|
public struct TestableNotificationService {
|
|
typealias DecryptRemoteNotification = (OsKeyChain, EncryptedPushNotification) async -> DecryptPushNotificationResult
|
|
|
|
private let decryptRemoteNotification: DecryptRemoteNotification
|
|
private let keychain = KeychainSDKWrapper()
|
|
private let userDefaults: UserDefaults
|
|
|
|
public init() {
|
|
self.init(userDefaults: .appGroup, decryptRemoteNotification: decryptPushNotification)
|
|
}
|
|
|
|
init(userDefaults: UserDefaults, decryptRemoteNotification: @escaping DecryptRemoteNotification) {
|
|
self.decryptRemoteNotification = decryptRemoteNotification
|
|
self.userDefaults = userDefaults
|
|
}
|
|
|
|
public func transform(originalContent: UNNotificationContent) async -> UNNotificationContent {
|
|
guard let mutableContent = (originalContent.mutableCopy() as? UNMutableNotificationContent) else {
|
|
AppLogger.log(message: "Notification content cannot be mutated", category: .notifications, isError: true)
|
|
return originalContent
|
|
}
|
|
|
|
// this is a temporary "marker" body to see if the extension has been launched by the OS, which is known to not be the case sometimes
|
|
mutableContent.body = "You received a new message!"
|
|
|
|
if let encryptedPushNotification = parseDecryptablePayload(from: originalContent.userInfo) {
|
|
await replaceTitleAndBody(of: mutableContent, byDecrypting: encryptedPushNotification)
|
|
}
|
|
|
|
unsetBadgeIfRecipientIsNotPrimaryAccount(mutableContent: mutableContent)
|
|
|
|
return mutableContent
|
|
}
|
|
|
|
private func parseDecryptablePayload(from userInfo: [AnyHashable: Any]) -> EncryptedPushNotification? {
|
|
guard
|
|
let encryptedMessage = userInfo["encryptedMessage"] as? String,
|
|
let sessionId = userInfo["UID"] as? String
|
|
else {
|
|
AppLogger.log(message: "Missing required fields in the payload", category: .notifications, isError: true)
|
|
return nil
|
|
}
|
|
|
|
return .init(sessionId: sessionId, encryptedMessage: encryptedMessage)
|
|
}
|
|
|
|
private func replaceTitleAndBody(
|
|
of mutableContent: UNMutableNotificationContent,
|
|
byDecrypting encryptedPushNotification: EncryptedPushNotification
|
|
) async {
|
|
switch await decryptRemoteNotification(keychain, encryptedPushNotification) {
|
|
case .ok(let notificationData):
|
|
mutableContent.title = notificationData.sender.displayableName
|
|
mutableContent.body = notificationData.body
|
|
|
|
switch notificationData {
|
|
case .email(let payload):
|
|
mutableContent.userInfo["messageId"] = payload.messageId.value
|
|
case .openUrl(let payload):
|
|
mutableContent.userInfo["url"] = payload.url
|
|
}
|
|
case .error(let error):
|
|
AppLogger.log(error: error, category: .notifications)
|
|
}
|
|
}
|
|
|
|
private func unsetBadgeIfRecipientIsNotPrimaryAccount(mutableContent: UNMutableNotificationContent) {
|
|
guard let sessionId = mutableContent.userInfo["UID"] as? String else {
|
|
return
|
|
}
|
|
|
|
if sessionId != userDefaults[.primaryAccountSessionId] {
|
|
mutableContent.badge = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension DecryptedPushNotification {
|
|
var body: String {
|
|
switch self {
|
|
case .email(let payload):
|
|
payload.subject
|
|
case .openUrl(let payload):
|
|
payload.content
|
|
}
|
|
}
|
|
|
|
var sender: NotificationSender {
|
|
switch self {
|
|
case .email(let payload):
|
|
payload.sender
|
|
case .openUrl(let payload):
|
|
payload.sender
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension NotificationSender {
|
|
var displayableName: String {
|
|
name.isEmpty ? address : name
|
|
}
|
|
}
|
|
|
|
extension ActionError: Error {}
|