React Native Integration

Intro

The SDK can be integrated into a React Native project.

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:

import androidx.annotation.NonNull;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.humansecurity.mobile_sdk.HumanSecurity;
import com.humansecurity.mobile_sdk.main.HSBotDefenderChallengeResult;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Objects;

public class HumanModule extends ReactContextBaseJavaModule {

    static HumanModule shared = null;

    String strNewHeaders = "HumanNewHeaders";
    String strChallengeResult = "HumanChallengeResult";
    String strSolved = "solved";
    String strCancelled = "cancelled";
    String strFalse = "false";

    HumanModule(ReactApplicationContext context) {
        super(context);
    }

    @NonNull
    @Override
    public String getName() {
        return "HumanModule";
    }

    public void handleUpdatedHeaders(HashMap<String, String> headers) {
        if (!this.getReactApplicationContext().hasCatalystInstance()) {
            return;
        }
        JSONObject json = new JSONObject(headers);
        this.getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(strNewHeaders, json.toString());
    }

    public void handleChallengeSolvedEvent() {
        if (!this.getReactApplicationContext().hasCatalystInstance()) {
            return;
        }
        this.getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(strChallengeResult, strSolved);
    }

    public void handleChallengeCancelledEvent() {
        if (!this.getReactApplicationContext().hasCatalystInstance()) {
            return;
        }
        this.getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(strChallengeResult, strCancelled);
    }

    @ReactMethod
    public void getHTTPHeaders(Promise promise) {
        JSONObject json = new JSONObject(Objects.requireNonNull(HumanSecurity.INSTANCE.getBD().headersForURLRequest(null)));
        promise.resolve(json.toString());
    }

    @ReactMethod
    public void handleResponse(String response, Integer code, String url, Promise promise) {
        boolean handled = HumanSecurity.INSTANCE.getBD().handleResponse(response, result -> {
            promise.resolve(result == HSBotDefenderChallengeResult.SOLVED ? strSolved : strCancelled);
            return null;
        });
        if (!handled) {
            promise.resolve(strFalse);
        }
    }
}
  1. Add the HumanModule to the HumanPackage and set the shared property.

Java / HumanPackage.java:

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HumanPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        HumanModule module = new HumanModule(reactContext);
        HumanModule.shared = module;
        modules.add(module);

        return modules;
    }
}

iOS

Create a native module called HumanModule.

Objective-C / HumanModule.h:

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

NS_ASSUME_NONNULL_BEGIN

@interface HumanModule : RCTEventEmitter <RCTBridgeModule>

+ (HumanModule *)shared;
- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers;
- (void)handleChallengeSolvedEvent;
- (void)handleChallengeCancelledEvent;

@end

NS_ASSUME_NONNULL_END

Objective-C / HumanModule.m:

#import "HumanModule.h"
@import HUMAN;

static NSString *strNewHeaders = @"HumanNewHeaders";
static NSString *strChallengeResult = @"HumanChallengeResult";
static NSString *strSolved = @"solved";
static NSString *strCancelled = @"cancelled";
static NSString *strFalse = @"false";

@implementation HumanModule

static HumanModule *shared = nil;

+ (HumanModule *)shared {
  return shared;
}

- (instancetype)init {
  self = [super init];
  shared = self;
  return self;
}

- (NSArray<NSString *> *)supportedEvents {
  return @[strNewHeaders, strChallengeResult];
}

- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers {
  NSData *data = [NSJSONSerialization dataWithJSONObject:headers options:0 error:nil];
  NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  [self sendEventWithName:strNewHeaders body:json];
}

- (void)handleChallengeSolvedEvent {
  [self sendEventWithName:strChallengeResult body:strSolved];
}

- (void)handleChallengeCancelledEvent {
  [self sendEventWithName:strChallengeResult body:strCancelled];
}

RCT_EXPORT_MODULE(HumanModule);

RCT_REMAP_METHOD(getHTTPHeaders,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  NSDictionary<NSString *, NSString *> *humanHttpHeaders = [HumanSecurity.BD headersForURLRequestForAppId:nil];
  NSData *data = [NSJSONSerialization dataWithJSONObject:humanHttpHeaders options:0 error:nil];
  NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  resolve(@[json]);
}

