Files
breez-sdk-liquid/lib/bindings/langs/swift/Sources/BreezSDKLiquid/TaskProtocol.swift
Ross Savage 42c7bfe285 Add LNURL-verify notification plugin tasks (#911)
* Add LNURL-verify notification plugin tasks

* Only settled when MRH payment is complete

* Ignore unknown JSON keys

* Log fail replyURL

* Prevent escaping slashes
2025-05-15 06:26:32 +02:00

111 lines
4.6 KiB
Swift

import UserNotifications
public protocol TaskProtocol : EventListener {
var payload: String { get set }
var contentHandler: ((UNNotificationContent) -> Void)? { get set }
var bestAttemptContent: UNMutableNotificationContent? { get set }
func start(liquidSDK: BindingLiquidSdk) throws
func onShutdown()
}
extension TaskProtocol {
func removePushNotifications(threadIdentifier: String, logger: ServiceLogger) {
let semaphore = DispatchSemaphore(value: 0)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications(completionHandler: { notifications in
defer {
semaphore.signal()
}
let removableNotifications = notifications.filter({ $0.request.content.threadIdentifier == threadIdentifier })
guard !removableNotifications.isEmpty else {
return
}
// The call to removeDeliveredNotifications() is async in a background thread and
// needs to be complete before calling contentHandler()
notificationCenter.removeDeliveredNotifications(withIdentifiers: removableNotifications.map({ $0.request.identifier }))
logger.log(tag: "TaskProtocol", line:"removePushNotifications: \(removableNotifications.count)", level: "INFO")
})
semaphore.wait()
}
func displayPushNotification(title: String, body: String? = nil, logger: ServiceLogger, threadIdentifier: String? = nil) {
logger.log(tag: "TaskProtocol", line:"displayPushNotification \(title)", level: "INFO")
guard
let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent
else {
return
}
removePushNotifications(threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE, logger: logger)
if let body = body {
bestAttemptContent.body = body
}
if let threadIdentifier = threadIdentifier {
bestAttemptContent.threadIdentifier = threadIdentifier
}
bestAttemptContent.title = title
// The call to contentHandler() needs to be done with a slight delay otherwise
// it will be killed before its finished removing the notifications
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
contentHandler(bestAttemptContent)
}
}
}
class ReplyableTask : TaskProtocol {
var payload: String
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var logger: ServiceLogger
var successNotificationTitle: String
var failNotificationTitle: String
init(payload: String, logger: ServiceLogger, contentHandler: ((UNNotificationContent) -> Void)? = nil, bestAttemptContent: UNMutableNotificationContent? = nil, successNotificationTitle: String, failNotificationTitle: String) {
self.payload = payload
self.contentHandler = contentHandler
self.bestAttemptContent = bestAttemptContent
self.logger = logger
self.successNotificationTitle = successNotificationTitle;
self.failNotificationTitle = failNotificationTitle;
}
func start(liquidSDK: BindingLiquidSdk) throws {}
public func onEvent(e: SdkEvent) {}
func onShutdown() {
displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
}
func replyServer(encodable: Encodable, replyURL: String) {
guard let serverReplyURL = URL(string: replyURL) else {
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
return
}
var request = URLRequest(url: serverReplyURL)
request.httpMethod = "POST"
let encoder = JSONEncoder()
encoder.outputFormatting = .withoutEscapingSlashes
request.httpBody = try! encoder.encode(encodable)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let statusCode = (response as! HTTPURLResponse).statusCode
if statusCode == 200 {
self.displayPushNotification(title: self.successNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
} else {
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
return
}
}
task.resume()
}
}