diff --git a/Package.resolved b/Package.resolved index 82f273c..54421ad 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/spruceid/mobile-sdk-rs.git", "state" : { - "revision" : "e8a1b7056989e4cb471281b464c1aeac298be97f", - "version" : "0.0.29" + "revision" : "b1e729b2adc97415019925c873b976648a2331cb", + "version" : "0.0.30" } }, { diff --git a/Package.swift b/Package.swift index 798581f..69c6613 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( targets: ["SpruceIDMobileSdk"]) ], dependencies: [ - .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.29"), + .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.30"), // .package(path: "../mobile-sdk-rs"), .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0") ], diff --git a/Sources/MobileSdk/OID4VCI.swift b/Sources/MobileSdk/OID4VCI.swift new file mode 100644 index 0000000..ef06ab0 --- /dev/null +++ b/Sources/MobileSdk/OID4VCI.swift @@ -0,0 +1,123 @@ +import Foundation + +import SpruceIDMobileSdkRs + +public class Oid4vciSyncHttpClient: SyncHttpClient { + public func httpClient(request: HttpRequest) throws -> HttpResponse { + guard let url = URL(string: request.url) else { + throw HttpClientError.Other(error: "failed to construct URL") + } + + let session = URLSession.shared + var req = URLRequest(url: url, + cachePolicy: .useProtocolCachePolicy, + timeoutInterval: 60) + req.httpMethod = request.method + req.httpBody = request.body + req.allHTTPHeaderFields = request.headers + + // Semaphore used to wait for the callback to complete + let semaphore = DispatchSemaphore(value: 0) + + var data: Data? + var response: URLResponse? + var error: Error? + + let dataTask = session.dataTask(with: req) { + data = $0 + response = $1 + error = $2 + + // Signaling from inside the callback will let the outside function + // know that `data`, `response` and `error` are ready to be read. + semaphore.signal() + } + // Initiate execution of the http request + dataTask.resume() + + // Blocking wait for the callback to signal back + _ = semaphore.wait(timeout: .distantFuture) + + if let error { + throw HttpClientError.Other(error: "failed to execute request: \(error)") + } + + guard let response = response as? HTTPURLResponse else { + throw HttpClientError.Other(error: "failed to parse response") + } + + guard let data = data else { + throw HttpClientError.Other(error: "failed to parse response data") + } + + guard let statusCode = UInt16(exactly: response.statusCode) else { + throw HttpClientError.Other(error: "failed to parse response status code") + } + + let headers = try response.allHeaderFields.map({ (key, value) in + guard let key = key as? String else { + throw HttpClientError.HeaderParse + } + + guard let value = value as? String else { + throw HttpClientError.HeaderParse + } + + return (key, value) + }) + + return HttpResponse( + statusCode: statusCode, + headers: Dictionary(uniqueKeysWithValues: headers), + body: data) + } +} + +public class Oid4vciAsyncHttpClient: AsyncHttpClient { + public func httpClient(request: HttpRequest) async throws -> HttpResponse { + guard let url = URL(string: request.url) else { + throw HttpClientError.Other(error: "failed to construct URL") + } + + let session = URLSession.shared + var req = URLRequest(url: url, + cachePolicy: .useProtocolCachePolicy, + timeoutInterval: 60) + req.httpMethod = request.method + req.httpBody = request.body + req.allHTTPHeaderFields = request.headers + + let data: Data + let response: URLResponse + do { + (data, response) = try await session.data(for: req) + } catch { + throw HttpClientError.Other(error: "failed to execute request: \(error)") + } + + guard let response = response as? HTTPURLResponse else { + throw HttpClientError.Other(error: "failed to parse response") + } + + guard let statusCode = UInt16(exactly: response.statusCode) else { + throw HttpClientError.Other(error: "failed to parse response status code") + } + + let headers = try response.allHeaderFields.map({ (key, value) in + guard let key = key as? String else { + throw HttpClientError.HeaderParse + } + + guard let value = value as? String else { + throw HttpClientError.HeaderParse + } + + return (key, value) + }) + + return HttpResponse( + statusCode: statusCode, + headers: Dictionary(uniqueKeysWithValues: headers), + body: data) + } +}