RCT_REMAP_METHOD(handleResponse,
                 response:(NSString *)response
                 code:(NSInteger)code
                 url:(NSString *)url
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
  NSHTTPURLResponse *httpURLResponse = [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:url] statusCode:code HTTPVersion:nil headerFields:nil];
  BOOL handled = [HumanSecurity.BD handleResponseWithResponse:httpURLResponse data:data callback:^(enum HSBotDefenderChallengeResult result) {
    resolve((result == HSBotDefenderChallengeResultSolved ? strSolved : strCancelled));
  }];
  if (!handled) {
    resolve(strFalse);
  }
}

@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:

import android.app.Application;
import android.content.Context;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.soloader.SoLoader;
import com.human_demo.newarchitecture.MainApplicationReactNativeHost;
import com.humansecurity.mobile_sdk.HumanSecurity;
import com.humansecurity.mobile_sdk.main.HSBotDefenderDelegate;
import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType;
import com.humansecurity.mobile_sdk.main.policy.HSPolicy;

public class MainApplication extends Application implements ReactApplication, HSBotDefenderDelegate {

    private final ReactNativeHost mReactNativeHost =
            new ReactNativeHost(this) {
                @Override
                public boolean getUseDeveloperSupport() {
                    return BuildConfig.DEBUG;
                }

                @Override
                protected List<ReactPackage> getPackages() {
                    List<ReactPackage> packages = new PackageList(this).getPackages();
                    packages.add(new HumanPackage());
                    return packages;
                }

                @Override
                protected String getJSMainModuleName() {
                    return "index";
                }
            };

    private final ReactNativeHost mNewArchitectureNativeHost =
            new MainApplicationReactNativeHost(this);

    @Override
    public ReactNativeHost getReactNativeHost() {
        if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
            return mNewArchitectureNativeHost;
        } else {
            return mReactNativeHost;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
        SoLoader.init(this, false);
        initializeFlipper(this, getReactNativeHost().getReactInstanceManager());

        startHumanSDK();
    }

