• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperwallet / hyperwallet-ios-sdk / 15826401626

21 May 2025 02:36PM UTC coverage: 96.705%. Remained the same
15826401626

push

github

web-flow
Initial commit for CodeQL (#152)

* Initial commit for CodeQL

* Updating language

1937 of 2003 relevant lines covered (96.7%)

18.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.63
/Sources/HTTPTransaction.swift
1
//
2
// Copyright 2018 - Present Hyperwallet
3
//
4
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5
// and associated documentation files (the "Software"), to deal in the Software without restriction,
6
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
7
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in all copies or
11
// substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18

19
import Foundation
20
import os.log
21
import UIKit
22

23
/// Builds and performs the HTTP request
24
final class HTTPTransaction {
25
    private let provider: HyperwalletAuthenticationTokenProvider
26
    private let httpClient: HTTPClientProtocol
27
    var configuration: Configuration?
28

29
    init(provider: HyperwalletAuthenticationTokenProvider,
30
         httpClient: HTTPClientProtocol = HTTPClient(configuration: HTTPTransaction.urlSessionConfiguration)) {
33✔
31
        self.provider = provider
33✔
32
        self.httpClient = httpClient
33✔
33
    }
33✔
34

35
    func invalidate() {
4✔
36
        self.httpClient.invalidateSession()
4✔
37
    }
4✔
38

39
    /// Performs the HTTP `GraphQL` request .
40
    ///
41
    /// - Parameters:
42
    ///   - payload: The payload will contain GraphQl query.
43
    ///   - completionHandler: The completionHandler should be performed at the end of request
44
    ///  with the data or `HyperwalletError`.
45
    func performGraphQl<Request, Response>(_ payload: Request,
46
                                           completionHandler handler: @escaping (_ response: Response?,
47
        _ error: HyperwalletErrorType?) -> Void)
48
        where Request: GraphQlQuery, Response: Codable {
16✔
49
            perform(transactionType: .graphQl,
16✔
50
                    httpMethod: .post,
16✔
51
                    payload: payload,
16✔
52
                    completionHandler: HTTPTransaction.graphQlHandler(handler))
16✔
53
    }
16✔
54

55
    /// Performs the HTTP `Rest` request .
56
    ///
57
    ///
58
    /// - Parameters: httpMethod - The HTTP verb should be performed the request.
59
    /// - Parameters: urlPath - The URL path.
60
    /// - Parameters: payload - The payload will contain GraphQl query.
61
    /// - Parameters: queryParam - The criteria to build the URL query.
62
    /// - Parameters: completionHandler - The completionHandler should be performed at the end of request
63
    ///  with the data or `HyperwalletError`.
64
    func performRest<Request, Response>(httpMethod: HTTPMethod,
65
                                        urlPath: String,
66
                                        payload: Request,
67
                                        queryParam: QueryParam? = nil,
68
                                        completionHandler handler: @escaping (_ response: Response?,
69
                                                                              _ error: HyperwalletErrorType?) -> Void)
70
        where Request: Encodable, Response: Decodable {
81✔
71
            perform(transactionType: .rest,
81✔
72
                    httpMethod: httpMethod,
81✔
73
                    urlPath: urlPath,
81✔
74
                    payload: payload,
81✔
75
                    urlQuery: queryParam?.toQuery(),
81✔
76
                    completionHandler: handler)
81✔
77
    }
81✔
78

79
    /// Performs the HTTP `transactionType` request.
80
    ///
81
    /// - Parameters:
82
    ///   - transactionType: The HTTP `transactionType` request.
83
    ///   - httpMethod: The HTTP verb.
84
    ///   - urlPath: The URL path.
85
    ///   - payload: The request payload.
86
    ///   - urlQuery: The URL query.
87
    ///   - handler: The completion handler should be performed at the end of request with the data or `ErrorType`.
88
    private func perform<Request, Response>(transactionType: TransactionType,
89
                                            httpMethod: HTTPMethod,
90
                                            urlPath: String = "",
91
                                            payload: Request?,
92
                                            urlQuery: [String: String]? = nil,
93
                                            completionHandler handler: @escaping ((_ response: Response?,
94
        _ error: HyperwalletErrorType?) -> Void))
95
        where Request: Encodable, Response: Decodable {
97✔
96
            if let configuration = configuration, !configuration.isTokenStale() {
97✔
97
                performRequest(transactionType, httpMethod, urlPath, urlQuery, payload, handler)
80✔
98
            } else {
80✔
99
                provider.retrieveAuthenticationToken(completionHandler:
17✔
100
                    authenticationTokenHandler(
17✔
101
                        transactionType,
17✔
102
                        httpMethod,
17✔
103
                        urlPath,
17✔
104
                        urlQuery,
17✔
105
                        payload,
17✔
106
                        handler))
17✔
107
            }
17✔
108
    }
97✔
109

110
    /// Performs the HTTP request
111
    ///
112
    /// - Parameters:
113
    ///   - transactionType: The transaction type defines the base url
114
    ///   - httpMethod: The HTTP verb should be performed the request
115
    ///   - urlPath: The URL path
116
    ///   - urlQuery: The URL query
117
    ///   - payload: The payload be sent in the request
118
    ///   - completionHandler: The completionHandler should be performed at the end of request
119
    private func performRequest<Request, Response>(_ transactionType: TransactionType,
120
                                                   _ httpMethod: HTTPMethod,
121
                                                   _ urlPath: String,
122
                                                   _ urlQuery: [String: String]? = nil,
123
                                                   _ payload: Request?,
124
                                                   _ completionHandler: @escaping (_ response: Response?,
125
                                                                                _ error: HyperwalletErrorType?) -> Void)
126
        where Request: Encodable, Response: Decodable {
94✔
127
            if let configuration = configuration {
94✔
128
                do {
94✔
129
                    let request = try transactionType.createRequest(configuration,
94✔
130
                                                                    method: httpMethod,
94✔
131
                                                                    urlPath: urlPath,
94✔
132
                                                                    urlQuery: urlQuery,
94✔
133
                                                                    httpBody: payload)
94✔
134
                    httpClient.perform(with: request,
93✔
135
                                       completionHandler: HTTPTransaction.requestHandler(completionHandler))
93✔
136
                } catch {
93✔
137
                    completionHandler(nil, ErrorTypeHelper.invalidRequest(for: error))
1✔
138
                }
94✔
139
            }
94✔
140
    }
94✔
141

142
    private static func graphQlHandler<Response>(_ completionHandler:
143
        @escaping (_ response: Response?,
144
        _ error: HyperwalletErrorType?) -> Void)
145
        -> (GraphQlResult<Response>?, HyperwalletErrorType?) -> Void
146
        where Response: Decodable { { (result, error) in
16✔
147
                if let error = error {
16✔
148
                    completionHandler(nil, error)
6✔
149
                }
6✔
150
                if let result = result {
16✔
151
                    if let graphQlErrors = result.errors, !graphQlErrors.isEmpty {
10✔
152
                        _ = ErrorTypeHelper.graphQlErrors(errors: graphQlErrors)
2✔
153
                    }
2✔
154
                    completionHandler(result.data, nil)
10✔
155
                }
10✔
156
            }
16✔
157
    }
16✔
158

159
    /// Handles the callback has been performed by HTTPClient
160
    static func requestHandler<Response>( _ completionHandler: @escaping (_ response: Response?,
161
                                                                          _ error: HyperwalletErrorType?) -> Void)
162
        -> HTTPClientProtocol.ResultHandler where Response: Decodable { { (data, response, error) in
102✔
163
                // Check the transport error has occurred;
102✔
164
                guard error == nil, let httpResponse = response as? HTTPURLResponse else {
102✔
165
                    completionHandler(nil, ErrorTypeHelper.connectionError(for: error))
6✔
166
                    return
6✔
167
                }
96✔
168
                // HTTP 204 - No Content
96✔
169
                if httpResponse.statusCode == 204 {
96✔
170
                    completionHandler(nil, nil)
11✔
171
                    return
11✔
172
                }
85✔
173
                // Check the data
85✔
174
                guard let data = data,
85✔
175
                    let mimeType = httpResponse.mimeType else {
85✔
176
                        completionHandler(nil, ErrorTypeHelper.unexpectedError())
1✔
177
                        return
1✔
178
                }
84✔
179
                // Check the MIME type is an expected value
84✔
180
                if mimeType.range(of: contentType) == nil {
84✔
181
                    completionHandler(nil, ErrorTypeHelper.unexpectedError(message:
1✔
182
                        "Invalid Content-Type specified in Response Header"))
1✔
183
                    return
1✔
184
                }
83✔
185

83✔
186
                parseResponse(httpResponse.statusCode, data, completionHandler)
83✔
187
            }
83✔
188
    }
102✔
189
    /// Parse the response HTTP Code 2xx to `Response.self` object or `HyperwalletErrors.self`.
190
    static func parseResponse<Response>(_ statusCode: Int,
191
                                        _ data: Data,
192
                                        _ completionHandler: @escaping (_ response: Response?,
193
                                                                        _ error: HyperwalletErrorType?) -> Void)
194
        where Response: Decodable {
83✔
195
            do {
83✔
196
                if (200 ..< 300).contains(statusCode) {
83✔
197
                    var responseObject: Response
58✔
198
                    responseObject = try JSONDecoder().decode(Response.self, from: data)
58✔
199
                    completionHandler(responseObject, nil)
57✔
200
                } else { // Handle error response
57✔
201
                    let hyperwalletErrors = try JSONDecoder().decode(HyperwalletErrors.self, from: data)
25✔
202
                    completionHandler(nil, HyperwalletErrorType.http(hyperwalletErrors, statusCode))
25✔
203
                }
82✔
204
            } catch {
82✔
205
                 completionHandler(nil, ErrorTypeHelper.parseError())
1✔
206
            }
83✔
207
    }
83✔
208

209
    func authenticationTokenHandler<Request, Response>(_ transaction: TransactionType,
210
                                                       _ method: HTTPMethod,
211
                                                       _ urlPath: String,
212
                                                       _ urlQuery: [String: String]? = nil,
213
                                                       _ payload: Request?,
214
                                                       _ completionHandler: @escaping ((_ response: Response?,
215
                                                                                _ error: HyperwalletErrorType?) -> Void)
216
        ) -> HyperwalletAuthenticationTokenProvider.CompletionHandler
217
        where Request: Encodable, Response: Decodable { { [weak self] (authenticationToken, error) in
18✔
218
            guard let strongSelf = self else {
18✔
219
                completionHandler(nil, ErrorTypeHelper.transactionAborted())
×
220
                return
×
221
            }
18✔
222

18✔
223
            guard error == nil else {
18✔
224
                completionHandler(nil, ErrorTypeHelper.authenticationError(
1✔
225
                    message: "Error occured while retrieving authentication token",
1✔
226
                    for: error as? HyperwalletAuthenticationErrorType ?? HyperwalletAuthenticationErrorType
1✔
227
                        .unexpected("Authentication token cannot be retrieved"))
×
228
                )
1✔
229
                return
1✔
230
            }
17✔
231

17✔
232
            do {
17✔
233
                strongSelf.configuration = try AuthenticationTokenDecoder.decode(from: authenticationToken)
17✔
234
                if let configuration = strongSelf.configuration, !configuration.isTokenExpired() {
16✔
235
                    strongSelf.performRequest(transaction, method, urlPath, urlQuery, payload, completionHandler)
14✔
236
                } else {
14✔
237
                    completionHandler(nil, ErrorTypeHelper.notInitialized())
2✔
238
                }
2✔
239
            } catch let error as HyperwalletErrorType {
16✔
240
                completionHandler(nil, error)
1✔
241
            } catch {
1✔
242
                completionHandler(nil, ErrorTypeHelper.unexpectedError(for: error))
×
243
            }
17✔
244
        }
17✔
245
    }
18✔
246

247
    private static let sdkVersion: String = {
1✔
248
        Bundle(for: Hyperwallet.self).infoDictionary?["TAG_VERSION"] as? String ?? "Unknown"
1✔
249
    }()
1✔
250

251
    /// Returns the `User-Agent` header.
252
    /// Returns the Hyperwallet SDK `User-Agent` header.
253
    ///
254
    /// Example: HyperwalletSDK/iOS/1.0.1; App: HyperwalletApp ; iOS: 11.3
255
    private static var userAgent: String = {
1✔
256
        guard let info = Bundle(for: Hyperwallet.self).infoDictionary
1✔
257
            else { return "Hyperwallet/iOS/UnknownVersion" }
1✔
258

1✔
259
        let version = ProcessInfo.processInfo.operatingSystemVersion
1✔
260
        let osVersion = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
1✔
261
        var displayName = info["CFBundleDisplayName"] as? String ?? "Unknown"
1✔
262
        if let appInfo = Bundle.main.infoDictionary as NSDictionary?,
1✔
263
            let appDisplayName = appInfo["CFBundleDisplayName"] as? String {
1✔
264
            displayName = appDisplayName
×
265
        }
×
266
        let sdkBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
1✔
267
        let deviceName = UIDevice.current.model
1✔
268

1✔
269
        return "HyperwalletSDK/iOS/\(sdkVersion); App: \(displayName); iOS: \(osVersion); \(deviceName)"
1✔
270
    }()
1✔
271

272
    /// Returns the accept content type.
273
    private static let contentType: String = {
1✔
274
        "application/json"
1✔
275
    }()
1✔
276

277
    /// Returns the default timeout - 30 seconds
278
    private static let defaultTimeout: Double = {
1✔
279
        30.0
1✔
280
    }()
1✔
281

282
    /// Returns `Accept-Language` header, generated by querying `Locale` for the user's `preferredLanguages`.
283
    private static let acceptLanguage: String = {
1✔
284
        Locale.preferredLanguages.prefix(6).first ?? "en-US"
1✔
285
    }()
1✔
286

287
    /// Returns the sdk type.
288
    private static let sdkType: String = {
1✔
289
        "ios"
1✔
290
    }()
1✔
291

292
    /// Builds the HTTP header configuration
293
    static let urlSessionConfiguration: URLSessionConfiguration = {
1✔
294
        let configuration = URLSessionConfiguration.ephemeral
1✔
295
        configuration.timeoutIntervalForResource = HTTPTransaction.defaultTimeout
1✔
296
        configuration.timeoutIntervalForRequest = HTTPTransaction.defaultTimeout
1✔
297
        configuration.httpAdditionalHeaders = [
1✔
298
            "User-Agent": userAgent,
1✔
299
            "x-sdk-version": sdkVersion,
1✔
300
            "x-sdk-type": sdkType,
1✔
301
            "x-sdk-contextId": UUID().uuidString,
1✔
302
            "Accept-Language": acceptLanguage,
1✔
303
            "Accept": contentType,
1✔
304
            "Content-Type": contentType
1✔
305
        ]
1✔
306
        return configuration
1✔
307
    }()
1✔
308
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc