|
1 | 1 | // Copyright 2023–2025 Skip |
2 | 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception |
| 3 | +#if !SKIP_BRIDGE |
| 4 | +#if canImport(UIKit) // UNUserNotificationCenter does not exist on macOS |
| 5 | +import Foundation |
| 6 | +#if !SKIP |
| 7 | +import UIKit |
| 8 | +import OSLog |
| 9 | +#else |
| 10 | +import kotlinx.coroutines.CoroutineScope |
| 11 | +import kotlinx.coroutines.Dispatchers |
| 12 | +import kotlinx.coroutines.launch |
| 13 | +import kotlinx.coroutines.tasks.await |
3 | 14 |
|
4 | | -public class SkipNotifyModule { |
5 | | - |
| 15 | +import com.google.firebase.messaging.FirebaseMessagingService |
| 16 | +import com.google.firebase.messaging.FirebaseMessaging |
| 17 | +import com.google.firebase.messaging.RemoteMessage |
| 18 | +#endif |
| 19 | + |
| 20 | +private let logger: Logger = Logger(subsystem: "skip.notify", category: "SkipNotify") // adb logcat '*:S' 'skip.notify.SkipNotify:V' |
| 21 | + |
| 22 | +public class SkipNotify { |
| 23 | + public static let shared = SkipNotify() |
| 24 | + |
| 25 | + private init() { |
| 26 | + } |
| 27 | + |
| 28 | + public func fetchNotificationToken() async throws -> String { |
| 29 | + #if SKIP |
| 30 | + FirebaseMessaging.getInstance().token.await() |
| 31 | + #else |
| 32 | + UNUserNotificationCenter.current().delegate = notificationCenterDelegate |
| 33 | + |
| 34 | + // these notifications are added to the default UIApplicationDelegate |
| 35 | + // created by skip init/skip create in Main.swift; |
| 36 | + // other project structures will need to manually add them as so: |
| 37 | + /** ``` |
| 38 | + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { |
| 39 | + NotificationCenter.default.post(name: NSNotification.Name("didRegisterForRemoteNotificationsWithDeviceToken"), object: application, userInfo: ["deviceToken": deviceToken]) |
| 40 | + } |
| 41 | + |
| 42 | + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) { |
| 43 | + NotificationCenter.default.post(name: NSNotification.Name("didFailToRegisterForRemoteNotificationsWithError"), object: application, userInfo: ["error": error]) |
| 44 | + } |
| 45 | + ``` */ |
| 46 | + let didRegisterNotification = NSNotification.Name("didRegisterForRemoteNotificationsWithDeviceToken") |
| 47 | + let didFailToRegisterNotification = NSNotification.Name("didFailToRegisterForRemoteNotificationsWithError") |
| 48 | + |
| 49 | + var observers: [Any] = [] |
| 50 | + func clearObservers() { |
| 51 | + observers.forEach({ NotificationCenter.default.removeObserver($0) }) |
| 52 | + } |
| 53 | + return try await withCheckedThrowingContinuation { continuation in |
| 54 | + observers += [NotificationCenter.default.addObserver(forName: didRegisterNotification, object: nil, queue: .main) { note in |
| 55 | + logger.log("recevied \(didRegisterNotification.rawValue) with userInfo: \(note.userInfo ?? [:])") |
| 56 | + guard let deviceToken = note.userInfo?["deviceToken"] as? Data else { |
| 57 | + return |
| 58 | + } |
| 59 | + // hex-encoded form of the deviceToken data |
| 60 | + let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() |
| 61 | + clearObservers() |
| 62 | + continuation.resume(returning: tokenString) |
| 63 | + }] |
| 64 | + observers += [NotificationCenter.default.addObserver(forName: didFailToRegisterNotification, object: nil, queue: .main) { note in |
| 65 | + logger.log("recevied \(didFailToRegisterNotification.rawValue) with userInfo: \(note.userInfo ?? [:])") |
| 66 | + guard let error = note.userInfo?["error"] as? Error else { |
| 67 | + return |
| 68 | + } |
| 69 | + clearObservers() |
| 70 | + continuation.resume(throwing: error) |
| 71 | + }] |
| 72 | + Task { @MainActor in |
| 73 | + logger.log("calling UIApplication.shared.registerForRemoteNotifications()") |
| 74 | + UIApplication.shared.registerForRemoteNotifications() |
| 75 | + } |
| 76 | + } |
| 77 | + #endif |
| 78 | + } |
| 79 | + |
| 80 | + #if !SKIP |
| 81 | + let notificationCenterDelegate = NotificationCenterDelegate() |
| 82 | + |
| 83 | + class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate { |
| 84 | + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { |
| 85 | + // Callback when app is in foreground and notification arrives |
| 86 | + // e.g., userNotificationCenter willPresent notification: <UNNotification: 0x1117e18f0; source: org.appfair.app.Showcase date: 2025-12-18 22:04:52 +0000, request: <UNNotificationRequest: 0x1117e1890; identifier: 8B514E79-74CD-4399-BBF5-C2F4F9663292, content: <UNNotificationContent: 0x111835400; title: <redacted>, subtitle: <redacted>, body: <redacted>, attributedBody: (null), summaryArgument: (null), summaryArgumentCount: 0, categoryIdentifier: , launchImageName: , threadIdentifier: , attachments: (), badge: (null), sound: (null), realert: 0, interruptionLevel: 1, relevanceScore: 0.00, filterCriteria: (null), screenCaptureProhibited: 0, speechLanguage: (null), trigger: <UNPushNotificationTrigger: 0x104ac3440; contentAvailable: NO, mutableContent: NO>>, intents: ( |
| 87 | + logger.log("userNotificationCenter willPresent notification: \(notification)") |
| 88 | + return UNNotificationPresentationOptions.banner |
| 89 | + } |
| 90 | + |
| 91 | + // Callback when app is in background and user taps notification to open the app |
| 92 | + // userNotificationCenter didReceive response: <UNNotificationResponse: 0x11186c510; actionIdentifier: com.apple.UNNotificationDefaultActionIdentifier, notification: <UNNotification: 0x11186f270; source: org.appfair.app.Showcase date: 2025-12-18 22:06:22 +0000, request: <UNNotificationRequest: 0x11186f360; identifier: 79B5A08E-84D4-4A03-A7D4-ABC60B8AAF77, content: <UNNotificationContent: 0x110dccc80; title: <redacted>, subtitle: <redacted>, body: <redacted>, attributedBody: (null), summaryArgument: (null), summaryArgumentCount: 0, categoryIdentifier: , launchImageName: , threadIdentifier: , attachments: (), badge: (null), sound: (null), realert: 0, interruptionLevel: 1, relevanceScore: 0.00, filterCriteria: (null), screenCaptureProhibited: 0, speechLanguage: (null), trigger: <UNPushNotificationTrigger: 0x111ab4550; contentAvailable: NO, mutableContent: NO>>, intents: ()>> |
| 93 | + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { |
| 94 | + logger.log("userNotificationCenter didReceive response: \(response)") |
| 95 | + } |
| 96 | + |
| 97 | + func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { |
| 98 | + logger.log("userNotificationCenter openSettingsFor notification: \(notification)") |
| 99 | + } |
| 100 | + |
| 101 | + } |
| 102 | + #endif |
6 | 103 | } |
| 104 | + |
| 105 | +#endif |
| 106 | +#endif |
0 commit comments