From e46d9831112571a1ff4802e87701fe22d6082e31 Mon Sep 17 00:00:00 2001 From: Alek Michelson Date: Thu, 9 Apr 2026 23:48:10 -0400 Subject: [PATCH] more error handling --- ntfy/App/AppDelegate.swift | 18 +++++++++-- ntfy/Persistence/SubscriptionManager.swift | 18 +++++++++-- ntfy/Utils/ApiService.swift | 37 ++++++++++++++++++---- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/ntfy/App/AppDelegate.swift b/ntfy/App/AppDelegate.swift index 9b8f270..7d23681 100644 --- a/ntfy/App/AppDelegate.swift +++ b/ntfy/App/AppDelegate.swift @@ -169,15 +169,27 @@ extension AppDelegate: MessagingDelegate { Log.d(tag, "Firebase token received: \(String(describing: fcmToken))") // Subscribe to ~poll topic - Messaging.messaging().subscribe(toTopic: pollTopic) + Messaging.messaging().subscribe(toTopic: pollTopic) { error in + if let error { + Log.e(self.tag, "Firebase subscribe failed for \(self.pollTopic)", error) + } else { + Log.d(self.tag, "Firebase subscribe succeeded for \(self.pollTopic)") + } + } // Re-subscribe to Firebase for all topics let store = Store.shared - let subscriptionManager = SubscriptionManager(store: store) store.getSubscriptions()?.forEach{ subscription in if let baseUrl = subscription.baseUrl, let topic = subscription.topic { + let firebaseTopicName = firebaseTopic(baseUrl: baseUrl, topic: topic) Log.d(tag, "Re-subscribing to topic \(baseUrl)/\(topic)") - Messaging.messaging().subscribe(toTopic: firebaseTopic(baseUrl: baseUrl, topic: topic)) + Messaging.messaging().subscribe(toTopic: firebaseTopicName) { error in + if let error { + Log.e(self.tag, "Firebase subscribe failed for \(firebaseTopicName)", error) + } else { + Log.d(self.tag, "Firebase subscribe succeeded for \(firebaseTopicName)") + } + } } } } diff --git a/ntfy/Persistence/SubscriptionManager.swift b/ntfy/Persistence/SubscriptionManager.swift index bf5e71d..8a1ab76 100644 --- a/ntfy/Persistence/SubscriptionManager.swift +++ b/ntfy/Persistence/SubscriptionManager.swift @@ -9,8 +9,15 @@ struct SubscriptionManager { func subscribe(baseUrl: String, topic: String) { let normalizedBaseUrl = normalizeBaseUrl(baseUrl) + let firebaseTopicName = firebaseTopic(baseUrl: normalizedBaseUrl, topic: topic) Log.d(tag, "Subscribing to \(topicUrl(baseUrl: normalizedBaseUrl, topic: topic))") - Messaging.messaging().subscribe(toTopic: firebaseTopic(baseUrl: normalizedBaseUrl, topic: topic)) + Messaging.messaging().subscribe(toTopic: firebaseTopicName) { error in + if let error { + Log.e(tag, "Firebase subscribe failed for \(firebaseTopicName)", error) + } else { + Log.d(tag, "Firebase subscribe succeeded for \(firebaseTopicName)") + } + } let subscription = store.saveSubscription(baseUrl: normalizedBaseUrl, topic: topic) poll(subscription) } @@ -19,7 +26,14 @@ struct SubscriptionManager { Log.d(tag, "Unsubscribing from \(subscription.urlString())") DispatchQueue.main.async { if let baseUrl = subscription.baseUrl, let topic = subscription.topic { - Messaging.messaging().unsubscribe(fromTopic: firebaseTopic(baseUrl: baseUrl, topic: topic)) + let firebaseTopicName = firebaseTopic(baseUrl: baseUrl, topic: topic) + Messaging.messaging().unsubscribe(fromTopic: firebaseTopicName) { error in + if let error { + Log.e(tag, "Firebase unsubscribe failed for \(firebaseTopicName)", error) + } else { + Log.d(tag, "Firebase unsubscribe succeeded for \(firebaseTopicName)") + } + } } store.delete(subscription: subscription) } diff --git a/ntfy/Utils/ApiService.swift b/ntfy/Utils/ApiService.swift index 2cb495d..dade2bb 100644 --- a/ntfy/Utils/ApiService.swift +++ b/ntfy/Utils/ApiService.swift @@ -19,7 +19,10 @@ class ApiService { } func poll(subscription: Subscription, messageId: String, user: BasicUser?, completionHandler: @escaping (Message?, Error?) -> Void) { - let url = URL(string: "\(subscription.urlString())/json?poll=1&id=\(messageId)")! + guard let url = URL(string: "\(subscription.urlString())/json?poll=1&id=\(messageId)") else { + completionHandler(nil, URLError(.badURL)) + return + } Log.d(tag, "Polling single message from \(url) with user \(user?.username ?? "anonymous")") let request = newRequest(url: url, user: user) @@ -28,8 +31,16 @@ class ApiService { completionHandler(nil, error) return } + guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else { + completionHandler(nil, URLError(.badServerResponse)) + return + } + guard let data = data else { + completionHandler(nil, URLError(.badServerResponse)) + return + } do { - let message = try JSONDecoder().decode(Message.self, from: data!) + let message = try JSONDecoder().decode(Message.self, from: data) completionHandler(message, nil) } catch { completionHandler(nil, error) @@ -98,19 +109,33 @@ class ApiService { } private func fetchJsonData(urlString: String, user: BasicUser?, completionHandler: @escaping ([T]?, Error?) -> ()) { - guard let url = URL(string: urlString) else { return } + guard let url = URL(string: urlString) else { + completionHandler(nil, URLError(.badURL)) + return + } let request = newRequest(url: url, user: user) newSession(timeout: 30).dataTask(with: request) { (data, response, error) in - if let error = error { + if let error { Log.e(self.tag, "Error fetching data", error) completionHandler(nil, error) return } + guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else { + completionHandler(nil, URLError(.badServerResponse)) + return + } + guard let data = data else { + completionHandler(nil, URLError(.badServerResponse)) + return + } do { - let lines = String(decoding: data!, as: UTF8.self).split(whereSeparator: \.isNewline) + let lines = String(decoding: data, as: UTF8.self).split(whereSeparator: \.isNewline) var notifications: [T] = [] for jsonLine in lines { - notifications.append(try JSONDecoder().decode(T.self, from: jsonLine.data(using: .utf8)!)) + guard let jsonData = jsonLine.data(using: .utf8) else { + throw URLError(.cannotDecodeContentData) + } + notifications.append(try JSONDecoder().decode(T.self, from: jsonData)) } completionHandler(notifications, nil) } catch {