React Native Integration (Legacy)

The SDK can be integrated into a React Native project.

This integration is the legacy version. For the most recent version, see our recommended integration.

Integration with C++ (iOS)

If your project is built with C++ (AppDelegate.mm), you should do the following:

  1. Open Xcode Build Settings.
  2. Navigate to Targets -> Build Settings -> Apple Clang - Custom Compiler Flags -> Other C++ Flags.
  3. Add the -fcxx-modules flag.

Add the Human Module

Android

  1. Create a native module called HumanModule.

Java / HumanModule.java:

1import androidx.annotation.NonNull;
2import com.facebook.react.bridge.Promise;
3import com.facebook.react.bridge.ReactApplicationContext;
4import com.facebook.react.bridge.ReactContextBaseJavaModule;
5import com.facebook.react.bridge.ReactMethod;
6import com.facebook.react.modules.core.DeviceEventManagerModule;
7import com.humansecurity.mobile_sdk.HumanSecurity;
8import com.humansecurity.mobile_sdk.main.HSBotDefenderChallengeResult;
9import org.json.JSONObject;
10import java.util.HashMap;
11import java.util.Objects;
12
13public class HumanModule extends ReactContextBaseJavaModule {
14
15 static HumanModule shared = null;
16
17 String strNewHeaders = "HumanNewHeaders";
18 String strChallengeResult = "HumanChallengeResult";
19 String strSolved = "solved";
20 String strCancelled = "cancelled";
21 String strFalse = "false";
22
23 HumanModule(ReactApplicationContext context) {
24 super(context);
25 }
26
27 @NonNull
28 @Override
29 public String getName() {
30 return "HumanModule";
31 }
32
33 public void handleUpdatedHeaders(HashMap<String, String> headers) {
34 if (!this.getReactApplicationContext().hasCatalystInstance()) {
35 return;
36 }
37 JSONObject json = new JSONObject(headers);
38 this.getReactApplicationContext()
39 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
40 .emit(strNewHeaders, json.toString());
41 }
42
43 public void handleChallengeSolvedEvent() {
44 if (!this.getReactApplicationContext().hasCatalystInstance()) {
45 return;
46 }
47 this.getReactApplicationContext()
48 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
49 .emit(strChallengeResult, strSolved);
50 }
51
52 public void handleChallengeCancelledEvent() {
53 if (!this.getReactApplicationContext().hasCatalystInstance()) {
54 return;
55 }
56 this.getReactApplicationContext()
57 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
58 .emit(strChallengeResult, strCancelled);
59 }
60
61 @ReactMethod
62 public void getHTTPHeaders(Promise promise) {
63 JSONObject json = new JSONObject(Objects.requireNonNull(HumanSecurity.INSTANCE.getBD().headersForURLRequest(null)));
64 promise.resolve(json.toString());
65 }
66
67 @ReactMethod
68 public void handleResponse(String response, Integer code, String url, Promise promise) {
69 boolean handled = HumanSecurity.INSTANCE.getBD().handleResponse(response, result -> {
70 promise.resolve(result == HSBotDefenderChallengeResult.SOLVED ? strSolved : strCancelled);
71 return null;
72 });
73 if (!handled) {
74 promise.resolve(strFalse);
75 }
76 }
77}
  1. Add the HumanModule to the HumanPackage and set the shared property.

Java / HumanPackage.java:

1import com.facebook.react.ReactPackage;
2import com.facebook.react.bridge.NativeModule;
3import com.facebook.react.bridge.ReactApplicationContext;
4import com.facebook.react.uimanager.ViewManager;
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.List;
8
9public class HumanPackage implements ReactPackage {
10
11 @Override
12 public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
13 return Collections.emptyList();
14 }
15
16 @Override
17 public List<NativeModule> createNativeModules(
18 ReactApplicationContext reactContext) {
19 List<NativeModule> modules = new ArrayList<>();
20
21 HumanModule module = new HumanModule(reactContext);
22 HumanModule.shared = module;
23 modules.add(module);
24
25 return modules;
26 }
27}

iOS

Create a native module called HumanModule.

Objective-C / HumanModule.h:

1#import <Foundation/Foundation.h>
2#import <React/RCTBridgeModule.h>
3#import <React/RCTEventEmitter.h>
4
5NS_ASSUME_NONNULL_BEGIN
6
7@interface HumanModule : RCTEventEmitter <RCTBridgeModule>
8
9+ (HumanModule *)shared;
10- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers;
11- (void)handleChallengeSolvedEvent;
12- (void)handleChallengeCancelledEvent;
13
14@end
15
16NS_ASSUME_NONNULL_END

