Merge pull request #32 from am7590/stability-fixes
fix iOS topic normalization and refresh after test push
This commit is contained in:
commit
06e90baf09
8 changed files with 48 additions and 34 deletions
|
|
@ -142,14 +142,11 @@ extension AppDelegate: MessagingDelegate {
|
||||||
|
|
||||||
// Re-subscribe to Firebase for all topics
|
// Re-subscribe to Firebase for all topics
|
||||||
let store = Store.shared
|
let store = Store.shared
|
||||||
|
let subscriptionManager = SubscriptionManager(store: store)
|
||||||
store.getSubscriptions()?.forEach{ subscription in
|
store.getSubscriptions()?.forEach{ subscription in
|
||||||
if let baseUrl = subscription.baseUrl, let topic = subscription.topic {
|
if let baseUrl = subscription.baseUrl, let topic = subscription.topic {
|
||||||
Log.d(tag, "Re-subscribing to topic \(baseUrl)/\(topic)")
|
Log.d(tag, "Re-subscribing to topic \(baseUrl)/\(topic)")
|
||||||
if baseUrl == Config.appBaseUrl {
|
Messaging.messaging().subscribe(toTopic: firebaseTopic(baseUrl: baseUrl, topic: topic))
|
||||||
Messaging.messaging().subscribe(toTopic: topic)
|
|
||||||
} else {
|
|
||||||
Messaging.messaging().subscribe(toTopic: topicHash(baseUrl: baseUrl, topic: topic))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,10 @@ class Store: ObservableObject {
|
||||||
|
|
||||||
func saveSubscription(baseUrl: String, topic: String) -> Subscription {
|
func saveSubscription(baseUrl: String, topic: String) -> Subscription {
|
||||||
let subscription = Subscription(context: context)
|
let subscription = Subscription(context: context)
|
||||||
subscription.baseUrl = baseUrl
|
subscription.baseUrl = normalizeBaseUrl(baseUrl)
|
||||||
subscription.topic = topic
|
subscription.topic = topic
|
||||||
DispatchQueue.main.sync {
|
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()
|
try? context.save()
|
||||||
}
|
}
|
||||||
return subscription
|
return subscription
|
||||||
|
|
@ -88,7 +88,7 @@ class Store: ObservableObject {
|
||||||
|
|
||||||
func getSubscription(baseUrl: String, topic: String) -> Subscription? {
|
func getSubscription(baseUrl: String, topic: String) -> Subscription? {
|
||||||
let fetchRequest = Subscription.fetchRequest()
|
let fetchRequest = Subscription.fetchRequest()
|
||||||
let baseUrlPredicate = NSPredicate(format: "baseUrl = %@", baseUrl)
|
let baseUrlPredicate = NSPredicate(format: "baseUrl = %@", normalizeBaseUrl(baseUrl))
|
||||||
let topicPredicate = NSPredicate(format: "topic = %@", topic)
|
let topicPredicate = NSPredicate(format: "topic = %@", topic)
|
||||||
|
|
||||||
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [baseUrlPredicate, topicPredicate])
|
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [baseUrlPredicate, topicPredicate])
|
||||||
|
|
@ -99,6 +99,11 @@ class Store: ObservableObject {
|
||||||
func getSubscriptions() -> [Subscription]? {
|
func getSubscriptions() -> [Subscription]? {
|
||||||
return try? context.fetch(Subscription.fetchRequest())
|
return try? context.fetch(Subscription.fetchRequest())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateSubscriptionBaseUrl(_ subscription: Subscription, baseUrl: String) {
|
||||||
|
subscription.baseUrl = normalizeBaseUrl(baseUrl)
|
||||||
|
try? context.save()
|
||||||
|
}
|
||||||
|
|
||||||
func delete(subscription: Subscription) {
|
func delete(subscription: Subscription) {
|
||||||
context.performAndWait {
|
context.performAndWait {
|
||||||
|
|
@ -175,7 +180,7 @@ class Store: ObservableObject {
|
||||||
func saveUser(baseUrl: String, username: String, password: String) {
|
func saveUser(baseUrl: String, username: String, password: String) {
|
||||||
do {
|
do {
|
||||||
let user = getUser(baseUrl: baseUrl) ?? User(context: context)
|
let user = getUser(baseUrl: baseUrl) ?? User(context: context)
|
||||||
user.baseUrl = baseUrl
|
user.baseUrl = normalizeBaseUrl(baseUrl)
|
||||||
user.username = username
|
user.username = username
|
||||||
user.password = password
|
user.password = password
|
||||||
try context.save()
|
try context.save()
|
||||||
|
|
@ -187,7 +192,7 @@ class Store: ObservableObject {
|
||||||
|
|
||||||
func getUser(baseUrl: String) -> User? {
|
func getUser(baseUrl: String) -> User? {
|
||||||
let request = User.fetchRequest()
|
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
|
return try? context.fetch(request).first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +207,7 @@ class Store: ObservableObject {
|
||||||
do {
|
do {
|
||||||
let pref = getPreference(key: Store.prefKeyDefaultBaseUrl) ?? Preference(context: context)
|
let pref = getPreference(key: Store.prefKeyDefaultBaseUrl) ?? Preference(context: context)
|
||||||
pref.key = Store.prefKeyDefaultBaseUrl
|
pref.key = Store.prefKeyDefaultBaseUrl
|
||||||
pref.value = baseUrl ?? Config.appBaseUrl
|
pref.value = baseUrl.map(normalizeBaseUrl) ?? Config.appBaseUrl
|
||||||
try context.save()
|
try context.save()
|
||||||
} catch let error {
|
} catch let error {
|
||||||
Log.w(Store.tag, "Cannot store preference", error)
|
Log.w(Store.tag, "Cannot store preference", error)
|
||||||
|
|
@ -215,7 +220,7 @@ class Store: ObservableObject {
|
||||||
if baseUrl == nil || baseUrl?.isEmpty == true {
|
if baseUrl == nil || baseUrl?.isEmpty == true {
|
||||||
return Config.appBaseUrl
|
return Config.appBaseUrl
|
||||||
}
|
}
|
||||||
return baseUrl!
|
return normalizeBaseUrl(baseUrl!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPreference(key: String) -> Preference? {
|
private func getPreference(key: String) -> Preference? {
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,10 @@ struct SubscriptionManager {
|
||||||
var store: Store
|
var store: Store
|
||||||
|
|
||||||
func subscribe(baseUrl: String, topic: String) {
|
func subscribe(baseUrl: String, topic: String) {
|
||||||
Log.d(tag, "Subscribing to \(topicUrl(baseUrl: baseUrl, topic: topic))")
|
let normalizedBaseUrl = normalizeBaseUrl(baseUrl)
|
||||||
if baseUrl == Config.appBaseUrl {
|
Log.d(tag, "Subscribing to \(topicUrl(baseUrl: normalizedBaseUrl, topic: topic))")
|
||||||
Messaging.messaging().subscribe(toTopic: topic)
|
Messaging.messaging().subscribe(toTopic: firebaseTopic(baseUrl: normalizedBaseUrl, topic: topic))
|
||||||
} else {
|
let subscription = store.saveSubscription(baseUrl: normalizedBaseUrl, topic: topic)
|
||||||
Messaging.messaging().subscribe(toTopic: topicHash(baseUrl: baseUrl, topic: topic))
|
|
||||||
}
|
|
||||||
let subscription = store.saveSubscription(baseUrl: baseUrl, topic: topic)
|
|
||||||
poll(subscription)
|
poll(subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,11 +19,7 @@ struct SubscriptionManager {
|
||||||
Log.d(tag, "Unsubscribing from \(subscription.urlString())")
|
Log.d(tag, "Unsubscribing from \(subscription.urlString())")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if let baseUrl = subscription.baseUrl, let topic = subscription.topic {
|
if let baseUrl = subscription.baseUrl, let topic = subscription.topic {
|
||||||
if baseUrl == Config.appBaseUrl {
|
Messaging.messaging().unsubscribe(fromTopic: firebaseTopic(baseUrl: baseUrl, topic: topic))
|
||||||
Messaging.messaging().unsubscribe(fromTopic: topic)
|
|
||||||
} else {
|
|
||||||
Messaging.messaging().unsubscribe(fromTopic: topicHash(baseUrl: baseUrl, topic: topic))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
store.delete(subscription: subscription)
|
store.delete(subscription: subscription)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ class ApiService {
|
||||||
message: String,
|
message: String,
|
||||||
title: String,
|
title: String,
|
||||||
priority: Int = 3,
|
priority: Int = 3,
|
||||||
tags: [String] = []
|
tags: [String] = [],
|
||||||
|
completionHandler: (() -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
guard let url = URL(string: subscription.urlString()) else { return }
|
guard let url = URL(string: subscription.urlString()) else { return }
|
||||||
var request = newRequest(url: url, user: user)
|
var request = newRequest(url: url, user: user)
|
||||||
|
|
@ -61,6 +62,7 @@ class ApiService {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Log.d(self.tag, "Publishing message succeeded", response)
|
Log.d(self.tag, "Publishing message succeeded", response)
|
||||||
|
completionHandler?()
|
||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
func topicUrl(baseUrl: String, topic: String) -> String {
|
func topicUrl(baseUrl: String, topic: String) -> String {
|
||||||
return "\(baseUrl)/\(topic)"
|
return "\(normalizeBaseUrl(baseUrl))/\(topic)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func topicShortUrl(baseUrl: String, topic: String) -> String {
|
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 {
|
func topicAuthUrl(baseUrl: String, topic: String) -> String {
|
||||||
return "\(baseUrl)/\(topic)/auth"
|
return "\(normalizeBaseUrl(baseUrl))/\(topic)/auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
func topicHash(baseUrl: String, topic: String) -> String {
|
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)
|
let digest = SHA256.hash(data: data)
|
||||||
return digest.compactMap { String(format: "%02x", $0)}.joined()
|
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 {
|
func shortUrl(url: String) -> String {
|
||||||
return url
|
return url
|
||||||
.replacingOccurrences(of: "http://", with: "")
|
.replacingOccurrences(of: "http://", with: "")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ enum ActiveAlert {
|
||||||
case clear, unsubscribe, selected
|
case clear, unsubscribe, selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct NotificationListView: View {
|
struct NotificationListView: View {
|
||||||
private let tag = "NotificationListView"
|
private let tag = "NotificationListView"
|
||||||
|
|
||||||
|
|
@ -201,7 +202,9 @@ struct NotificationListView: View {
|
||||||
title: "Test: You can set a title if you like",
|
title: "Test: You can set a title if you like",
|
||||||
priority: priority,
|
priority: priority,
|
||||||
tags: tags
|
tags: tags
|
||||||
)
|
) {
|
||||||
|
subscriptionManager.poll(subscription)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ struct DefaultServerView: View {
|
||||||
if newDefaultBaseUrl == "" {
|
if newDefaultBaseUrl == "" {
|
||||||
store.saveDefaultBaseUrl(baseUrl: nil)
|
store.saveDefaultBaseUrl(baseUrl: nil)
|
||||||
} else {
|
} else {
|
||||||
store.saveDefaultBaseUrl(baseUrl: newDefaultBaseUrl)
|
store.saveDefaultBaseUrl(baseUrl: normalizeBaseUrl(newDefaultBaseUrl))
|
||||||
}
|
}
|
||||||
resetAndHide()
|
resetAndHide()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ struct SubscriptionAddView: View {
|
||||||
return false
|
return false
|
||||||
} else if selectedBaseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
|
} else if selectedBaseUrl.range(of: "^https?://.+", options: .regularExpression, range: nil, locale: nil) == nil {
|
||||||
return false
|
return false
|
||||||
} else if store.getSubscription(baseUrl: selectedBaseUrl, topic: topic) != nil {
|
} else if store.getSubscription(baseUrl: selectedBaseUrl, topic: sanitizedTopic) != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
@ -153,7 +153,7 @@ struct SubscriptionAddView: View {
|
||||||
loading = true
|
loading = true
|
||||||
addError = nil
|
addError = nil
|
||||||
let user = store.getUser(baseUrl: selectedBaseUrl)?.toBasicUser()
|
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 {
|
switch result {
|
||||||
case .Success:
|
case .Success:
|
||||||
DispatchQueue.global(qos: .background).async {
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
|
@ -180,7 +180,7 @@ struct SubscriptionAddView: View {
|
||||||
loading = true
|
loading = true
|
||||||
loginError = nil
|
loginError = nil
|
||||||
let user = BasicUser(username: username, password: password)
|
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 {
|
switch result {
|
||||||
case .Success:
|
case .Success:
|
||||||
DispatchQueue.global(qos: .background).async {
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
|
@ -204,7 +204,7 @@ struct SubscriptionAddView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectedBaseUrl: String {
|
private var selectedBaseUrl: String {
|
||||||
return (useAnother) ? baseUrl : store.getDefaultBaseUrl()
|
return normalizeBaseUrl((useAnother) ? baseUrl : store.getDefaultBaseUrl())
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resetAndHide() {
|
private func resetAndHide() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue