mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-23 07:36:48 +00:00
Fix push notification
This commit is contained in:
parent
864cc26e08
commit
fca76849bc
4 changed files with 98 additions and 82 deletions
|
@ -1203,7 +1203,7 @@
|
|||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
};
|
||||
name = Debug;
|
||||
|
@ -1238,7 +1238,7 @@
|
|||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
|
|
|
@ -56,8 +56,12 @@
|
|||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "1"
|
||||
BundleIdentifier = "com.thomasricouard.IceCubesApp"
|
||||
RemotePath = "/Users/dimillian/Library/Developer/CoreSimulator/Devices/8EF923D0-4CF1-49B6-B287-5F05AD5440C1/data/Containers/Bundle/Application/C447A1D1-9BC9-49C9-8FA5-130E8403972F/Ice Cubes.app">
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9FBFE638292A715500C250E9"
|
||||
|
@ -65,7 +69,7 @@
|
|||
BlueprintName = "IceCubesApp"
|
||||
ReferencedContainer = "container:IceCubesApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
|
@ -9,18 +9,35 @@ import Notifications
|
|||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
extension UNMutableNotificationContent: @unchecked @retroactive Sendable { }
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
override func didReceive(_ request: UNNotificationRequest,
|
||||
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
|
||||
let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
|
||||
let provider = NotificationServiceContentProvider(bestAttemptContent: bestAttemptContent)
|
||||
let casted = unsafeBitCast(contentHandler,
|
||||
to: (@Sendable (UNNotificationContent) -> Void).self)
|
||||
Task {
|
||||
if let content = await provider.buildContent() {
|
||||
casted(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actor NotificationServiceContentProvider {
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
private let pushKeys = PushKeys()
|
||||
private let keychainAccounts = AppAccount.retrieveAll()
|
||||
|
||||
@MainActor
|
||||
override func didReceive(_ request: UNNotificationRequest,
|
||||
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
init(bestAttemptContent: UNMutableNotificationContent? = nil) {
|
||||
self.bestAttemptContent = bestAttemptContent
|
||||
}
|
||||
|
||||
func buildContent() async -> UNMutableNotificationContent? {
|
||||
if var bestAttemptContent {
|
||||
let privateKey = pushKeys.notificationsPrivateKeyAsKey
|
||||
let auth = pushKeys.notificationsAuthKeyAsKey
|
||||
|
@ -28,23 +45,20 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
guard let encodedPayload = bestAttemptContent.userInfo["m"] as? String,
|
||||
let payload = Data(base64Encoded: encodedPayload.URLSafeBase64ToBase64())
|
||||
else {
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
return bestAttemptContent
|
||||
}
|
||||
|
||||
guard let encodedPublicKey = bestAttemptContent.userInfo["k"] as? String,
|
||||
let publicKeyData = Data(base64Encoded: encodedPublicKey.URLSafeBase64ToBase64()),
|
||||
let publicKey = try? P256.KeyAgreement.PublicKey(x963Representation: publicKeyData)
|
||||
else {
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
return bestAttemptContent
|
||||
}
|
||||
|
||||
guard let encodedSalt = bestAttemptContent.userInfo["s"] as? String,
|
||||
let salt = Data(base64Encoded: encodedSalt.URLSafeBase64ToBase64())
|
||||
else {
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
return bestAttemptContent
|
||||
}
|
||||
|
||||
guard let plaintextData = NotificationService.decrypt(payload: payload,
|
||||
|
@ -54,39 +68,26 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
publicKey: publicKey),
|
||||
let notification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintextData)
|
||||
else {
|
||||
contentHandler(bestAttemptContent)
|
||||
return
|
||||
return bestAttemptContent
|
||||
}
|
||||
|
||||
bestAttemptContent.title = notification.title
|
||||
if AppAccountsManager.shared.availableAccounts.count > 1 {
|
||||
if keychainAccounts.count > 1 {
|
||||
bestAttemptContent.subtitle = bestAttemptContent.userInfo["i"] as? String ?? ""
|
||||
}
|
||||
bestAttemptContent.body = notification.body.escape()
|
||||
bestAttemptContent.userInfo["plaintext"] = plaintextData
|
||||
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf"))
|
||||
|
||||
let preferences = UserPreferences.shared
|
||||
let tokens = AppAccountsManager.shared.pushAccounts.map(\.token)
|
||||
preferences.reloadNotificationsCount(tokens: tokens)
|
||||
|
||||
if let token = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == notification.accessToken })?.oauthToken {
|
||||
var currentCount = preferences.notificationsCount[token] ?? 0
|
||||
currentCount += 1
|
||||
preferences.notificationsCount[token] = currentCount
|
||||
}
|
||||
|
||||
bestAttemptContent.badge = .init(integerLiteral: preferences.totalNotificationsCount)
|
||||
let badgeCount = await updateBadgeCoung(notification: notification)
|
||||
bestAttemptContent.badge = .init(integerLiteral: badgeCount)
|
||||
|
||||
if let urlString = notification.icon,
|
||||
let url = URL(string: urlString)
|
||||
{
|
||||
let url = URL(string: urlString) {
|
||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("notification-attachments")
|
||||
try? FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||
let filename = url.lastPathComponent
|
||||
let fileURL = temporaryDirectoryURL.appendingPathComponent(filename)
|
||||
|
||||
Task {
|
||||
// Warning: Non-sendable type '(any URLSessionTaskDelegate)?' exiting main actor-isolated
|
||||
// context in call to non-isolated instance method 'data(for:delegate:)' cannot cross actor
|
||||
// boundary.
|
||||
|
@ -102,6 +103,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
let intent = buildMessageIntent(remoteNotification: remoteNotification,
|
||||
currentUser: bestAttemptContent.userInfo["i"] as? String ?? "",
|
||||
avatarURL: fileURL)
|
||||
do {
|
||||
bestAttemptContent = try bestAttemptContent.updating(from: intent) as! UNMutableNotificationContent
|
||||
bestAttemptContent.threadIdentifier = remoteNotification.type
|
||||
if type == .mention {
|
||||
|
@ -110,27 +112,32 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
let newBody = "\(NSLocalizedString(type.notificationKey(), bundle: .main, comment: ""))\(notification.body.escape())"
|
||||
bestAttemptContent.body = newBody
|
||||
}
|
||||
return bestAttemptContent
|
||||
} catch {
|
||||
return bestAttemptContent
|
||||
}
|
||||
} else {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: filename, url: fileURL, options: nil) {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: filename,
|
||||
url: fileURL,
|
||||
options: nil) {
|
||||
bestAttemptContent.attachments = [attachment]
|
||||
}
|
||||
}
|
||||
}
|
||||
contentHandler(bestAttemptContent)
|
||||
} else {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
return bestAttemptContent
|
||||
}
|
||||
} else {
|
||||
contentHandler(bestAttemptContent)
|
||||
return bestAttemptContent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
private func toRemoteNotification(localNotification: MastodonPushNotification) async -> Models.Notification? {
|
||||
do {
|
||||
if let account = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == localNotification.accessToken }) {
|
||||
if let account = keychainAccounts.first(where: { $0.oauthToken?.accessToken == localNotification.accessToken }) {
|
||||
let client = Client(server: account.server, oauthToken: account.oauthToken)
|
||||
let remoteNotification: Models.Notification = try await client.get(endpoint: Notifications.notification(id: String(localNotification.notificationID)))
|
||||
return remoteNotification
|
||||
|
@ -141,7 +148,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
return nil
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func buildMessageIntent(remoteNotification: Models.Notification,
|
||||
currentUser: String,
|
||||
avatarURL: URL) -> INSendMessageIntent
|
||||
|
@ -156,7 +162,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
customIdentifier: nil)
|
||||
var recipents: [INPerson]?
|
||||
var groupName: INSpeakableString?
|
||||
if AppAccountsManager.shared.availableAccounts.count > 1 {
|
||||
if keychainAccounts.count > 1 {
|
||||
let me = INPerson(personHandle: .init(value: currentUser, type: .unknown),
|
||||
nameComponents: nil,
|
||||
displayName: currentUser,
|
||||
|
@ -179,4 +185,18 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func updateBadgeCoung(notification: MastodonPushNotification) -> Int {
|
||||
let preferences = UserPreferences.shared
|
||||
let tokens = AppAccountsManager.shared.pushAccounts.map(\.token)
|
||||
preferences.reloadNotificationsCount(tokens: tokens)
|
||||
|
||||
if let token = keychainAccounts.first(where: { $0.oauthToken?.accessToken == notification.accessToken })?.oauthToken {
|
||||
var currentCount = preferences.notificationsCount[token] ?? 0
|
||||
currentCount += 1
|
||||
preferences.notificationsCount[token] = currentCount
|
||||
}
|
||||
return preferences.totalNotificationsCount
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,14 +111,6 @@ public struct HandledNotification: Equatable {
|
|||
UNUserNotificationCenter.current().delegate = self
|
||||
}
|
||||
|
||||
private var keychain: KeychainSwift {
|
||||
let keychain = KeychainSwift()
|
||||
#if !DEBUG && !targetEnvironment(simulator)
|
||||
keychain.accessGroup = AppInfo.keychainGroup
|
||||
#endif
|
||||
return keychain
|
||||
}
|
||||
|
||||
public func requestPushNotifications() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { @Sendable _, _ in
|
||||
DispatchQueue.main.async {
|
||||
|
|
Loading…
Reference in a new issue