-
Notifications
You must be signed in to change notification settings - Fork 295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[v7] Make APIClient Non-optional & Update BTAmericanExpressClient
Init
#1527
base: v7
Are you sure you want to change the base?
Changes from 11 commits
2726050
6b6e23b
8637752
1f41cad
eaa1e04
1eb4fea
cca4160
370bb58
1159d5b
0ae85a7
e6f01c3
703725c
15bed08
3de9709
c627783
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Foundation | ||
|
||
/// An invalid authorization type | ||
class InvalidAuthorization: ClientAuthorization { | ||
|
||
let type = AuthorizationType.invalidAuthorization | ||
let configURL: URL | ||
let bearer: String | ||
let originalValue: String | ||
Comment on lines
+6
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of these values are actually going to be used / we're just setting these properties to conform to the protocol but never using them functionally. I crossed this bridge when messing around with this in this branch. I wonder if there's a cleaner pattern. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potentially but I do think there is some value in enforcing them for the valid auth types. Since they won't be used it seems harmless. We also do something similar on Android. |
||
|
||
init(_ rawValue: String) { | ||
self.bearer = rawValue | ||
self.originalValue = rawValue | ||
|
||
// swiftlint:disable force_unwrapping | ||
/// This URL is never used in the SDK as we always return an error if the authorization type is `.invalidAuthorization` | ||
/// before construting or using the `configURL` in any way | ||
self.configURL = URL(string: "https://paypal.com")! | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,5 +1,6 @@ | ||||
import Foundation | ||||
|
||||
// swiftlint:disable type_body_length file_length | ||||
/// This class acts as the entry point for accessing the Braintree APIs via common HTTP methods performed on API endpoints. | ||||
/// - Note: It also manages authentication via tokenization key and provides access to a merchant's gateway configuration. | ||||
@objcMembers public class BTAPIClient: NSObject, BTHTTPNetworkTiming { | ||||
|
@@ -12,7 +13,7 @@ import Foundation | |||
|
||||
/// The TokenizationKey or ClientToken used to authorize the APIClient | ||||
public var authorization: ClientAuthorization | ||||
|
||||
/// Client metadata that is used for tracking the client session | ||||
public private(set) var metadata: BTClientMetadata | ||||
|
||||
|
@@ -34,7 +35,7 @@ import Foundation | |||
public init?(authorization: String) { | ||||
self.metadata = BTClientMetadata() | ||||
|
||||
guard let authorizationType = Self.authorizationType(for: authorization) else { return nil } | ||||
let authorizationType = Self.authorizationType(for: authorization) | ||||
|
||||
switch authorizationType { | ||||
case .tokenizationKey: | ||||
|
@@ -50,6 +51,8 @@ import Foundation | |||
} catch { | ||||
return nil | ||||
} | ||||
case .invalidAuthorization: | ||||
return nil | ||||
} | ||||
|
||||
let btHttp = BTHTTP(authorization: self.authorization) | ||||
|
@@ -65,6 +68,31 @@ import Foundation | |||
// No-op | ||||
} | ||||
} | ||||
|
||||
// TODO: rename param to authorization in final PR - set as newAuthorization currently since otherwise the two inits have the same signature | ||||
/// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. | ||||
/// Initialize a new API client. | ||||
/// - Parameter authorization: Your tokenization key or client token. | ||||
@_documentation(visibility: private) | ||||
public init(newAuthorization: String) { | ||||
self.authorization = Self.authorization(from: newAuthorization) | ||||
self.metadata = BTClientMetadata() | ||||
|
||||
let btHTTP = BTHTTP(authorization: self.authorization) | ||||
jaxdesmarais marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
http = btHTTP | ||||
configurationLoader = ConfigurationLoader(http: btHTTP) | ||||
|
||||
super.init() | ||||
|
||||
analyticsService.setAPIClient(self) | ||||
jaxdesmarais marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
http?.networkTimingDelegate = self | ||||
|
||||
// Kickoff the background request to fetch the config | ||||
fetchOrReturnRemoteConfiguration { _, _ in | ||||
// No-op | ||||
} | ||||
} | ||||
|
||||
|
||||
// MARK: - Deinit | ||||
|
||||
|
@@ -93,20 +121,28 @@ import Foundation | |||
@_documentation(visibility: private) | ||||
public func fetchOrReturnRemoteConfiguration(_ completion: @escaping (BTConfiguration?, Error?) -> Void) { | ||||
// TODO: - Consider updating all feature clients to use async version of this method? | ||||
|
||||
Task { @MainActor in | ||||
do { | ||||
let configuration = try await configurationLoader.getConfig() | ||||
setupHTTPCredentials(configuration) | ||||
completion(configuration, nil) | ||||
} catch { | ||||
completion(nil, error) | ||||
|
||||
if authorization.type == .invalidAuthorization { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On android we do this check in each request (POST, GET, etc). Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could miss something by nesting it within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO it will be better to call the auth string checks explicitly, vs relying on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, nvm we can still do them before that network call. Updated here: 15bed08 and checked that |
||||
completion(nil, BTAPIClientError.invalidAuthorization(authorization.originalValue)) | ||||
} else { | ||||
Task { @MainActor in | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain why we're dispatching to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a change in code, it's just moving this logic into an else. But our code expects fetch or return config to be run on the main thread according to this test:
|
||||
do { | ||||
let configuration = try await configurationLoader.getConfig() | ||||
setupHTTPCredentials(configuration) | ||||
completion(configuration, nil) | ||||
} catch { | ||||
completion(nil, error) | ||||
} | ||||
} | ||||
} | ||||
} | ||||
|
||||
@MainActor func fetchConfiguration() async throws -> BTConfiguration { | ||||
try await configurationLoader.getConfig() | ||||
if authorization.type == .invalidAuthorization { | ||||
throw BTAPIClientError.invalidAuthorization(authorization.originalValue) | ||||
} else { | ||||
try await configurationLoader.getConfig() | ||||
} | ||||
} | ||||
|
||||
/// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. | ||||
|
@@ -291,9 +327,11 @@ import Foundation | |||
|
||||
// MARK: - Internal Static Methods | ||||
|
||||
static func authorizationType(for authorization: String) -> AuthorizationType? { | ||||
static func authorizationType(for authorization: String) -> AuthorizationType { | ||||
let pattern: String = "([a-zA-Z0-9]+)_[a-zA-Z0-9]+_([a-zA-Z0-9_]+)" | ||||
guard let regularExpression = try? NSRegularExpression(pattern: pattern) else { return nil } | ||||
guard let regularExpression = try? NSRegularExpression(pattern: pattern) else { | ||||
return .invalidAuthorization | ||||
} | ||||
|
||||
let tokenizationKeyMatch: NSTextCheckingResult? = regularExpression.firstMatch( | ||||
in: authorization, | ||||
|
@@ -335,14 +373,35 @@ import Foundation | |||
} | ||||
} | ||||
|
||||
func payPalAPIURL(forEnvironment environment: String) -> URL? { | ||||
private func payPalAPIURL(forEnvironment environment: String) -> URL? { | ||||
if environment.caseInsensitiveCompare("sandbox") == .orderedSame || | ||||
environment.caseInsensitiveCompare("development") == .orderedSame { | ||||
return BTCoreConstants.payPalSandboxURL | ||||
} else { | ||||
return BTCoreConstants.payPalProductionURL | ||||
} | ||||
} | ||||
|
||||
private static func authorization(from authorization: String) -> ClientAuthorization { | ||||
let authorizationType = Self.authorizationType(for: authorization) | ||||
|
||||
switch authorizationType { | ||||
case .tokenizationKey: | ||||
do { | ||||
return try TokenizationKey(authorization) | ||||
} catch { | ||||
return InvalidAuthorization(authorization) | ||||
} | ||||
case .clientToken: | ||||
do { | ||||
return try BTClientToken(clientToken: authorization) | ||||
} catch { | ||||
return InvalidAuthorization(authorization) | ||||
} | ||||
case .invalidAuthorization: | ||||
return InvalidAuthorization(authorization) | ||||
} | ||||
} | ||||
|
||||
// MARK: BTAPITimingDelegate conformance | ||||
|
||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some TODOs throughout this PR to update in our final PR for this work