Ionic Integration

Intro

The SDK can be integrated into a Ionic project.

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 will send an 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.
  • Create a HumanManager object that will handle calls from Ionic side.

Here is an example of how it should be:

Android

Java / MainApplication.java:

1import android.app.Application;
2
3public class MainApplication extends Application {
4
5 @Override
6 public void onCreate() {
7 super.onCreate();
8 HumanManager.startHumanSDK(this);
9 }
10}

Java / HumanManager.kt:

1import android.app.Application;
2import com.getcapacitor.JSObject;
3import com.getcapacitor.Plugin;
4import com.getcapacitor.PluginCall;
5import com.getcapacitor.PluginMethod;
6import com.getcapacitor.annotation.CapacitorPlugin;
7import com.humansecurity.mobile_sdk.HumanSecurity;
8import com.humansecurity.mobile_sdk.main.policy.HSPolicy;
9import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType;
10import com.humansecurity.mobile_sdk.main.HSBotDefenderChallengeResult
11import java.util.HashMap;
12import android.util.Log;
13
14@CapacitorPlugin(name = "Human")
15public class HumanManager extends Plugin {
16
17 String strSolved = "solved";
18 String strCancelled = "cancelled";
19 String strFalse = "false";
20 String strValue = "value";
21
22 static void startHumanSDK(Application application) {
23 HSPolicy policy = new HSPolicy();
24 policy.getAutomaticInterceptorPolicy().setInterceptorType(HSAutomaticInterceptorType.NONE);
25 try {
26 HumanSecurity.INSTANCE.start(application, "<APP_ID>", policy);
27 }
28 catch (Exception exception) {
29 Log.e("HumanManager","Exception: " + exception.getMessage());
30 }
31 }
32
33 @PluginMethod()
34 public void getHttpHeaders(PluginCall call) {
35 HashMap headers = HumanSecurity.INSTANCE.getBD().headersForURLRequest("<APP_ID>");
36 JSObject ret = new JSObject();
37 for (Object key : headers.keySet()) {
38 ret.put((String)key, headers.get(key));
39 }
40 call.resolve(ret);
41 }
42
43 @PluginMethod()
44 public void handleResponse(PluginCall call) {
45 String response = call.getString(strValue);
46 JSObject ret = new JSObject();
47 boolean handled = HumanSecurity.INSTANCE.getBD().handleResponse(response, result -> {
48 ret.put(strValue, result == HSBotDefenderChallengeResult.SOLVED ? strSolved : strCancelled);
49 call.resolve(ret);
50 return null;
51 });
52 if (!handled) {
53 ret.put(strValue, strFalse);
54 call.resolve(ret);
55 }
56 }
57}

Java / MainActivity.java:

1import android.os.Bundle;
2import com.getcapacitor.BridgeActivity;
3
4public class MainActivity extends BridgeActivity {
5
6 @Override
7 protected void onCreate(Bundle savedInstanceState) {
8 registerPlugin(HumanManager.class);
9 super.onCreate(savedInstanceState);
10 }
11}

iOS

Swift / AppDelegate.swift:

1import UIKit
2import Capacitor
3
4@UIApplicationMain
5class AppDelegate: UIResponder, UIApplicationDelegate {
6
7 var window: UIWindow?
8
9 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
10 HumanManagerPlugin.shared.start()
11 return true
12 }
13
14 func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
15 return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
16 }
17
18 func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
19 return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
20 }
21}

Swift / HumanManager.swift:

1import Foundation
2import Capacitor
3import HUMAN
4
5@objc(HumanManagerPlugin)
6class HumanManagerPlugin: CAPPlugin {
7
8 let strSolved = "solved"
9 let strCancelled = "cancelled"
10 let strFalse = "false"
11 let strValue = "value"
12
13 static let shared = HumanManagerPlugin()
14
15 func start() {
16 do {
17 let policy = HSPolicy()
18 policy.automaticInterceptorPolicy.interceptorType = .none
19 try HumanSecurity.start(appId: "<APP_ID>", policy: policy)
20 }
21 catch {
22 print("Error: \(error)")
23 }
24 }
25
26 @objc func getHttpHeaders(_ call: CAPPluginCall) {
27 call.resolve(HumanSecurity.BD.headersForURLRequest(forAppId: "<APP_ID>"))
28 }
29
30 @objc func handleResponse(_ call: CAPPluginCall) {
31 let response = call.getString(strValue) ?? ""
32 let data = response.data(using: .utf8)
33 let httpURLResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 403, httpVersion: nil, headerFields: nil)
34 let handled = HumanSecurity.BD.handleResponse(response: httpURLResponse!, data: data!) { challengeResult in
35 call.resolve([self.strValue: challengeResult == .solved ? self.strSolved : self.strCancelled])
36 }
37 if !handled {
38 call.resolve([strValue: strFalse])
39 }
40 }
41}

Objective-C / HumanManagerPlugin.m:

1#import <Foundation/Foundation.h>
2#import <Capacitor/Capacitor.h>
3
4CAP_PLUGIN(HumanManagerPlugin, "HUMAN",
5 CAP_PLUGIN_METHOD(getHttpHeaders, CAPPluginReturnPromise);
6 CAP_PLUGIN_METHOD(handleResponse, CAPPluginReturnPromise);
7)

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. This object is used to configure the SDK’s behavior. Here, we set the HSAutomaticInterceptorPolicy/interceptorType property to HSAutomaticInterceptorType/none. This means that the Automatic Interception feature of the SDK is disabled. In short, this feature allows the SDK to manipulate URL requests and handle their responses by itself. However, this feature is not supported in Ionic.
  3. We call the HumanSecurity/start(appId:policy:) function of the SDK. We provide the following parameters:
    • The Application instance (Android only).
    • Your AppID.
    • The policy object that we configured.
  4. We send the response from the Ionic to the native side.
  5. We call the HSBotDefender/handleResponse(response:data:forAppId:callback:) function. This function will present a challenge to the user when the provided response is a blocked response.

Note: If your app communicates with several servers that have different AppID, you can call the HumanSecurity/start(appIds:policy:) function which allow you to pass an array of AppIDs. You should 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 do the following:

  1. Export the HumanManager plugin.
  2. Add The SDK’s HTTP headers to your URL requests.
  3. When a request is blocked, send the block response to the SDK.
  4. Handle the challenge’s result.

Here is an example of how it should be:

Javascript:

1import { registerPlugin } from '@capacitor/core';
2
3export interface HumanPlugin {
4 getHttpHeaders():Promise<{ value: string }>;
5 handleResponse(value : any):Promise<{ value: string }>;
6}
7
8const Human = registerPlugin<HumanPlugin>('HUMAN');
9
10const sendUrlRequest = async () => {
11 const result = await Human.getHttpHeaders()
12 const json = JSON.parse(JSON.stringify(result))
13 const map = new Map(Object.entries(json))
14
15 var xhr = new XMLHttpRequest()
16 xhr.addEventListener('load', async () => {
17 const challengeResult = await Human.handleResponse({ "value": xhr.responseText })
18 const challengeResultJson = JSON.parse(JSON.stringify(challengeResult))
19 const challengeResultMap = new Map(Object.entries(challengeResultJson))
20 console.log("challenge result = ", challengeResultMap.get("value"))
21 })
22 xhr.open('GET', 'https://example.com')
23 for (const [key, value] of map) {
24 xhr.setRequestHeader(key, value as string)
25 }
26 xhr.send()
27};

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. The HTTP status code is 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.