summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Stibane <marc@taler.net>2024-02-29 14:31:16 +0100
committerMarc Stibane <marc@taler.net>2024-02-29 14:40:26 +0100
commitf39b4cae710ff997ac1415343e6c0fb765e8d4a9 (patch)
treeeccb7bd139463a687b52a06c36fc97769d073b8b
parentd6e2f8425dd0f904c5af1302e862fcfa9703f676 (diff)
downloadtaler-ios-f39b4cae710ff997ac1415343e6c0fb765e8d4a9.tar.gz
taler-ios-f39b4cae710ff997ac1415343e6c0fb765e8d4a9.tar.bz2
taler-ios-f39b4cae710ff997ac1415343e6c0fb765e8d4a9.zip
Native Networking via URLSession.dataTask
-rw-r--r--TalerWallet.xcodeproj/project.pbxproj6
-rw-r--r--TalerWallet1/Quickjs/QuickDataTask.swift156
-rw-r--r--TalerWallet1/Quickjs/quickjs.swift74
3 files changed, 211 insertions, 25 deletions
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
index e5ce46c..410f541 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -244,6 +244,8 @@
4ED80E892B8F5FB8008BD576 /* CStringArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E872B8F5FB8008BD576 /* CStringArray.swift */; };
4ED80E8B2B8F60E7008BD576 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */; };
4ED80E8C2B8F60E7008BD576 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */; };
+ 4ED80E8E2B8F6212008BD576 /* QuickDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */; };
+ 4ED80E8F2B8F6212008BD576 /* QuickDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */; };
4EDBDCD92AB787CB00925C02 /* CallStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD82AB787CB00925C02 /* CallStack.swift */; };
4EDBDCDA2AB787CB00925C02 /* CallStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD82AB787CB00925C02 /* CallStack.swift */; };
4EE171882B49635800BF9FF5 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE171872B49635800BF9FF5 /* MarkdownUI */; };
@@ -431,6 +433,7 @@
4ED2F94A2A278F5100453B40 /* ThreeAmountsV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeAmountsV.swift; sourceTree = "<group>"; };
4ED80E872B8F5FB8008BD576 /* CStringArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CStringArray.swift; sourceTree = "<group>"; };
4ED80E8A2B8F60E7008BD576 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
+ 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickDataTask.swift; sourceTree = "<group>"; };
4EDBDCD82AB787CB00925C02 /* CallStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallStack.swift; sourceTree = "<group>"; };
4EEC118C2B83DE4700146CFF /* AmountInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountInputV.swift; sourceTree = "<group>"; };
4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectInputV.swift; sourceTree = "<group>"; };
@@ -634,6 +637,7 @@
isa = PBXGroup;
children = (
4EB0950D2989CB9A0043A8A1 /* quickjs.swift */,
+ 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */,
);
path = Quickjs;
sourceTree = "<group>";
@@ -1099,6 +1103,7 @@
4E3EAE2B2A990778009F1BE8 /* LoadingView.swift in Sources */,
4E3EAE8C2AA0933C009F1BE8 /* Font+Taler.swift in Sources */,
4E3327BA2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */,
+ 4ED80E8E2B8F6212008BD576 /* QuickDataTask.swift in Sources */,
4E3EAE2C2A990778009F1BE8 /* ManualWithdraw.swift in Sources */,
4E3EAE2D2A990778009F1BE8 /* Model+Exchange.swift in Sources */,
4EBC0F012B7B3CD600C0CB19 /* DepositIbanV.swift in Sources */,
@@ -1214,6 +1219,7 @@
4EB0956D2989CBFE0043A8A1 /* LoadingView.swift in Sources */,
4E3EAE8D2AA0933C009F1BE8 /* Font+Taler.swift in Sources */,
4E3327BB2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */,
+ 4ED80E8F2B8F6212008BD576 /* QuickDataTask.swift in Sources */,
4E50B3502A1BEE8000F9F01C /* ManualWithdraw.swift in Sources */,
4E3B4BC92A42BC4800CC88B8 /* Model+Exchange.swift in Sources */,
4EBC0F022B7B3CD600C0CB19 /* DepositIbanV.swift in Sources */,
diff --git a/TalerWallet1/Quickjs/QuickDataTask.swift b/TalerWallet1/Quickjs/QuickDataTask.swift
new file mode 100644
index 0000000..e4afe88
--- /dev/null
+++ b/TalerWallet1/Quickjs/QuickDataTask.swift
@@ -0,0 +1,156 @@
+/*
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Marc Stibane
+ */
+import Foundation
+//import "Foundation/NSURLError.h"
+import os.log
+
+import FTalerWalletcore
+
+// will be called from wallet-core for networking
+func request_create(userdata: Optional<UnsafeMutableRawPointer>,
+ requestInfo: Optional<UnsafeMutablePointer<JSHttpRequestInfo>>) -> Int32 {
+ let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+
+ if let requestInfo {
+ if let url = URL(string: String(cString: requestInfo.pointee.url)) {
+ let responseCb = requestInfo.pointee.response_cb
+ let responseCbCls = requestInfo.pointee.response_cb_cls
+ let method = String(cString: requestInfo.pointee.method)
+ let requestHeaders = requestInfo.pointee.request_headers
+ let redirect = requestInfo.pointee.redirect // TODO: redirect
+ let timeoutMs = requestInfo.pointee.timeout_ms
+ let debug = requestInfo.pointee.debug // TODO: debug
+ let reqBody = requestInfo.pointee.req_body
+ let bodyLen = requestInfo.pointee.req_body_len
+
+ var request = URLRequest(url: url)
+ request.httpMethod = method
+ request.timeoutInterval = TimeInterval(timeoutMs)
+ if let reqBody { // caller will deallocate the req_body after dataTask finish or cancel
+ let body = Data(bytesNoCopy: reqBody, count: Int(bodyLen), deallocator: .none)
+ request.httpBody = body
+ }
+ if var ptr = requestHeaders {
+ while let cString = ptr.pointee {
+ let string = String(cString: cString)
+ if let index = string.firstIndex(of: ":") {
+ let headerField = string.prefix(upTo: index)
+ let nextIndex = string.index(index, offsetBy: 1) // skip the ":"
+ let value = string.suffix(from: nextIndex)
+ request.addValue(String(value), forHTTPHeaderField: String(headerField))
+ }
+ ptr += 1
+ }
+ }
+
+ return quickjs.reqCreate(request, responseCb, responseCbCls)
+ }
+ }
+ return 0
+}
+
+func request_cancel(userdata: Optional<UnsafeMutableRawPointer>,
+ requestID: Int32) -> Int32 {
+ let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+ return quickjs.reqCancel(requestID)
+}
+// MARK: -
+class QuickDataTask: NSObject {
+ let urlSession: URLSession
+ let request: URLRequest
+ let requestID: Int32
+ var requests: [Int32: QuickDataTask]
+ let responseCb: JSHttpResponseCb?
+ let responseCbCls: Optional<UnsafeMutableRawPointer>
+
+ var dataTask: URLSessionDataTask? = nil
+ var logger: Logger
+
+ init(urlSession: URLSession,
+ request: URLRequest,
+ requestID: Int32,
+ requests: inout [Int32: QuickDataTask],
+ responseCb: JSHttpResponseCb?,
+ responseCbCls: Optional<UnsafeMutableRawPointer>
+ ) {
+ self.logger = Logger(subsystem: "net.taler.gnu", category: "Networking")
+ self.urlSession = urlSession
+ self.request = request
+ self.requestID = requestID
+ self.requests = requests
+ self.responseCb = responseCb
+ self.responseCbCls = responseCbCls
+ }
+ func run() {
+ if let responseCb, let responseCbCls {
+ let method = self.request.httpMethod ?? "Unknown"
+ let url = self.request.url?.absoluteString ?? EMPTYSTRING
+ logger.trace("❓\(self.requestID, privacy: .public) \(method, privacy: .public) \(url, privacy: .public)")
+ dataTask = urlSession.dataTask(with: request) { [self] data, response, error in
+ let err: Error
+ if let response = response as? HTTPURLResponse {
+ var headerArray: [String] = []
+ var numHeaders: Int32 = 0
+ var status = Int32(response.statusCode)
+ var errmsg = HTTPURLResponse.localizedString(forStatusCode: Int(status))
+ var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8))
+ // Initialization of 'UnsafeMutablePointer<CChar>' (aka 'UnsafeMutablePointer<Int8>') results in a dangling pointer
+ let headers = response.allHeaderFields
+ for (key,value) in headers {
+ headerArray.append("\(key): \(value)")
+ numHeaders += 1
+ }
+ let cHeaders = CStringArray(headerArray)
+
+ if let data {
+ var ndata:NSData = data as NSData
+ var bodyPtr = UnsafeMutableRawPointer(mutating: ndata.bytes)
+ var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
+ status: status,
+ errmsg: errmsg_p0,
+ response_headers: cHeaders.pointer,
+ num_response_headers: numHeaders,
+ body: bodyPtr,
+ body_len: data.count)
+ let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+ // Initialization of 'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer
+ logger.trace("❗️ \(self.requestID, privacy: .public) \(url, privacy: .public)")
+ responseCb(responseCbCls, responseInfoPtr)
+ return
+ } else { // data == nil
+ logger.error("‼️\(self.requestID, privacy: .public) \(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, privacy: .public)")
+ var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
+ status: status,
+ errmsg: errmsg_p0,
+ response_headers: cHeaders.pointer,
+ num_response_headers: numHeaders,
+ body: nil,
+ body_len: 0)
+ let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+ responseCb(responseCbCls, responseInfoPtr)
+ }
+ } else {
+ var errmsg = "No http response from \(url)"
+ logger.error("⁉️\(self.requestID, privacy: .public) \(method, privacy: .public) \(errmsg, privacy: .public)")
+ var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8))
+ var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
+ status: 0,
+ errmsg: errmsg_p0,
+ response_headers: nil,
+ num_response_headers: 0,
+ body: nil,
+ body_len: 0)
+ let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+ responseCb(responseCbCls, responseInfoPtr)
+ }
+ requests[requestID] = nil
+ }
+ dataTask?.resume()
+ }
+ }
+}
diff --git a/TalerWallet1/Quickjs/quickjs.swift b/TalerWallet1/Quickjs/quickjs.swift
index 06878af..dbe796d 100644
--- a/TalerWallet1/Quickjs/quickjs.swift
+++ b/TalerWallet1/Quickjs/quickjs.swift
@@ -16,17 +16,17 @@ public protocol QuickjsMessageHandler: AnyObject {
// MARK: -
func notification_callback(userdata: Optional<UnsafeMutableRawPointer>,
payload: Optional<UnsafePointer<Int8>>) {
- let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+ let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
let string = String(cString: payload!)
- native.internalOnNotify(payload: string)
+ quickjs.internalOnNotify(payload: string)
}
func logging_callback(userdata: Optional<UnsafeMutableRawPointer>,
level: TALER_WALLET_LogLevel,
tag: Optional<UnsafePointer<Int8>>,
message: Optional<UnsafePointer<Int8>>) {
- let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
- let logger = native.logger
+ let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+ let logger = quickjs.logger
let swiftTag = String(cString: tag!)
let swiftMessage = String(cString: message!)
@@ -43,14 +43,21 @@ func logging_callback(userdata: Optional<UnsafeMutableRawPointer>,
logger.trace("\(swiftTag, privacy: .public) \(swiftMessage, privacy: .public)")
default: break
}
-// native.internalOnLog(message: swiftMessage)
}
// MARK: -
-public class Quickjs {
+public class Quickjs { // acts as singleton, since only one instance ever exists
var talerWalletInstance: OpaquePointer!
public weak var messageHandler: QuickjsMessageHandler?
var logger: Logger
+ @Atomic(default: 0)
+ private var lastRequestID: Int32
+ private var requests: [Int32: QuickDataTask] = [:]
+
+ private lazy var urlSession: URLSession = {
+ return URLSession(configuration: .default)
+ }()
+
public init() {
self.logger = Logger(subsystem: "net.taler.gnu", category: "QuickJS")
self.talerWalletInstance = TALER_WALLET_create()
@@ -60,18 +67,48 @@ public class Quickjs {
TALER_WALLET_set_log_handler(talerWalletInstance,
logging_callback,
Unmanaged.passUnretained(self).toOpaque())
- let http_curl = js_curl_http_client_create()
- TALER_set_http_client_implementation(talerWalletInstance, http_curl)
+#if USE_HTTP_CLIENT_CURL
+ let http_impl = js_curl_http_client_create()
+#else
+ let http_impl = TALER_pack_http_client_implementation(request_create, request_cancel,
+ Unmanaged.passUnretained(self).toOpaque())
+#endif
+ // http_impl got malloc'd, and could possibly be free'd when the app terminates
+ TALER_set_http_client_implementation(talerWalletInstance, http_impl)
+ // - but we never free anything on termination, thus we don't save http_impl here
TALER_WALLET_run(talerWalletInstance)
}
- deinit {
- // FIXME: TALER_WALLET_destroy
-// TALER_WALLET_destroy(talerWalletInstance)
+ func reqCreate(_ request: URLRequest,
+ _ responseCb: JSHttpResponseCb?,
+ _ responseCbCls: Optional<UnsafeMutableRawPointer>) -> Int32 {
+ let requestID = $lastRequestID.atomicIncrement()
+ let quickDataTask = QuickDataTask(urlSession: urlSession,
+ request: request,
+ requestID: requestID,
+ requests: &requests,
+ responseCb: responseCb,
+ responseCbCls: responseCbCls)
+ quickDataTask.run()
+ requests[requestID] = quickDataTask
+ return requestID
}
+ func reqCancel(_ requestID: Int32) -> Int32 {
+ if let quickDataTask = requests[requestID] {
+ if let dataTask = quickDataTask.dataTask {
+ dataTask.cancel()
+ }
+ }
+ requests[requestID] = nil
+ return 0
+ }
- public func internalOnNotify(payload: String) {
+ deinit {
+ // No need to call TALER_WALLET_destroy - memory gets purged anyway
+ }
+
+ func internalOnNotify(payload: String) {
if let handler = messageHandler {
handler.handleMessage(message: payload)
}
@@ -90,17 +127,4 @@ public class Quickjs {
public func sendMessage(message: String) {
TALER_WALLET_send_request(talerWalletInstance, message)
}
-
- /// Note: This *must* be called before releasing the object, or else the thread will keep going.
-// public func waitStopped() {
-// scheduleNodeThreadSync {
-// self.stopped = true
-// }
-// thread.cancel()
-// }
-
-// public func putModuleCode(modName: String, code: String) {
-// __putModuleCodeNative(self.instance, modName.cString(using: .utf8),
-// code.cString(using: .utf8))
-// }
}