Native iOS and Android risk signals

Kade React Native SDK

Collect device integrity, fingerprint, network, and location signals, then evaluate geofence and fraud policy through native Swift and Kotlin modules.

0.4.0 current version React Native 0.71+ iOS 13+ Native Swift and Kotlin

Overview

The package exports a GeoFenceSDK class, public TypeScript types, and a Permissions namespace. React Native autolinking connects the JavaScript wrapper to the native modules.

Native collection

Location, device fingerprinting, integrity checks, secure storage, sensors, and network probes run in the SDK's own native code. No additional native peer package is required.

Foreground lifecycle

Continuous checks pause when the app backgrounds and resume against the server-provided interval when the app becomes active. Version 0.4.0 does not perform background location checks.

Native builds are required. This package cannot run in Expo Go because Expo Go does not include its custom native modules. Use a bare React Native app or an Expo development/production build.

Install

The package is published to the Kade project package registry on GitLab, not the public npm registry. Configure the @videntrix scope and an authorized registry token before installing.

Configure npm

Add this project-level .npmrc. Supply NPM_TOKEN through your local environment or CI secret store; do not commit the token.

@videntrix:registry=https://gitlab.com/api/v4/projects/geofence1%2Freact-native-sdk/packages/npm/
//gitlab.com/api/v4/projects/geofence1%2Freact-native-sdk/packages/npm/:_authToken=${NPM_TOKEN}

Use a GitLab personal, project, deploy, or CI job token that can read this project's package registry. A deploy token needs read_package_registry; a personal or project access token commonly uses the api scope.

Install the package

npm install @videntrix/react-native-geofence-sdk@0.4.0

# Or with Yarn after the same registry configuration
yarn add @videntrix/react-native-geofence-sdk@0.4.0
Registry access is required for every fresh install. A plain npm install without the scoped registry and a token will attempt the wrong registry or fail authorization.

Bare React Native setup

iOS

The podspec supports iOS 13 or newer and autolinks both the native core and React Native bridge. Keep the deployment target at 13.0 or higher, add the foreground location usage text, then install pods.

# ios/Podfile
platform :ios, '13.0'

target 'YourApp' do
  config = use_native_modules!
  use_react_native!(path: config[:reactNativePath])
end
<!-- ios/YourApp/Info.plist -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>We verify your location to confirm you are in a licensed region.</string>
cd ios
pod install
cd ..

The pod uses Swift 5.7. No manual pod 'GeoFenceSdk' entry is needed when React Native autolinking is enabled.

Android

The included React Native configuration points autolinking at the package's bridge class. The library manifest declares:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

You do not need to register the package manually or duplicate these permissions in the app manifest. Before a location-enabled ping() or startContinuousPing(), the wrapper requests runtime location permission and gives Android an opportunity to upgrade coarse access to precise access.

The SDK does not request Wi-Fi scan permissions and does not read BSSIDs. Use permissionRationale in the constructor to provide Android rationale copy when required by the OS.

Expo setup

The bundled config plugin writes the iOS foreground location description and ensures the Android location and network permissions are present.

{
  "expo": {
    "plugins": [
      [
        "@videntrix/react-native-geofence-sdk",
        {
          "locationUsageDescription": "We verify your location to confirm you are in a licensed region."
        }
      ]
    ]
  }
}
npx expo prebuild

# Build and install a native development client
eas build --profile development
Not supported in Expo Go. Run expo prebuild and use a development build, or let EAS Build generate the native projects. Changing plugin options requires regenerating/rebuilding the native app.

Quick start

Create one instance, attach an error observer, initialize it, and run a ping. Public operations initialize the native client automatically, but explicit startup makes initialization failures easier to handle.

import GeoFenceSDK from '@videntrix/react-native-geofence-sdk';

const sdk = new GeoFenceSDK({
  apiToken: 'your-api-token',
  regionId: 123,
  locationAccuracy: 'high',
  debug: __DEV__,
  permissionRationale: {
    title: 'Location access',
    message: 'Kade uses your location to confirm your licensed region.',
  },
});

