commit b06bbaa6fe32f2642f505b5355720fd1b04ed1b4
parent 410dc5caf926e52e2082ce47521ef684f89d2931
Author: Marc Stibane <marc@taler.net>
Date: Mon, 2 Jun 2025 15:56:46 +0200
Copy JSON
Diffstat:
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