Objective-C / HumanModule.m:

1#import "HumanModule.h"
2@import HUMAN;
3
4static NSString *strNewHeaders = @"HumanNewHeaders";
5static NSString *strChallengeResult = @"HumanChallengeResult";
6static NSString *strSolved = @"solved";
7static NSString *strCancelled = @"cancelled";
8static NSString *strFalse = @"false";
9
10@implementation HumanModule
11
12static HumanModule *shared = nil;
13
14+ (HumanModule *)shared {
15 return shared;
16}
17
18- (instancetype)init {
19 self = [super init];
20 shared = self;
21 return self;
22}
23
24- (NSArray<NSString *> *)supportedEvents {
25 return @[strNewHeaders, strChallengeResult];
26}
27
28- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers {
29 NSData *data = [NSJSONSerialization dataWithJSONObject:headers options:0 error:nil];
30 NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
31 [self sendEventWithName:strNewHeaders body:json];
32}
33
34- (void)handleChallengeSolvedEvent {
35 [self sendEventWithName:strChallengeResult body:strSolved];
36}
37
38- (void)handleChallengeCancelledEvent {
39 [self sendEventWithName:strChallengeResult body:strCancelled];
40}
41
42RCT_EXPORT_MODULE(HumanModule);
43
44RCT_REMAP_METHOD(getHTTPHeaders,
45 resolver:(RCTPromiseResolveBlock)resolve
46 rejecter:(RCTPromiseRejectBlock)reject) {
47 NSDictionary<NSString *, NSString *> *humanHttpHeaders = [HumanSecurity.BD headersForURLRequestForAppId:nil];
48 NSData *data = [NSJSONSerialization dataWithJSONObject:humanHttpHeaders options:0 error:nil];
49 NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
50 resolve(@[json]);
51}
52
53RCT_REMAP_METHOD(handleResponse,
54 response:(NSString *)response
55 code:(NSInteger)code
56 url:(NSString *)url
57 resolver:(RCTPromiseResolveBlock)resolve
58 rejecter:(RCTPromiseRejectBlock)reject) {
59 NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
60 NSHTTPURLResponse *httpURLResponse = [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:url] statusCode:code HTTPVersion:nil headerFields:nil];
61 BOOL handled = [HumanSecurity.BD handleResponseWithResponse:httpURLResponse data:data callback:^(enum HSBotDefenderChallengeResult result) {
62 resolve((result == HSBotDefenderChallengeResultSolved ? strSolved : strCancelled));
63 }];
64 if (!handled) {
65 resolve(strFalse);
66 }
67}
68
69@end

How to start the SDK

  • The most important thing is to start the SDK as soon as possible in your app flow. The reason for that is when your app sends a URL request to your server before the SDK was started, the request will not include the SDK’s HTTP headers. As a result, HUMAN’s Enforcer could block the request, and the SDK will not be able to present a challenge to the user. The best place to start the SDK is in the:
    • Application’s onCreate function on Android.
    • AppDelegate’s didFinishLaunchingWithOptions function on iOS.
  • You should start the SDK on the main thread.
  • Implement the HumanDelegate to receive events from the SDK.

Here is an example of how it should be:

Android

Java / MainApplication.java:

1import android.app.Application;
2import android.content.Context;
3import android.util.Log;
4import java.lang.reflect.InvocationTargetException;
5import java.util.HashMap;
6import java.util.List;
7import androidx.annotation.NonNull;
8import androidx.annotation.Nullable;
9import com.facebook.react.PackageList;
10import com.facebook.react.ReactApplication;
11import com.facebook.react.ReactInstanceManager;
12import com.facebook.react.ReactNativeHost;
13import com.facebook.react.config.ReactFeatureFlags;
14import com.facebook.soloader.SoLoader;
15import com.human_demo.newarchitecture.MainApplicationReactNativeHost;
16import com.humansecurity.mobile_sdk.HumanSecurity;
17import com.humansecurity.mobile_sdk.main.HSBotDefenderDelegate;
18import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType;
19import com.humansecurity.mobile_sdk.main.policy.HSPolicy;
20
21public class MainApplication extends Application implements ReactApplication, HSBotDefenderDelegate {
22
23 private final ReactNativeHost mReactNativeHost =
24 new ReactNativeHost(this) {
25 @Override
26 public boolean getUseDeveloperSupport() {
27 return BuildConfig.DEBUG;
28 }
29
30 @Override
31 protected List<ReactPackage> getPackages() {
32 List<ReactPackage> packages = new PackageList(this).getPackages();
33 packages.add(new HumanPackage());
34 return packages;
35 }
36
37 @Override
38 protected String getJSMainModuleName() {
39 return "index";
40 }
41 };
42
43 private final ReactNativeHost mNewArchitectureNativeHost =
44 new MainApplicationReactNativeHost(this);
45
46 @Override
47 public ReactNativeHost getReactNativeHost() {
48 if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
49 return mNewArchitectureNativeHost;
50 } else {
51 return mReactNativeHost;
52 }
53 }
54
55 @Override
56 public void onCreate() {
57 super.onCreate();
58 ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
59 SoLoader.init(this, false);
60 initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
61
62 startHumanSDK();
63 }
64
65 private static void initializeFlipper(
66 Context context, ReactInstanceManager reactInstanceManager) {
67 if (BuildConfig.DEBUG) {
68 try {
69 Class<?> aClass = Class.forName("<package_name>.ReactNativeFlipper");
70 aClass
71 .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
72 .invoke(null, context, reactInstanceManager);
73 } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
74 InvocationTargetException e) {
75 e.printStackTrace();
76 }
77 }
78 }
79
80 private void startHumanSDK() {
81 HSPolicy policy = new HSPolicy();
82 policy.getAutomaticInterceptorPolicy().setInterceptorType(HSAutomaticInterceptorType.NONE);
83 try {
84 HumanSecurity.INSTANCE.start(this, "<APP_ID>", policy);
85 HumanSecurity.INSTANCE.getBD().setDelegate(this);
86 }
87 catch (Exception exception) {
88 Log.e("MainApplication","Exception: " + exception.getMessage());
89 }
90 }
91
92 @Override
93 public void botDefenderDidUpdateHeaders(@NonNull HashMap<String, String> headers, @NonNull String appId) {
94 if (HumanModule.shared != null) {
95 HumanModule.shared.handleUpdatedHeaders(headers);
96 }
97 }
98
99 @Override
100 public void botDefenderRequestBlocked(@Nullable String url, @NonNull String appId) {
101
102 }
103
104 @Override
105 public void botDefenderChallengeSolved(@NonNull String appId) {
106 HumanModule.shared.handleChallengeSolvedEvent();
107 }
108
109 @Override
110 public void botDefenderChallengeCancelled(@NonNull String appId) {
111 HumanModule.shared.handleChallengeCancelledEvent();
112 }
113
114 @Override
115 public void botDefenderChallengeRendered(@NonNull String appId) {
116
117 }
118
119 @Override
120 public void botDefenderChallengeRenderFailed(@NonNull String appId) {
121
122 }
123}

iOS

Objective-C / AppDelegate.h:

1#import <React/RCTBridgeDelegate.h>
2#import <UIKit/UIKit.h>
3@import HUMAN;
4
5@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, HSBotDefenderDelegate>
6
7@property (nonatomic, strong) UIWindow *window;
8
9@end

Objective-C / AppDelegate.m:

1#import "AppDelegate.h"
2#import "HumanModule.h"
3#import <React/RCTBridge.h>
4#import <React/RCTBundleURLProvider.h>
5#import <React/RCTRootView.h>
6#import <React/RCTAppSetupUtils.h>
7
8@implementation AppDelegate
9
10- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
11 HSPolicy *policy = [[HSPolicy alloc] init];
12 policy.automaticInterceptorPolicy.interceptorType = HSAutomaticInterceptorTypeNone;
13
14 NSError *error = nil;
15 [HumanSecurity startWithAppId:@"<APP_ID>" policy:policy error:&error];
16 HumanSecurity.BD.delegate = self;
17 if (error != nil) {
18 NSLog(@"error: %@", error);
19 }
20
21 [[HumanModule shared] startObserving];
22
23 // Configure React Native...
24
25 return YES;
26}
27
28// MARK: - HSBotDefenderDelegate
29
30- (void)botDefenderRequestBlockedWithUrl:(NSURL *)url appId:(NSString *)appId {
31
32}
33
34- (void)botDefenderChallengeSolvedForAppId:(NSString *)appId {
35 [[HumanModule shared] handleChallengeSolvedEvent];
36}
37
38- (void)botDefenderChallengeCancelledForAppId:(NSString *)appId {
39 [[HumanModule shared] handleChallengeCancelledEvent];
40}
41
42- (void)botDefenderDidUpdateHeadersWithHeaders:(NSDictionary<NSString *,NSString *> *)headers forAppId:(NSString *)appId {
43 [[HumanModule shared] handleUpdatedHeaders:headers];
44}
45
46- (void)botDefenderChallengeRenderedForAppId:(NSString *)appId {
47
48}
49
50- (void)botDefenderChallengeRenderFailedForAppId:(NSString *)appId {
51
52}
53
54@end

