Native location and device risk signals

Kade iOS Core SDK

Integrate encrypted geofence checks, continuous verification, persisted fingerprints, and native device signals directly in a Swift application.

iOS 13.0+ deployment target Swift 5.7 No React in Core Singleton client lifecycle

Overview

GeoFenceClient.shared is the high-level native entry point. It owns configuration, encryption, pings, continuous checks, and fingerprint submission. GeoFenceCore.shared exposes lower-level permissions, location, device, and marker collection.

Native Core

The Core source is platform-native Swift and does not import React. The React Native bridge is a separate layer and is not part of this API.

One process-wide client

Use the shared client, start it before calling ping or training methods, and destroy it when the integration is no longer needed.

Native Core does not expose browser-only email evaluation or network-based location estimation APIs. Those methods are intentionally absent from this guide.

Availability

The iOS Core SDK is not currently distributed as a standalone public CocoaPods artifact. There is no public pod installation command to use.

Contact Kade for access. Kade will provide the approved source or integration package and the packaging instructions for your application. The repository podspec defines a React-free Core subspec, but that does not constitute a public release.

The source configuration sets an iOS 13.0 minimum, Swift 5.7, and links Foundation, CoreLocation, CoreMotion, SystemConfiguration, Network, Security, and DeviceCheck.

Integration

After Kade supplies the native package, add it to the application target using the delivery-specific instructions. Do not add the React Native bridge to a native-only target.

Application requirements

Build your application target to verify the delivered package:

xcodebuild \
  -workspace YourApp.xcworkspace \
  -scheme YourApp \
  -sdk iphonesimulator \
  build
Repository packaging currently uses the GeoFenceSdk pod name. The import name may differ if Kade supplies Core in another form; follow the package-specific instructions you receive.

Quick start

Start the shared client asynchronously, then issue a ping only after configuration succeeds.

import GeoFenceSdk

let client = GeoFenceClient.shared
let config = GeoFenceConfig(
    apiToken: "your-api-token",
    regionId: 123
)

client.start(config: config) { result in
    switch result {
    case .success:
        let options = PingOptions(
            userId: "player-123",
            source: "login",
            email: "player@example.com"
        )

        client.ping(options: options) { pingResult in
            switch pingResult {
            case .success(let response):
                if response.blocked == true {
                    print("Blocked:", response.code ?? "unknown")
                } else {
                    print("Inside:", response.inside)
                    print("Area:", response.area?.name ?? "none")
                }
            case .failure(let error):
                print("Kade ping failed:", error)
            }
        }
    case .failure(let error):
        print("Kade startup failed:", error)
    }
}
start(config:completion:) derives the encryption key on a background queue and invokes its completion with a Swift Result. Do not assume the client is ready immediately after calling start.

Location permissions

Add a user-facing location purpose string to the host application's Info.plist, then use GeoFenceCore to inspect and request authorization.

<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location is used to verify availability in your region.</string>
let core = GeoFenceCore.shared

switch core.getLocationPermissionStatus() {
case .granted:
    beginVerification()
case .notDetermined:
    core.requestLocationPermission { status in
        DispatchQueue.main.async {
            if status == .granted {
                beginVerification()
            } else {
                showLocationUnavailable()
            }
        }
    }
case .denied, .restricted:
    showLocationUnavailable()
}

The native permission states are granted, denied, restricted, and notDetermined. If your policy permits a server-side check without device location, set skipLocation: true on PingOptions; confirm that policy with Kade.

The Core source documents errors for missing permission, insufficient precision, disabled location services, timeouts, and unavailable location. Explain the request before prompting and handle every non-granted state.

Core API

These are native Swift signatures from the Core layer, not React Native bridge methods.

GeoFenceClient