sdk.onError((error, context) => {
  console.warn(`Kade SDK error in ${context}`, error);
});

await sdk.init();

const response = await sdk.ping({
  userId: 'player-123',
  source: 'login',
  email: 'player@example.com',
});

if (response.blocked) {
  handleBlocked(response.code, response.message);
} else {
  console.log(response.inside, response.area?.name);
}

Omit skipLocation for normal GPS-backed verification. Pass skipLocation: true when the flow intentionally relies on server-side network location and must not request device location.

Configuration

apiToken is the only required constructor option. serverUrl defaults to https://api.videntrix.com.

OptionTypeBehavior
apiTokenstringRequired API token sent with SDK requests.
serverUrlstring?Overrides the default Kade API origin.
regionIdnumber?Default region; a ping-level region overrides it.
signingSecretstring?Enables HMAC request signing in the native client.
locationStrategy'race' | 'sequential'?race runs high- and low-accuracy acquisition in parallel; sequential tries high accuracy first. The default is race.
locationMaxAgenumber?Maximum cached GPS fix age in milliseconds. Default 120,000.
locationMaxStalenessRadiusnumber?Fallback maximum displacement in metres when speed is unavailable. Default 100.
locationAccuracy'high' | 'balanced' | 'low'?Native location accuracy tier. Default high.
permissionRationale{ title, message }?Android runtime permission rationale copy.
debugboolean?Enables verbose native diagnostics. Lifecycle, warning, and error logs still emit when false.

Core API

new GeoFenceSDK(config: GeoFenceConfig)

Creates an instance and validates that apiToken is present. Native resources are prepared by init() or the first operation that initializes automatically.

onError(handler: SdkErrorHandler): this

Observes JavaScript and native errors handled by the SDK and returns the instance for chaining. Continue to catch rejected method promises; initialization and ping failures can still reject.

init(): Promise<void>

Starts the native client, installs event listeners, and prepares SDK state. Repeated calls after initialization are no-ops.

ping(options?: PingOptions): Promise<PingResponse>

Runs one geofence and risk evaluation. Unless skipLocation is true, it handles runtime location permission before calling the native ping client.

getFingerprint(userId?: string): Promise<FingerprintResult | FingerprintServerResponse>

Without a user ID, returns the local device fingerprint result. With a user ID, the native client can send the fingerprint for server-side association and return the enriched server response.

evaluateEmail(options: EvaluateEmailOptions): Promise<void>

Posts registration identity data to /public/email-check. It returns no verdict to the app; results are operator-only. Network errors are reported through the SDK error path and swallowed so registration is not interrupted.

startContinuousPing(options?: PingOptions, callback?: (response: PingResponse, error?: Error) => void): Promise<void>

Starts native recurring checks and reuses the supplied options for every check. Responses and loop errors are delivered to the callback.

stopContinuousPing(): Promise<void>

Stops the loop and location watcher and clears its callback.

destroy(): Promise<void>

Stops continuous checks, removes native listeners, releases native resources, and allows a later call to initialize again.

Email evaluation

await sdk.evaluateEmail({
  email: 'new.user@example.com',
  firstName: 'New',
  lastName: 'User',
  userId: 'player-123',
});

// The promise resolves without a client-visible verdict.

Location permissions

Import the package-level Permissions namespace when your UI needs to inspect or request location access before a ping.

import {
  Permissions,
  type PermissionStatus,
} from '@videntrix/react-native-geofence-sdk';

const current: PermissionStatus = await Permissions.getLocationStatus();

if (current !== 'granted') {
  const result = await Permissions.requestLocation({
    title: 'Location access',
    message: 'Location is required to verify your licensed region.',
  });

  if (result !== 'granted') {
    showLocationSettingsHelp(result);
  }
}
HelperReturn valueBehavior
Permissions.getLocationStatus()Promise<PermissionStatus>Reads granted, denied, restricted, or notDetermined.
Permissions.requestLocation(rationale?)Promise<PermissionStatus>Requests foreground location. Concurrent calls share one in-flight native request; the first rationale wins.