Don’t forget to change the <APP_ID> to your own AppID.

Let’s talk about what we have in the code here:

  1. We start the SDK as soon as possible and on the main thread.
  2. We create a HSPolicy instance to configure the SDK’s behavior. Here, we set the interceptorType property to HSAutomaticInterceptorType.NONE. This disables the automatic interception feature, which is not supported in React Native.
  3. We call the HumanSecurity.start(appId, policy) function of the SDK with:
    • The Application instance (Android only).
    • Your AppID.
    • The policy object.
  4. On iOS, we call the startObserving function on the HumanModule.

Note: If your app communicates with multiple servers with different AppIDs, you can call the HumanSecurity.start(appIds, policy) function to pass an array of AppIDs. Specify the relevant AppID for each API call in the SDK.

How to add HTTP headers and handle block response

In your JavaScript code, you should:

  1. Import the HumanModule.
  2. Create a new native event emitter.
  3. Add a listener to the “New Headers” event.
  4. Add a listener to the “Challenge Result” event.
  5. Add the SDK’s HTTP headers to your URL requests.
  6. When a request is blocked, send the block response to the SDK.
  7. Handle the challenge’s result.

JavaScript:

1/* Import HumanModule */
2import { NativeModules, NativeEventEmitter } from 'react-native';
3const { HumanModule } = NativeModules;
4
5/* Create a new native event emitter */
6const humanEventEmitter = new NativeEventEmitter(HumanModule);
7
8/* Add listener to the "New Headers" event and store the SDK's HTTP headers in a variable */
9let humanHttpHeaders = null;
10const onNewHeaders = headers => {
11 const obj = JSON.parse(headers);
12 console.log(`Got new HTTP headers: ${JSON.stringify(obj)}`);
13 humanHttpHeaders = obj;
14};
15const subscriptionHumanNewHeaders = humanEventEmitter.addListener('HumanNewHeaders', onNewHeaders);
16
17/* Add listener to the "Challenge Result" event */
18const onChallengeResult = result => {
19 if (result === 'solved') {
20 console.log('Challenge solved');
21 } else if (result === 'cancelled') {
22 console.log('Challenge cancelled');
23 }
24};
25const subscriptionHumanChallengeResult = humanEventEmitter.addListener('HumanChallengeResult', onChallengeResult);
26
27/* Get Human's HTTP headers */
28const getHumanHeaders = async () => {
29 if (humanHttpHeaders != null) {
30 return humanHttpHeaders;
31 }
32 const headers = await HumanModule.getHTTPHeaders();
33 const obj = JSON.parse(headers);
34 console.log(`[HUMAN] Got HTTP headers: ${JSON.stringify(obj)}`);
35 return obj;
36};
37
38/* Send URL request */
39async function sendRequest(url) {
40 /* Get Human's HTTP headers */
41 const humanHeaders = await getHumanHeaders();
42
43 try {
44 const response = await fetch(url, {
45 method: 'GET',
46 headers: humanHeaders,
47 });
48
49 const json = await response.json();
50
51 /* Send the response to the SDK */
52 const result = await HumanModule.handleResponse(JSON.stringify(json));
53 if (result === 'solved') {
54 console.log('Challenge solved');
55 await sendRequest(url);
56 } else if (result === 'cancelled') {
57 console.log('Challenge cancelled');
58 } else if (result === 'false') {
59 console.log('Request was not blocked by Human');
60 }
61 } catch (error) {
62 console.error(error);
63 }
64}

