Merge pull request #32 from am7590/stability-fixes

fix iOS topic normalization and refresh after test push
This commit is contained in:
Philipp C. Heckel 2026-04-09 18:46:17 -04:00 committed by GitHub
commit 06e90baf09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 48 additions and 34 deletions

View file

@ -142,14 +142,11 @@ extension AppDelegate: MessagingDelegate {
// 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 {
Log.d(tag, "Re-subscribing to topic \(baseUrl)/\(topic)")
if baseUrl == Config.appBaseUrl {
Messaging.messaging().subscribe(toTopic: topic)
} else {
Messaging.messaging().subscribe(toTopic: topicHash(baseUrl: baseUrl, topic: topic))
}
Messaging.messaging().subscribe(toTopic: firebaseTopic(baseUrl: baseUrl, topic: topic))
}
}
}

View file

@ -77,10 +77,10 @@ class Store: ObservableObject {
func saveSubscription(baseUrl: String, topic: String) -> Subscription {
let subscription = Subscription(context: context)
subscription.baseUrl = baseUrl
subscription.baseUrl = normalizeBaseUrl(baseUrl)
subscription.topic = topic
DispatchQueue.main.sync {
Log.d(Store.tag, "Storing subscription baseUrl=\(baseUrl), topic=\(topic)")
Log.d(Store.tag, "Storing subscription baseUrl=\(subscription.baseUrl ?? "?"), topic=\(topic)")
try? context.save()
}
return subscription
@ -88,7 +88,7 @@ class Store: ObservableObject {
func getSubscription(baseUrl: String, topic: String) -> Subscription? {
let fetchRequest = Subscription.fetchRequest()
let baseUrlPredicate = NSPredicate(format: "baseUrl = %@", baseUrl)
let baseUrlPredicate = NSPredicate(format: "baseUrl = %@", normalizeBaseUrl(baseUrl))
let topicPredicate = NSPredicate(format: "topic = %@", topic)
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [baseUrlPredicate, topicPredicate])
@ -99,6 +99,11 @@ class Store: ObservableObject {
func getSubscriptions() -> [Subscription]? {
return try? context.fetch(Subscription.fetchRequest())
}
func updateSubscriptionBaseUrl(_ subscription: Subscription, baseUrl: String) {
subscription.baseUrl = normalizeBaseUrl(baseUrl)
try? context.save()
}
func delete(subscription: Subscription) {
context.performAndWait {
@ -175,7 +180,7 @@ class Store: ObservableObject {
func saveUser(baseUrl: String, username: String, password: String) {
do {
let user = getUser(baseUrl: baseUrl) ?? User(context: context)
user.baseUrl = baseUrl
user.baseUrl = normalizeBaseUrl(baseUrl)
user.username = username
user.password = password
try context.save()
@ -187,7 +192,7 @@ class Store: ObservableObject {
func getUser(baseUrl: String) -> User? {
let request = User.fetchRequest()
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(format: "baseUrl = %@", baseUrl)])
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(format: "baseUrl = %@", normalizeBaseUrl(baseUrl))])
return try? context.fetch(request).first
}
@ -202,7 +207,7 @@ class Store: ObservableObject {
do {
let pref = getPreference(key: Store.prefKeyDefaultBaseUrl) ?? Preference(context: context)
pref.key = Store.prefKeyDefaultBaseUrl
pref.value = baseUrl ?? Config.appBaseUrl
pref.value = baseUrl.map(normalizeBaseUrl) ?? Config.appBaseUrl
try context.save()
} catch let error {
Log.w(Store.tag, "Cannot store preference", error)
@ -215,7 +220,7 @@ class Store: ObservableObject {
if baseUrl == nil || baseUrl?.isEmpty == true {
return Config.appBaseUrl
}
return baseUrl!
return normalizeBaseUrl(baseUrl!)
}
private func getPreference(key: String) -> Preference? {

View file

@ -8,13 +8,10 @@ struct SubscriptionManager {
var store: Store
func subscribe(baseUrl: String, topic: String) {
Log.d(tag, "Subscribing to \(topicUrl(baseUrl: baseUrl, topic: topic))")
if baseUrl == Config.appBaseUrl {
Messaging.messaging().subscribe(toTopic: topic)
} else {
Messaging.messaging().subscribe(toTopic: topicHash(baseUrl: baseUrl, topic: topic))
}
let subscription = store.saveSubscription(baseUrl: baseUrl, topic: topic)
let normalizedBaseUrl = normalizeBaseUrl(baseUrl)
Log.d(tag, "Subscribing to \(topicUrl(baseUrl: normalizedBaseUrl, topic: topic))")
Messaging.messaging().subscribe(toTopic: firebaseTopic(baseUrl: normalizedBaseUrl, topic: topic))
let subscription = store.saveSubscription(baseUrl: normalizedBaseUrl, topic: topic)
poll(subscription)
}
@ -22,11 +19,7 @@ struct SubscriptionManager {
Log.d(tag, "Unsubscribing from \(subscription.urlString())")
DispatchQueue.main.async {
if let baseUrl = subscription.baseUrl, let topic = subscription.topic {
if baseUrl == Config.appBaseUrl {
Messaging.messaging().unsubscribe(fromTopic: topic)
} else {
Messaging.messaging().unsubscribe(fromTopic: topicHash(baseUrl: baseUrl, topic: topic))
}
Messaging.messaging().unsubscribe(fromTopic: firebaseTopic(baseUrl: baseUrl, topic: topic))
}
store.delete(subscription: subscription)
}

View file

@ -43,7 +43,8 @@ class ApiService {
message: String,
title: String,
priority: Int = 3,
tags: [String] = []
tags: [String] = [],
completionHandler: (() -> Void)? = nil
) {
guard let url = URL(string: subscription.urlString()) else { return }
var request = newRequest(url: url, user: user)
@ -61,6 +62,7 @@ class ApiService {
return
}
Log.d(self.tag, "Publishing message succeeded", response)
completionHandler?()
}.resume()
}

View file

@ -2,7 +2,7 @@ import Foundation
import CryptoKit
func topicUrl(baseUrl: String, topic: String) -> String {
return "\(baseUrl)/\(topic)"
return "\(normalizeBaseUrl(baseUrl))/\(topic)"
}
func topicShortUrl(baseUrl: String, topic: String) -> String {
@ -10,15 +10,29 @@ func topicShortUrl(baseUrl: String, topic: String) -> String {
}
func topicAuthUrl(baseUrl: String, topic: String) -> String {
return "\(baseUrl)/\(topic)/auth"
return "\(normalizeBaseUrl(baseUrl))/\(topic)/auth"
}
func topicHash(baseUrl: String, topic: String) -> String {
let data = Data(topicUrl(baseUrl: baseUrl, topic: topic).utf8)
let data = Data(topicUrl(baseUrl: normalizeBaseUrl(baseUrl), topic: topic).utf8)
let digest = SHA256.hash(data: data)
return digest.compactMap { String(format: "%02x", $0)}.joined()
}
func firebaseTopic(baseUrl: String, topic: String) -> String {
return normalizeBaseUrl(baseUrl) == normalizeBaseUrl(Config.appBaseUrl)
? topic
: topicHash(baseUrl: baseUrl, topic: topic)
}
func normalizeBaseUrl(_ baseUrl: String) -> String {
var normalized = baseUrl.trimmingCharacters(in: .whitespacesAndNewlines)
while normalized.hasSuffix("/") {
normalized.removeLast()
}
return normalized
}
func shortUrl(url: String) -> String {
return url
.replacingOccurrences(of: "http://", with: "")

View file

@ -5,6 +5,7 @@ enum ActiveAlert {
case clear, unsubscribe, selected
}
struct NotificationListView: View {
private let tag = "NotificationListView"
@ -201,7 +202,9 @@ struct NotificationListView: View {
title: "Test: You can set a title if you like",
priority: priority,
tags: tags
)
) {
subscriptionManager.poll(subscription)
}
}
}

View file

@ -106,7 +106,7 @@ struct DefaultServerView: View {
if newDefaultBaseUrl == "" {
store.saveDefaultBaseUrl(baseUrl: nil)
} else {
store.saveDefaultBaseUrl(baseUrl: newDefaultBaseUrl)
store.saveDefaultBaseUrl(baseUrl: normalizeBaseUrl(newDefaultBaseUrl))
}
resetAndHide()
}

View file

@ -136,7 +136,7 @@ struct SubscriptionAddView: View {
return false
} else if selectedBaseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
return false
} else if store.getSubscription(baseUrl: selectedBaseUrl, topic: topic) != nil {
} else if store.getSubscription(baseUrl: selectedBaseUrl, topic: sanitizedTopic) != nil {
return false
}
return true
@ -153,7 +153,7 @@ struct SubscriptionAddView: View {
loading = true
addError = nil
let user = store.getUser(baseUrl: selectedBaseUrl)?.toBasicUser()
ApiService.shared.checkAuth(baseUrl: selectedBaseUrl, topic: topic, user: user) { result in
ApiService.shared.checkAuth(baseUrl: selectedBaseUrl, topic: sanitizedTopic, user: user) { result in
switch result {
case .Success:
DispatchQueue.global(qos: .background).async {
@ -180,7 +180,7 @@ struct SubscriptionAddView: View {
loading = true
loginError = nil
let user = BasicUser(username: username, password: password)
ApiService.shared.checkAuth(baseUrl: selectedBaseUrl, topic: topic, user: user) { result in
ApiService.shared.checkAuth(baseUrl: selectedBaseUrl, topic: sanitizedTopic, user: user) { result in
switch result {
case .Success:
DispatchQueue.global(qos: .background).async {
@ -204,7 +204,7 @@ struct SubscriptionAddView: View {
}
private var selectedBaseUrl: String {
return (useAnother) ? baseUrl : store.getDefaultBaseUrl()
return normalizeBaseUrl((useAnother) ? baseUrl : store.getDefaultBaseUrl())
}
private func resetAndHide() {