    private static void initializeFlipper(
            Context context, ReactInstanceManager reactInstanceManager) {
        if (BuildConfig.DEBUG) {
            try {
                Class<?> aClass = Class.forName("<package_name>.ReactNativeFlipper");
                aClass
                        .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
                        .invoke(null, context, reactInstanceManager);
            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
                     InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    private void startHumanSDK() {
        HSPolicy policy = new HSPolicy();
        policy.getAutomaticInterceptorPolicy().setInterceptorType(HSAutomaticInterceptorType.NONE);
        try {
            HumanSecurity.INSTANCE.start(this, "<APP_ID>", policy);
            HumanSecurity.INSTANCE.getBD().setDelegate(this);
        }
        catch (Exception exception) {
            Log.e("MainApplication","Exception: " + exception.getMessage());
        }
    }

    @Override
    public void botDefenderDidUpdateHeaders(@NonNull HashMap<String, String> headers, @NonNull String appId) {
        if (HumanModule.shared != null) {
            HumanModule.shared.handleUpdatedHeaders(headers);
        }
    }

    @Override
    public void botDefenderRequestBlocked(@Nullable String url, @NonNull String appId) {

    }

    @Override
    public void botDefenderChallengeSolved(@NonNull String appId) {
        HumanModule.shared.handleChallengeSolvedEvent();
    }

    @Override
    public void botDefenderChallengeCancelled(@NonNull String appId) {
        HumanModule.shared.handleChallengeCancelledEvent();
    }

    @Override
    public void botDefenderChallengeRendered(@NonNull String appId) {

    }

    @Override
    public void botDefenderChallengeRenderFailed(@NonNull String appId) {

    }
}

iOS

Objective-C / AppDelegate.h:

#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
@import HUMAN;

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, HSBotDefenderDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

Objective-C / AppDelegate.m:

#import "AppDelegate.h"
#import "HumanModule.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTAppSetupUtils.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    HSPolicy *policy = [[HSPolicy alloc] init];
    policy.automaticInterceptorPolicy.interceptorType = HSAutomaticInterceptorTypeNone;

    NSError *error = nil;
    [HumanSecurity startWithAppId:@"<APP_ID>" policy:policy error:&error];
    HumanSecurity.BD.delegate = self;
    if (error != nil) {
        NSLog(@"error: %@", error);
    }

    [[HumanModule shared] startObserving];

    // Configure React Native...

    return YES;
}

// MARK: - HSBotDefenderDelegate

- (void)botDefenderRequestBlockedWithUrl:(NSURL *)url appId:(NSString *)appId {

}

- (void)botDefenderChallengeSolvedForAppId:(NSString *)appId {
    [[HumanModule shared] handleChallengeSolvedEvent];
}

- (void)botDefenderChallengeCancelledForAppId:(NSString *)appId {
    [[HumanModule shared] handleChallengeCancelledEvent];
}

- (void)botDefenderDidUpdateHeadersWithHeaders:(NSDictionary<NSString *,NSString *> *)headers forAppId:(NSString *)appId {
    [[HumanModule shared] handleUpdatedHeaders:headers];
}

- (void)botDefenderChallengeRenderedForAppId:(NSString *)appId {

}

- (void)botDefenderChallengeRenderFailedForAppId:(NSString *)appId {

}

@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:

/* Import HumanModule */
import { NativeModules, NativeEventEmitter } from 'react-native';
const { HumanModule } = NativeModules;

/* Create a new native event emitter */
const humanEventEmitter = new NativeEventEmitter(HumanModule);

/* Add listener to the "New Headers" event and store the SDK's HTTP headers in a variable */
let humanHttpHeaders = null;
const onNewHeaders = headers => {
  const obj = JSON.parse(headers);
  console.log(`Got new HTTP headers: ${JSON.stringify(obj)}`);
  humanHttpHeaders = obj;
};
const subscriptionHumanNewHeaders = humanEventEmitter.addListener('HumanNewHeaders', onNewHeaders);

/* Add listener to the "Challenge Result" event */
const onChallengeResult = result => {
  if (result === 'solved') {
    console.log('Challenge solved');
  } else if (result === 'cancelled') {
    console.log('Challenge cancelled');
  }
};
const subscriptionHumanChallengeResult = humanEventEmitter.addListener('HumanChallengeResult', onChallengeResult);

/* Get Human's HTTP headers */
const getHumanHeaders = async () => {
  if (humanHttpHeaders != null) {
    return humanHttpHeaders;
  }
  const headers = await HumanModule.getHTTPHeaders();
  const obj = JSON.parse(headers);
  console.log(`[HUMAN] Got HTTP headers: ${JSON.stringify(obj)}`);
  return obj;
};

/* Send URL request */
async function sendRequest(url) {
  /* Get Human's HTTP headers */
  const humanHeaders = await getHumanHeaders();

  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: humanHeaders,
    });

    const json = await response.json();