GeoFenceClient.shared Returns the process-wide client instance. Its initializer is private.
start(config: GeoFenceConfig, completion: @escaping (Result<Void, Error>) -> Void) Configures Core, derives the encryption key, and prepares the ping client.
ping(options: PingOptions, completion: @escaping (Result<PingResponse, Error>) -> Void) Sends one geofence check. It fails with notInitialized if startup has not completed.
startContinuousPing(options: PingOptions) Starts one continuous controller. Responses and errors are delivered through onPingResponse and onPingError.
stopContinuousPing() Stops and releases the continuous controller.
currentFingerprint() -> FingerprintResult Returns the current local fingerprint, containing visitorId.
sendFingerprintForTraining(userId: String, completion: @escaping (Result<[String: Any], Error>) -> Void) Sends the encrypted fingerprint and static markers to the configured server and returns its raw JSON dictionary.
destroy() Stops continuous work, destroys Core resources, resets active markers, and clears client configuration and key material.

Selected GeoFenceCore methods

let core = GeoFenceCore.shared

let status = core.getLocationPermissionStatus()
let device = core.getDeviceInfo()
let jailbreak = core.getJailbreak()
let developerMode = core.getDeveloperMode()
let mockLocation = core.getMockLocation()
let locationSource = core.getLocationSource()
let vpn = core.getVpn()
let battery = core.getBattery()

core.getCurrentLocation(
    accuracy: .high,
    timeoutMs: 10_000,
    maxAgeMs: 120_000
) { result in
    // Result<GeoLocation, Error>
}

Core also exposes sensor collection, integrity attestation, aggregate mobile markers, persisted fingerprint ID accessors, and explicit location watcher controls. Most integrations should prefer GeoFenceClient for policy checks.

Configuration

GeoFenceConfig

FieldType and defaultBehavior
apiTokenString, requiredWorkspace API token.
serverUrlString, https://api.videntrix.comAPI base URL.
regionIdInt?, nilDefault region; ping-level value overrides it.
signingSecretString?, nilEnables request signing when non-empty.
locationStrategyLocationStrategy, .raceLocation retrieval strategy.
locationMaxAgeMsInt, 120,000Maximum preferred cached-fix age.
locationMaxStalenessRadiusDouble, 100Staleness radius used by location selection.
locationAccuracyLocationAccuracy, .high.high, .balanced, or .low.
debugBool, falseEnables verbose SDK logging.

PingOptions

let options = PingOptions(
    userId: "player-123",
    silent: false,
    skipLocation: false,
    source: "deposit",
    regionId: 123,
    email: "player@example.com",
    firstName: "Player",
    lastName: "One"
)

Every option has a default. source is a free-form flow tag, regionId overrides configuration for that ping, and identity fields are associated with the server-side client record.

Lifecycle and continuous checks

Register handlers before starting continuous checks, retain no second client instance, and stop checks when the protected flow ends.

let client = GeoFenceClient.shared

client.onPingResponse = { response in
    DispatchQueue.main.async {
        updateAccessState(using: response)
    }
}

client.onPingError = { error in
    DispatchQueue.main.async {
        showVerificationError(error)
    }
}

client.startContinuousPing(
    options: PingOptions(userId: "player-123", source: "session")
)

// When the verified flow ends:
client.stopContinuousPing()

// When disposing the entire integration:
client.destroy()

Response handling

Branch on blocked first, tolerate optional fields, and keep transport failures separate from server verdicts.

func handle(_ response: PingResponse) {
    if response.blocked == true {
        denyAccess(
            code: response.code,
            message: response.message
        )
        return
    }

    if response.inside {
        allowAccess(areaName: response.area?.name)
    } else {
        showOutsideRegion(distance: response.distanceToBorder)
    }

    if response.requestLocation == true {
        // Revisit the permission/location flow before the next check.
    }
}
FieldTypeMeaning
insideBoolWhether the server resolved the client inside the relevant geofence.
statusIntServer response status value.
message, codeString?Optional response detail and machine-readable code.
intervalIntServer-provided interval used by continuous checks.
distanceToBorderDoubleDistance value returned by the server.
regionIdInt?Resolved region when present.
areaPingArea?Matched area with id and name.
blockedBool?Explicit block decision when supplied.
requestLocationBool?Whether the response asks the client to obtain location.
deviceTrustTtlInt?Optional device-trust lifetime supplied by the server.

Get the native iOS package

Talk to Kade for SDK access, approved packaging, credentials, and integration guidance for your application.

Contact Kade