From 1c7a8a0b72b2f320240f4f03109f5b1776cce707 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Fri, 20 May 2022 09:53:10 -0400 Subject: [PATCH] More cleanup and comments --- .../xcdebugger/Breakpoints_v2.xcbkptlist | 4 +-- ntfy/App/AppDelegate.swift | 33 +++++++------------ ntfy/App/AppMain.swift | 3 ++ ntfy/Persistence/Notification.swift | 9 ++--- ntfy/Persistence/Store.swift | 17 ++++++---- ntfy/Persistence/Subscription.swift | 2 ++ ntfy/Persistence/SubscriptionManager.swift | 4 ++- ntfy/Utils/Helpers.swift | 7 ---- ntfy/Views/NotificationListView.swift | 2 +- ntfyNSE/NotificationService.swift | 14 ++++---- 10 files changed, 42 insertions(+), 53 deletions(-) diff --git a/ntfy.xcodeproj/xcuserdata/pheckel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/ntfy.xcodeproj/xcuserdata/pheckel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 0539ea4..3092e99 100644 --- a/ntfy.xcodeproj/xcuserdata/pheckel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/ntfy.xcodeproj/xcuserdata/pheckel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -14,8 +14,8 @@ filePath = "ntfyNSE/NotificationService.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "25" - endingLineNumber = "25" + startingLineNumber = "23" + endingLineNumber = "23" landmarkName = "didReceive(_:withContentHandler:)" landmarkType = "7"> diff --git a/ntfy/App/AppDelegate.swift b/ntfy/App/AppDelegate.swift index 799d541..9781391 100644 --- a/ntfy/App/AppDelegate.swift +++ b/ntfy/App/AppDelegate.swift @@ -10,7 +10,6 @@ import CoreData class AppDelegate: UIResponder, UIApplicationDelegate { let tag = "AppDelegate" - let store = Store.shared func application( _ application: UIApplication, @@ -18,8 +17,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ) -> Bool { Log.d(tag, "ApplicationDelegate didFinishLaunchingWithOptions.") - // FirebaseApp.configure() DOES NOT WORK - FirebaseConfiguration.shared.setLoggerLevel(.max) Messaging.messaging().delegate = self registerForPushNotifications() @@ -63,15 +60,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func registerForPushNotifications() { + Log.d(tag, "Registering for local push notifications") UNUserNotificationCenter.current() - .requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in - print("granted: \(granted)") - guard granted else { return } - self?.getNotificationSettings() + .requestAuthorization(options: [.alert, .sound, .badge]) { success, error in + guard success else { + Log.e(self.tag, "Failed to register for local push notifications", error) + return + } + Log.d(self.tag, "Successfully registered for local push notifications") + self.registerForRemoteNotifications() } } - func getNotificationSettings() { + func registerForRemoteNotifications() { UNUserNotificationCenter.current().getNotificationSettings { settings in print("Notification settings: \(settings)") guard settings.authorizationStatus == .authorized else { return } @@ -105,20 +106,10 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } extension AppDelegate: MessagingDelegate { - func messaging( - _ messaging: Messaging, - didReceiveRegistrationToken fcmToken: String? - ) { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { Log.d(tag, "Firebase token received: \(String(describing: fcmToken))") - // FIXME: Is this necessary? - - let dataDict: [String: String] = ["token": fcmToken ?? ""] - NotificationCenter.default.post( - name: UserNotifications.Notification.Name("FCMToken"), - object: nil, - userInfo: dataDict - ) + // We don't actually need the FCM token, since we're just using topics. + // We still print it so we can see if things were successful. } } - diff --git a/ntfy/App/AppMain.swift b/ntfy/App/AppMain.swift index 73e8584..1610154 100644 --- a/ntfy/App/AppMain.swift +++ b/ntfy/App/AppMain.swift @@ -9,7 +9,10 @@ struct AppMain: App { @StateObject private var store = Store.shared init() { + // We must configure Firebase here, and not in the AppDelegate. For some reason + // configuring it there did not work. FirebaseApp.configure() + FirebaseConfiguration.shared.setLoggerLevel(.max) } var body: some Scene { diff --git a/ntfy/Persistence/Notification.swift b/ntfy/Persistence/Notification.swift index 55cc9bc..761bbd1 100644 --- a/ntfy/Persistence/Notification.swift +++ b/ntfy/Persistence/Notification.swift @@ -1,12 +1,6 @@ -// -// Notification.swift -// ntfy -// -// Created by Philipp Heckel on 5/16/22. -// - import Foundation +/// Extensions to make the notification easier to display extension Notification { func shortDateTime() -> String { let date = Date(timeIntervalSince1970: TimeInterval(self.time)) @@ -31,6 +25,7 @@ extension Notification { } } +/// This is the "on the wire" message as it is received from the ntfy server struct Message: Decodable { var id: String var time: Int64 diff --git a/ntfy/Persistence/Store.swift b/ntfy/Persistence/Store.swift index af2f9b1..8c4c74b 100644 --- a/ntfy/Persistence/Store.swift +++ b/ntfy/Persistence/Store.swift @@ -32,10 +32,12 @@ class Store: ObservableObject { context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyStoreTrumpMergePolicyType) // https://stackoverflow.com/a/60362945/1440785 context.transactionAuthor = Bundle.main.bundlePath.hasSuffix(".appex") ? "ntfy.appex" : "ntfy" + // When a remote change comes in (= the app extension updated entities in Core Data), + // we force refresh the view with horrible means. Please help me make this better! NotificationCenter.default .publisher(for: .NSPersistentStoreRemoteChange) - .sink { - Log.d(Store.tag, "Remote change detected", $0) + .sink { value in + Log.d(Store.tag, "Remote change detected, refreshing view", value) // Hack: This is the only way I could make the UI update the subscription list. // I'm pretty sure I got the @FetchRequest wrong, but I don @@ -67,21 +69,22 @@ class Store: ObservableObject { return try? context.fetch(fetchRequest).first } - func deleteSubscription(subscription: Subscription) { + func delete(subscription: Subscription) { context.delete(subscription) try? context.save() } - func saveNotification(fromUserInfo userInfo: [AnyHashable: Any]) { + func save(notificationFromUserInfo userInfo: [AnyHashable: Any]) { guard let id = userInfo["id"] as? String, - let topic = userInfo["topic"] as? String, // FIXME: Notification should also contain baseUrl + let topic = userInfo["topic"] as? String, let time = userInfo["time"] as? String, let timeInt = Int64(time), let message = userInfo["message"] as? String else { Log.d(Store.tag, "Unknown or irrelevant message", userInfo) return } - guard let subscription = getSubscription(baseUrl: appBaseUrl, topic: topic) else { + let baseUrl = appBaseUrl // Firebase messages all come from the main ntfy server + guard let subscription = getSubscription(baseUrl: baseUrl, topic: topic) else { Log.d(Store.tag, "Subscription for topic \(topic) unknown") return } @@ -100,7 +103,7 @@ class Store: ObservableObject { } } - func saveNotification(fromMessage message: Message, subscription: Subscription) { + func save(notificationFromMessage message: Message, withSubscription subscription: Subscription) { do { let notification = Notification(context: context) notification.id = message.id diff --git a/ntfy/Persistence/Subscription.swift b/ntfy/Persistence/Subscription.swift index 336990c..076033e 100644 --- a/ntfy/Persistence/Subscription.swift +++ b/ntfy/Persistence/Subscription.swift @@ -1,5 +1,7 @@ import Foundation +// FIXME: Store last notification ID in Subscription + extension Subscription { func urlString() -> String { return topicUrl(baseUrl: baseUrl!, topic: topic!) diff --git a/ntfy/Persistence/SubscriptionManager.swift b/ntfy/Persistence/SubscriptionManager.swift index b302bc1..4ff7233 100644 --- a/ntfy/Persistence/SubscriptionManager.swift +++ b/ntfy/Persistence/SubscriptionManager.swift @@ -1,6 +1,8 @@ import Foundation import FirebaseMessaging +/// Manager to combine persisting a subscription to the data store and subscribing to Firebase. +/// This is to centralize the logic in one place. struct SubscriptionManager { private let tag = "Store" var store: Store @@ -17,7 +19,7 @@ struct SubscriptionManager { if let topic = subscription.topic { Messaging.messaging().unsubscribe(fromTopic: topic) } - store.deleteSubscription(subscription: subscription) + store.delete(subscription: subscription) } } } diff --git a/ntfy/Utils/Helpers.swift b/ntfy/Utils/Helpers.swift index 3da2f31..3da5414 100644 --- a/ntfy/Utils/Helpers.swift +++ b/ntfy/Utils/Helpers.swift @@ -1,10 +1,3 @@ -// -// Helpers.swift -// ntfy -// -// Created by Philipp Heckel on 5/16/22. -// - import Foundation let appBaseUrl = "http://192.168.1.4" // FIXME diff --git a/ntfy/Views/NotificationListView.swift b/ntfy/Views/NotificationListView.swift index dd0db01..9b51159 100644 --- a/ntfy/Views/NotificationListView.swift +++ b/ntfy/Views/NotificationListView.swift @@ -170,7 +170,7 @@ struct NotificationListView: View { if !messages.isEmpty { DispatchQueue.main.async { for message in messages { - store.saveNotification(fromMessage: message, subscription: subscription) + store.save(notificationFromMessage: message, withSubscription: subscription) } } } diff --git a/ntfyNSE/NotificationService.swift b/ntfyNSE/NotificationService.swift index 0159a17..65756cf 100644 --- a/ntfyNSE/NotificationService.swift +++ b/ntfyNSE/NotificationService.swift @@ -9,19 +9,17 @@ class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? - override func didReceive( - _ request: UNNotificationRequest, - withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void - ) { + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler - bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + Log.d(tag, "Notification received (in service)") // Logs from extensions are not printed in Xcode! if let bestAttemptContent = bestAttemptContent { // bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" let userInfo = bestAttemptContent.userInfo - Store.shared.saveNotification(fromUserInfo: userInfo) + Store.shared.save(notificationFromUserInfo: userInfo) contentHandler(bestAttemptContent) } @@ -29,7 +27,9 @@ class NotificationService: UNNotificationServiceExtension { override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. - // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + // Use this as an opportunity to deliver your "best attempt" at modified content, + // otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) }