DataDome iOS SDK 2.0
Install
Swift package manager
DataDomeSDK is available on Swift Package Manager. To get the SDK integrated in your project, follow the steps below:
- Go to "Xcode > File > Swift Packages > Add Package Dependency", select the target where to integrate DataDome
- Paste the following git URL in the search bar
https://github.com/DataDome/datadome-ios-package
- Select
DataDomeSDK
and pressAdd
.
CocoaPods
Alternatively, you can get the DataDomeSDK on CocoaPods. Simply add the following line in your Podfile:
pod "DataDomeSDK"
Run pod install
to download and integrate the framework into your project.
Note
Please note that starting with SDK version 2.5.0, we use the XCFramework
format. Old CocoaPods versions don't handle this new format well. Please make sure you are using CocoaPods version 1.10
or later to avoid any linking issue.
Carthage
DataDomeSDK is available on Carthage. To get the SDK integrated in your project, follow the steps below:
- Create a Cartfile if not already created.
- Add the following dependency in your project Cartfile
binary "https://package.datadome.co/ios/DataDomeSDK.json"
- Run the following command to fetch and integrate the package
carthage update --use-xcframeworks
Note
Please note that Carthage supports the XCFramework format starting from version 0.38.0
. With SDK version 2.5.0, we use the XCFramework
format. Old Carthage builds don't handle this new format well. Please make sure you are using Carthage version 0.38.0
or later to avoid any linking issue.
Getting Started
- Run your application. It will crash and return the following log:
Fatal error: [DataDome] Missing DataDomeKey (Your client side key) in your Info.plist
- In your Info.plist, add a new entry with String type, use DataDomeKey as "key" and your actual client side key as "value".
- Now you can run the app without crashing. You should get a log confirming the SDK is running:
[DataDome] Version x.y.z
Congrats, the DataDome framework is now properly integrated.
Logging
If you need to view the logs produced by the framework, you can configure the log level to control the detail level in the logs you get:
import DataDomeSDK
DataDomeSDK.setLogLevel(level: .verbose)
By default, the framework is completely silent.
The following table contains different logging levels that you may consider using:
Level | Description |
---|---|
verbose | Everything is logged |
info | Info messages, warnings and errors are shown |
warning | Only warning and error messages are printed |
error | Only errors are printed |
none | Silent mode (default) |
Share cookies between native and web
If you are using WKWebView
in your application to display web pages that are protected with DataDome, you can share the generated cookie by resolving a Captcha on the native part of the app, therefore a second Captcha won't be required on the web-based part of the app.
To share the DataDome cookie from the SDK to your WKWebView
instance, use the following API:
import DataDomeSDK
if let cookie = DataDomeSDK.getCookie() {
webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}
The getCookie
API returns the stored Captcha generated by the last resolved Captcha, else it returns nothing if there was no Captcha stored.
If your web page is displayed before the app sends any request natively, you can consider providing the cookie stored by your web view after resolving the Captcha on the web view side. Alternatively, if you need to store a cookie, use the following API:
import DataDomeSDK
DataDomeSDK.setCookie(cookie: cookie)
Disable method swizzling
The DataDome Framework is using method swizzling to setup a proxy
that listens to all URLRequests
performed by the app. The framework automatically detects blocked requests, performs a dynamic lookup on the top view controller in your app hierarchy and attaches a Captcha view controller to it. Once resolved, the framework will dismiss the Captcha view controller and will retry the blocked request with a newly generated cookie. All this is done "under the hood".
If you want to disable the method swizzling and manually instruct the framework with which requests to monitor instead of monitoring all traffic, and/or you want to manually present and dismiss the Captcha controller, please consider the following steps:
- In your Info.plist, add a new entry with Boolean type, using DataDomeProxyEnabled as key and NO as value. This will disable method swizzling in the framework.
- For each request to be protected with DataDome, use the following methods to create the data task:
func protectedDataTask(with url: URL,
captchaDelegate: CaptchaDelegate?,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
-> URLSessionDataTask
func protectedDataTask(with request: URLSessionRequest,
captchaDelegate: CaptchaDelegate?,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
-> URLSessionDataTask
Only the URLSessionDataTasks
created by the two methods above are monitored and protected by DataDome.
Below is a sample code to create and execute a data task:
import DataDomeSDK
guard let url = URL(string: "https://datadome.co") else {
return
}
let task = URLSession.shared.protectedDataTask(with: url, captchaDelegate: self, completionHandler: { _, _, _ in
print("Request completed")
})
task.resume()
To manually manage the presentation of the Captcha view controller, you need to implement the CaptchaDelegate
protocol:
import DataDomeSDK
extension MyViewController: CaptchaDelegate {
func present(captchaController controller: UIViewController) {
self.navigationController?.present(controller, animated: true, completion: nil)
}
func dismiss(captchaController controller: UIViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
Important
If you are creating your own URLSession
, make sure to NOT user URLSessionConfiguration.ephemeral
as it may result in a captcha loop. Ephemeral sessions does not use disk based Cookie Storage. Here an exemple of a custom url session
var sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.httpCookieStorage = HTTPCookieStorage.shared
sessionConfiguration.httpShouldSetCookies = true
let session = URLSession(configuration: sessionConfiguration)
Using Combine & URLSession.DataTaskPublisher
If you are using Combine
and DataTaskPublisher
to perform networking requests in your app, we have you covered.
Use the following apis to create a DataTaskPublisher
with the DataDome operators
/// Provides a publisher with downstream DataDome operators
/// - Parameters:
/// - url: The url to be requested
/// - captchaDelegate: The captcha delegate
/// - Returns: A publisher with the DataDome operators
func protectedDataTaskPublisher(forURL url: URL,
captchaDelegate: CaptchaDelegate?)
-> AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
Or by using an URLSessionRequest instance as parameter
/// Provides a publisher with downstream DataDome operators
/// - Parameters:
/// - request: The request to be processed
/// - captchaDelegate: The captcha delegate
/// - Returns: A publisher with the DataDome operators
func protectedDataTaskPublisher(forRequest request: URLRequest,
captchaDelegate: CaptchaDelegate?)
-> AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
Here an example on how to use the DataDome protected publisher
URLSession
.shared
.protectedDataTaskPublisher(forURL: url, captchaDelegate: nil)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { response in
guard let httpResponse = response.response as? HTTPURLResponse else {
print("Invalid response")
return
}
print("Did receive response with code \(httpResponse.statusCode)")
}
})
You can use any other operators to validate or transform the publisher or part of its elements. For more details, please visit Apple Documentation
Using Alamofire / Moya
If you are using Alamofire or Moya to build your app's networking layer, we have you covered. Install the DataDome Alamofire plugin
to protect your requests using the DataDome SDK.
Swift package manager
- Go to "Xcode > File > Swift Packages > Add Package Dependency", select the target where to integrate DataDomeAlamofire
- Paste the following git URL in the search bar
https://github.com/DataDome/datadome-alamofire-package
- Select
DataDomeAlamofire
and pressAdd
.
Cocoapods
To install the plugin, use the following Cocoapods instruction:
pod "DataDomeAlamofire"
Carthage
DataDomeAlamofire is available on Carthage. To get the SDK integrated in your project, follow the steps below:
- Create a Cartfile if not already created.
- Add the following dependency in your project Cartfile
github "DataDome/datadome-alamofire-package"
- Run the following command to fetch and integrate the package
carthage update
Make sure method swizzling for URLSession is disabled to avoid any interference with Alamofire's "under the hood" URLSession usage.
In your Info.plist, add a new entry with Boolean type, using DataDomeProxyEnabled as key and NO as value. This will disable method swizzling in the framework.
Create your Alamofire Session Manager as shows in the example below:
import DataDomeAlamofire
let configuration = URLSessionConfiguration.default
configuration.headers = .default
configuration.httpCookieStorage = HTTPCookieStorage.shared
configuration.httpShouldSetCookies = true
let dataDome = AlamofireInterceptor()
let interceptor = Interceptor(adapter: dataDome.sessionAdapter,
retrier: dataDome.sessionRetrier)
let alamofireSessionManager = Session(configuration: configuration,
interceptor: interceptor)
The remainder of your app won't change. Only make sure to use the created alamofireSessionManager
to perform your requests.
Alternatively, you can conform to the CaptchaDelegate protocol and handle manually the navigation of the Captcha View Controller
let dataDome = AlamofireInterceptor(captchaDelegate: self)
Implement the CaptchaDelegate protocol
import DataDomeSDK
extension AlamofireViewController: CaptchaDelegate {
func present(captchaController controller: UIViewController) {
present(controller, animated: true) {
print("Captcha displayed")
}
}
func dismiss(captchaController controller: UIViewController) {
controller.dismiss(animated: true) {
print("Captcha dismissed")
}
}
}
Important
When using Alamofire, make sure you call .validate()
for each request to make sure Alamofire does call the retry function and hands the execution to the DataDome SDK in case of a 403 response with a Captcha Challenge.
self.alamofireSessionManager
.request(endpoint)
//validate here is mandatory
.validate()
.responseData { response in
}
When using Moya, make sure you explicitly implement validationType
in your service implementation
extension MyService: TargetType {
var validationType: ValidationType {
let type = ValidationType.successAndRedirectCodes
return type
}
}
The validationType attribute will make Moya call the .validate()
function when handing the request to Alamofire.
Important
If you are creating an instance of URLRequest and passing it to Alamofire Session Manager, make sure to hint iOS to handle cookies otherwise, you may experience captcha loops in the app.
var request = URLRequest(url: url)
request.httpShouldHandleCookies = true
session.request(request).validate()
Using AFNetworking client
We forked AFNetworking to adapt it to DataDomeSDK.
To install DataDomeAFNetworking
, use the following statement.
pod 'DataDomeAFNetworking', '~> 4.0.1'
You should use the above dependency instead of AFNetworking. The library contains the same source code as AFNetworking with an adaptation to make it work with DataDome.
Replace your import statements as the following.
// AFNetworking import statement to be replaced
#import <AFNetworking/AFNetworking.h>
// The new import statement to be used
@import DataDomeAFNetworking;
No other changes are required to keep your code base working with AFNetworking and DataDome.
Using Apollo GraphQL Client
If your api is a GraphQL api, the chances are that you are using Apollo SDK. If so, we have a dedicated framework called DataDomeApollo
designed to ease the DataDome SDK integration.
To install the plugin, use the following Cocoapods instruction:
pod "DataDomeApollo"
Make sure method swizzling for URLSession is disabled to avoid any interference with Apollo's "under the hood" URLSession usage.
In your Info.plist, add a new entry with Boolean type, using DataDomeProxyEnabled as key and NO as value. This will disable method swizzling in the framework.
We use the Interceptor
pattern built in the Apollo SDK to make sure we intercept all requests and protect them. The following is the init of the DataDome Interceptor
let interceptor = DataDomeResponseInterceptor()
The interceptor could be used then in your pipeline. It will intercept all requests, catch requests blocked by DataDome, display the captcha and eventually retry the request automatically once the captcha is resolved.
Important
The order in defining the interceptors is important in Apollo. Make sure the DataDome response Interceptor is the first interceptor after the NetworkFetchInterceptor
.
Alternatively, you can use the DataDomeInterceptorProvider
which is a pre-built sequence of interceptors ready to be used by your app. This provider is providing the same interceptors as the Legacy Interceptor provider. You can customise the interceptors to be added in the provider or use the default set.
private(set) lazy var apollo: ApolloClient = {
// Create your own store needed to init the DataDomeInterceptor provider
let cache = InMemoryNormalizedCache()
let store = ApolloStore(cache: cache)
// Use DataDomeURLSessionClient to enable updating the user-agent
let client = DataDomeURLSessionClient()
// Create the DataDome Interceptor Provider
let provider = DataDomeInterceptorProvider(store: store, client: client)
// Create your GraphQL URL
guard let url = URL(string: "YOUR_ENDPOINT") else {
fatalError("Unable to create url")
}
let requestChainTransport = RequestChainNetworkTransport(interceptorProvider: provider,
endpointURL: url)
// Create the client with the request chain transport
return ApolloClient(networkTransport: requestChainTransport,
store: store)
}()
In the above exemple we used the default configuration of DataDomeInterceptorProvider
. You can specify custom interceptors using the following init method
/// Creates an interceptor provider with a setup instance of DataDome
/// - Parameters:
/// - store: The apollo store
/// - client: The URLSession client
/// - preFetchInterceptors: The list of interceptors to go before the fetch operation
/// - fetchInterceptor: The fetch operation
/// - postFetchInterceptors: The list of interceptors to go after the fetch operation
public init(store: ApolloStore,
client: URLSessionClient,
preFetchInterceptors: [ApolloInterceptor] = [],
fetchInterceptor: ApolloInterceptor? = nil,
postFetchInterceptors: [ApolloInterceptor] = [])
preFetchInterceptors
is a set of interceptors to be added before firing the request. By default we add:
- CacheReadInterceptor
fetchInterceptor
is the interceptor that will execute the request. By default, we use:
- NetworkFetchInterceptor
The DataDomeResponseInterceptor
is then added to the chain just after the fetch interceptor.
postFetchInterceptors
is a set of interceptors to be added after the fetch interceptor. By default we add:
- ResponseCodeInterceptor
- JSONResponseParsingInterceptor
- AutomaticPersistedQueryInterceptor
- CacheWriteInterceptor
Updated about 2 years ago