Horrible working prototype
This commit is contained in:
parent
379ed1bed1
commit
92c0da036d
8 changed files with 89 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
.DS_Store
|
||||
GoogleService-Info.plist
|
||||
ntfy.xcodeproj/xcuserdata
|
||||
ntfy.xcodeproj/project.xcworkspace/xcuserdata/
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
9474F217283531A300CDE4DD /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F216283531A200CDE4DD /* Log.swift */; };
|
||||
94867143283EC9960093C7A4 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94867142283EC9950093C7A4 /* Actions.swift */; };
|
||||
94867144283ECD370093C7A4 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94867142283EC9950093C7A4 /* Actions.swift */; };
|
||||
94867145284058C60093C7A4 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474F20E283326C500CDE4DD /* ApiService.swift */; };
|
||||
94A3F7C8283734D900C48E79 /* SubscriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A3F7C7283734D900C48E79 /* SubscriptionManager.swift */; };
|
||||
94A3F7CA28386B2100C48E79 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A3F7C928386B2100C48E79 /* Config.swift */; };
|
||||
94A3F7CB28386B2100C48E79 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A3F7C928386B2100C48E79 /* Config.swift */; };
|
||||
|
|
@ -374,6 +375,7 @@
|
|||
9474F1E7282F3FFD00CDE4DD /* NotificationService.swift in Sources */,
|
||||
94CD196B283E666900973B93 /* EmojiManager.swift in Sources */,
|
||||
94867144283ECD370093C7A4 /* Actions.swift in Sources */,
|
||||
94867145284058C60093C7A4 /* ApiService.swift in Sources */,
|
||||
9474F2052831D51500CDE4DD /* Store.swift in Sources */,
|
||||
9474F2062831D73C00CDE4DD /* ntfy.xcdatamodeld in Sources */,
|
||||
94A3F7CB28386B2100C48E79 /* Config.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ struct Message: Decodable {
|
|||
var tags: [String]?
|
||||
var actions: [Action]?
|
||||
var click: String?
|
||||
var pollId: String?
|
||||
|
||||
func toUserInfo() -> [AnyHashable: Any] {
|
||||
// This should mimic the way that the ntfy server encodes a message.
|
||||
|
|
@ -85,7 +86,8 @@ struct Message: Decodable {
|
|||
"priority": String(priority ?? 3),
|
||||
"tags": tags?.joined(separator: ",") ?? "",
|
||||
"actions": Actions.shared.encode(actions),
|
||||
"click": click ?? ""
|
||||
"click": click ?? "",
|
||||
"poll_id": pollId ?? ""
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +105,7 @@ struct Message: Decodable {
|
|||
let tags = (userInfo["tags"] as? String ?? "").components(separatedBy: ",")
|
||||
let actions = userInfo["actions"] as? String
|
||||
let click = userInfo["click"] as? String
|
||||
let pollId = userInfo["poll_id"] as? String
|
||||
return Message(
|
||||
id: id,
|
||||
time: timeInt,
|
||||
|
|
@ -112,7 +115,8 @@ struct Message: Decodable {
|
|||
priority: priority,
|
||||
tags: tags,
|
||||
actions: Actions.shared.parse(actions),
|
||||
click: click
|
||||
click: click,
|
||||
pollId: pollId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ extension Subscription {
|
|||
}
|
||||
|
||||
func topicName() -> String {
|
||||
return topic ?? "<unknown>"
|
||||
return topic ?? "?"
|
||||
}
|
||||
|
||||
func urlHash() -> String {
|
||||
return topicHash(baseUrl: baseUrl ?? "?", topic: topic ?? "?")
|
||||
}
|
||||
|
||||
func notificationCount() -> Int {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ struct SubscriptionManager {
|
|||
|
||||
func subscribe(baseUrl: String, topic: String) {
|
||||
Log.d(tag, "Subscribing to \(topicUrl(baseUrl: baseUrl, topic: topic))")
|
||||
Messaging.messaging().subscribe(toTopic: 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)
|
||||
poll(subscription)
|
||||
}
|
||||
|
|
@ -17,8 +21,12 @@ struct SubscriptionManager {
|
|||
func unsubscribe(_ subscription: Subscription) {
|
||||
Log.d(tag, "Unsubscribing from \(subscription.urlString())")
|
||||
DispatchQueue.main.async {
|
||||
if let topic = subscription.topic {
|
||||
Messaging.messaging().unsubscribe(fromTopic: topic)
|
||||
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))
|
||||
}
|
||||
}
|
||||
store.delete(subscription: subscription)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,34 @@ class ApiService {
|
|||
static let shared = ApiService()
|
||||
|
||||
func poll(subscription: Subscription, completionHandler: @escaping ([Message]?, Error?) -> Void) {
|
||||
guard let url = URL(string: subscription.urlString()) else { return }
|
||||
guard let url = URL(string: subscription.urlString()) else {
|
||||
// FIXME
|
||||
return
|
||||
}
|
||||
let since = subscription.lastNotificationId ?? "all"
|
||||
let urlString = "\(url)/json?poll=1&since=\(since)"
|
||||
|
||||
Log.d(tag, "Polling from \(urlString)")
|
||||
fetchJsonData(urlString: urlString, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func poll(subscription: Subscription, messageId: String, completionHandler: @escaping (Message?, Error?) -> Void) {
|
||||
let url = URL(string: "\(subscription.urlString())/json?poll=1&id=\(messageId)")!
|
||||
Log.d(tag, "Polling single message from \(url)")
|
||||
|
||||
URLSession.shared.dataTask(with: URLRequest(url: url)) { (data, response, error) in
|
||||
if let error = error {
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
do {
|
||||
let message = try JSONDecoder().decode(Message.self, from: data!)
|
||||
completionHandler(message, nil)
|
||||
} catch {
|
||||
completionHandler(nil, error)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func publish(
|
||||
subscription: Subscription,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
func topicUrl(baseUrl: String, topic: String) -> String {
|
||||
return "\(baseUrl)/\(topic)"
|
||||
|
|
@ -10,6 +11,12 @@ func topicShortUrl(baseUrl: String, topic: String) -> String {
|
|||
.replacingOccurrences(of: "https://", with: "")
|
||||
}
|
||||
|
||||
func topicHash(baseUrl: String, topic: String) -> String {
|
||||
let data = Data(topicUrl(baseUrl: baseUrl, topic: topic).utf8)
|
||||
let digest = SHA256.hash(data: data)
|
||||
return digest.compactMap { String(format: "%02x", $0)}.joined()
|
||||
}
|
||||
|
||||
func parseAllTags(_ tags: String?) -> [String] {
|
||||
return (tags?.components(separatedBy: ",") ?? [])
|
||||
.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import UserNotifications
|
||||
import CoreData
|
||||
import CryptoKit
|
||||
|
||||
/// This app extension is responsible for persisting the incoming notification to the data store (Core Data). It will eventually be the entity that
|
||||
/// fetches notification content from selfhosted servers (when a "poll request" is received). This is not implemented yet.
|
||||
|
|
@ -22,11 +23,37 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
if let bestAttemptContent = bestAttemptContent {
|
||||
let store = Store.shared
|
||||
let userInfo = bestAttemptContent.userInfo
|
||||
let baseUrl = userInfo["base_url"] as? String ?? Config.appBaseUrl
|
||||
let topic = userInfo["topic"] as? String ?? ""
|
||||
guard let message = Message.from(userInfo: userInfo) else {
|
||||
Log.w(Store.tag, "Message cannot be parsed from userInfo", userInfo)
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
if message.event == "poll_request" {
|
||||
let subscription = store.getSubscriptions()?.first { $0.urlHash() == topic }
|
||||
guard let subscription = subscription, let pollId = message.pollId else {
|
||||
Log.w(tag, "Cannot find subscription", message)
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
//let semaphore = DispatchSemaphore(value: 0)
|
||||
ApiService.shared.poll(subscription: subscription, messageId: pollId) { message, error in
|
||||
guard let message = message else {
|
||||
Log.w(self.tag, "Error fetching message", error)
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
bestAttemptContent.title = message.title ?? subscription.urlString()
|
||||
bestAttemptContent.body = message.message ?? ""
|
||||
contentHandler(bestAttemptContent)
|
||||
//semaphore.signal()
|
||||
}
|
||||
//semaphore.wait(timeout: .distantFuture)
|
||||
Thread.sleep(forTimeInterval: 5)
|
||||
return
|
||||
}
|
||||
|
||||
if message.event != "message" {
|
||||
Log.w(tag, "Irrelevant message received", message)
|
||||
contentHandler(request.content)
|
||||
|
|
@ -34,8 +61,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
}
|
||||
|
||||
// Only handle "message" events
|
||||
let baseUrl = userInfo["base_url"] as? String ?? Config.appBaseUrl
|
||||
let topic = userInfo["topic"] as? String ?? ""
|
||||
guard let subscription = store.getSubscription(baseUrl: baseUrl, topic: topic) else {
|
||||
Log.w(tag, "Subscription for topic \(topic) unknown")
|
||||
contentHandler(request.content)
|
||||
|
|
@ -115,4 +140,12 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage() {
|
||||
|
||||
}
|
||||
|
||||
func handlePollRequest() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue