diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt index 785e9b2b..c5e2810e 100644 --- a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt +++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Device.kt @@ -7,17 +7,16 @@ import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.os.BatteryManager -import android.os.Build import io.ionic.android_js_engine.capacitor_api.DeviceAPI import org.json.JSONObject -class Device (context: android.content.Context) : DeviceAPI { - private val context: android.content.Context +class Device (context: Context) : DeviceAPI { + private val context: Context private val connectivityManager: ConnectivityManager init { this.context = context - this.connectivityManager = this.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager; + this.connectivityManager = this.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } override fun getBatteryStatus(): String { @@ -30,25 +29,20 @@ class Device (context: android.content.Context) : DeviceAPI { override fun getNetworkStatus(): String { var status = NetworkStatus() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val activeNetwork: Network? = this.connectivityManager.activeNetwork - val capabilities: NetworkCapabilities? = - this.connectivityManager.getNetworkCapabilities(this.connectivityManager.activeNetwork) - if (activeNetwork != null && capabilities != null) { - status.connected = - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - status.connectionType = NetworkStatus.ConnectionType.WIFI - } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - status.connectionType = NetworkStatus.ConnectionType.CELLULAR - } else { - status.connectionType = NetworkStatus.ConnectionType.UNKNOWN - } + val activeNetwork: Network? = this.connectivityManager.activeNetwork + val capabilities: NetworkCapabilities? = + this.connectivityManager.getNetworkCapabilities(this.connectivityManager.activeNetwork) + if (activeNetwork != null && capabilities != null) { + status.connected = + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + status.connectionType = NetworkStatus.ConnectionType.WIFI + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + status.connectionType = NetworkStatus.ConnectionType.CELLULAR + } else { + status.connectionType = NetworkStatus.ConnectionType.UNKNOWN } - } else { - status = getAndParseNetworkInfo() } val networkInfo = JSONObject() @@ -79,20 +73,4 @@ class Device (context: android.content.Context) : DeviceAPI { } return false } - - @Suppress("DEPRECATION") - private fun getAndParseNetworkInfo(): NetworkStatus { - val networkStatus = NetworkStatus() - val networkInfo = connectivityManager.activeNetworkInfo - if (networkInfo != null) { - networkStatus.connected = networkInfo.isConnected - val typeName = networkInfo.typeName - if (typeName == "WIFI") { - networkStatus.connectionType = NetworkStatus.ConnectionType.WIFI - } else if (typeName == "MOBILE") { - networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR - } - } - return networkStatus - } } \ No newline at end of file diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt index a10fb408..0a892407 100644 --- a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt +++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt @@ -125,42 +125,38 @@ class Notifications(context: Context) : NotificationsAPI { } private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val name: CharSequence = "Default" - val description = "Default" - val importance = NotificationManager.IMPORTANCE_DEFAULT - val channel = NotificationChannel(defaultNotificationChannelID, name, importance) - channel.description = description - val audioAttributes = AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ALARM) - .build() - val soundUri = this.getDefaultSoundUrl(context) - if (soundUri != null) { - channel.setSound(soundUri, audioAttributes) - } - - val notificationManager = context.getSystemService( - NotificationManager::class.java - ) - notificationManager.createNotificationChannel(channel) + val name: CharSequence = "Default" + val description = "Default" + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = NotificationChannel(defaultNotificationChannelID, name, importance) + channel.description = description + val audioAttributes = AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ALARM) + .build() + val soundUri = this.getDefaultSoundUrl(context) + if (soundUri != null) { + channel.setSound(soundUri, audioAttributes) } + + val notificationManager = context.getSystemService( + NotificationManager::class.java + ) + notificationManager.createNotificationChannel(channel) } private fun createBadgeNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val name: CharSequence = "Badge" - val description = "Badge" - val importance = NotificationManager.IMPORTANCE_LOW - val channel = NotificationChannel(defaultBadgeNotificationChannelID, name, importance) - channel.description = description - channel.setShowBadge(true) - - val notificationManager = context.getSystemService( - NotificationManager::class.java - ) - notificationManager.createNotificationChannel(channel) - } + val name: CharSequence = "Badge" + val description = "Badge" + val importance = NotificationManager.IMPORTANCE_LOW + val channel = NotificationChannel(defaultBadgeNotificationChannelID, name, importance) + channel.description = description + channel.setShowBadge(true) + + val notificationManager = context.getSystemService( + NotificationManager::class.java + ) + notificationManager.createNotificationChannel(channel) } private fun getDefaultSoundUrl(context: Context): Uri? { diff --git a/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar b/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar index 2ff2bfdf..26a90dc8 100644 Binary files a/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar and b/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar differ diff --git a/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorAPI/Notifications.swift b/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorAPI/Notifications.swift index f611994a..70f0ded9 100644 --- a/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorAPI/Notifications.swift +++ b/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorAPI/Notifications.swift @@ -189,11 +189,7 @@ class CapacitorNotifications: NSObject, CapacitorNotificationsExports { let badgeOptions = SetBadgeOption(from: jsonDict) DispatchQueue.main.async { - if #available(iOS 16.0, *) { - UNUserNotificationCenter.current().setBadgeCount(badgeOptions.count) - } else { - UIApplication.shared.applicationIconBadgeNumber = badgeOptions.count - } + UNUserNotificationCenter.current().setBadgeCount(badgeOptions.count) } } catch { JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current()) @@ -207,11 +203,7 @@ class CapacitorNotifications: NSObject, CapacitorNotificationsExports { } DispatchQueue.main.async { - if #available(iOS 16.0, *) { - UNUserNotificationCenter.current().setBadgeCount(0) - } else { - UIApplication.shared.applicationIconBadgeNumber = 0 - } + UNUserNotificationCenter.current().setBadgeCount(0) } } catch { JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current()) diff --git a/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift b/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift index 2648a45c..461b5ab8 100644 --- a/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift +++ b/packages/capacitor-plugin/ios/Sources/CapacitorBackgroundRunner/CapacitorBackgroundRunner.swift @@ -5,304 +5,302 @@ import WatchConnectivity @objc(BackgroundRunnerPlugin) public class BackgroundRunnerPlugin: CAPPlugin, CAPBridgedPlugin { - public let identifier = "CapacitorBackgroundRunnerPlugin" - public let jsName = "CapacitorBackgroundRunner" - public let pluginMethods: [CAPPluginMethod] = [ - CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "dispatchEvent", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "registerBackgroundTask", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "removeNotificationListeners", returnType: CAPPluginReturnPromise), - ] - private let impl = BackgroundRunner() - private weak var originalDelegate: UNUserNotificationCenterDelegate? - - override public func load() { - NotificationCenter.default.addObserver( - self, - selector: #selector(didEnterBackground), - name: UIApplication.didEnterBackgroundNotification, - object: nil - ) - - // Register as notification delegate - registerNotificationCategories() - - initWatchConnectivity() - } - - @objc override public func checkPermissions(_ call: CAPPluginCall) { - // check geolocation permissions - let geolocationState = CapacitorGeolocation.checkPermission() - - // check notification permissions - let notificationState = CapacitorNotifications.checkPermission() - - call.resolve([ - "geolocation": geolocationState, - "notifications": notificationState, - ]) - } - - @objc override public func requestPermissions(_ call: CAPPluginCall) { - Task { - let permissions = call.getArray("apis") as? [String] ?? [] - - do { - if permissions.contains("notifications") { - try CapacitorNotifications.requestPermission() - } + public let identifier = "CapacitorBackgroundRunnerPlugin" + public let jsName = "CapacitorBackgroundRunner" + public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "dispatchEvent", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "registerBackgroundTask", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeNotificationListeners", returnType: CAPPluginReturnPromise) + ] + private let impl = BackgroundRunner() + private weak var originalDelegate: UNUserNotificationCenterDelegate? - if permissions.contains("geolocation") { - let geolocation = CapacitorGeolocation() - print("geolocation requested...") - try await geolocation.requestPermission() - print("geolocation requested...done") - } + override public func load() { + NotificationCenter.default.addObserver( + self, + selector: #selector(didEnterBackground), + name: UIApplication.didEnterBackgroundNotification, + object: nil + ) + + // Register as notification delegate + registerNotificationCategories() - self.checkPermissions(call) - } catch { - call.reject("\(error)") - } + initWatchConnectivity() } - } - @objc func dispatchEvent(_ call: CAPPluginCall) { - do { - guard let runnerEvent = call.getString("event"), !runnerEvent.isEmpty else { - throw BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid") - } + @objc override public func checkPermissions(_ call: CAPPluginCall) { + // check geolocation permissions + let geolocationState = CapacitorGeolocation.checkPermission() - let details = call.getObject("details", JSObject()) + // check notification permissions + let notificationState = CapacitorNotifications.checkPermission() - guard var config = impl.config else { - throw BackgroundRunnerPluginError.noRunnerConfig - } + call.resolve([ + "geolocation": geolocationState, + "notifications": notificationState + ]) + } - config.event = runnerEvent + @objc override public func requestPermissions(_ call: CAPPluginCall) { + Task { + let permissions = call.getArray("apis") as? [String] ?? [] + + do { + if permissions.contains("notifications") { + try CapacitorNotifications.requestPermission() + } + + if permissions.contains("geolocation") { + let geolocation = CapacitorGeolocation() + print("geolocation requested...") + try await geolocation.requestPermission() + print("geolocation requested...done") + } + + self.checkPermissions(call) + } catch { + call.reject("\(error)") + } + } + } - // swiftlint:disable:next unowned_variable_capture - DispatchQueue.global(qos: .userInitiated).async { [unowned self] in + @objc func dispatchEvent(_ call: CAPPluginCall) { do { - let result = try self.impl.execute( - config: config, - inputArgs: details as [String: Any], - callbackId: call.callbackId - ) - - if let result = result { - call.resolve(result) - return - } - - call.resolve() + guard let runnerEvent = call.getString("event"), !runnerEvent.isEmpty else { + throw BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid") + } + + let details = call.getObject("details", JSObject()) + + guard var config = impl.config else { + throw BackgroundRunnerPluginError.noRunnerConfig + } + + config.event = runnerEvent + + // swiftlint:disable:next unowned_variable_capture + DispatchQueue.global(qos: .userInitiated).async { [unowned self] in + do { + let result = try self.impl.execute( + config: config, + inputArgs: details as [String: Any], + callbackId: call.callbackId + ) + + if let result = result { + call.resolve(result) + return + } + + call.resolve() + } catch { + call.reject("\(error)") + } + } } catch { - call.reject("\(error)") + call.reject("\(error)") } - } - } catch { - call.reject("\(error)") } - } - - @objc func registerBackgroundTask(_ call: CAPPluginCall) { - call.resolve() - } - - @objc func removeNotificationListeners(_ call: CAPPluginCall) { - notifyListeners("backgroundRunnerNotificationReceived", data: nil) - call.resolve() - } - - @objc private func didEnterBackground() { - do { - try impl.scheduleBackgroundTasks() - } catch { - print("could not schedule background task: \(error)") - } - } - public static func registerBackgroundTask() { - do { - try BackgroundRunner.shared.registerBackgroundTask() - } catch { - print("could not register background task: \(error)") - } - } - - public static func dispatchEvent( - event: String, eventArgs: [AnyHashable: Any], completionHandler: ((Result) -> Void) - ) { - if event.isEmpty { - completionHandler( - .failure(BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid")) - ) - return + @objc func registerBackgroundTask(_ call: CAPPluginCall) { + call.resolve() } - var args: [String: Any] = [:] - - eventArgs.forEach { (key: AnyHashable, value: Any) in - if let strKey = key as? String { - args[strKey] = value - } + @objc func removeNotificationListeners(_ call: CAPPluginCall) { + notifyListeners("backgroundRunnerNotificationReceived", data: nil) + call.resolve() } - do { - try BackgroundRunner.shared.dispatchEvent(event: event, inputArgs: args) - completionHandler(.success(true)) - } catch { - completionHandler(.failure(error)) + @objc private func didEnterBackground() { + do { + try impl.scheduleBackgroundTasks() + } catch { + print("could not schedule background task: \(error)") + } } - } - public static func handleApplicationDidFinishLaunching( - launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) { - guard let launchOptions = launchOptions else { - return + public static func registerBackgroundTask() { + do { + try BackgroundRunner.shared.registerBackgroundTask() + } catch { + print("could not register background task: \(error)") + } } - var events: [String: [String: Any]?] = [:] + public static func dispatchEvent( + event: String, eventArgs: [AnyHashable: Any], completionHandler: ((Result) -> Void) + ) { + if event.isEmpty { + completionHandler( + .failure(BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid")) + ) + return + } - if launchOptions[.location] != nil { - // application was launched for a location event - var details: [String: Any] = [:] - details["locations"] = nil + var args: [String: Any] = [:] - if let location = CapacitorGeolocation.getCurrentKnownLocation() { - var details: [String: Any] = [:] - details["locations"] = [location] - } + eventArgs.forEach { (key: AnyHashable, value: Any) in + if let strKey = key as? String { + args[strKey] = value + } + } - events["currentLocation"] = details + do { + try BackgroundRunner.shared.dispatchEvent(event: event, inputArgs: args) + completionHandler(.success(true)) + } catch { + completionHandler(.failure(error)) + } } - print("emit events: \(events)") + public static func handleApplicationDidFinishLaunching( + launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) { + guard let launchOptions = launchOptions else { + return + } - } + var events: [String: [String: Any]?] = [:] - private func initWatchConnectivity() { - if !WCSession.isSupported() { - return - } + if launchOptions[.location] != nil { + // application was launched for a location event + var details: [String: Any] = [:] + details["locations"] = nil - guard let config = impl.config else { - return - } + if let location = CapacitorGeolocation.getCurrentKnownLocation() { + var details: [String: Any] = [:] + details["locations"] = [location] + } + + events["currentLocation"] = details + } + + print("emit events: \(events)") - if !config.enableWatchConnectivity { - return } - WCSession.default.delegate = self - WCSession.default.activate() + private func initWatchConnectivity() { + if !WCSession.isSupported() { + return + } + + guard let config = impl.config else { + return + } - print("Watch Connectivity Enabled") - } + if !config.enableWatchConnectivity { + return + } - @objc func registerNotificationCategories() { - let notificationCenter = UNUserNotificationCenter.current() + WCSession.default.delegate = self + WCSession.default.activate() - // Capture the original delegate if it's not self - if notificationCenter.delegate !== self { - originalDelegate = notificationCenter.delegate + print("Watch Connectivity Enabled") } - // Set the plugin as the notification delegate - notificationCenter.delegate = self - } + @objc func registerNotificationCategories() { + let notificationCenter = UNUserNotificationCenter.current() + + // Capture the original delegate if it's not self + if notificationCenter.delegate !== self { + originalDelegate = notificationCenter.delegate + } + + // Set the plugin as the notification delegate + notificationCenter.delegate = self + } } // MARK: - UNUserNotificationCenterDelegate extension BackgroundRunnerPlugin: UNUserNotificationCenterDelegate { - // This method will be called when a notification is tapped - public func userNotificationCenter( - _ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse, - withCompletionHandler completionHandler: @escaping () -> Void - ) { - let userInfo = response.notification.request.content.userInfo - - if isBackgroundRunnerNotification(userInfo) { - handleBackgroundRunnerNotification(response, completionHandler: completionHandler) - } else { - forwardToOriginalDelegate( - center, didReceive: response, withCompletionHandler: completionHandler) + // This method will be called when a notification is tapped + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + let userInfo = response.notification.request.content.userInfo + + if isBackgroundRunnerNotification(userInfo) { + handleBackgroundRunnerNotification(response, completionHandler: completionHandler) + } else { + forwardToOriginalDelegate( + center, didReceive: response, withCompletionHandler: completionHandler) + } } - } - - // This is needed to present notifications when the app is in the foreground - public func userNotificationCenter( - _ center: UNUserNotificationCenter, - willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void - ) { - let userInfo = notification.request.content.userInfo - - if isBackgroundRunnerNotification(userInfo) { - handleBackgroundRunnerPresentation(completionHandler: completionHandler) - } else { - forwardToOriginalDelegate( - center, willPresent: notification, withCompletionHandler: completionHandler) + + // This is needed to present notifications when the app is in the foreground + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + let userInfo = notification.request.content.userInfo + + if isBackgroundRunnerNotification(userInfo) { + handleBackgroundRunnerPresentation(completionHandler: completionHandler) + } else { + forwardToOriginalDelegate( + center, willPresent: notification, withCompletionHandler: completionHandler) + } } - } - // MARK: - Helper Methods + // MARK: - Helper Methods - private func isBackgroundRunnerNotification(_ userInfo: [AnyHashable: Any]) -> Bool { - return (userInfo["source"] as? String) == "capacitor-background-runner" - } + private func isBackgroundRunnerNotification(_ userInfo: [AnyHashable: Any]) -> Bool { + return (userInfo["source"] as? String) == "capacitor-background-runner" + } - private func handleBackgroundRunnerNotification( - _ response: UNNotificationResponse, completionHandler: @escaping () -> Void - ) { - let actionTypeId = response.notification.request.content.categoryIdentifier - let notificationId = Int(response.notification.request.identifier) ?? -1 + private func handleBackgroundRunnerNotification( + _ response: UNNotificationResponse, completionHandler: @escaping () -> Void + ) { + let actionTypeId = response.notification.request.content.categoryIdentifier + let notificationId = Int(response.notification.request.identifier) ?? -1 - let eventData: [String: Any] = [ - "actionTypeId": actionTypeId, - "notificationId": notificationId, - ] + let eventData: [String: Any] = [ + "actionTypeId": actionTypeId, + "notificationId": notificationId + ] - notifyListeners( - "backgroundRunnerNotificationReceived", data: eventData, retainUntilConsumed: true) - completionHandler() - } - - private func forwardToOriginalDelegate( - _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, - withCompletionHandler completionHandler: @escaping () -> Void - ) { - if let originalDelegate = originalDelegate, - originalDelegate.responds( - to: #selector(userNotificationCenter(_:didReceive:withCompletionHandler:))) - { - originalDelegate.userNotificationCenter?( - center, didReceive: response, withCompletionHandler: completionHandler) - } else { - completionHandler() + notifyListeners( + "backgroundRunnerNotificationReceived", data: eventData, retainUntilConsumed: true) + completionHandler() } - } - - private func handleBackgroundRunnerPresentation( - completionHandler: @escaping (UNNotificationPresentationOptions) -> Void - ) { - completionHandler([.banner, .sound, .badge]) - } - - private func forwardToOriginalDelegate( - _ center: UNUserNotificationCenter, willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void - ) { - if let originalDelegate = originalDelegate, - originalDelegate.responds( - to: #selector(userNotificationCenter(_:willPresent:withCompletionHandler:))) - { - originalDelegate.userNotificationCenter?( - center, willPresent: notification, withCompletionHandler: completionHandler) - } else { - handleBackgroundRunnerPresentation(completionHandler: completionHandler) + + private func forwardToOriginalDelegate( + _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + if let originalDelegate = originalDelegate, + originalDelegate.responds( + to: #selector(userNotificationCenter(_:didReceive:withCompletionHandler:))) { + originalDelegate.userNotificationCenter?( + center, didReceive: response, withCompletionHandler: completionHandler) + } else { + completionHandler() + } + } + + private func handleBackgroundRunnerPresentation( + completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + completionHandler([.banner, .sound, .badge]) + } + + private func forwardToOriginalDelegate( + _ center: UNUserNotificationCenter, willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + if let originalDelegate = originalDelegate, + originalDelegate.responds( + to: #selector(userNotificationCenter(_:willPresent:withCompletionHandler:))) { + originalDelegate.userNotificationCenter?( + center, willPresent: notification, withCompletionHandler: completionHandler) + } else { + handleBackgroundRunnerPresentation(completionHandler: completionHandler) + } } - } }