All files / src use-visitor-data.ts

97.72% Statements 43/44
100% Branches 13/13
87.5% Functions 7/8
97.29% Lines 36/37

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 99 100 101 1023x 3x   3x 3x 3x 3x                                         30x 30x 30x   27x   27x   27x 27x   27x 27x   27x 16x 10x   10x     10x   10x   10x   10x       9x 9x   1x   1x   1x   1x   10x           27x 27x 5x           81x   27x                             3x  
import { FpjsContextInterface, FpjsContext, GetDataOptions, QueryResult, VisitorQueryContext } from './fpjs-context'
import { useCallback, useContext, useEffect, useState } from 'react'
import { GetOptions, VisitorData } from '@fingerprintjs/fingerprintjs-pro-spa'
import { usePrevious } from './utils/use-previous'
import deepEquals from 'fast-deep-equal'
import { toError } from './utils/to-error'
import { assertIsTruthy } from './utils/assert-is-truthy'
 
export type UseVisitorDataOptions<TExtended extends boolean> = GetDataOptions<TExtended>
 
/**
 *  @example
 * ```js
 *  const {
 *    // Request state
 *    data,
 *    isLoading,
 *    error,
 *    // A method to be called manually when the `immediate` field in the config is set to `false`:
 *    getData,
 *  } = useVisitorData({ extended: true }, { immediate: false });
 * ```
 * Use the `useVisitorData` hook in your components to perform identification requests with the FingerprintJS API. The returned object contains information about loading status, errors, and visitor.
 *
 * @param getOptions options for the `fp.get()` request
 * @param config config for the hook
 */
export function useVisitorData<TExtended extends boolean>(
  getOptions: UseVisitorDataOptions<TExtended> = {},
  config: UseVisitorDataConfig = defaultUseVisitorDataConfig
): VisitorQueryContext<TExtended> {
  assertIsTruthy(getOptions, 'getOptions')
 
  const previousGetOptions = usePrevious(getOptions)
 
  const { immediate } = config
  const { getVisitorData } = useContext<FpjsContextInterface<TExtended>>(FpjsContext)
 
  const initialState = { isLoading: config.immediate ? true : false }
  const [state, setState] = useState<QueryResult<VisitorData<TExtended>>>(initialState)
 
  const getData = useCallback<VisitorQueryContext<TExtended>['getData']>(
    async (params = {}) => {
      assertIsTruthy(params, 'getDataParams')
 
      const { ignoreCache, ...getDataPassedOptions } = params
 
      try {
        setState((state) => ({ ...state, isLoading: true }))
 
        const { ignoreCache: defaultIgnoreCache, ...getVisitorDataOptions } = getOptions
 
        const getDataOptions: GetOptions<TExtended> = { ...getVisitorDataOptions, ...getDataPassedOptions }
 
        const result = await getVisitorData(
          getDataOptions,
          typeof ignoreCache === 'boolean' ? ignoreCache : defaultIgnoreCache
        )
        setState((state) => ({ ...state, data: result, isLoading: false, error: undefined }))
        return result
      } catch (unknownError) {
        const error = toError(unknownError)
 
        error.name = 'FPJSAgentError'
 
        setState((state) => ({ ...state, data: undefined, error }))
 
        throw error
      } finally {
        setState((state) => (state.isLoading ? { ...state, isLoading: false } : state))
      }
    },
    [getOptions, getVisitorData]
  )
 
  useEffect(() => {
    if (immediate && (!previousGetOptions || !deepEquals(getOptions, previousGetOptions))) {
      getData().catch((error) => {
        console.error(`Failed to fetch visitor data on mount: ${error}`)
      })
    }
  }, [immediate, getData, previousGetOptions, getOptions])
 
  const { isLoading, data, error } = state
 
  return {
    getData,
    isLoading,
    data,
    error,
  }
}
 
export interface UseVisitorDataConfig {
  /**
   * Determines whether the `getData()` method will be called immediately after the hook mounts or not
   */
  immediate: boolean
}
 
const defaultUseVisitorDataConfig = { immediate: true }