// // AppDelegate.swift // SportsTime // // Handles push notification registration and CloudKit subscription notifications // import UIKit import CloudKit import UserNotifications class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { // Register for remote notifications (required for CloudKit subscriptions) application.registerForRemoteNotifications() return true } func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { #if DEBUG print("📡 [Push] Registered for remote notifications") #endif } func application( _ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error ) { #if DEBUG print("📡 [Push] Failed to register: \(error.localizedDescription)") #endif } func application( _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void ) { // Ensure completionHandler fires exactly once, even if sync hangs var hasCompleted = false let complete: (UIBackgroundFetchResult) -> Void = { result in guard !hasCompleted else { return } hasCompleted = true completionHandler(result) } // Timeout: iOS kills background fetches after ~30s, so fire at 25s as safety net DispatchQueue.main.asyncAfter(deadline: .now() + 25) { complete(.failed) } guard let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKQueryNotification, let subscriptionID = notification.subscriptionID, CloudKitService.canonicalSubscriptionIDs.contains(subscriptionID), let recordType = CloudKitService.recordType(forSubscriptionID: subscriptionID) else { complete(.noData) return } Task { @MainActor in var changed = false if notification.queryNotificationReason == .recordDeleted, let recordID = notification.recordID { changed = await BackgroundSyncManager.shared.applyDeletionHint( recordType: recordType, recordName: recordID.recordName ) } let updated = await BackgroundSyncManager.shared.triggerSyncFromPushNotification(subscriptionID: subscriptionID) complete((changed || updated) ? .newData : .noData) } } }