visionOS Integration (Sheet)

Introduction

  • In this article we will learn how integrate the SDK to your visionOS app.

  • The sheet option will present the HUMAN challenge in a sheet over the window the request has left from, this will disable the user to continue any interactions in the screen until the challenge is solved.

  • You will need to add the sheet integration code to every view sending requests that might be blocked.

  • We will cover the following topics:

    • How to start the SDK
    • Bot Defender integration
      • How to add the SDK's HTTP headers to your URL requests.
      • How to handle the blocked request.
      • How to present a challenge to the user in a sheet.
      • How to set custom parameters (optional).
    • Account Defender integration
      • How to enable it.
      • How to notify HUMAN's backend on outgoing URL requests from the app.
      • How to set additional data (optional).

Some features, including the Doctor App, Automatic Interceptor and Hybrid App support, are not available in the SDK for visionOS yet. We will add those features soon.

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 App's init function on visionOS.
  • You should start the SDK on the main thread.
  • Note that some SDK features that exist in iOS are currently not available in visionOS.

Here is an example of how it should be:

import UIKit
import HUMAN

@main
struct MyApp: App {

    init() {
        startHumanSDK()
    }

    func startHumanSDK() {
        do {
            let policy = HSPolicy()

            try HumanSecurity.start(appId: "<APP_ID>", policy: policy)
        }
        catch {
            print("Error: \(error)")
        }
    }
}

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. This object is used to configure the SDK's behavior.
  3. We call the start function of the SDK. We provide the following parameters:
    • Your AppID.
    • The policy object that we configured.

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.

Bot Defender integration

How to add the SDK’s HTTP headers to your URL requests and handle the blocked request

  • The SDK provides HTTP headers that should be added to your app's URL requests. It is essential that those HTTP headers will be included on every URL request.
  • You should not cache those HTTP headers. They contain a token with expiration date. The SDK manages this token to be up-to-date.
  • You should add the HSChallengeView for each view that could have blocked requests. The HSChallengeView must be the first component in the body. The HSChallengeView will handle the presentation of the challenge.
  • You should call the HSChallengeModelViewHelper/registerError(error:uuid:callback:) function for every block error you get. You need to provide a unique identifier that represents the view which the HSChallengeView is placed into. You may provide a callback that will be called once the challenge was solved/cancelled.
  • You should call the HSChallengeModelViewHelper/unregister(uuid:) when your view is disappearing. This will release related resources to prevent a memory leak.

Here is an example of how it should be:

import SwiftUI
import HUMAN

struct ContentView: View {

    @State private var model = ViewModel()

    var body: some View {
        VStack {
            HSChallengeView(uuid: model.challengeUUID)

            HStack {
                Button("Send request") {
                    model.performRequest()
                }
                .padding()
            }
        }
        .onDisappear() {
            HSChallengeModelViewHelper.unregister(uuid: model.challengeUUID)
        }
    }
}
import Foundation
import HUMAN

@Observable
class ViewModel {

    let myURL: URL
    let challengeUUID = UUID().uuidString

    func performRequest() {
        sendUrlRequest(url: myURL) { result, error in
            if let error {
                HSChallengeModelViewHelper.registerError(error: error, uuid: challengeUUID) { result in
                    print("challenge result = \(result)")
                }
            }
        }
    }
}
import Foundation
import HUMAN

func sendUrlRequest(url: URL, completion: @escaping (Bool, Error?) -> ()) {
    var request = URLRequest(url: url)
    let myHttpHeaders = [String : String]()

    // config your request and HTTP headers...

    let humanHttpHeaders = HumanSecurity.BD.headersForURLRequest(forAppId: "<APP_ID>")
    request.allHTTPHeaderFields = myHttpHeaders.merging(humanHttpHeaders) { $1 }

    let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
        if let data = data, let response = response as? HTTPURLResponse {
            if let error = HumanSecurity.blockError(response: response, data: data) {
                completion(false, error)
                return
            }
            // handle the response...
        }
    }
    dataTask.resume()
}

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

  • View:

    1. We add the HSChallengeView to the ContentView's body.
    2. We call the HSChallengeModelViewHelper/unregister(uuid:) function when the ContentView is disappearing.
  • Model / HTTP client:

    1. We get the SDK's HTTP headers.
    2. We add those HTTP headers to our URL requests.
    3. We send the URL request.
    4. We send the response to the SDK. The SDK will check if it's a blocked request. If yes, it will return a block error.
    5. We return the block error back to the ViewModel.
  • View Model:

    1. We declare a unique identifier (challengeUUID) for the view.
    2. We call the HSChallengeModelViewHelper/registerError(error:uuid:callback:) function when we get an error (the SDK handles blocked requests only and ignores other kinds of error).
    3. We provide an optional callback that will be called when the challenge was solved/cancelled.

