lnlink

iOS app for connecting to lightning nodes
git clone git://jb55.com/lnlink
Log | Files | Refs | Submodules | README | LICENSE

commit 6e3e784be868d96aed0b3993dad30549018ee2ca
parent 654a8b28b45ea0bff0f109f7c052ecf3021b51eb
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 11 Mar 2022 20:06:21 -0800

handle lnlink: and lightning: urls

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mlightninglink/Info.plist | 28+++++++++++++++++++++++++++-
Mlightninglink/Views/ContentView.swift | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mlightninglink/Views/SetupView.swift | 17+++++++++++------
3 files changed, 99 insertions(+), 21 deletions(-)

diff --git a/lightninglink/Info.plist b/lightninglink/Info.plist @@ -1,5 +1,31 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> -<dict/> +<dict> + <key>CFBundleURLTypes</key> + <array> + <dict> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>CFBundleURLIconFile</key> + <string> q</string> + <key>CFBundleURLName</key> + <string>com.jb55.lightning</string> + <key>CFBundleURLSchemes</key> + <array> + <string>lightning</string> + </array> + </dict> + <dict> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>CFBundleURLName</key> + <string>com.jb55.lnlink</string> + <key>CFBundleURLSchemes</key> + <array> + <string>lnlink</string> + </array> + </dict> + </array> +</dict> </plist> diff --git a/lightninglink/Views/ContentView.swift b/lightninglink/Views/ContentView.swift @@ -82,13 +82,15 @@ struct ContentView: View { @State private var dashboard: Dashboard @State private var funds: Funds @State private var is_reset: Bool = false + @State private var scan_invoice: String? - private var lnlink: LNLink + private let lnlink: LNLink - init(dashboard: Dashboard, lnlink: LNLink) { + init(dashboard: Dashboard, lnlink: LNLink, scan_invoice: String?) { self.dashboard = dashboard self.lnlink = lnlink self.has_alert = false + self.scan_invoice = scan_invoice self.funds = Funds.from_listfunds(fs: dashboard.funds) } @@ -193,18 +195,7 @@ struct ContentView: View { CodeScannerView(codeTypes: SCAN_TYPES) { res in switch res { case .success(let scan_res): - let code = scan_res.string - var invstr: String = code - if code.starts(with: "lightning:") { - let index = code.index(code.startIndex, offsetBy: 10) - invstr = String(code[index...]) - } - invstr = invstr.trimmingCharacters(in: .whitespacesAndNewlines) - let m_parsed = parseInvoiceString(invstr) - guard let parsed = m_parsed else { - return - } - self.active_sheet = .pay(parsed, invstr) + handle_scan(scan_res.string) case .failure: self.active_sheet = nil @@ -228,12 +219,37 @@ struct ContentView: View { .onReceive(NotificationCenter.default.publisher(for: .donate)) { _ in self.active_sheet = .pay(.offer, "lno1pfsycnjvd9hxkgrfwvsxvun9v5s8xmmxw3mkzun9yysyyateypkk2grpyrcflrd6ypek7gzfyp3kzm3qvdhkuarfde6k2grd0ysxzmrrda5x7mrfwdkj6en4v4kx2epqvdhkg6twvusxzerkv4h8gatjv4eju9q2d3hxc6twdvhxzursrcs08sggen2ndwzjdpqlpfw9sgfth8n9sjs7kjfssrnurnp5lqk66u0sgr32zxwrh0kmxnvmt5hyn0my534209573mp9ck5ekvywvugm5x3kq8ztex8yumafeft0arh6dke04jqgckmdzekqxegxzhecl23lurrj") } + .onOpenURL() { url in + handle_scan(url.absoluteString) + } + .onAppear() { + if scan_invoice != nil { + handle_scan(scan_invoice!) + scan_invoice = nil + } + } .navigationBarTitle("", displayMode: .inline) .navigationBarHidden(true) } } + + func handle_scan(_ str: String) { + switch handle_qrcode(str) { + case .left(let err): + print("scan error: \(err)") + break + case .right(let scanres): + switch scanres { + case .lightning(let decode, let invstr): + self.active_sheet = .pay(decode, invstr) + case .lnlink: + print("got a lnlink, not an invoice") + // TODO: report that this is an lnlink, not an invoice + } + } + } var body: some View { if is_reset { @@ -254,6 +270,37 @@ struct ContentView_Previews: PreviewProvider { } */ +public enum LNScanResult { + case lightning(DecodeType, String) + case lnlink(LNLink) +} + + +func handle_qrcode(_ qr: String) -> Either<String, LNScanResult> { + var invstr = qr.trimmingCharacters(in: .whitespacesAndNewlines) + let lowered = invstr.lowercased() + + if lowered.starts(with: "lnlink:") { + switch parse_auth_qr(invstr) { + case .left(let err): + return .left(err) + case .right(let lnlink): + return .right(.lnlink(lnlink)) + } + } + + if lowered.starts(with: "lightning:") { + let index = invstr.index(invstr.startIndex, offsetBy: 10) + invstr = String(lowered[index...]) + } + + guard let parsed = parseInvoiceString(invstr) else { + return .left("Failed to parse invoice") + } + + return .right(.lightning(parsed, invstr)) +} + func get_clipboard_invoice() -> (DecodeType, String)? { guard let inv = UIPasteboard.general.string else { diff --git a/lightninglink/Views/SetupView.swift b/lightninglink/Views/SetupView.swift @@ -47,6 +47,7 @@ struct SetupView: View { @State var error: String? = nil @State var dashboard: Dashboard = .empty @State var lnlink: LNLink? = nil + @State var scan_invoice: String? = nil func perform_validation(_ lnlink: LNLink) { DispatchQueue.global(qos: .background).async { @@ -101,6 +102,9 @@ struct SetupView: View { Link("What the heck is LNLink?", destination: URL(string:"http://lnlink.app/qr")!) } .padding() + .onOpenURL() { url in + self.scan_invoice = url.absoluteString + } .sheet(item: $active_sheet) { active_sheet in switch active_sheet { case .qr: @@ -128,12 +132,16 @@ struct SetupView: View { } func validating_view(lnlink: LNLink) -> some View { - Text("Connecting...") + ProgressView() + .progressViewStyle(.circular) .onAppear() { self.perform_validation(lnlink) } + .onOpenURL() { url in + self.scan_invoice = url.absoluteString + } } - + var body: some View { Group { switch self.state { @@ -142,7 +150,7 @@ struct SetupView: View { case .validating(let lnlink): validating_view(lnlink: lnlink) case .validated: - ContentView(dashboard: self.dashboard, lnlink: self.lnlink!) + ContentView(dashboard: self.dashboard, lnlink: self.lnlink!, scan_invoice: self.scan_invoice) } } } @@ -166,9 +174,6 @@ func parse_auth_qr(_ qr: String) -> Either<String, LNLink> { auth_qr = qr.replacingOccurrences(of: "lnlink:", with: "lnlink://") } - // some qrcodes are weird like this - auth_qr = auth_qr.trimmingCharacters(in: .whitespacesAndNewlines) - guard let url = URL(string: auth_qr) else { return .left("Invalid url") }