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
'sinit
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:
- We start the SDK as soon as possible and on the main thread.
- We create a
HSPolicy
instance. This object is used to configure the SDK's behavior. - 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. TheHSChallengeView
must be the first component in thebody
. TheHSChallengeView
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 theHSChallengeView
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:
- We add the
HSChallengeView
to theContentView
'sbody
. - We call the
HSChallengeModelViewHelper/unregister(uuid:)
function when theContentView
is disappearing.
- We add the
-
Model / HTTP client:
- We get the SDK's HTTP headers.
- We add those HTTP headers to our URL requests.
- We send the URL request.
- 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.
- We return the block error back to the
ViewModel
.
-
View Model:
- We declare a unique identifier (
challengeUUID
) for the view. - 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). - We provide an optional callback that will be called when the challenge was solved/cancelled.
- We declare a unique identifier (
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:
- The callback is called out of the original request's scope. Meaning, you will need to ensure your app handles this case correctly.
- The challenge could be canceled by the user. In this case, you should not retry the request because it will be blocked again.
- It's possible that the retry attempt will be blocked as well. DO NOT assume that it will go through.
Understanding the block response
- 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"
}
- The JSON contains metadata for the SDK.
- This metadata included inside the block error you get via the
HumanSecurity/blockError(response:data:forAppId:)
function. - 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:
- 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)")
}
}
Updated 6 days ago