Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
76a2fec
build(deps): use stable 4.0.0 version of the @fingerprint/agent
TheUnderScorer Jan 13, 2026
fb59d7f
feat: add usePromiseStore to prevent duplicate async requests
TheUnderScorer Jan 27, 2026
3f4afaf
build: migrate to vite and vitest
TheUnderScorer Jan 27, 2026
444587b
refactor: add useConst hook and update usePromiseStore to utilize it
TheUnderScorer Jan 27, 2026
f7c61b5
refactor: adjust @fingerprint/agent export structure
TheUnderScorer Jan 27, 2026
aba662c
refactor: remove unnecessary console log in isEnvDetails function
TheUnderScorer Jan 27, 2026
e28d921
build: update lock
TheUnderScorer Jan 27, 2026
87bbb80
refactor: replace FpjsProvider with FpProvider in Next.js app example
TheUnderScorer Jan 27, 2026
e9b5998
build: update lock
TheUnderScorer Jan 27, 2026
d94c8d9
build: update Vite external dependencies configuration
TheUnderScorer Jan 27, 2026
979e3ee
refactor: replace FpjsProvider with FpProvider in documentation and e…
TheUnderScorer Jan 27, 2026
b196ff5
refactor: remove redundant error name assignment in use-visitor-data
TheUnderScorer Jan 27, 2026
d9d9835
docs: update links and integration instructions in README and contrib…
TheUnderScorer Jan 27, 2026
4714fdf
feat: rename package to `@fingerprint/react`
TheUnderScorer Jan 27, 2026
e1eb0a1
docs: fix typo
TheUnderScorer Jan 27, 2026
3ff7810
test: use resetModules()
TheUnderScorer Jan 27, 2026
149a4a1
docs: fix link
TheUnderScorer Jan 27, 2026
c6ad1b6
build: update package output filenames and references to `fp-react`
TheUnderScorer Jan 27, 2026
63e437e
Merge remote-tracking branch 'origin/feature/INTER-1708-agent-v4' int…
TheUnderScorer Jan 27, 2026
1b6ca6e
refactor: rename `Fp` to `Fingerprint` in exports
TheUnderScorer Jan 27, 2026
a0ee321
Merge remote-tracking branch 'origin/test' into feature/INTER-1708-ag…
TheUnderScorer Jan 27, 2026
64c0c9c
refactor: replace `Fp` with `Fingerprint` in components, context, and…
TheUnderScorer Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/coverage-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ jobs:
checks: write
pull-requests: write
uses: fingerprintjs/dx-team-toolkit/.github/workflows/coverage-diff.yml@v1
with:
testScript: pnpm test:coverage:diff
44 changes: 22 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<p align="center">
<a href="https://github.com/fingerprintjs/fingerprintjs-pro-react/actions/workflows/release.yml"><img src="https://github.com/fingerprintjs/fingerprintjs-pro-react/actions/workflows/release.yml/badge.svg" alt="CI badge" /></a>
<a href="https://fingerprintjs.github.io/fingerprintjs-pro-react/coverage/"><img src="https://fingerprintjs.github.io/fingerprintjs-pro-react/coverage/badges.svg" alt="coverage"></a>
<a href="https://www.npmjs.com/package/@fingerprintjs/fingerprintjs-pro-react"><img src="https://img.shields.io/npm/v/@fingerprintjs/fingerprintjs-pro-react.svg" alt="Current NPM version"></a>
<a href="https://www.npmjs.com/package/@fingerprintjs/fingerprintjs-pro-react"><img src="https://img.shields.io/npm/dm/@fingerprintjs/fingerprintjs-pro-react.svg" alt="Monthly downloads from NPM"></a>
<a href="https://www.npmjs.com/package/@fingerprint/react"><img src="https://img.shields.io/npm/v/@fingerprint/react.svg" alt="Current NPM version"></a>
<a href="https://www.npmjs.com/package/@fingerprint/react"><img src="https://img.shields.io/npm/dm/@fingerprint/react.svg" alt="Monthly downloads from NPM"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/:license-mit-blue.svg" alt="MIT license"></a>
<a href="https://discord.gg/39EpE2neBg"><img src="https://img.shields.io/discord/852099967190433792?style=logo&label=Discord&logo=Discord&logoColor=white" alt="Discord server"></a>
<a href="https://fingerprintjs.github.io/fingerprintjs-pro-react/"><img src="https://img.shields.io/badge/-Documentation-green" alt="Discord server"></a>
Expand All @@ -29,7 +29,7 @@ Fingerprint is a device intelligence platform offering industry-leading accuracy
- [Requirements](#requirements)
- [Installation](#installation)
- [Getting started](#getting-started)
- [1. Wrap your application (or component) in `<FpjsProvider>`.](#1-wrap-your-application-or-component-in-fpjsprovider)
- [1. Wrap your application (or component) in `<FingerprintProvider>`.](#1-wrap-your-application-or-component-in-FingerprintProvider)
- [2. Use the `useVisitorData()` hook in your components to identify visitors](#2-use-the-usevisitordata-hook-in-your-components-to-identify-visitors)
- [Linking and tagging information](#linking-and-tagging-information)
- [Caching strategy](#caching-strategy)
Expand All @@ -46,59 +46,59 @@ Fingerprint is a device intelligence platform offering industry-leading accuracy
- For Typescript users: Typescript 4.8 or higher

> [!NOTE]
> This package assumes you have a Fingerprint Pro subscription or trial, it is not compatible with the [source-available FingerprintJS](https://github.com/fingerprintjs/fingerprintjs). See our documentation to learn more about the [differences between Fingerprint Pro and FingerprintJS](https://dev.fingerprint.com/docs/identification-vs-fingerprintjs).
> This package assumes you have a Fingerprint Pro subscription or trial, it is not compatible with the [source-available FingerprintJS](https://github.com/fingerprintjs/fingerprintjs). See our documentation to learn more about the [differences between Fingerprint Pro and FingerprintJS](https://docs.fingerprint.com/docs/identification-vs-fingerprintjs).

## Installation

Using [npm](https://npmjs.org):

```sh
npm install @fingerprintjs/fingerprintjs-pro-react
npm install @fingerprint/react
```

Using [yarn](https://yarnpkg.com):

```sh
yarn add @fingerprintjs/fingerprintjs-pro-react
yarn add @fingerprint/react
```

Using [pnpm](https://pnpm.js.org):

```sh
pnpm add @fingerprintjs/fingerprintjs-pro-react
pnpm add @fingerprint/react
```

## Getting started

In order to identify visitors, you'll need a Fingerprint Pro account (you can [sign up for free](https://dashboard.fingerprint.com/signup/)).
To get your API key and get started, see the [Fingerprint Pro Quick Start Guide](https://dev.fingerprint.com/docs/quick-start-guide).
To get your API key and get started, see the [Fingerprint Pro Quick Start Guide](https://docs.fingerprint.com/docs/quick-start-guide).

### 1. Wrap your application (or component) in `<FpjsProvider>`.
### 1. Wrap your application (or component) in `<FingerprintProvider>`.

- Set `apiKey` to your Fingerprint [Public API Key](https://dashboard.fingerprint.com/api-keys).
- Set `region` if you have chosen a non-global [region](https://dev.fingerprint.com/docs/regions) during registration.
- Set `endpoint` and `scriptUrlPattern` if you are using [one of our proxy integrations to increase accuracy](https://dev.fingerprint.com/docs/protecting-the-javascript-agent-from-adblockers) and effectiveness of visitor identification.
- You can use all the [load options](https://dev.fingerprint.com/reference/load-function#load-options) available in the JavaScript agent `load` function.
- Set `region` if you have chosen a non-global [region](https://docs.fingerprint.com/docs/regions) during registration.
- Set `endpoint` if you are using [one of our proxy integrations to increase accuracy](https://docs.fingerprint.com/docs/protecting-the-javascript-agent-from-adblockers) and effectiveness of visitor identification.
- You can use all the [start options](https://docs.fingerprint.com/reference/js-agent-v4-start-function#start-options) available in the JavaScript agent `load` function.

```jsx
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
FpProvider,
FingerprintProvider,
FingerprintJSPro,
} from '@fingerprintjs/fingerprintjs-pro-react'
} from '@fingerprint/react'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('app'))

// <FpProvider /> supports the same options as `start()` function.
// <FingerprintProvider /> supports the same options as `start()` function.
root.render(
<FpProvider
<FingerprintProvider
apiKey='your-public-api-key'
>
<App />
</FpProvider>
</FingerprintProvider>
)
```

Expand All @@ -107,7 +107,7 @@ root.render(
```jsx
// src/App.js
import React from 'react'
import { useVisitorData } from '@fingerprintjs/fingerprintjs-pro-react'
import { useVisitorData } from '@fingerprint/react'

function App() {
const { isLoading, error, isFetched, data } = useVisitorData()
Expand All @@ -133,12 +133,12 @@ The `useVisitorData` hook also returns a `getData` method you can use to make an

- You can pass `{ immediate: false }` to `useVisitorData` to disable automatic visitor identification on render.

Both `useVisitorData` and `getData` accept all the [get options](https://dev.fingerprint.com/reference/get-function#get-options) available in the JavaScript agent `get` function.
Both `useVisitorData` and `getData` accept all the [get options](https://docs.fingerprint.com/reference/get-function#get-options) available in the JavaScript agent `get` function.

```jsx
// src/App.js
import React, { useState } from 'react'
import { useVisitorData } from '@fingerprintjs/fingerprintjs-pro-react'
import { useVisitorData } from '@fingerprint/react'

function App() {
const { isLoading, error, getData } = useVisitorData(
Expand Down Expand Up @@ -189,7 +189,7 @@ export default App

## Linking and tagging information

The `visitorId` provided by Fingerprint Identification is especially useful when combined with information you already know about your users, for example, account IDs, order IDs, etc. To learn more about various applications of the `linkedId` and `tag`, see [Linking and tagging information](https://dev.fingerprint.com/docs/tagging-information).
The `visitorId` provided by Fingerprint Identification is especially useful when combined with information you already know about your users, for example, account IDs, order IDs, etc. To learn more about various applications of the `linkedId` and `tag`, see [Linking and tagging information](https://docs.fingerprint.com/docs/tagging-information).

Associate the visitor ID with your data using the `linkedId` or `tag` parameter of the options object passed into the `useVisitorData()` hook or the `getData` function:

Expand All @@ -209,7 +209,7 @@ function App() {

## Error handling

The `getData` function throws errors directly from the JS Agent without changing them. See [JS Agent error handling](https://dev.fingerprint.com/reference/error-handling) for more details.
The `getData` function throws errors directly from the JS Agent without changing them. See [JS Agent error handling](https://docs.fingerprint.com/reference/js-agent-v4-error-handling) for more details.

## API Reference

Expand Down
1 change: 1 addition & 0 deletions __tests__/detect-env.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { detectEnvironment } from '../src/detect-env'
import { Env } from '../src/env.types'
import { describe, it, expect } from 'vitest'

describe('Detect user env', () => {
describe('Preact', () => {
Expand Down
17 changes: 7 additions & 10 deletions __tests__/fpjs-provider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { useContext } from 'react'
import { renderHook } from '@testing-library/react'
import { FpContext } from '../src'
import { FingerprintContext } from '../src'
import { createWrapper, getDefaultLoadOptions } from './helpers'
import { version } from '../package.json'
import { describe, it, expect, vi } from 'vitest'
import * as agent from '@fingerprint/agent'

jest.mock('@fingerprint/agent', () => {
return {
...jest.requireActual<any>('@fingerprint/agent'),
start: jest.fn(),
}
})
vi.mock('@fingerprint/agent', { spy: true })

const mockStart = jest.requireMock('@fingerprint/agent').start as jest.Mock
const mockStart = vi.mocked(agent.start)

describe('FpProvider', () => {
describe('FingerprintProvider', () => {
it('should configure an instance of the Fp Agent', async () => {
const loadOptions = getDefaultLoadOptions()
const wrapper = createWrapper({
Expand All @@ -23,7 +20,7 @@ describe('FpProvider', () => {
duration: 100,
},
})
renderHook(() => useContext(FpContext), {
renderHook(() => useContext(FingerprintContext), {
wrapper,
})
expect(mockStart).toHaveBeenCalledWith({
Expand Down
10 changes: 5 additions & 5 deletions __tests__/helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { PropsWithChildren } from 'react'
import { FpProvider, FpProviderOptions } from '../src'
import { act } from 'react-dom/test-utils'
import { FingerprintProvider, FingerprintProviderOptions } from '../src'
import { act } from '@testing-library/react'

export const getDefaultLoadOptions = () => ({
apiKey: 'test_api_key',
})

export const createWrapper =
(providerProps: Partial<FpProviderOptions> = {}) =>
(providerProps: Partial<FingerprintProviderOptions> = {}) =>
({ children }: PropsWithChildren<{}>) => (
<FpProvider {...getDefaultLoadOptions()} {...providerProps}>
<FingerprintProvider {...getDefaultLoadOptions()} {...providerProps}>
{children}
</FpProvider>
</FingerprintProvider>
)

export const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
Expand Down
50 changes: 38 additions & 12 deletions __tests__/use-visitor-data.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useVisitorData, UseVisitorDataReturn } from '../src'
import { render, renderHook } from '@testing-library/react'
import { actWait, createWrapper } from './helpers'
import { act } from 'react-dom/test-utils'
import { act, render, renderHook } from '@testing-library/react'
import { actWait, createWrapper, wait } from './helpers'
import { useEffect, useState } from 'react'
import userEvent from '@testing-library/user-event'
import * as agent from '@fingerprint/agent'
import { GetResult } from '@fingerprint/agent'
import { beforeEach, describe, expect, it, vi } from 'vitest'

const mockGetResult = {
visitor_id: 'kOzFgO0kw2Eivvb14mRL',
Expand All @@ -14,23 +15,19 @@ const mockGetResult = {
suspect_score: 0.5,
} satisfies GetResult

const mockGet = jest.fn()
const mockGet = vi.fn()
const mockAgent = {
get: mockGet,
collect: vi.fn(),
}

const mockStart = jest.requireMock('@fingerprint/agent').start as jest.Mock
vi.mock('@fingerprint/agent', { spy: true })

jest.mock('@fingerprint/agent', () => {
return {
...jest.requireActual('@fingerprint/agent'),
start: jest.fn(),
}
})
const mockStart = vi.mocked(agent.start)

describe('useVisitorData', () => {
beforeEach(() => {
jest.resetAllMocks()
vi.resetAllMocks()

mockStart.mockReturnValue(mockAgent)
})
Expand Down Expand Up @@ -71,6 +68,35 @@ describe('useVisitorData', () => {
)
})

it('should avoid duplicate requests if one is already pending', async () => {
mockGet.mockImplementation(async () => {
await wait(250)
return mockGetResult
})

const wrapper = createWrapper()
const { result } = renderHook(() => useVisitorData({ immediate: false }), { wrapper })
expect(result.current).toMatchObject(
expect.objectContaining({
isLoading: false,
data: undefined,
})
)

await Promise.all([result.current.getData(), result.current.getData()])

await actWait(500)

expect(mockStart).toHaveBeenCalled()
expect(mockGet).toHaveBeenCalledTimes(1)
expect(result.current).toMatchObject(
expect.objectContaining({
isLoading: false,
data: mockGetResult,
})
)
})

it("shouldn't call getData on mount if 'immediate' option is set to false", async () => {
mockGet.mockImplementation(() => mockGetResult)

Expand Down
14 changes: 8 additions & 6 deletions __tests__/with-environment.preact.test.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { render as preactRender } from '@testing-library/preact'
import { h } from 'preact'
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
import * as compat from 'preact/compat'

describe('WithEnvironment', () => {
describe('when running within Preact', () => {
beforeEach(() => {
jest.doMock('react-dom', () => require('preact/compat'))
jest.doMock('react', () => require('preact/compat'))
vi.doMock('react', () => compat)
vi.doMock('react-dom', () => compat)
})

afterEach(() => {
jest.resetModules()
vi.resetModules()
})

it('should detect env as "preact"', () => {
const { WithEnvironment } = require('../src/components/with-environment')
it('should detect env as "preact"', async () => {
const { WithEnvironment } = await import('../src/components/with-environment')
const PrintEnv = (props: any) => h('div', null, props?.env?.name)

// @ts-ignore
const { container } = preactRender(h(WithEnvironment, null, h(PrintEnv, null)))

expect(container.innerHTML).toContain('preact')
Expand Down
3 changes: 2 additions & 1 deletion __tests__/with-environment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { WithEnvironment } from '../src/components/with-environment'
import { Link, MemoryRouter, Route, Routes } from 'react-router-dom'
import userEvent from '@testing-library/user-event'
import { actWait } from './helpers'
import { describe, it, expect, vi } from 'vitest'

describe('WithEnvironment', () => {
it('enhances provided element with `env` prop', () => {
const Mock = jest.fn(() => <div>foo</div>) as FunctionComponent
const Mock = vi.fn(() => <div>foo</div>) as FunctionComponent

render(
<WithEnvironment>
Expand Down
6 changes: 2 additions & 4 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ There are 4 demo pages for this integration:
4. In `/examples/preact` folder. It is a demo built with Preact. You can find more info about configuration and starting demo in the [readme](examples/preact/README.md).
5. In `/examples/webpack-based` folder. It is a simple demo built with raw webpack.

If you want to test integration with [fingerprintjs-pro-spa](https://github.com/fingerprintjs/fingerprintjs-pro-spa), just [link the package](https://pnpm.io/cli/link#replace-an-installed-package-with-a-local-version-of-it) with the `pnpm link <spa-directory>`.

❗ Build projects before testing integration. First build `fingerprintjs-pro-spa`, then `fingerprintjs-pro-react`, and then start spa example app.
❗ Build projects before testing integration. First build `fingerprintjs-pro-react`, and then start any of the example apps.

### How to build

Expand All @@ -44,7 +42,7 @@ pnpm lint:fix

### How to test

Tests are located in `__tests__` folder and run by [jest](https://jestjs.io/) in [jsdom](https://github.com/jsdom/jsdom) environment.
Tests are located in `__tests__` folder and run by [vitest](https://vitest.dev/) in [jsdom](https://github.com/jsdom/jsdom) environment.

To run tests you can use IDE instruments or just run:

Expand Down
2 changes: 1 addition & 1 deletion examples/create-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fingerprintjs/fingerprintjs-pro-react": "workspace:*",
"@fingerprint/react": "workspace:*",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { FpProvider } from '@fingerprintjs/fingerprintjs-pro-react'
import { FingerprintProvider } from '@fingerprint/react'
import { Outlet } from 'react-router-dom'
import { FPJS_API_KEY } from '../shared/utils/env'
import { Nav } from '../shared/components/Nav'

function InMemoryCache() {
return (
<FpProvider apiKey={FPJS_API_KEY} cache={{ storage: 'agent', duration: 'optimize-cost' }}>
<FingerprintProvider apiKey={FPJS_API_KEY} cache={{ storage: 'agent', duration: 'optimize-cost' }}>
<div className='App'>
<header className='header'>
<h2>Solution with an in-memory cache</h2>
Expand All @@ -16,7 +16,7 @@ function InMemoryCache() {
<Nav />
<Outlet />
</div>
</FpProvider>
</FingerprintProvider>
)
}

Expand Down
Loading
Loading