Skip to content

Commit

Permalink
Update Breez SDK Flutter package to version v0.4.0-rc5
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions committed Apr 17, 2024
1 parent 8762a59 commit 6fc4037
Show file tree
Hide file tree
Showing 15 changed files with 667 additions and 6 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.breez.breez_sdk'
version '0.4.0-rc2'
version '0.4.0-rc5'

buildscript {
ext.kotlin_version = '1.8.20'
Expand Down
49 changes: 49 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/BreezSDKConnector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Foundation
import os.log

#if DEBUG && true
fileprivate var logger = OSLog(
subsystem: Bundle.main.bundleIdentifier!,
category: "BreezSDKConnector"
)
#else
fileprivate var logger = OSLog.disabled
#endif

class BreezSDKConnector {
private static var breezSDK: BlockingBreezServices? = nil
fileprivate static var queue = DispatchQueue(label: "BreezSDKConnector")
fileprivate static var sdkListener: EventListener? = nil

static func register(connectRequest: ConnectRequest, listener: EventListener) throws -> BlockingBreezServices? {
try BreezSDKConnector.queue.sync { [] in
BreezSDKConnector.sdkListener = listener
if BreezSDKConnector.breezSDK == nil {
BreezSDKConnector.breezSDK = try BreezSDKConnector.connectSDK(connectRequest: connectRequest)
}
return BreezSDKConnector.breezSDK
}
}

static func unregister() {
BreezSDKConnector.queue.sync { [] in
BreezSDKConnector.sdkListener = nil
}
}

static func connectSDK(connectRequest: ConnectRequest) throws -> BlockingBreezServices? {
// Connect to the Breez SDK make it ready for use
os_log("Connecting to Breez SDK", log: logger, type: .debug)
let breezSDK = try connect(req: connectRequest, listener: BreezSDKEventListener())
os_log("Connected to Breez SDK", log: logger, type: .debug)
return breezSDK
}
}

class BreezSDKEventListener: EventListener {
func onEvent(e: BreezEvent) {
BreezSDKConnector.queue.async { [] in
BreezSDKConnector.sdkListener?.onEvent(e: e)
}
}
}
39 changes: 39 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/Constants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

struct Constants {
// Notification Threads
static let NOTIFICATION_THREAD_LNURL_PAY = "LNURL_PAY"
static let NOTIFICATION_THREAD_PAYMENT_RECEIVED = "PAYMENT_RECEIVED"
static let NOTIFICATION_THREAD_SWAP_TX_CONFIRMED = "SWAP_TX_CONFIRMED"

// Message Data
static let MESSAGE_DATA_TYPE = "notification_type"
static let MESSAGE_DATA_PAYLOAD = "notification_payload"

static let MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED = "address_txs_confirmed"
static let MESSAGE_TYPE_LNURL_PAY_INFO = "lnurlpay_info"
static let MESSAGE_TYPE_LNURL_PAY_INVOICE = "lnurlpay_invoice"
static let MESSAGE_TYPE_PAYMENT_RECEIVED = "payment_received"

// Resource Identifiers
static let LNURL_PAY_INFO_NOTIFICATION_TITLE = "lnurl_pay_info_notification_title"
static let LNURL_PAY_INVOICE_NOTIFICATION_TITLE = "lnurl_pay_invoice_notification_title"
static let LNURL_PAY_METADATA_PLAIN_TEXT = "lnurl_pay_metadata_plain_text"
static let LNURL_PAY_NOTIFICATION_FAILURE_TITLE = "lnurl_pay_notification_failure_title"
static let LNURL_PAY_NOTIFICATION_LIQUIDITY_FAILURE_TITLE = "lnurl_pay_notification_liquidity_failure_title"
static let PAYMENT_RECEIVED_NOTIFICATION_TITLE = "payment_received_notification_title"
static let PAYMENT_RECEIVED_NOTIFICATION_FAILURE_TITLE = "payment_received_notification_failure_title"
static let SWAP_TX_CONFIRMED_NOTIFICATION_TITLE = "swap_tx_confirmed_notification_title"
static let SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE = "swap_tx_confirmed_notification_failure_title"

// Resource Identifier Defaults
static let DEFAULT_LNURL_PAY_INFO_NOTIFICATION_TITLE = "Retrieving Payment Information"
static let DEFAULT_LNURL_PAY_INVOICE_NOTIFICATION_TITLE = "Fetching Invoice"
static let DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT = "Pay with LNURL"
static let DEFAULT_LNURL_PAY_NOTIFICATION_FAILURE_TITLE = "Receive Payment Failed"
static let DEFAULT_LNURL_PAY_NOTIFICATION_LIQUIDITY_FAILURE_TITLE = "Fee Limit Too Low"
static let DEFAULT_PAYMENT_RECEIVED_NOTIFICATION_TITLE = "Received %d sats"
static let DEFAULT_PAYMENT_RECEIVED_NOTIFICATION_FAILURE_TITLE = "Receive Payment Failed"
static let DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_TITLE = "Swap Confirmed"
static let DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE = "Redeem Swap Failed"
}
19 changes: 19 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/ResourceHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

public class ResourceHelper {
public static let shared = ResourceHelper()

private init() {/* must use shared instance */}

public func getString(key: String, fallback: String) -> String {
return getString(key: key, validateContains: nil, fallback: fallback)
}

public func getString(key: String, validateContains: String?, fallback: String) -> String {
if let str = Bundle.main.object(forInfoDictionaryKey: key) as? String {
if validateContains == nil || str.contains(validateContains!) {
return str
}
}
return fallback
}}
109 changes: 109 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/SDKNotificationService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import UserNotifications
import os.log

open class SDKNotificationService: UNNotificationServiceExtension {
fileprivate let TAG = "SDKNotificationService"

var breezSDK: BlockingBreezServices?
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var currentTask: TaskProtocol?
var logger: ServiceLogger = ServiceLogger(logStream: nil)
var config: ServiceConfig = ServiceConfig.default

override init() { }

override open func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.logger.log(tag: TAG, line: "Notification received", level: "INFO")
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

if let config = self.getServiceConfig() {
setConfig(config: config)
}

guard let connectRequest = self.getConnectRequest() else {
if let content = bestAttemptContent {
contentHandler(content)
}
return
}

if let currentTask = self.getTaskFromNotification() {
self.currentTask = currentTask

DispatchQueue.main.async { [self] in
do {
logger.log(tag: TAG, line: "Breez SDK is not connected, connecting...", level: "INFO")
breezSDK = try BreezSDKConnector.register(connectRequest: connectRequest, listener: currentTask)
logger.log(tag: TAG, line: "Breez SDK connected successfully", level: "INFO")
try currentTask.start(breezSDK: breezSDK!)
} catch {
logger.log(tag: TAG, line: "Breez SDK connection failed \(error)", level: "ERROR")
shutdown()
}
}
}
}

open func getConnectRequest() -> ConnectRequest? {
return nil
}

open func getServiceConfig() -> ServiceConfig? {
return nil
}

open func getTaskFromNotification() -> TaskProtocol? {
guard let content = bestAttemptContent else { return nil }
guard let notificationType = content.userInfo[Constants.MESSAGE_DATA_TYPE] as? String else { return nil }
self.logger.log(tag: TAG, line: "Notification payload: \(content.userInfo)", level: "INFO")
self.logger.log(tag: TAG, line: "Notification type: \(notificationType)", level: "INFO")

guard let payload = content.userInfo[Constants.MESSAGE_DATA_PAYLOAD] as? String else {
contentHandler!(content)
return nil
}

self.logger.log(tag: TAG, line: "\(notificationType) data string: \(payload)", level: "INFO")
switch(notificationType) {
case Constants.MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED:
return RedeemSwapTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
case Constants.MESSAGE_TYPE_LNURL_PAY_INFO:
return LnurlPayInfoTask(payload: payload, logger: self.logger, config: self.config, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
case Constants.MESSAGE_TYPE_LNURL_PAY_INVOICE:
return LnurlPayInvoiceTask(payload: payload, logger: self.logger, config: self.config, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
case Constants.MESSAGE_TYPE_PAYMENT_RECEIVED:
return ReceivePaymentTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
default:
return nil
}
}

override open func serviceExtensionTimeWillExpire() {
self.logger.log(tag: TAG, line: "serviceExtensionTimeWillExpire()", level: "INFO")

// iOS calls this function just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
self.shutdown()
}

private func shutdown() -> Void {
self.logger.log(tag: TAG, line: "shutting down...", level: "INFO")
BreezSDKConnector.unregister()
self.logger.log(tag: TAG, line: "task unregistered", level: "INFO")
self.currentTask?.onShutdown()
}

func setLogger(logger: LogStream) {
self.logger = ServiceLogger(logStream: logger)
}

private func setConfig(config: ServiceConfig) {
self.config = config
}
}
20 changes: 20 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/ServiceConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
public struct ServiceConfig {
// MARK: - Constants
public struct Constants {
/// Channel setup fee limit (in msats) for the default instance of ServiceConfig
public static let defaultChannelFeeLimitMsat = UInt64(0)
}

// MARK: - Default instance
/// The default ServiceConfig object
public static let `default`: ServiceConfig = ServiceConfig(channelFeeLimitMsat: ServiceConfig.Constants.defaultChannelFeeLimitMsat)

// MARK: - Properties
/// Channel setup fee limit (in msats)
public var channelFeeLimitMsat: UInt64! = UInt64(0)

// MARK: - Life Cycle
public init(channelFeeLimitMsat: UInt64) {
self.channelFeeLimitMsat = channelFeeLimitMsat
}
}
40 changes: 40 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/ServiceLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation
import os.log

#if DEBUG && true
fileprivate var logger = OSLog(
subsystem: Bundle.main.bundleIdentifier!,
category: "ServiceLogger"
)
#else
fileprivate var logger = OSLog.disabled
#endif

class ServiceLogger {
var logStream: LogStream?

init(logStream: LogStream?) {
self.logStream = logStream
}

func log(tag: String, line: String, level: String) {
if let logger = logStream {
logger.log(l: LogEntry(line: line, level: level))
} else {
switch(level) {
case "ERROR":
os_log("[%{public}@] %{public}@", log: logger, type: .error, tag, line)
break
case "INFO", "WARN":
os_log("[%{public}@] %{public}@", log: logger, type: .info, tag, line)
break
case "TRACE":
os_log("[%{public}@] %{public}@", log: logger, type: .debug, tag, line)
break
default:
os_log("[%{public}@] %{public}@", log: logger, type: .default, tag, line)
return
}
}
}
}
75 changes: 75 additions & 0 deletions ios/bindings-swift/Sources/BreezSDK/Task/LnurlPay.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import UserNotifications
import Foundation

struct LnurlErrorResponse: Decodable, Encodable {
let status: String
let reason: String

init(status: String, reason: String) {
self.status = status
self.reason = reason
}
}

class LnurlPayTask : TaskProtocol {
var payload: String
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var logger: ServiceLogger
var config: ServiceConfig
var successNotificationTitle: String
var failNotificationTitle: String

init(payload: String, logger: ServiceLogger, config: ServiceConfig, contentHandler: ((UNNotificationContent) -> Void)? = nil, bestAttemptContent: UNMutableNotificationContent? = nil, successNotificationTitle: String, failNotificationTitle: String) {
self.payload = payload
self.contentHandler = contentHandler
self.bestAttemptContent = bestAttemptContent
self.logger = logger
self.config = config
self.successNotificationTitle = successNotificationTitle;
self.failNotificationTitle = failNotificationTitle;
}

public func onEvent(e: BreezEvent) {}

func start(breezSDK: BlockingBreezServices) throws {}

func onShutdown() {
displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_LNURL_PAY)
}

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_LNURL_PAY)
return
}
var request = URLRequest(url: serverReplyURL)
request.httpMethod = "POST"
request.httpBody = try! JSONEncoder().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_LNURL_PAY)
} else {
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_LNURL_PAY)
return
}
}
task.resume()
}

func fail(withError: String, replyURL: String, failNotificationTitle: String? = nil) {
if let serverReplyURL = URL(string: replyURL) {
var request = URLRequest(url: serverReplyURL)
request.httpMethod = "POST"
request.httpBody = try! JSONEncoder().encode(LnurlErrorResponse(status: "ERROR", reason: withError))
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let _ = (response as! HTTPURLResponse).statusCode
}
task.resume()
}
var title = failNotificationTitle != nil ? failNotificationTitle! : self.failNotificationTitle
self.displayPushNotification(title: title, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_LNURL_PAY)
}
}
Loading

0 comments on commit 6fc4037

Please sign in to comment.