More cleanup and comments

This commit is contained in:
Philipp Heckel 2022-05-20 09:53:10 -04:00
parent 2ff702057c
commit 1c7a8a0b72
10 changed files with 42 additions and 53 deletions

View file

@ -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">
</BreakpointContent>

View file

@ -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.
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,7 @@
import Foundation
// FIXME: Store last notification ID in Subscription
extension Subscription {
func urlString() -> String {
return topicUrl(baseUrl: baseUrl!, topic: topic!)

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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)
}