From 428fa2b19dea5b58686b90e1ffef84d8927ed03f Mon Sep 17 00:00:00 2001 From: shannon Date: Tue, 1 Apr 2025 10:51:44 -0400 Subject: [PATCH] Additional error handling for push notification subscriptions Contributes to IOS-384 --- Mastodon/Supporting Files/AppDelegate.swift | 2 +- .../Notification/NotificationService.swift | 58 +++++++++++++++---- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 7fa8ca2f9..a5d2bc0c7 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -68,7 +68,7 @@ extension AppDelegate { } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) { - NotificationService.shared.registrationStatus.send(.error(error)) + NotificationService.shared.registrationStatus.send(.errorRegisteringWithAPNS(error)) } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift index 3689c0891..f5d26a45f 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift @@ -18,8 +18,19 @@ public final class NotificationService { public enum PushNotificationRegistrationStatus { case registering - case error(Error) + case errorRegisteringWithAPNS(Error) case registrationTokenReceived(Data) + case subscriptionsUpdated(deviceToken: Data, subscribedAccounts: [String]) + case errorUpdatingSubscriptions(Error, deviceToken: Data) + + var deviceToken: Data? { + switch self { + case .registering, .errorRegisteringWithAPNS(_): + return nil + case .registrationTokenReceived(let token), .subscriptionsUpdated(let token, _), .errorUpdatingSubscriptions(_, let token): + return token + } + } } public static let shared = { NotificationService() }() @@ -53,9 +64,14 @@ public final class NotificationService { switch (auth, registrationStatus) { case (nil, _): break - case (_, .registrationTokenReceived): + case (_, .registrationTokenReceived), (_, .errorUpdatingSubscriptions): self.requestNotificationPermissionAndUpdateSubscriptions() - case (_, .registering), (_, .error): + case (_, .subscriptionsUpdated(_, let subscribedAccounts)): + guard let userIdentifier = auth?.globallyUniqueUserIdentifier else { return } + if !subscribedAccounts.contains(userIdentifier) { + self.requestNotificationPermissionAndUpdateSubscriptions() + } + case (_, .registering), (_, .errorRegisteringWithAPNS): break } }) @@ -95,11 +111,14 @@ extension NotificationService { guard self.isNotificationPermissionGranted.value != granted else { return } self.isNotificationPermissionGranted.value = granted switch (granted, registrationStatus.value) { - case (true, .registrationTokenReceived(let token)): + case (true, .registrationTokenReceived), (true, .errorUpdatingSubscriptions), (true, .subscriptionsUpdated): + guard let token = registrationStatus.value.deviceToken else { return } Task { await self.updatePushNotificationSubscriptions(deviceToken: token) } - case (true, .error), (true, .registering): + case (true, .errorRegisteringWithAPNS): + UIApplication.shared.registerForRemoteNotifications() + case (true, .registering): break case (false, _): break @@ -252,11 +271,14 @@ extension NotificationService { extension NotificationService { private func updatePushNotificationSubscriptions(deviceToken: Data) async { - do { - for userAuthBox in AuthenticationServiceProvider.shared.mastodonAuthenticationBoxes { - guard let setting = SettingService.shared.setting(for: userAuthBox) else { continue } - guard let subscription = setting.activeSubscription else { continue } - + + var accountsSubscribed = [String]() + var hasNewError = false + for userAuthBox in AuthenticationServiceProvider.shared.mastodonAuthenticationBoxes { + guard let setting = SettingService.shared.setting(for: userAuthBox) else { continue } + guard let subscription = setting.activeSubscription else { continue } + + do { let queryData = Mastodon.API.Subscriptions.QueryData( policy: subscription.policy, alerts: Mastodon.API.Subscriptions.QueryData.Alerts( @@ -281,9 +303,21 @@ extension NotificationService { ) } let _ = try await createSubscriptionTask.value + accountsSubscribed.append(userAuthBox.globallyUniqueUserIdentifier) + } catch { + assertionFailure("error creating push notification subscription") + registrationStatus.send(.errorUpdatingSubscriptions(error, deviceToken: deviceToken)) + hasNewError = true + do { + try await Task.sleep(nanoseconds: 3 * UInt64.nanosPerUnit) + } catch { + } } - } catch { - assertionFailure("error creating push notification subscription") + } + if accountsSubscribed.isEmpty && hasNewError { + return + } else { + registrationStatus.send(.subscriptionsUpdated(deviceToken: deviceToken, subscribedAccounts: accountsSubscribed)) } }