    /* Send the response to the SDK */
    const result = await HumanModule.handleResponse(JSON.stringify(json));
    if (result === 'solved') {
      console.log('Challenge solved');
      await sendRequest(url);
    } else if (result === 'cancelled') {
      console.log('Challenge cancelled');
    } else if (result === 'false') {
      console.log('Request was not blocked by Human');
    }
  } catch (error) {
    console.error(error);
  }
}

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:

    {
      "vid": "928d7ab3-9cf1-11ee-a624-b802520f369f",
      "uuid": "fd01e6d6-9cf2-11ee-808c-acde48001122",
      "page": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPgogICAgPHRpdGxlPkFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkLjwvdGl0bGU+CiAgICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9T3BlbitTYW5zOjMwMCIgcmVsPSJzdHlsZXNoZWV0Ij4KICAgIDxzdHlsZT4KICAgICAgICBodG1sLCBib2R5IHsKICAgICAgICAgICAgbWFyZ2luOiAwOwogICAgICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgICAgICBmb250LWZhbWlseTogJ09wZW4gU2FucycsIHNhbnMtc2VyaWY7CiAgICAgICAgICAgIGNvbG9yOiAjMDAwOwogICAgICAgIH0KCiAgICAgICAgYSB7CiAgICAgICAgICAgIGNvbG9yOiAjYzVjNWM1OwogICAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyIHsKICAgICAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICAgICAgZmxleDogMTsKICAgICAgICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyID4gZGl2IHsKICAgICAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAgIH0KCiAgICAgICAgLmNvbnRhaW5lciA+IGRpdiA+IGRpdiB7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIHdpZHRoOiA4MCU7CiAgICAgICAgfQoKICAgICAgICAuY3VzdG9tZXItbG9nby13cmFwcGVyIHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDJyZW07CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsKICAgICAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgIH0KCiAgICAgICAgLmN1c3RvbWVyLWxvZ28gewogICAgICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgIzAwMDsKICAgICAgICB9CgogICAgICAgIC5jdXN0b21lci1sb2dvID4gaW1nIHsKICAgICAgICAgICAgcGFkZGluZy1ib3R0b206IDFyZW07CiAgICAgICAgICAgIG1heC1oZWlnaHQ6IDUwcHg7CiAgICAgICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgICB9CgogICAgICAgIC5wYWdlLXRpdGxlLXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDI7CiAgICAgICAgfQoKICAgICAgICAucGFnZS10aXRsZSB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW4tcmV2ZXJzZTsKICAgICAgICB9CgogICAgICAgIC5jb250ZW50LXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDU7CiAgICAgICAgfQoKICAgICAgICAuY29udGVudCB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgfQoKICAgICAgICAucGFnZS1mb290ZXItd3JhcHBlciB7CiAgICAgICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMC4yOwogICAgICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgICAgICAgICBjb2xvcjogI2M1YzVjNTsKICAgICAgICAgICAgZm9udC1zaXplOiA3MCU7CiAgICAgICAgfQoKICAgICAgICBAbWVkaWEgKG1pbi13aWR0aDogNzY4cHgpIHsKICAgICAgICAgICAgaHRtbCwgYm9keSB7CiAgICAgICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICA8L3N0eWxlPgogICAgPCEtLSBDdXN0b20gQ1NTIC0tPgo8L2hlYWQ+Cgo8Ym9keT4KPHNlY3Rpb24gY2xhc3M9ImNvbnRhaW5lciI+CiAgICA8ZGl2IGNsYXNzPSJjdXN0b21lci1sb2dvLXdyYXBwZXIiPgogICAgICAgIDxkaXYgY2xhc3M9ImN1c3RvbWVyLWxvZ28iPgogICAgICAgICAgICA8aW1nIHNyYz0iIiBhbHQ9IkxvZ28iLz4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS10aXRsZS13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJwYWdlLXRpdGxlIj4KICAgICAgICAgICAgPGgxPlBsZWFzZSB2ZXJpZnkgeW91IGFyZSBhIGh1bWFuPC9oMT4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGVudC13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50Ij4KICAgICAgICAgICAgPGRpdiBpZD0icHgtY2FwdGNoYSI+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIEFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkIGJlY2F1c2Ugd2UgYmVsaWV2ZSB5b3UgYXJlIHVzaW5nIGF1dG9tYXRpb24gdG9vbHMgdG8gYnJvd3NlIHRoZQogICAgICAgICAgICAgICAgd2Vic2l0ZS4KICAgICAgICAgICAgPC9wPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFRoaXMgbWF5IGhhcHBlbiBhcyBhIHJlc3VsdCBvZiB0aGUgZm9sbG93aW5nOgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgICAgICBKYXZhc2NyaXB0IGlzIGRpc2FibGVkIG9yIGJsb2NrZWQgYnkgYW4gZXh0ZW5zaW9uIChhZCBibG9ja2VycyBmb3IgZXhhbXBsZSkKICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICAgICAgWW91ciBicm93c2VyIGRvZXMgbm90IHN1cHBvcnQgY29va2llcwogICAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgPC91bD4KICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBQbGVhc2UgbWFrZSBzdXJlIHRoYXQgSmF2YXNjcmlwdCBhbmQgY29va2llcyBhcmUgZW5hYmxlZCBvbiB5b3VyIGJyb3dzZXIgYW5kIHRoYXQgeW91IGFyZSBub3QgYmxvY2tpbmcKICAgICAgICAgICAgICAgIHRoZW0gZnJvbSBsb2FkaW5nLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgICAgUmVmZXJlbmNlIElEOiAjZmQwMWU2ZDYtOWNmMi0xMWVlLTgwOGMtYWNkZTQ4MDAxMTIyCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXItd3JhcHBlciI+CiAgICAgICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXIiPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFBvd2VyZWQgYnkKICAgICAgICAgICAgICAgIDxhIGhyZWY9Imh0dHBzOi8vd3d3LnBlcmltZXRlcnguY29tL3doeXdhc2libG9ja2VkIj5QZXJpbWV0ZXJYPC9hPgogICAgICAgICAgICAgICAgLCBJbmMuCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2Pgo8L3NlY3Rpb24+CjwhLS0gUHggLS0+CjxzY3JpcHQ+CiAgICB3aW5kb3cuX3B4QXBwSWQgPSAnUFhqOXk0UThFbSc7CiAgICB3aW5kb3cuX3B4SnNDbGllbnRTcmMgPSAnLy9jbGllbnQucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9tYWluLm1pbi5qcyc7CiAgICB3aW5kb3cuX3B4Rmlyc3RQYXJ0eUVuYWJsZWQgPSBmYWxzZTsKICAgIHdpbmRvdy5fcHhWaWQgPSAnOTI4ZDdhYjMtOWNmMS0xMWVlLWE2MjQtYjgwMjUyMGYzNjlmJzsKICAgIHdpbmRvdy5fcHhVdWlkID0gJ2ZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMic7CiAgICB3aW5kb3cuX3B4SG9zdFVybCA9ICcvL2NvbGxlY3Rvci1QWGo5eTRROEVtLnBlcmltZXRlcngubmV0JzsKPC9zY3JpcHQ+CjwhLS0gQ2FwdGNoYSAtLT4KPHNjcmlwdD4KICAgIHZhciBzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7CiAgICBzLnNyYyA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnOwogICAgdmFyIHAgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdOwogICAgcC5pbnNlcnRCZWZvcmUocywgbnVsbCk7CiAgICBpZiAoZmFsc2UpIHsKICAgICAgICBzLm9uZXJyb3IgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgIHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsKICAgICAgICAgICAgdmFyIHN1ZmZpeEluZGV4ID0gJy8vY2FwdGNoYS5wZXJpbWV0ZXJ4Lm5ldC9QWGo5eTRROEVtL2NhcHRjaGEuanM/YT1jJm09MSZ1PWZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMiZ2PTkyOGQ3YWIzLTljZjEtMTFlZS1hNjI0LWI4MDI1MjBmMzY5ZicuaW5kZXhPZignY2FwdGNoYS5qcycpOwogICAgICAgICAgICB2YXIgdGVtcGVyZWRCbG9ja1NjcmlwdCA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnLnN1YnN0cmluZyhzdWZmaXhJbmRleCk7CiAgICAgICAgICAgIHMuc3JjID0gJy8vY2FwdGNoYS5weC1jZG4ubmV0L1BYajl5NFE4RW0vJyArIHRlbXBlcmVkQmxvY2tTY3JpcHQ7CiAgICAgICAgICAgIHAucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUocywgcCk7CiAgICAgICAgfTsKICAgIH0KPC9zY3JpcHQ+CjwhLS0gQ3VzdG9tIFNjcmlwdCAtLT4KPC9ib2R5Pgo8L2h0bWw+Cg==",
      "appId": "PXj9y4Q8Em",
      "action": "captcha",
      "collectorUrl": "https://collector-pxj9y4q8em.perimeterx.net"
    }
    
  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.