taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit b06bbaa6fe32f2642f505b5355720fd1b04ed1b4
parent 410dc5caf926e52e2082ce47521ef684f89d2931
Author: Marc Stibane <marc@taler.net>
Date:   Mon,  2 Jun 2025 15:56:46 +0200

Copy JSON

Diffstat:
MTalerWallet1/Backend/WalletCore.swift | 40++++++++++++++++++++++++----------------
MTalerWallet1/Model/WalletModel.swift | 23+++++++++++++++++++++--
MTalerWallet1/Views/HelperViews/CopyShare.swift | 15++++++++++++++-
MTalerWallet1/Views/Transactions/TransactionSummaryV.swift | 17+++++++++++++++++
4 files changed, 76 insertions(+), 19 deletions(-)

diff --git a/TalerWallet1/Backend/WalletCore.swift b/TalerWallet1/Backend/WalletCore.swift @@ -30,7 +30,7 @@ class WalletCore: QuickjsMessageHandler { private let quickjs: Quickjs private var requestsMade: UInt // counter for array of completion closures - private var completions: [UInt : (Date, (UInt, Date, Data?, WalletBackendResponseError?) -> Void)] = [:] + private var completions: [UInt : (Date, (UInt, Date, String?, Data?, WalletBackendResponseError?) -> Void)] = [:] var delegate: WalletBackendDelegate? var versionInfo: VersionInfo? // shown in SettingsView @@ -101,7 +101,7 @@ class WalletCore: QuickjsMessageHandler { } // MARK: - completionHandler functions extension WalletCore { - private func handleError(_ decoded: ResponseOrNotification) throws { + private func handleError(_ decoded: ResponseOrNotification, _ message: String?) throws { guard let requestId = decoded.id else { logger.error("didn't find requestId in error response") // TODO: show error alert @@ -119,18 +119,18 @@ extension WalletCore { let responseCode = walletError.errorResponse?.code ?? 0 logger.error("wallet-core sent back error \(walletError.code, privacy: .public), \(responseCode, privacy: .public) for request \(requestId, privacy: .public)") symLog.log("id:\(requestId) \(walletError)") - completion(requestId, timeSent, jsonData, walletError) + completion(requestId, timeSent, message, jsonData, walletError) } catch { // JSON encoding of response.result failed / should never happen symLog.log(decoded) logger.error("cannot encode wallet-core Error") - completion(requestId, timeSent, nil, WalletCore.parseFailureError()) + completion(requestId, timeSent, message, nil, WalletCore.parseFailureError()) } } else { // JSON decoding of error message failed - completion(requestId, timeSent, nil, WalletCore.parseFailureError()) + completion(requestId, timeSent, message, nil, WalletCore.parseFailureError()) } } - private func handleResponse(_ decoded: ResponseOrNotification) throws { + private func handleResponse(_ decoded: ResponseOrNotification, _ message: String?) throws { guard let requestId = decoded.id else { logger.error("didn't find requestId in response") symLog.log(decoded) // TODO: .error @@ -149,10 +149,10 @@ extension WalletCore { let jsonData = try JSONEncoder().encode(result) // symLog.log("\"id\":\(requestId) \(result)") // logger.info(result) TODO: log result - completion(requestId, timeSent, jsonData, nil) + completion(requestId, timeSent, message, jsonData, nil) } catch { // JSON encoding of response.result failed / should never happen symLog.log(result) // TODO: .error - completion(requestId, timeSent, nil, WalletCore.parseResponseError()) + completion(requestId, timeSent, message, nil, WalletCore.parseResponseError()) } } @@ -429,10 +429,10 @@ extension WalletCore { switch decoded.type { case "error": symLog.log("\"id\":\(decoded.id ?? 0) \(message)") - try handleError(decoded) + try handleError(decoded, message) case "response": symLog.log(message) - try handleResponse(decoded) + try handleResponse(decoded, message) case "notification": // symLog.log(message) try handleNotification(decoded.payload, message) @@ -461,7 +461,7 @@ extension WalletCore { } } - private func encodeAndSend(_ request: WalletBackendRequest, completionHandler: @escaping (UInt, Date, Data?, WalletBackendResponseError?) -> Void) { + private func encodeAndSend(_ request: WalletBackendRequest, completionHandler: @escaping (UInt, Date, String?, Data?, WalletBackendResponseError?) -> Void) { // Encode the request and send it to the backend. queue.async { self.semaphore.wait() // guard access to requestsMade @@ -487,7 +487,7 @@ extension WalletCore { self.semaphore.signal() // free requestsMade self.logger.error("\(error.localizedDescription)") // self.symLog.log(error) - completionHandler(requestId, sendTime, nil, WalletCore.serializeRequestError()); + completionHandler(requestId, sendTime, nil, nil, WalletCore.serializeRequestError()); } } } @@ -495,11 +495,11 @@ extension WalletCore { // MARK: - async / await function extension WalletCore { /// send async requests to wallet-core - func sendFormattedRequest<T: WalletBackendFormattedRequest> (_ request: T) async throws -> (T.Response, UInt) { + func sendFormattedRequest<T: WalletBackendFormattedRequest> (_ request: T, asJSON: Bool = false) async throws -> (T.Response, UInt) { let reqData = WalletBackendRequest(operation: request.operation(), args: AnyEncodable(request.args())) return try await withCheckedThrowingContinuation { continuation in - encodeAndSend(reqData) { [self] requestId, timeSent, result, error in + encodeAndSend(reqData) { [self] requestId, timeSent, message, result, error in let timeUsed = Date.now - timeSent let millisecs = timeUsed.milliseconds if let error { @@ -512,8 +512,16 @@ extension WalletCore { var err: Error? = nil if let json = result, error == nil { do { - let decoded = try JSONDecoder().decode(T.Response.self, from: json) - continuation.resume(returning: (decoded, requestId)) + if asJSON { + if let message { + continuation.resume(returning: (message as! T.Response, requestId)) + } else { + continuation.resume(throwing: TransactionDecodingError.invalidStringValue) + } + } else { + let decoded = try JSONDecoder().decode(T.Response.self, from: json) + continuation.resume(returning: (decoded, requestId)) + } return } catch DecodingError.dataCorrupted(let context) { logger.error("\(context.debugDescription)") diff --git a/TalerWallet1/Model/WalletModel.swift b/TalerWallet1/Model/WalletModel.swift @@ -132,14 +132,14 @@ class WalletModel: ObservableObject { } } - func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, viewHandles: Bool = false) + func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, viewHandles: Bool = false, asJSON: Bool = false) async throws -> T.Response { // T for any Thread #if !DEBUG logger.log("sending: \(request.operation(), privacy: .public)") #endif let sendTime = Date.now do { - let (response, id) = try await WalletCore.shared.sendFormattedRequest(request) + let (response, id) = try await WalletCore.shared.sendFormattedRequest(request, asJSON: asJSON) #if !DEBUG let timeUsed = Date.now - sendTime logger.log("received: \(request.operation(), privacy: .public) (\(id, privacy: .public)) after \(timeUsed.milliseconds, privacy: .public) ms") @@ -193,6 +193,20 @@ fileprivate struct GetTransactionById: WalletBackendFormattedRequest { } } +fileprivate struct JSONTransactionById: WalletBackendFormattedRequest { + typealias Response = String + func operation() -> String { "getTransactionById" } + func args() -> Args { Args(transactionId: transactionId, includeContractTerms: withTerms) } + + var transactionId: String + var withTerms: Bool? + + struct Args: Encodable { + var transactionId: String + var includeContractTerms: Bool? + } +} + extension WalletModel { /// get the specified transaction from Wallet-Core. No networking involved nonisolated func getTransactionById(_ transactionId: String, withTerms: Bool? = nil, viewHandles: Bool = false) @@ -200,6 +214,11 @@ extension WalletModel { let request = GetTransactionById(transactionId: transactionId, withTerms: withTerms) return try await sendRequest(request, viewHandles: viewHandles) } + nonisolated func jsonTransactionById(_ transactionId: String, withTerms: Bool? = nil, viewHandles: Bool = false) + async throws -> String { + let request = JSONTransactionById(transactionId: transactionId, withTerms: withTerms) + return try await sendRequest(request, viewHandles: viewHandles, asJSON: true) + } } // MARK: - /// The info returned from Wallet-core init diff --git a/TalerWallet1/Views/HelperViews/CopyShare.swift b/TalerWallet1/Views/HelperViews/CopyShare.swift @@ -13,10 +13,23 @@ struct CopyButton: View { private let symLog = SymLogV(0) let textToCopy: String let vertical: Bool + let title: String? @Environment(\.isEnabled) private var isEnabled: Bool @EnvironmentObject private var controller: Controller + init(textToCopy: String, vertical: Bool) { + self.textToCopy = textToCopy + self.vertical = vertical + self.title = nil + } + + init(textToCopy: String, title: String) { + self.textToCopy = textToCopy + self.vertical = false + self.title = title + } + func copyAction() -> Void { symLog.log(textToCopy) controller.hapticFeedback(.medium) @@ -38,7 +51,7 @@ struct CopyButton: View { HStack { Image(systemName: "doc.on.doc") .accessibility(hidden: true) - Text(longCopy) + Text(title ?? longCopy) } } } diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift @@ -55,6 +55,7 @@ struct TransactionSummaryV: View { @State private var ignoreThis: Bool = false @State private var didDelete: Bool = false @State var transaction = TalerTransaction(dummyCurrency: DEMOCURRENCY) + @State var jsonTransaction: String = "" @State var viewId = UUID() @Namespace var topID @@ -66,8 +67,17 @@ struct TransactionSummaryV: View { if outTransaction != nil { outTransaction = reloadedTransaction } + if developerMode { + if let json = try? await model.jsonTransactionById(transactionId, + withTerms: true, viewHandles: false) { + jsonTransaction = json + } else { + jsonTransaction = "" + } + } } else { withAnimation{ transaction = TalerTransaction(dummyCurrency: DEMOCURRENCY); viewId = UUID() } + jsonTransaction = "" } } @@ -138,6 +148,7 @@ struct TransactionSummaryV: View { warning: nil, didExecute: $ignoreThis, action: suspendAction) + .listRowSeparator(.hidden) } } if transaction.isResumable { if let resumeAction { TransactionButton(transactionId: common.transactionId, @@ -145,6 +156,7 @@ struct TransactionSummaryV: View { warning: nil, didExecute: $ignoreThis, action: resumeAction) + .listRowSeparator(.hidden) } } } // Suspend + Resume buttons Group { @@ -182,6 +194,11 @@ struct TransactionSummaryV: View { vLayout } } else { vLayout } // view for iOS 15 + if developerMode { + if !jsonTransaction.isEmpty { + CopyButton(textToCopy: jsonTransaction, title: "Copy JSON") + } + } } .listRowSeparator(.hidden) .talerFont(.title) .onAppear { // doesn't work - view still jumps