20 minute interval, selfhosted servers, works, whoa

This commit is contained in:
Philipp Heckel 2022-05-25 22:16:20 -04:00
parent fe75ad22c4
commit a8236367c3
4 changed files with 76 additions and 3 deletions

View file

@ -6,7 +6,8 @@ import FirebaseCore
import CoreData
class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
let tag = "AppDelegate"
private let tag = "AppDelegate"
private let pollTimerTopic = "~poll" // See ntfy server if ever changed
// Implements navigation from notifications, see https://stackoverflow.com/a/70731861/1440785
@Published var selectedBaseUrl: String? = nil
@ -30,9 +31,49 @@ class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
// Set self as messaging delegate
Messaging.messaging().delegate = self
// Register to timerkeeper topic
Messaging.messaging().subscribe(toTopic: pollTimerTopic)
return true
}
/// Executed when a background notification arrives. This is used to trigger polling of local topics.
/// See https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
Log.d(tag, "Background notification received", userInfo)
// Exit out early if this message is not expected
let topic = userInfo["topic"] as? String ?? ""
if topic != pollTimerTopic {
completionHandler(.noData)
return
}
// Poll and display new messages
let store = Store.shared
let center = UNUserNotificationCenter.current()
let subscriptionManager = SubscriptionManager(store: store)
store.getSubscriptions()?.forEach { subscription in
subscriptionManager.poll(subscription) { messages in
messages.forEach { message in
let content = UNMutableNotificationContent()
content.title = message.title ?? ""
content.body = message.message ?? ""
content.sound = .default
let request = UNNotificationRequest(identifier: message.id, content: content, trigger: nil /* now */)
center.add(request) { (error) in
if let error = error {
Log.e(self.tag, "Unable to create notification", error)
}
}
}
}
}
completionHandler(.newData)
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { data in String(format: "%02.2hhx", data) }.joined()
Messaging.messaging().apnsToken = deviceToken

View file

@ -70,6 +70,10 @@ class Store: ObservableObject {
return try? context.fetch(fetchRequest).first
}
func getSubscriptions() -> [Subscription]? {
return try? context.fetch(Subscription.fetchRequest())
}
func delete(subscription: Subscription) {
context.delete(subscription)
try? context.save()

View file

@ -25,20 +25,26 @@ struct SubscriptionManager {
}
func poll(_ subscription: Subscription) {
poll(subscription) { _ in }
}
func poll(_ subscription: Subscription, completionHandler: @escaping ([Message]) -> Void) {
Log.d(tag, "Polling from \(subscription.urlString())")
ApiService.shared.poll(subscription: subscription) { messages, error in
guard let messages = messages else {
Log.e(tag, "Polling failed", error)
completionHandler([])
return
}
Log.d(tag, "Polling success, \(messages.count) new message(s)", messages)
if !messages.isEmpty {
DispatchQueue.main.async {
DispatchQueue.main.sync {
for message in messages {
store.save(notificationFromMessage: message, withSubscription: subscription)
}
}
}
completionHandler(messages)
}
}
}

View file

@ -7,6 +7,8 @@ struct SubscriptionAddView: View {
@EnvironmentObject private var store: Store
@State private var topic: String = ""
@State private var useAnother: Bool = false
@State private var baseUrl: String = ""
private var subscriptionManager: SubscriptionManager {
return SubscriptionManager(store: store)
@ -24,6 +26,14 @@ struct SubscriptionAddView: View {
.disableAutocapitalization()
.disableAutocorrection(true)
}
Section {
Toggle("Use another server", isOn: $useAnother)
if useAnother {
TextField("Server URL, e.g. https://ntfy.example.com", text: $baseUrl)
.disableAutocapitalization()
.disableAutocorrection(true)
}
}
}
}
.navigationTitle("Add subscription")
@ -61,8 +71,9 @@ struct SubscriptionAddView: View {
}
private func subscribeAction() {
let baseUrl = (useAnother) ? baseUrl : Config.appBaseUrl
DispatchQueue.global(qos: .background).async {
subscriptionManager.subscribe(baseUrl: Config.appBaseUrl, topic: sanitize(topic: topic))
subscriptionManager.subscribe(baseUrl: baseUrl, topic: sanitize(topic: topic))
}
isShowing = false
}
@ -71,3 +82,14 @@ struct SubscriptionAddView: View {
isShowing = false
}
}
struct SubscriptionAddView_Previews: PreviewProvider {
@State static var isShowing = true
static var previews: some View {
let store = Store.preview
SubscriptionAddView(isShowing: $isShowing)
.environmentObject(store)
}
}