This commit is contained in:
Thomas Ricouard 2024-08-12 21:59:39 +02:00
parent b56ed36f17
commit a184351cf3

View file

@ -8,47 +8,17 @@ import Observation
import SwiftUI import SwiftUI
import UserNotifications import UserNotifications
extension UNNotification: @unchecked @retroactive Sendable {}
extension UNNotificationResponse: @unchecked @retroactive Sendable {} extension UNNotificationResponse: @unchecked @retroactive Sendable {}
extension UNUserNotificationCenter: @unchecked @retroactive Sendable {} extension UNUserNotificationCenter: @unchecked @retroactive Sendable {}
public struct PushAccount: Equatable { public struct PushKeys: Sendable {
public let server: String
public let token: OauthToken
public let accountName: String?
public init(server: String, token: OauthToken, accountName: String?) {
self.server = server
self.token = token
self.accountName = accountName
}
}
public struct HandledNotification: Equatable {
public let account: PushAccount
public let notification: Models.Notification
}
@MainActor
@Observable public class PushNotificationsService: NSObject {
enum Constants { enum Constants {
static let endpoint = "https://icecubesrelay.fly.dev"
static let keychainAuthKey = "notifications_auth_key" static let keychainAuthKey = "notifications_auth_key"
static let keychainPrivateKey = "notifications_private_key" static let keychainPrivateKey = "notifications_private_key"
} }
public static let shared = PushNotificationsService() public init() { }
public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = []
public var pushToken: Data?
public var handledNotification: HandledNotification?
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
private var keychain: KeychainSwift { private var keychain: KeychainSwift {
let keychain = KeychainSwift() let keychain = KeychainSwift()
@ -58,43 +28,6 @@ public struct HandledNotification: Equatable {
return keychain return keychain
} }
public func requestPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
public func setAccounts(accounts: [PushAccount]) {
subscriptions = []
for account in accounts {
let sub = PushNotificationSubscriptionSettings(account: account,
key: notificationsPrivateKeyAsKey.publicKey.x963Representation,
authKey: notificationsAuthKeyAsKey,
pushToken: pushToken)
subscriptions.append(sub)
}
}
public func updateSubscriptions(forceCreate: Bool) async {
for subscription in subscriptions {
await withTaskGroup(of: Void.self, body: { group in
group.addTask {
await subscription.fetchSubscription()
if await subscription.subscription != nil, !forceCreate {
await subscription.deleteSubscription()
await subscription.updateSubscription()
} else if forceCreate {
await subscription.updateSubscription()
}
}
})
}
}
// MARK: - Key management
public var notificationsPrivateKeyAsKey: P256.KeyAgreement.PrivateKey { public var notificationsPrivateKeyAsKey: P256.KeyAgreement.PrivateKey {
if let key = keychain.get(Constants.keychainPrivateKey), if let key = keychain.get(Constants.keychainPrivateKey),
let data = Data(base64Encoded: key) let data = Data(base64Encoded: key)
@ -139,6 +72,89 @@ public struct HandledNotification: Equatable {
} }
} }
public struct PushAccount: Equatable {
public let server: String
public let token: OauthToken
public let accountName: String?
public init(server: String, token: OauthToken, accountName: String?) {
self.server = server
self.token = token
self.accountName = accountName
}
}
public struct HandledNotification: Equatable {
public let account: PushAccount
public let notification: Models.Notification
}
@MainActor
@Observable public class PushNotificationsService: NSObject {
enum Constants {
static let endpoint = "https://icecubesrelay.fly.dev"
}
public static let shared = PushNotificationsService()
private let pushKeys = PushKeys()
public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = []
public var pushToken: Data?
public var handledNotification: HandledNotification?
override init() {
super.init()
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]) { _, _ in
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
public func setAccounts(accounts: [PushAccount]) {
subscriptions = []
for account in accounts {
let sub = PushNotificationSubscriptionSettings(account: account,
key: pushKeys.notificationsPrivateKeyAsKey.publicKey.x963Representation,
authKey: pushKeys.notificationsAuthKeyAsKey,
pushToken: pushToken)
subscriptions.append(sub)
}
}
public func updateSubscriptions(forceCreate: Bool) async {
for subscription in subscriptions {
await withTaskGroup(of: Void.self, body: { group in
group.addTask {
await subscription.fetchSubscription()
if await subscription.subscription != nil, !forceCreate {
await subscription.deleteSubscription()
await subscription.updateSubscription()
} else if forceCreate {
await subscription.updateSubscription()
}
}
})
}
}
}
extension PushNotificationsService: UNUserNotificationCenterDelegate { extension PushNotificationsService: UNUserNotificationCenterDelegate {
public func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { public func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
guard let plaintext = response.notification.request.content.userInfo["plaintext"] as? Data, guard let plaintext = response.notification.request.content.userInfo["plaintext"] as? Data,
@ -154,6 +170,11 @@ extension PushNotificationsService: UNUserNotificationCenterDelegate {
handledNotification = .init(account: account.account, notification: notification) handledNotification = .init(account: account.account, notification: notification)
} catch {} } catch {}
} }
public func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
return [.banner, .sound]
}
} }
extension Data { extension Data {