What should I do when a request is blocked

  • Handle it as a failure.

    Your app should handle the blocked request as a failure. However, you should consider that other windows in your app's UI are still shown while the challenge is presented to the user. If the request was triggered by a user's action, you should make it clear that the user may try again the same action.

  • Use the handler callback to write analytics, logs, etc.

  • You may use the handler callback to retry the original request when appropriate. You should consider the following:

    1. The callback is called out of the original request's scope. Meaning, you will need to ensure your app handles this case correctly.
    2. The challenge could be canceled by the user. In this case, you should not retry the request because it will be blocked again.
    3. It's possible that the retry attempt will be blocked as well. DO NOT assume that it will go through.

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:
{
  "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"
}
  1. The JSON contains metadata for the SDK.
  2. This metadata included inside the block error you get via the HumanSecurity/blockError(response:data:forAppId:) function.
  3. Your app should pass the error object to the SDK via the HSChallengeModelViewHelper/registerError(error:uuid:callback:) function with the view's uuid. Otherwise, the SDK won't present a challenge to the user.

How to set custom parameters for Bot Defender (optional)

You can set custom parameters in order to configure HUMAN's backend with additional parameters.

Those parameters can be set with a dictionary (iOS) or an hash map (Android), with the key "custom_param[x]" where [x] is a number between 1-10.

You should call the HSBotDefender/setCustomParameters(parameters:forAppId:) only after the HumanSecurity/start(appId:policy:) function was already called.

Here is an example of how it should be:

import HUMAN

func setCustomParametersForBotDefender() {
    do {
        var customParameters = [String: String]()
        customParameters["custom_param1"] = "hello"
        customParameters["custom_param2"] = "world"
        try HumanSecurity.BD.setCustomParameters(parameters: customParameters, forAppId: "<APP_ID>")
    }
    catch {
        print("Error: \(error)")
    }
}

Account Defender integration

How to enable Account Defender in your app?

In order to enable Account Defender, you should set the UserID of your current logged-in user in the SDK.

Here is an example of how it should be:

import HUMAN

func onUserLoggedIn(userID: String) {
    do {
        try HumanSecurity.AD.setUserId(userId: userID, forAppId: "<APP_ID>")
    }
    catch {
        print("Error: \(error)")
    }
}

How to notify HUMAN's backend on outgoing URL requests from the app

In order to allow Account Defender protect the user's account, your app has to provide the SDK with outgoing URL requests.

import HUMAN

func sendUrlRequest(url: URL) {
    do {
        try HumanSecurity.AD.registerOutgoingUrlRequest(url: url.absoluteString, forAppId: "<APP_ID>")
    }
    catch {
        print("Error: \(error)")
    }

    // Send the request...
}

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

  1. We call the HSAccountDefender/registerOutgoingUrlRequest(url:forAppId:) function before we send the URL request.

How to set additional data (optional)

You can set additional data in order to configure HUMAN's backend with additional parameters.

Those parameters can be set with a dictionary.

You should call the HSAccountDefender/setAdditionalData(parameters:forAppId:) only after the HumanSecurity/start(appId:policy:) function was already called.

import HUMAN

func setAdditionalDataForAccountDefender() {
    do {
        var additionalData = [String: String]()
        additionalData["my_key1"] = "hello"
        additionalData["my_key2"] = "world"
        try HumanSecurity.AD.setAdditionalData(parameters: additionalData, forAppId: "<APP_ID>")
    }
    catch {
        print("Error: \(error)")
    }
}