Normal ping() and startContinuousPing() calls perform this permission flow automatically unless skipLocation: true is set.

Continuous ping and lifecycle

The native loop pings immediately, then schedules each subsequent check using the server's response.interval.

await sdk.startContinuousPing(
  {
    userId: 'player-123',
    source: 'gameplay',
    regionId: 123,
  },
  (response, error) => {
    if (error) {
      console.warn('Continuous ping failed', error);
      return;
    }

    if (response.blocked) {
      handleBlocked(response.code);
      return;
    }

    updateLocationState(response.inside, response.distanceToBorder);
  },
);

// Await this when the protected flow ends.
await sdk.stopContinuousPing();

Background

Timers and the location watcher stop when the app enters the background. Version 0.4.0 is foreground-only and does not request always-on location.

Foreground

If the last server interval elapsed while paused, the SDK pings immediately. Otherwise it waits only the remaining interval.

The native loop stops after a blocked response or HTTP status 403/451. It also adapts location watching to the server interval and disables the watcher for skipLocation.

Types and responses

PingOptions

interface PingOptions {
  userId?: string;
  silent?: boolean;
  skipLocation?: boolean;
  source?: string;
  regionId?: number;
  email?: string;
  firstName?: string;
  lastName?: string;
}

PingResponse

interface PingArea {
  id: number;
  name: string;
}

interface PingResponse {
  inside: boolean;
  status: number;
  message?: string;
  code?: GeoFenceResponseCode;
  interval: number;
  distanceToBorder: number;
  regionId?: number;
  area?: PingArea;
  blocked?: boolean;
  requestLocation?: boolean;
  deviceTrustTtl?: number;
}

requestLocation lets the server ask the native client to escalate toward GPS on a later ping. deviceTrustTtl is a server-provided millisecond window in which the native client may skip expensive integrity probes.

Fingerprint results

interface FingerprintResult {
  visitorId: string;
}

interface FingerprintServerResponse {
  clientId: string;
  fingerprintRequestCount: number;
  visitorId?: string;
  confidence: {
    score: number;
    comment: string;
  };
  ip: string;
  ja3?: string;
  ja4?: string;
  device: {
    userAgent?: string;
    browser?: string;
    os?: string;
  };
}

Mobile block codes

Branch on blocked === true first. The code union permits future strings, so unknown codes must still be treated as blocked.

CodeMeaning
BLOCKEDGeneric security or compliance rejection.
BLACKLISTEDThe client was already blacklisted.
DEV_TOOLS_DETECTEDDebugger-style inspection contributed to the block.
VPN_DETECTEDVPN, proxy, hosting, datacenter, or related network evidence contributed to the block.
DEVELOPER_MODE_DETECTEDiOS/Android developer mode, ADB, or an attached debugger contributed to the block.
EMULATOR_DETECTEDAn emulator or simulator contributed to the block.
LOCATION_PERMISSION_MISSINGForeground location permission was not granted.
LOCATION_PERMISSION_PRECISE_REQUIREDPrecise location was required but unavailable.
LOCATION_SERVICES_DISABLEDDevice-level location services are disabled.
LOCATION_TIMEOUTThe SDK timed out waiting for a usable location fix.
LOCATION_UNAVAILABLEThe SDK could not obtain a usable location fix.

Web and mobile differences

These GeoFenceSDK methods are Web-only: estimateLocation(), primeLocation(), and requestLocationPermission(). They are not methods on the React Native GeoFenceSDK class.

On React Native, use Permissions.getLocationStatus() and Permissions.requestLocation() for explicit UI flows. For the normal case, call ping() or startContinuousPing() and let the SDK perform automatic foreground permission handling.

React Native

Uses native GPS, integrity, sensor, secure-storage, and network probes. It pauses continuous work while the app is backgrounded.

Web

Uses browser APIs and exposes browser-specific location priming and network-estimation methods. Those methods do not transfer to the native package.

Need help with your integration?

Talk to the Kade team about package registry access, native setup, permission UX, configuration, or production rollout.

Contact Kade