Understanding the block response

  1. The HUMAN’s Enforcer, when it decides to block a request, returns a JSON string in the response’s body with an HTTP status code of 403. Here is an example of the response:

    1{
    2 "vid": "928d7ab3-9cf1-11ee-a624-b802520f369f",
    3 "uuid": "fd01e6d6-9cf2-11ee-808c-acde48001122",
    4 "page": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPgogICAgPHRpdGxlPkFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkLjwvdGl0bGU+CiAgICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9T3BlbitTYW5zOjMwMCIgcmVsPSJzdHlsZXNoZWV0Ij4KICAgIDxzdHlsZT4KICAgICAgICBodG1sLCBib2R5IHsKICAgICAgICAgICAgbWFyZ2luOiAwOwogICAgICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgICAgICBmb250LWZhbWlseTogJ09wZW4gU2FucycsIHNhbnMtc2VyaWY7CiAgICAgICAgICAgIGNvbG9yOiAjMDAwOwogICAgICAgIH0KCiAgICAgICAgYSB7CiAgICAgICAgICAgIGNvbG9yOiAjYzVjNWM1OwogICAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyIHsKICAgICAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICAgICAgZmxleDogMTsKICAgICAgICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyID4gZGl2IHsKICAgICAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAgIH0KCiAgICAgICAgLmNvbnRhaW5lciA+IGRpdiA+IGRpdiB7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIHdpZHRoOiA4MCU7CiAgICAgICAgfQoKICAgICAgICAuY3VzdG9tZXItbG9nby13cmFwcGVyIHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDJyZW07CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsKICAgICAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgIH0KCiAgICAgICAgLmN1c3RvbWVyLWxvZ28gewogICAgICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgIzAwMDsKICAgICAgICB9CgogICAgICAgIC5jdXN0b21lci1sb2dvID4gaW1nIHsKICAgICAgICAgICAgcGFkZGluZy1ib3R0b206IDFyZW07CiAgICAgICAgICAgIG1heC1oZWlnaHQ6IDUwcHg7CiAgICAgICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgICB9CgogICAgICAgIC5wYWdlLXRpdGxlLXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDI7CiAgICAgICAgfQoKICAgICAgICAucGFnZS10aXRsZSB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW4tcmV2ZXJzZTsKICAgICAgICB9CgogICAgICAgIC5jb250ZW50LXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDU7CiAgICAgICAgfQoKICAgICAgICAuY29udGVudCB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgfQoKICAgICAgICAucGFnZS1mb290ZXItd3JhcHBlciB7CiAgICAgICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMC4yOwogICAgICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgICAgICAgICBjb2xvcjogI2M1YzVjNTsKICAgICAgICAgICAgZm9udC1zaXplOiA3MCU7CiAgICAgICAgfQoKICAgICAgICBAbWVkaWEgKG1pbi13aWR0aDogNzY4cHgpIHsKICAgICAgICAgICAgaHRtbCwgYm9keSB7CiAgICAgICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICA8L3N0eWxlPgogICAgPCEtLSBDdXN0b20gQ1NTIC0tPgo8L2hlYWQ+Cgo8Ym9keT4KPHNlY3Rpb24gY2xhc3M9ImNvbnRhaW5lciI+CiAgICA8ZGl2IGNsYXNzPSJjdXN0b21lci1sb2dvLXdyYXBwZXIiPgogICAgICAgIDxkaXYgY2xhc3M9ImN1c3RvbWVyLWxvZ28iPgogICAgICAgICAgICA8aW1nIHNyYz0iIiBhbHQ9IkxvZ28iLz4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS10aXRsZS13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJwYWdlLXRpdGxlIj4KICAgICAgICAgICAgPGgxPlBsZWFzZSB2ZXJpZnkgeW91IGFyZSBhIGh1bWFuPC9oMT4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGVudC13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50Ij4KICAgICAgICAgICAgPGRpdiBpZD0icHgtY2FwdGNoYSI+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIEFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkIGJlY2F1c2Ugd2UgYmVsaWV2ZSB5b3UgYXJlIHVzaW5nIGF1dG9tYXRpb24gdG9vbHMgdG8gYnJvd3NlIHRoZQogICAgICAgICAgICAgICAgd2Vic2l0ZS4KICAgICAgICAgICAgPC9wPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFRoaXMgbWF5IGhhcHBlbiBhcyBhIHJlc3VsdCBvZiB0aGUgZm9sbG93aW5nOgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgICAgICBKYXZhc2NyaXB0IGlzIGRpc2FibGVkIG9yIGJsb2NrZWQgYnkgYW4gZXh0ZW5zaW9uIChhZCBibG9ja2VycyBmb3IgZXhhbXBsZSkKICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICAgICAgWW91ciBicm93c2VyIGRvZXMgbm90IHN1cHBvcnQgY29va2llcwogICAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgPC91bD4KICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBQbGVhc2UgbWFrZSBzdXJlIHRoYXQgSmF2YXNjcmlwdCBhbmQgY29va2llcyBhcmUgZW5hYmxlZCBvbiB5b3VyIGJyb3dzZXIgYW5kIHRoYXQgeW91IGFyZSBub3QgYmxvY2tpbmcKICAgICAgICAgICAgICAgIHRoZW0gZnJvbSBsb2FkaW5nLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgICAgUmVmZXJlbmNlIElEOiAjZmQwMWU2ZDYtOWNmMi0xMWVlLTgwOGMtYWNkZTQ4MDAxMTIyCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXItd3JhcHBlciI+CiAgICAgICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXIiPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFBvd2VyZWQgYnkKICAgICAgICAgICAgICAgIDxhIGhyZWY9Imh0dHBzOi8vd3d3LnBlcmltZXRlcnguY29tL3doeXdhc2libG9ja2VkIj5QZXJpbWV0ZXJYPC9hPgogICAgICAgICAgICAgICAgLCBJbmMuCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2Pgo8L3NlY3Rpb24+CjwhLS0gUHggLS0+CjxzY3JpcHQ+CiAgICB3aW5kb3cuX3B4QXBwSWQgPSAnUFhqOXk0UThFbSc7CiAgICB3aW5kb3cuX3B4SnNDbGllbnRTcmMgPSAnLy9jbGllbnQucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9tYWluLm1pbi5qcyc7CiAgICB3aW5kb3cuX3B4Rmlyc3RQYXJ0eUVuYWJsZWQgPSBmYWxzZTsKICAgIHdpbmRvdy5fcHhWaWQgPSAnOTI4ZDdhYjMtOWNmMS0xMWVlLWE2MjQtYjgwMjUyMGYzNjlmJzsKICAgIHdpbmRvdy5fcHhVdWlkID0gJ2ZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMic7CiAgICB3aW5kb3cuX3B4SG9zdFVybCA9ICcvL2NvbGxlY3Rvci1QWGo5eTRROEVtLnBlcmltZXRlcngubmV0JzsKPC9zY3JpcHQ+CjwhLS0gQ2FwdGNoYSAtLT4KPHNjcmlwdD4KICAgIHZhciBzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7CiAgICBzLnNyYyA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnOwogICAgdmFyIHAgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdOwogICAgcC5pbnNlcnRCZWZvcmUocywgbnVsbCk7CiAgICBpZiAoZmFsc2UpIHsKICAgICAgICBzLm9uZXJyb3IgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgIHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsKICAgICAgICAgICAgdmFyIHN1ZmZpeEluZGV4ID0gJy8vY2FwdGNoYS5wZXJpbWV0ZXJ4Lm5ldC9QWGo5eTRROEVtL2NhcHRjaGEuanM/YT1jJm09MSZ1PWZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMiZ2PTkyOGQ3YWIzLTljZjEtMTFlZS1hNjI0LWI4MDI1MjBmMzY5ZicuaW5kZXhPZignY2FwdGNoYS5qcycpOwogICAgICAgICAgICB2YXIgdGVtcGVyZWRCbG9ja1NjcmlwdCA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnLnN1YnN0cmluZyhzdWZmaXhJbmRleCk7CiAgICAgICAgICAgIHMuc3JjID0gJy8vY2FwdGNoYS5weC1jZG4ubmV0L1BYajl5NFE4RW0vJyArIHRlbXBlcmVkQmxvY2tTY3JpcHQ7CiAgICAgICAgICAgIHAucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUocywgcCk7CiAgICAgICAgfTsKICAgIH0KPC9zY3JpcHQ+CjwhLS0gQ3VzdG9tIFNjcmlwdCAtLT4KPC9ib2R5Pgo8L2h0bWw+Cg==",
    5 "appId": "PXj9y4Q8Em",
    6 "action": "captcha",
    7 "collectorUrl": "https://collector-pxj9y4q8em.perimeterx.net"
    8}
  2. The JSON contains metadata for the SDK.

  3. Your app should pass the whole JSON to the SDK via the HSBotDefender/handleResponse(response:data:callback:) function. Otherwise, the SDK won’t present a challenge to the user.