All files / src FingerprintClient.ts

100% Statements 25/25
90% Branches 27/30
100% Functions 6/6
100% Lines 25/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98              3x   3x             30x 26x   4x 4x 4x               25x 19x   6x 5x   1x       22x                   30x 30x     30x 30x   30x                       25x 25x 25x 25x 22x   2x                                         30x    
import { Platform, type CodegenTypes } from 'react-native'
import RNFingerprint, { type NativeVisitorData } from './specs/NativeRNFingerprint'
import type { FingerprintClient, FingerprintResponse, GetOptions, StartOptions, TagsValue } from './types'
import { unwrapError } from './unwrapError'
import { isDefined, isTruthy } from './utils'
import { validateTags } from './tags'
 
const packageVersion = '__VERSION__'
 
const DEFAULT_LOCATION_TIMEOUT_MILLIS = 5000
 
/**
 * Splits the {@link StartOptions.endpoints} value into a primary endpoint plus fallbacks, matching
 * the positional native `configure` contract.
 */
function normalizeEndpoints(endpoints: StartOptions['endpoints']): { endpointUrl: string | null; fallbacks: string[] } {
  if (!isDefined(endpoints)) {
    return { endpointUrl: null, fallbacks: [] }
  }
  const list = Array.isArray(endpoints) ? endpoints : [endpoints]
  const [primary, ...fallbacks] = list
  return { endpointUrl: list.length > 0 ? primary : null, fallbacks }
}
 
/**
 * The native SDKs model tags as a string→value map. Objects pass through unchanged; primitives and
 * arrays are wrapped under a `tag` key so both iOS and Android receive a valid object.
 */
function toNativeTag(tag: TagsValue | undefined): CodegenTypes.UnsafeObject | null {
  if (!isDefined(tag)) {
    return null
  }
  if (typeof tag === 'object' && !Array.isArray(tag)) {
    return tag
  }
  return { tag }
}
 
function normalizeResponse(data: NativeVisitorData): FingerprintResponse {
  return {
    visitor_id: isTruthy(data.visitorId) ? data.visitorId : undefined,
    event_id: data.eventId,
    suspect_score: data.suspectScore >= 0 ? data.suspectScore : undefined,
    sealed_result: isTruthy(data.sealedResult) ? data.sealedResult : null,
  }
}
 
class NativeFingerprintClient implements FingerprintClient {
  constructor(options: StartOptions) {
    const { apiKey, region, endpoints, android, ios } = options
    const { endpointUrl, fallbacks } = normalizeEndpoints(endpoints)
 
    const allowUseOfLocationData =
      (Platform.OS === 'ios' ? ios?.allowUseOfLocationData : android?.allowUseOfLocationData) ?? false
    const locationTimeoutMillis = android?.locationTimeoutMillis ?? DEFAULT_LOCATION_TIMEOUT_MILLIS
 
    RNFingerprint.configure(
      apiKey,
      packageVersion,
      fallbacks,
      allowUseOfLocationData,
      locationTimeoutMillis,
      region ?? null,
      endpointUrl
    )
  }
 
  public async get(options?: GetOptions): Promise<FingerprintResponse> {
    validateTags(options?.tags)
    const nativeTags = toNativeTag(options?.tags)
    try {
      const data = await RNFingerprint.getVisitorData(nativeTags, options?.linkedId ?? null, options?.timeout ?? null)
      return normalizeResponse(data)
    } catch (error) {
      throw unwrapError(error)
    }
  }
}
 
/**
 * Creates a Fingerprint client with the given options.
 *
 * Applications should create only one client: either through the API client (`start()`)
 * or through a single `FingerprintProvider`. Creating another client will overwrite the underlying native client of the
 * previous one.
 *
 * @group API Client approach
 *
 * @example
 * ```ts
 * const fp = start({ apiKey: 'PUBLIC_API_KEY', region: 'eu' })
 * const result = await fp.get({ linkedId: 'user_1234' })
 * ```
 */
export function start(options: StartOptions): FingerprintClient {
  return new NativeFingerprintClient(options)
}