diff --git a/.changeset/new-architecture-support.md b/.changeset/new-architecture-support.md
new file mode 100644
index 00000000..082fbd0b
--- /dev/null
+++ b/.changeset/new-architecture-support.md
@@ -0,0 +1,7 @@
+---
+'@fingerprintjs/fingerprintjs-pro-react-native': major
+---
+
+Dropped React Native Old Architecture support. The native module is now a TurboModule (New Architecture only) exposed via a Codegen spec.
+
+This is a breaking change: the SDK now requires React Native >= 0.79 (Expo SDK >= 53) with the [New Architecture](https://reactnative.dev/architecture/landing-page) enabled.
diff --git a/.changeset/pre.json b/.changeset/pre.json
new file mode 100644
index 00000000..e4bf2ce4
--- /dev/null
+++ b/.changeset/pre.json
@@ -0,0 +1,8 @@
+{
+ "mode": "pre",
+ "tag": "rc",
+ "initialVersions": {
+ "@fingerprintjs/fingerprintjs-pro-react-native": "3.17.0"
+ },
+ "changesets": []
+}
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index d191cd54..80e258a0 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -18,7 +18,7 @@ jobs:
steps:
- id: set-matrix
run: |
- echo "matrix=['local', '0.73', '0.74', '0.75', '0.76', '0.77', '0.79', '0.81', '0.83', '0.85']" >> $GITHUB_OUTPUT
+ echo "matrix=['local', '0.79', '0.81', '0.83', '0.85']" >> $GITHUB_OUTPUT
build-android:
strategy:
@@ -81,11 +81,14 @@ jobs:
run: rm -rf node_modules && pnpm install --frozen-lockfile
working-directory: e2e-app
+ - run: pnpm why react-native
+ working-directory: e2e-app
+
- name: Prebuild
run: pnpm prebuild
working-directory: e2e-app
- - run: pnpm exec detox build --configuration android.emu.release
+ - run: node scripts/retry-cmd.mjs --retries=3 "pnpm exec detox build --configuration android.emu.release"
working-directory: e2e-app
- name: Upload artifacts
@@ -191,8 +194,9 @@ jobs:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
arch: x86_64
+ working-directory: e2e-app
avd-name: ${{ steps.device.outputs.AVD_NAME }}
- script: cd e2e-app && pnpm exec detox test --configuration android.emu.release --headless --record-logs all --record-videos failing
+ script: node scripts/retry-cmd.mjs --retries=3 "pnpm exec detox test --configuration android.emu.release --headless --record-logs all --record-videos failing"
- name: Upload artifacts
if: always()
@@ -277,11 +281,14 @@ jobs:
run: rm -rf node_modules && pnpm install --frozen-lockfile
working-directory: e2e-app
+ - run: pnpm why react-native
+ working-directory: e2e-app
+
- name: Prebuild
run: pnpm prebuild
working-directory: e2e-app
- - run: pnpm exec detox build --configuration ios.sim.release
+ - run: node scripts/retry-cmd.mjs --retries=3 "pnpm exec detox build --configuration ios.sim.release"
working-directory: e2e-app
- name: Upload artifacts
@@ -355,7 +362,7 @@ jobs:
if: github.event_name != 'release'
working-directory: e2e-app
- - run: pnpm exec detox test --configuration ios.sim.release --cleanup --headless --record-logs all --record-videos failing
+ - run: node scripts/retry-cmd.mjs --retries=3 "pnpm exec detox test --configuration ios.sim.release --cleanup --headless --record-logs all --record-videos failing"
working-directory: e2e-app
env:
MINIMUM_US_DEFAULT_PUBLIC_KEY: ${{ secrets.MINIMUM_US_DEFAULT_PUBLIC_KEY }}
diff --git a/README.md b/README.md
index 1ae08ee4..8f0f4f38 100644
--- a/README.md
+++ b/README.md
@@ -62,8 +62,8 @@ application to call the native Fingerprint Pro libraries (Android and iOS) and i
## Requirements and limitations
-- React Native versions 0.73 through 0.86 are supported
-- Expo 51.0.0 or higher is supported
+- React Native 0.79 or higher is supported (New Architecture only)
+- Expo 53.0.0 or higher is supported
- Android 6.0 (API level 23+) or higher
- iOS 13+/tvOS 15+, Swift 5.9 or higher (stable releases)
diff --git a/e2e-app/App.tsx b/e2e-app/App.tsx
index 14f780cc..ce773f65 100644
--- a/e2e-app/App.tsx
+++ b/e2e-app/App.tsx
@@ -1,15 +1,23 @@
// SafeAreaView is deprecated in newer RN versions, react-native-safe-area-context is recommended
// but it doesn't work nicely with older RN versions that we also run tests against, so keep SafeAreaView import from react-native for now
+/* eslint-disable @typescript-eslint/no-deprecated */
import { Pressable, SafeAreaView, Text, View } from 'react-native'
import { FingerprintJsProProvider, useVisitorData } from '@fingerprintjs/fingerprintjs-pro-react-native'
import { testIds } from '@/e2e/ids'
import { useEffect } from 'react'
import { testTags } from '@/e2e/tags'
import config from '@/src/config'
+import { ArchInfo } from '@/src/components/ArchInfo'
function InnerApp() {
const { isLoading, error, data, getData } = useVisitorData()
+ const doGetData = async () => {
+ const tags = config.useTags ? testTags : undefined
+
+ await getData(tags, config.linkedId)
+ }
+
useEffect(() => {
if (error) {
console.error(error)
@@ -18,7 +26,6 @@ function InnerApp() {
return (
- {/* eslint-disable-next-line @typescript-eslint/no-deprecated */}
+
{isLoading ? Loading... : null}
{error ? (
@@ -37,20 +45,23 @@ function InnerApp() {
) : null}
{data ? {JSON.stringify(data)} : null}
- {
- const tags = config.useTags ? testTags : undefined
- await getData(tags, config.linkedId)
- }}
- style={{
- paddingBottom: 48,
- paddingLeft: 24,
- }}
- >
- Get data
-
+
+
+ Get data
+
+
)
diff --git a/e2e-app/app-config-tools/android.ts b/e2e-app/app-config-tools/android.ts
index 1360b7fc..422551f7 100644
--- a/e2e-app/app-config-tools/android.ts
+++ b/e2e-app/app-config-tools/android.ts
@@ -14,24 +14,6 @@ const androidBuildProperties: AndroidBuildProperties = {
enableBundleCompression: false,
}
-export function withAndroidBuildProperties(props: Partial) {
- return () => {
- Object.assign(androidBuildProperties, props)
- }
-}
-
-export function setCompileSdkVersion(version: number) {
- return withAndroidBuildProperties({
- compileSdkVersion: version,
- })
-}
-
-export function stripAndroidExtraBuildProperties() {
- delete androidBuildProperties.compileSdkVersion
- delete androidBuildProperties.targetSdkVersion
- delete androidBuildProperties.buildToolsVersion
-}
-
export function getAndroidBuildProperties() {
return { ...androidBuildProperties }
}
diff --git a/e2e-app/app-config-tools/arch.ts b/e2e-app/app-config-tools/arch.ts
deleted file mode 100644
index d8cebe82..00000000
--- a/e2e-app/app-config-tools/arch.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-let newArch = false
-
-export function disableNewArch() {
- newArch = false
-}
-
-export function getNewArch() {
- return newArch
-}
diff --git a/e2e-app/app-config-tools/expoConfigPatchers.ts b/e2e-app/app-config-tools/expoConfigPatchers.ts
deleted file mode 100644
index ce52dfad..00000000
--- a/e2e-app/app-config-tools/expoConfigPatchers.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { getNewArch } from './arch'
-
-export function withNewArchFlag(config: { newArchEnabled?: boolean }) {
- config.newArchEnabled = getNewArch()
-}
-
-// Older expo versions require splash to be defined
-export function withSplashscreen(config: { splash?: { backgroundColor?: string } }) {
- config.splash = {
- backgroundColor: '#ffffff',
- }
-}
-
-// Expo SDK 50's autolinking (1.10.x) ignores the `android.extraMavenRepos` gradle property the
-// Fingerprint SDK plugin uses, so the Fingerprint Maven repo must be injected into build.gradle
-// directly. Only needed for Expo 50 (RN 0.73).
-export function withFingerprintMavenRepo(config: { plugins?: unknown[] }) {
- config.plugins ??= []
- config.plugins.push('./plugins/withAndroidMavenRepo.js')
-}
diff --git a/e2e-app/app-config-tools/reactNativeMetadata.ts b/e2e-app/app-config-tools/reactNativeMetadata.ts
index 1fab0807..3e030cf7 100644
--- a/e2e-app/app-config-tools/reactNativeMetadata.ts
+++ b/e2e-app/app-config-tools/reactNativeMetadata.ts
@@ -1,7 +1,3 @@
-import { disableNewArch } from './arch'
-import { setCompileSdkVersion, stripAndroidExtraBuildProperties } from './android'
-import { withFingerprintMavenRepo, withNewArchFlag, withSplashscreen } from './expoConfigPatchers'
-
// config is typed as any, as different expo version have different types for the config object
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ExpoConfigPatcher = (config: any) => void
@@ -19,29 +15,11 @@ type ReactNativeMetadata = {
* - The key represents the version of React Native.
* - The value is an object containing:
* - `callbacks`: An array of functions to execute in the context of the respective React Native version.
+ *
+ * The SDK supports React Native >= 0.79 (New Architecture only), so no version currently needs
+ * version-specific handling. New entries can be added here if a future version requires it.
*/
-const reactNativeMetadata: Record = {
- 0.73: {
- callbacks: [disableNewArch, stripAndroidExtraBuildProperties, setCompileSdkVersion(34)],
- patchExpoConfig: [withNewArchFlag, withSplashscreen, withFingerprintMavenRepo],
- },
- 0.74: {
- callbacks: [disableNewArch, stripAndroidExtraBuildProperties, setCompileSdkVersion(34)],
- patchExpoConfig: [withNewArchFlag, withSplashscreen],
- },
- 0.75: {
- callbacks: [disableNewArch, stripAndroidExtraBuildProperties, setCompileSdkVersion(34)],
- patchExpoConfig: [withNewArchFlag, withSplashscreen],
- },
- 0.76: {
- callbacks: [stripAndroidExtraBuildProperties, setCompileSdkVersion(35)],
- patchExpoConfig: [withNewArchFlag, withSplashscreen],
- },
- 0.77: {
- callbacks: [stripAndroidExtraBuildProperties, setCompileSdkVersion(35)],
- patchExpoConfig: [withNewArchFlag, withSplashscreen],
- },
-}
+const reactNativeMetadata: Record = {}
export function handleReactNativeVersion(rnVersion: string) {
if (reactNativeMetadata[rnVersion]?.callbacks?.length) {
diff --git a/e2e-app/app.config.ts b/e2e-app/app.config.ts
index 08b780f8..56367f82 100644
--- a/e2e-app/app.config.ts
+++ b/e2e-app/app.config.ts
@@ -48,4 +48,8 @@ const config: ExpoConfig = {
],
}
+// The SDK supports the New Architecture only (React Native >= 0.79), so always enable it.
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+;(config as any).newArchEnabled = true
+
export default patchExpoConfig(rnVersionStr, config)
diff --git a/e2e-app/e2e/arch.test.ts b/e2e-app/e2e/arch.test.ts
new file mode 100644
index 00000000..588de481
--- /dev/null
+++ b/e2e-app/e2e/arch.test.ts
@@ -0,0 +1,39 @@
+import { by, device, element, waitFor } from 'detox'
+import { beforeAll, describe, expect, it, jest } from '@jest/globals'
+import { DeviceLaunchAppConfig } from 'detox/detox'
+import { wait } from './wait'
+import { Config } from '@/src/config.types'
+import { testIds } from './ids'
+import { getElementText } from './getElementText'
+
+async function launchApp(params?: DeviceLaunchAppConfig) {
+ await device.launchApp(params)
+
+ await wait(4000)
+}
+
+jest.retryTimes(3, {
+ waitBeforeRetry: 1000,
+ logErrorsBeforeRetry: false,
+})
+
+describe('React Native architecture', () => {
+ beforeAll(async () => {
+ await launchApp({
+ newInstance: true,
+ launchArgs: {
+ apiKey: process.env.MINIMUM_US_DEFAULT_PUBLIC_KEY,
+ region: 'us',
+ } as Config,
+ })
+ })
+
+ it('runs on fabric', async () => {
+ await waitFor(element(by.id(testIds.uiManager)))
+ .toExist()
+ .withTimeout(10_000)
+
+ const uiManager = await getElementText(element(by.id(testIds.uiManager)))
+ expect(uiManager.toLowerCase()).toEqual('fabric')
+ })
+})
diff --git a/e2e-app/e2e/identification.test.ts b/e2e-app/e2e/identification.test.ts
index 2b9ad2bf..4b129b5e 100644
--- a/e2e-app/e2e/identification.test.ts
+++ b/e2e-app/e2e/identification.test.ts
@@ -21,7 +21,7 @@ async function launchApp(params?: DeviceLaunchAppConfig) {
}
jest.retryTimes(3, {
- waitBeforeRetry: 10_000,
+ waitBeforeRetry: 1000,
logErrorsBeforeRetry: false,
})
diff --git a/e2e-app/e2e/ids.ts b/e2e-app/e2e/ids.ts
index 9e4473fd..b5fbb137 100644
--- a/e2e-app/e2e/ids.ts
+++ b/e2e-app/e2e/ids.ts
@@ -6,4 +6,5 @@ export const testIds = {
errorCause: 'error-cause',
data: 'data',
getData: 'get-data',
+ uiManager: 'ui-manager',
}
diff --git a/e2e-app/plugins/withAndroidMavenRepo.js b/e2e-app/plugins/withAndroidMavenRepo.js
deleted file mode 100644
index d371017a..00000000
--- a/e2e-app/plugins/withAndroidMavenRepo.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const { withProjectBuildGradle } = require('@expo/config-plugins')
-
-// The Fingerprint Android SDK (com.fingerprint.android:pro) is hosted on a dedicated Maven repo.
-// The SDK's config plugin adds it via the `android.extraMavenRepos` gradle property, but that is
-// only honored by expo-modules-autolinking >= 1.11 (Expo SDK 51+). Expo 50 (RN 0.73) ships 1.10.x,
-// which ignores it, so the repo is never added and the build fails with "Could not find any
-// matches for com.fingerprint.android:pro". This injects it straight into the root build.gradle's
-// allprojects.repositories block. Only wired in for the Expo 50 / RN 0.73 build; an Expo 50 (or
-// bare RN) customer adds this repo manually the same way, per the SDK README.
-const MAVEN_REPO = 'https://maven.fpregistry.io/releases'
-
-module.exports = (config) => {
- return withProjectBuildGradle(config, (gradleConfig) => {
- if (gradleConfig.modResults.language !== 'groovy') {
- throw new Error('withAndroidMavenRepo: expected a groovy build.gradle')
- }
-
- const contents = gradleConfig.modResults.contents
-
- if (contents.includes(MAVEN_REPO)) {
- return gradleConfig
- }
-
- const updated = contents.replace(
- /allprojects\s*\{\s*repositories\s*\{/,
- (match) => `${match}\n maven { url '${MAVEN_REPO}' }`
- )
-
- if (updated === contents) {
- throw new Error('withAndroidMavenRepo: could not find the allprojects.repositories block in build.gradle')
- }
-
- gradleConfig.modResults.contents = updated
- return gradleConfig
- })
-}
diff --git a/e2e-app/pnpm-workspace.yaml b/e2e-app/pnpm-workspace.yaml
index c2741eb7..b22abf00 100644
--- a/e2e-app/pnpm-workspace.yaml
+++ b/e2e-app/pnpm-workspace.yaml
@@ -4,3 +4,5 @@ allowBuilds:
detox: true
dtrace-provider: true
esbuild: true
+
+shamefullyHoist: true
\ No newline at end of file
diff --git a/e2e-app/scripts/compatibility.mjs b/e2e-app/scripts/compatibility.mjs
index 430a1cdb..bedbb314 100644
--- a/e2e-app/scripts/compatibility.mjs
+++ b/e2e-app/scripts/compatibility.mjs
@@ -4,38 +4,6 @@ import pkg from '../package.json' with { type: 'json' }
const devPackages = Object.keys(pkg.devDependencies)
const reactNativeMetadata = {
- 0.73: {
- // Expo 50 (the only SDK bundling RN 0.73) predates autolinking's `android.extraMavenRepos`
- // support, so the SDK's Maven repo is injected directly via plugins/withAndroidMavenRepo.js
- // (wired in for 0.73 through reactNativeMetadata). @config-plugins/detox@7 is the SDK 50 peer.
- packages: ['expo@50', 'detox@20.20.3', '@config-plugins/detox@7', 'react@18.2.0', 'react-dom@18.2.0'],
- },
- 0.74: {
- packages: ['expo@51', 'detox@20.20.3', '@config-plugins/detox@8', 'react@18.2.0', 'react-dom@18.2.0'],
- },
- 0.75: {
- packages: [
- 'expo@51',
- 'detox@20.20.3',
- '@config-plugins/detox@8',
- 'react@18.2.0',
- 'react-dom@18.2.0',
- '@react-native-community/cli@14',
- ],
- },
- 0.76: {
- packages: [
- 'expo@52',
- 'detox@20.28.0',
- '@config-plugins/detox@9',
- 'react@18.3.1',
- 'react-dom@18.3.1',
- '@react-native-community/cli@15',
- ],
- },
- 0.77: {
- packages: ['expo@52', 'detox@20.28.0', '@config-plugins/detox@9', 'react@18.3.1', 'react-dom@18.3.1'],
- },
0.79: {
packages: ['expo@53', 'detox@20.37.0', '@config-plugins/detox@11', 'react@19.0.0', 'react-dom@19.0.0'],
},
diff --git a/e2e-app/scripts/retry-cmd.mjs b/e2e-app/scripts/retry-cmd.mjs
new file mode 100644
index 00000000..b844a781
--- /dev/null
+++ b/e2e-app/scripts/retry-cmd.mjs
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+import { spawnSync } from 'node:child_process'
+
+// Usage: node retry-cmd.mjs --retries=3 ""
+// Runs and retries up to additional times if it exits non-zero.
+
+function parseArgs(argv) {
+ let retries = 0
+ const rest = []
+
+ for (const arg of argv) {
+ const match = /^--retries=(\d+)$/.exec(arg)
+ if (match) {
+ retries = Number(match[1])
+ } else {
+ rest.push(arg)
+ }
+ }
+
+ return { retries, command: rest.join(' ') }
+}
+
+const { retries, command } = parseArgs(process.argv.slice(2))
+
+if (!command) {
+ console.error('Usage: node retry-cmd.mjs --retries= ""')
+ process.exit(1)
+}
+
+const totalAttempts = retries + 1
+
+for (let attempt = 1; attempt <= totalAttempts; attempt++) {
+ if (attempt > 1) {
+ console.log(`[retry] Attempt ${attempt}/${totalAttempts}: ${command}`)
+ }
+
+ const result = spawnSync(command, { stdio: 'inherit', shell: true })
+
+ if (result.error) {
+ console.error(`[retry] Failed to start command: ${result.error.message}`)
+ }
+
+ const code = result.status
+
+ if (code === 0) {
+ process.exit(0)
+ }
+
+ console.error(`[retry] Attempt ${attempt} exited with code ${code ?? 'null'}.`)
+
+ if (attempt < totalAttempts) {
+ console.error('[retry] Retrying...')
+ }
+}
+
+console.error(`[retry] Command failed after ${totalAttempts} attempt(s).`)
+process.exit(1)
diff --git a/e2e-app/src/components/ArchInfo.tsx b/e2e-app/src/components/ArchInfo.tsx
new file mode 100644
index 00000000..21e3dffb
--- /dev/null
+++ b/e2e-app/src/components/ArchInfo.tsx
@@ -0,0 +1,17 @@
+import { Text, View } from 'react-native'
+import { testIds } from '@/e2e/ids'
+
+export function ArchInfo() {
+ const hasTurboModules = Boolean(global.__turboModuleProxy)
+ const isBridgeless = Boolean(global.RN$Bridgeless)
+ const uiManager = global.nativeFabricUIManager ? 'Fabric' : 'Paper'
+
+ return (
+
+ {`TurboModules: ${hasTurboModules}`}
+ {`Bridgeless: ${isBridgeless}`}
+ UI Manager:
+ {uiManager}
+
+ )
+}
diff --git a/e2e-app/src/global.d.ts b/e2e-app/src/global.d.ts
new file mode 100644
index 00000000..8bf6f312
--- /dev/null
+++ b/e2e-app/src/global.d.ts
@@ -0,0 +1,7 @@
+declare global {
+ var __turboModuleProxy: ?unknown
+ var RN$Bridgeless: boolean | undefined
+ var nativeFabricUIManager: boolean | undefined
+}
+
+export {}
diff --git a/sdk/README.md b/sdk/README.md
index 1ae08ee4..8f0f4f38 100644
--- a/sdk/README.md
+++ b/sdk/README.md
@@ -62,8 +62,8 @@ application to call the native Fingerprint Pro libraries (Android and iOS) and i
## Requirements and limitations
-- React Native versions 0.73 through 0.86 are supported
-- Expo 51.0.0 or higher is supported
+- React Native 0.79 or higher is supported (New Architecture only)
+- Expo 53.0.0 or higher is supported
- Android 6.0 (API level 23+) or higher
- iOS 13+/tvOS 15+, Swift 5.9 or higher (stable releases)
diff --git a/sdk/RNFingerprintjsPro.podspec b/sdk/RNFingerprintjsPro.podspec
index 0a612662..7e36e6fe 100644
--- a/sdk/RNFingerprintjsPro.podspec
+++ b/sdk/RNFingerprintjsPro.podspec
@@ -11,10 +11,20 @@ Pod::Spec.new do |s|
s.tvos.deployment_target = "15.0"
s.source_files = "ios/**/*.{h,m,mm,swift}"
+ # Never sweep build artifacts (e.g. generated Codegen headers under ios/build) into the pod's
+ # sources. Otherwise CocoaPods exposes the C++ `*Spec.h`/`*SpecJSI.h` as public headers of this
+ # pod, and building the Swift pod's ObjC module (`-import-underlying-module`) tries to compile
+ # them as Obj-C, failing with "This file must be compiled as Obj-C++".
+ s.exclude_files = "ios/build/**/*"
# s.requires_arc = true
s.dependency "React-Core"
# See: https://guides.cocoapods.org/using/the-podfile.html#specifying-pod-versions
# ~> 2.8.2 means >= 2.8.2 and < 2.9.0 (pessimistic operator)
s.dependency "FingerprintPro", '~> 2.17.0'
+
+ # Wires up the TurboModule/Codegen dependencies (ReactCommon, generated specs, ...) and defines
+ # `RCT_NEW_ARCH_ENABLED` for the pod when the app is built with the New Architecture. On the old
+ # architecture it is effectively a no-op, keeping the module backward compatible.
+ install_modules_dependencies(s)
end
diff --git a/sdk/__tests__/FingerprintJsProProvider.test.ts b/sdk/__tests__/FingerprintJsProProvider.test.ts
index c725ea26..b995acf3 100644
--- a/sdk/__tests__/FingerprintJsProProvider.test.ts
+++ b/sdk/__tests__/FingerprintJsProProvider.test.ts
@@ -5,7 +5,7 @@ import { FingerprintJsProContext } from '../src/FingerprintJsProContext'
import { NativeModules } from 'react-native'
import { FingerprintJsProAgent } from '../src'
-const { configure, getVisitorData, getVisitorIdWithTimeout, getVisitorDataWithTimeout } =
+const { configure, getVisitorId, getVisitorData } =
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
NativeModules.RNFingerprintjsPro as unknown as Record
@@ -26,15 +26,15 @@ describe(`FingerprintJsProProvider`, () => {
wrapper,
})
- expect(NativeModules.RNFingerprintjsPro.configure).toHaveBeenCalledWith(
+ expect(configure).toHaveBeenCalledWith(
options.apiKey,
- options.region,
- options.endpointUrl,
- [],
- false,
pluginVersion,
false,
- 5000
+ [],
+ false,
+ 5000,
+ options.region,
+ options.endpointUrl
)
})
@@ -49,15 +49,15 @@ describe(`FingerprintJsProProvider`, () => {
wrapper,
})
- expect(NativeModules.RNFingerprintjsPro.configure).toHaveBeenCalledWith(
+ expect(configure).toHaveBeenCalledWith(
options.apiKey,
- options.region,
- options.endpointUrl,
- options.fallbackEndpointUrls,
- false,
pluginVersion,
false,
- 5000
+ options.fallbackEndpointUrls,
+ false,
+ 5000,
+ options.region,
+ options.endpointUrl
)
})
@@ -72,15 +72,15 @@ describe(`FingerprintJsProProvider`, () => {
wrapper,
})
- expect(NativeModules.RNFingerprintjsPro.configure).toHaveBeenCalledWith(
+ expect(configure).toHaveBeenCalledWith(
options.apiKey,
- options.region,
- options.endpointUrl,
- [],
- options.extendedResponseFormat,
pluginVersion,
+ options.extendedResponseFormat,
+ [],
false,
- 5000
+ 5000,
+ options.region,
+ options.endpointUrl
)
})
@@ -96,15 +96,15 @@ describe(`FingerprintJsProProvider`, () => {
wrapper,
})
- expect(NativeModules.RNFingerprintjsPro.configure).toHaveBeenCalledWith(
+ expect(configure).toHaveBeenCalledWith(
options.apiKey,
- options.region,
- options.endpointUrl,
- [],
- false,
pluginVersion,
+ false,
+ [],
true,
- options.locationTimeoutMillisAndroid
+ options.locationTimeoutMillisAndroid,
+ options.region,
+ options.endpointUrl
)
})
@@ -113,33 +113,25 @@ describe(`FingerprintJsProProvider`, () => {
const fingerprintClient = new FingerprintJsProAgent(options)
void fingerprintClient.getVisitorId()
- expect(NativeModules.RNFingerprintjsPro.getVisitorId).toHaveBeenCalledWith(undefined, undefined)
+ expect(getVisitorId).toHaveBeenCalledWith(null, null, null)
})
- it('should pass timeout to `getVisitorIdWithTimeout` function', () => {
+ it('should pass timeout to `getVisitorId` function', () => {
const options = getDefaultLoadOptions()
options.requestOptions = { timeout: 18_000 }
const fingerprintClient = new FingerprintJsProAgent(options)
void fingerprintClient.getVisitorId()
- expect(NativeModules.RNFingerprintjsPro.getVisitorIdWithTimeout).toHaveBeenCalledWith(
- undefined,
- undefined,
- options.requestOptions.timeout
- )
+ expect(getVisitorId).toHaveBeenCalledWith(null, null, options.requestOptions.timeout)
})
- it('should pass timeout to `getVisitorIdWithTimeout` function when timeout is 0', () => {
+ it('should pass timeout to `getVisitorId` function when timeout is 0', () => {
const options = getDefaultLoadOptions()
options.requestOptions = { timeout: 0 }
const fingerprintClient = new FingerprintJsProAgent(options)
void fingerprintClient.getVisitorId()
- expect(NativeModules.RNFingerprintjsPro.getVisitorIdWithTimeout).toHaveBeenCalledWith(
- undefined,
- undefined,
- options.requestOptions.timeout
- )
+ expect(getVisitorId).toHaveBeenCalledWith(null, null, options.requestOptions.timeout)
})
it('For `getVisitorId` function timeout from params should be more important than the timeout from client configuration', () => {
@@ -149,20 +141,11 @@ describe(`FingerprintJsProProvider`, () => {
options.requestOptions = { timeout: clientTimeout }
const fingerprintClient = new FingerprintJsProAgent(options)
- const mockedJsonAnswer = {
- visitorId: mockedVisitorId,
- }
- getVisitorIdWithTimeout.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
- )
+ getVisitorId.mockReturnValueOnce(Promise.resolve(mockedVisitorId))
void fingerprintClient.getVisitorId(undefined, undefined, { timeout: getRequestTimeout })
- expect(NativeModules.RNFingerprintjsPro.getVisitorIdWithTimeout).toHaveBeenCalledWith(
- undefined,
- undefined,
- getRequestTimeout
- )
+ expect(getVisitorId).toHaveBeenCalledWith(null, null, getRequestTimeout)
})
it('should call `getVisitorData` function when there is no timeout', () => {
@@ -173,15 +156,19 @@ describe(`FingerprintJsProProvider`, () => {
visitorId: mockedVisitorId,
}
getVisitorData.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
void fingerprintClient.getVisitorData()
- expect(NativeModules.RNFingerprintjsPro.getVisitorData).toHaveBeenCalledWith(undefined, undefined)
+ expect(getVisitorData).toHaveBeenCalledWith(null, null, null)
})
- it('should pass timeout to `getVisitorDataWithTimeout` function', () => {
+ it('should pass timeout to `getVisitorData` function', () => {
const options = getDefaultLoadOptions()
options.requestOptions = { timeout: 18_000 }
const fingerprintClient = new FingerprintJsProAgent(options)
@@ -189,20 +176,20 @@ describe(`FingerprintJsProProvider`, () => {
const mockedJsonAnswer = {
visitorId: mockedVisitorId,
}
- getVisitorDataWithTimeout.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ getVisitorData.mockReturnValueOnce(
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
void fingerprintClient.getVisitorData()
- expect(NativeModules.RNFingerprintjsPro.getVisitorDataWithTimeout).toHaveBeenCalledWith(
- undefined,
- undefined,
- options.requestOptions.timeout
- )
+ expect(getVisitorData).toHaveBeenCalledWith(null, null, options.requestOptions.timeout)
})
- it('should pass timeout to `getVisitorDataWithTimeout` function when timeout is 0', () => {
+ it('should pass timeout to `getVisitorData` function when timeout is 0', () => {
const options = getDefaultLoadOptions()
options.requestOptions = { timeout: 0 }
const fingerprintClient = new FingerprintJsProAgent(options)
@@ -210,17 +197,17 @@ describe(`FingerprintJsProProvider`, () => {
const mockedJsonAnswer = {
visitorId: mockedVisitorId,
}
- getVisitorDataWithTimeout.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ getVisitorData.mockReturnValueOnce(
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
void fingerprintClient.getVisitorData()
- expect(NativeModules.RNFingerprintjsPro.getVisitorDataWithTimeout).toHaveBeenCalledWith(
- undefined,
- undefined,
- options.requestOptions.timeout
- )
+ expect(getVisitorData).toHaveBeenCalledWith(null, null, options.requestOptions.timeout)
})
it('For `getVisitorData` function timeout from params should be more important than the timeout from client configuration', () => {
@@ -233,17 +220,17 @@ describe(`FingerprintJsProProvider`, () => {
const mockedJsonAnswer = {
visitorId: mockedVisitorId,
}
- getVisitorDataWithTimeout.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ getVisitorData.mockReturnValueOnce(
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
void fingerprintClient.getVisitorData(undefined, undefined, { timeout: getRequestTimeout })
- expect(NativeModules.RNFingerprintjsPro.getVisitorDataWithTimeout).toHaveBeenCalledWith(
- undefined,
- undefined,
- getRequestTimeout
- )
+ expect(getVisitorData).toHaveBeenCalledWith(null, null, getRequestTimeout)
})
describe('agent params changes propagation', () => {
@@ -257,7 +244,7 @@ describe(`FingerprintJsProProvider`, () => {
renderProvider({ apiKey: 'key-1' })
expect(configure).toHaveBeenCalledTimes(1)
- expect(configure).toHaveBeenLastCalledWith('key-1', undefined, undefined, [], false, pluginVersion, false, 5000)
+ expect(configure).toHaveBeenLastCalledWith('key-1', pluginVersion, false, [], false, 5000, null, null)
})
it('reconfigures the agent when a param changes by value', () => {
@@ -267,12 +254,12 @@ describe(`FingerprintJsProProvider`, () => {
rerenderWithParams({ apiKey: 'key-2' })
expect(configure).toHaveBeenCalledTimes(2)
- expect(configure).toHaveBeenLastCalledWith('key-2', undefined, undefined, [], false, pluginVersion, false, 5000)
+ expect(configure).toHaveBeenLastCalledWith('key-2', pluginVersion, false, [], false, 5000, null, null)
})
it('propagates changes across every configure argument', () => {
const { rerenderWithParams } = renderProvider({ apiKey: 'key' })
- expect(configure).toHaveBeenLastCalledWith('key', undefined, undefined, [], false, pluginVersion, false, 5000)
+ expect(configure).toHaveBeenLastCalledWith('key', pluginVersion, false, [], false, 5000, null, null)
rerenderWithParams({
apiKey: 'key',
@@ -286,13 +273,13 @@ describe(`FingerprintJsProProvider`, () => {
expect(configure).toHaveBeenCalledTimes(2)
expect(configure).toHaveBeenLastCalledWith(
'key',
- 'eu',
- 'https://example.com',
- [],
- true,
pluginVersion,
true,
- 6000
+ [],
+ true,
+ 6000,
+ 'eu',
+ 'https://example.com'
)
})
@@ -300,13 +287,13 @@ describe(`FingerprintJsProProvider`, () => {
const { rerenderWithParams } = renderProvider({ apiKey: 'key', fallbackEndpointUrls: ['https://a.example'] })
expect(configure).toHaveBeenLastCalledWith(
'key',
- undefined,
- undefined,
- ['https://a.example'],
- false,
pluginVersion,
false,
- 5000
+ ['https://a.example'],
+ false,
+ 5000,
+ null,
+ null
)
rerenderWithParams({ apiKey: 'key', fallbackEndpointUrls: ['https://a.example', 'https://b.example'] })
@@ -314,13 +301,13 @@ describe(`FingerprintJsProProvider`, () => {
expect(configure).toHaveBeenCalledTimes(2)
expect(configure).toHaveBeenLastCalledWith(
'key',
- undefined,
- undefined,
- ['https://a.example', 'https://b.example'],
- false,
pluginVersion,
false,
- 5000
+ ['https://a.example', 'https://b.example'],
+ false,
+ 5000,
+ null,
+ null
)
})
diff --git a/sdk/__tests__/setup.ts b/sdk/__tests__/setup.ts
index 1337e119..ceecf20e 100644
--- a/sdk/__tests__/setup.ts
+++ b/sdk/__tests__/setup.ts
@@ -1,19 +1,18 @@
jest.mock('react-native', () => {
- const configure = jest.fn()
- const getVisitorId = jest.fn()
- const getVisitorData = jest.fn()
- const getVisitorDataWithTimeout = jest.fn()
- const getVisitorIdWithTimeout = jest.fn()
+ const RNFingerprintjsPro = {
+ configure: jest.fn(),
+ getVisitorId: jest.fn(),
+ getVisitorData: jest.fn(),
+ }
return {
+ TurboModuleRegistry: {
+ get: jest.fn(() => RNFingerprintjsPro),
+ getEnforcing: jest.fn(() => RNFingerprintjsPro),
+ },
+ // Kept so the same mock instances remain reachable via `NativeModules` (legacy access path).
NativeModules: {
- RNFingerprintjsPro: {
- configure,
- getVisitorId,
- getVisitorData,
- getVisitorDataWithTimeout,
- getVisitorIdWithTimeout,
- },
+ RNFingerprintjsPro,
},
}
})
diff --git a/sdk/__tests__/useVisitorData.test.ts b/sdk/__tests__/useVisitorData.test.ts
index 7955f235..d7153a7b 100644
--- a/sdk/__tests__/useVisitorData.test.ts
+++ b/sdk/__tests__/useVisitorData.test.ts
@@ -4,10 +4,7 @@ import { RequestOptions, useVisitorData } from '../src'
import { createWrapper } from './helpers'
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
-const { getVisitorData, getVisitorDataWithTimeout } = NativeModules.RNFingerprintjsPro as unknown as Record<
- string,
- jest.Mock
->
+const { getVisitorData } = NativeModules.RNFingerprintjsPro as unknown as Record
const mockedVisitorId = 'some visitor id'
const mockedRequestId = 'some request id'
@@ -43,7 +40,11 @@ describe('useVisitorData', () => {
}
getVisitorData.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
const wrapper = createWrapper()
const { result } = renderHook(() => useVisitorData(), { wrapper })
@@ -119,7 +120,11 @@ describe('useVisitorData', () => {
const mockedLinkedId = 'test_id'
getVisitorData.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
const wrapper = createWrapper()
@@ -128,7 +133,7 @@ describe('useVisitorData', () => {
void result.current.getData(mockedTags, mockedLinkedId)
})
await waitFor(() => {
- expect(getVisitorData).toHaveBeenCalledWith(mockedTags, mockedLinkedId)
+ expect(getVisitorData).toHaveBeenCalledWith(mockedTags, mockedLinkedId, null)
})
})
@@ -152,7 +157,11 @@ describe('useVisitorData', () => {
const options: RequestOptions = { timeout: undefined }
getVisitorData.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
const wrapper = createWrapper()
@@ -161,11 +170,11 @@ describe('useVisitorData', () => {
void result.current.getData(mockedTags, mockedLinkedId, options)
})
await waitFor(() => {
- expect(getVisitorData).toHaveBeenCalledWith(mockedTags, mockedLinkedId)
+ expect(getVisitorData).toHaveBeenCalledWith(mockedTags, mockedLinkedId, null)
})
})
- it('non-empty timeout should call `getVisitorDataWithTimeout` function', async () => {
+ it('non-empty timeout should pass the timeout to `getVisitorData` function', async () => {
const mockedJsonAnswer = {
visitorId: mockedVisitorId,
}
@@ -184,8 +193,12 @@ describe('useVisitorData', () => {
const options: RequestOptions = { timeout: 15_000 }
- getVisitorDataWithTimeout.mockReturnValueOnce(
- Promise.resolve([mockedRequestId, mockedConfidenceScore, JSON.stringify(mockedJsonAnswer)])
+ getVisitorData.mockReturnValueOnce(
+ Promise.resolve({
+ requestId: mockedRequestId,
+ confidenceScore: mockedConfidenceScore,
+ visitorDataJson: JSON.stringify(mockedJsonAnswer),
+ })
)
const wrapper = createWrapper()
@@ -194,7 +207,7 @@ describe('useVisitorData', () => {
void result.current.getData(mockedTags, mockedLinkedId, options)
})
await waitFor(() => {
- expect(getVisitorDataWithTimeout).toHaveBeenCalledWith(mockedTags, mockedLinkedId, options.timeout)
+ expect(getVisitorData).toHaveBeenCalledWith(mockedTags, mockedLinkedId, options.timeout)
})
})
})
diff --git a/sdk/android/build.gradle b/sdk/android/build.gradle
index 4e75641d..b6b3e9f9 100644
--- a/sdk/android/build.gradle
+++ b/sdk/android/build.gradle
@@ -3,6 +3,10 @@ plugins {
id 'org.jetbrains.kotlin.android'
}
+// The SDK supports the New Architecture only. Applying the React Gradle plugin runs library Codegen
+// (generates the `NativeRNFingerprintjsProSpec` base class).
+apply plugin: 'com.facebook.react'
+
apply from: 'fingerprint.gradle'
android {
@@ -33,6 +37,13 @@ android {
lint {
abortOnError = false
}
+
+ sourceSets {
+ main {
+ // Generated Codegen sources (the `NativeRNFingerprintjsProSpec` base class).
+ java.srcDirs += [layout.buildDirectory.dir('generated/source/codegen/java').get().asFile.path]
+ }
+ }
}
repositories {
diff --git a/sdk/android/gradle.properties b/sdk/android/gradle.properties
index fa4feae5..19b45129 100644
--- a/sdk/android/gradle.properties
+++ b/sdk/android/gradle.properties
@@ -31,10 +31,3 @@ FLIPPER_VERSION=0.125.0
# You can also override it from the CLI using
# ./gradlew -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
-
-# Use this property to enable support to the new architecture.
-# This will allow you to use TurboModules and the Fabric render in
-# your application. You should enable this flag either if you want
-# to write custom TurboModules/Fabric components OR use libraries that
-# are providing them.
-newArchEnabled=false
diff --git a/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProModule.kt b/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProModule.kt
index b270c82c..694f8c31 100644
--- a/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProModule.kt
+++ b/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProModule.kt
@@ -1,6 +1,7 @@
package com.fingerprintjs.reactnative
import com.facebook.react.bridge.*
+import com.facebook.react.module.annotations.ReactModule
import com.fingerprintjs.android.fpjs_pro.Configuration
import com.fingerprintjs.android.fpjs_pro.FingerprintJS
import com.fingerprintjs.android.fpjs_pro.FingerprintJSProResponse
@@ -31,23 +32,23 @@ import com.fingerprintjs.android.fpjs_pro.ProxyIntegrationSecretEnvironmentMisma
import java.lang.Exception
-class RNFingerprintjsProModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
+@ReactModule(name = RNFingerprintjsProModule.NAME)
+class RNFingerprintjsProModule(reactContext: ReactApplicationContext) : NativeRNFingerprintjsProSpec(reactContext) {
private var fpjsClient: FingerprintJS? = null
override fun getName(): String {
- return "RNFingerprintjsPro"
+ return NAME
}
- @ReactMethod
- fun configure(
+ override fun configure(
apiToken: String,
- regionKey: String?,
- endpointUrl: String?,
- fallbackEndpointUrls: ReadableArray,
- extendedResponseFormat: Boolean,
pluginVersion: String,
+ extendedResponseFormat: Boolean,
+ fallbackEndpointUrls: ReadableArray,
allowUseOfLocationData: Boolean,
- locationTimeoutMillis: Double
+ locationTimeoutMillis: Double,
+ regionKey: String?,
+ endpointUrl: String?
) {
val factory = FingerprintJSFactory(reactApplicationContext)
val region = when(regionKey) {
@@ -70,13 +71,15 @@ class RNFingerprintjsProModule(reactContext: ReactApplicationContext) : ReactCon
fpjsClient = factory.createInstance(configuration)
}
- @ReactMethod
- fun getVisitorId(tags: ReadableMap?, linkedId: String?, promise: Promise) {
- this.getVisitorIdWithTimeout(tags, linkedId, null, promise)
+ override fun getVisitorId(tags: ReadableMap?, linkedId: String?, timeout: Double?, promise: Promise) {
+ getVisitorIdInternal(tags, linkedId, timeout?.toInt(), promise)
}
- @ReactMethod
- fun getVisitorIdWithTimeout(tags: ReadableMap?, linkedId: String?, timeout: Int?, promise: Promise) {
+ override fun getVisitorData(tags: ReadableMap?, linkedId: String?, timeout: Double?, promise: Promise) {
+ getVisitorDataInternal(tags, linkedId, timeout?.toInt(), promise)
+ }
+
+ private fun getVisitorIdInternal(tags: ReadableMap?, linkedId: String?, timeout: Int?, promise: Promise) {
try {
if (timeout != null) {
fpjsClient?.getVisitorId(
@@ -99,16 +102,16 @@ class RNFingerprintjsProModule(reactContext: ReactApplicationContext) : ReactCon
}
}
- @ReactMethod
- fun getVisitorData(tags: ReadableMap?, linkedId: String?, promise: Promise) {
- this.getVisitorDataWithTimeout(tags, linkedId, null, promise)
- }
-
- @ReactMethod
- fun getVisitorDataWithTimeout(tags: ReadableMap?, linkedId: String?, timeout: Int?, promise: Promise) {
+ private fun getVisitorDataInternal(tags: ReadableMap?, linkedId: String?, timeout: Int?, promise: Promise) {
try {
val callback = { result: FingerprintJSProResponse ->
- promise.resolve(Arguments.fromList(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: "")))
+ val visitorData = Arguments.createMap().apply {
+ putString("requestId", result.requestId)
+ putDouble("confidenceScore", result.confidenceScore.score.toDouble())
+ putString("visitorDataJson", result.asJson)
+ putString("sealedResult", result.sealedResult ?: "")
+ }
+ promise.resolve(visitorData)
}
if (timeout != null) {
@@ -168,4 +171,8 @@ class RNFingerprintjsProModule(reactContext: ReactApplicationContext) : ReactCon
}
return errorType + ":" + error.description
}
+
+ companion object {
+ const val NAME = "RNFingerprintjsPro"
+ }
}
diff --git a/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProPackage.kt b/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProPackage.kt
index 4efd17ed..60dd7820 100644
--- a/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProPackage.kt
+++ b/sdk/android/src/main/java/com/fingerprintjs/reactnative/RNFingerprintjsProPackage.kt
@@ -1,18 +1,36 @@
package com.fingerprintjs.reactnative
-import com.facebook.react.ReactPackage
+import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
-import com.facebook.react.uimanager.ViewManager
-import java.util.*
+import com.facebook.react.module.model.ReactModuleInfo
+import com.facebook.react.module.model.ReactModuleInfoProvider
-class RNFingerprintjsProPackage : ReactPackage {
- override fun createNativeModules(reactContext: ReactApplicationContext): List {
- return Arrays.asList(RNFingerprintjsProModule(reactContext))
+/**
+ * Registers the module as a TurboModule via `BaseReactPackage` + `ReactModuleInfoProvider`.
+ */
+class RNFingerprintjsProPackage : BaseReactPackage() {
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
+ return if (name == RNFingerprintjsProModule.NAME) {
+ RNFingerprintjsProModule(reactContext)
+ } else {
+ null
+ }
}
- override fun createViewManagers(reactContext: ReactApplicationContext): List> {
- return emptyList()
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
+ return ReactModuleInfoProvider {
+ mapOf(
+ RNFingerprintjsProModule.NAME to ReactModuleInfo(
+ RNFingerprintjsProModule.NAME,
+ RNFingerprintjsProModule.NAME,
+ false, // canOverrideExistingModule
+ false, // needsEagerInit
+ false, // isCxxModule
+ true // isTurboModule
+ )
+ )
+ }
}
}
diff --git a/sdk/ios/RNFingerprintjsPro.m b/sdk/ios/RNFingerprintjsPro.m
deleted file mode 100644
index 9b551ae8..00000000
--- a/sdk/ios/RNFingerprintjsPro.m
+++ /dev/null
@@ -1,41 +0,0 @@
-#import
-
-@interface RCT_EXTERN_MODULE(RNFingerprintjsPro, NSObject)
-
-RCT_EXTERN_METHOD(configure:(NSString *)apiToken
- region:(NSString *)region
- endpoint:(NSString *)endpointUrl
- endpointFallbacks:(NSArray *)fallbackEndpointUrls
- extendedResponseFormat:(BOOL)extendedResponseFormat
- pluginVersion:(NSString *)pluginVersion
- allowUseOfLocationData:(BOOL)allowUseOfLocationData
- locationTimeoutMillis:(nonnull NSNumber)locationTimeoutMillis
-)
-
-RCT_EXTERN_METHOD(getVisitorId:(NSDictionary *)tags
- linkedId:(NSString *)linkedId
- resolve:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject
-)
-
-RCT_EXTERN_METHOD(getVisitorIdWithTimeout:(NSDictionary *)tags
- linkedId:(NSString *)linkedId
- timeout:(nonnull NSNumber)timeout
- resolve:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject
-)
-
-RCT_EXTERN_METHOD(getVisitorData:(NSDictionary *)tags
- linkedId:(NSString *)linkedId
- resolve:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject
-)
-
-RCT_EXTERN_METHOD(getVisitorDataWithTimeout:(NSDictionary *)tags
- linkedId:(NSString *)linkedId
- timeout:(nonnull NSNumber)timeout
- resolve:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject
-)
-
-@end
diff --git a/sdk/ios/RNFingerprintjsPro.mm b/sdk/ios/RNFingerprintjsPro.mm
new file mode 100644
index 00000000..030ac544
--- /dev/null
+++ b/sdk/ios/RNFingerprintjsPro.mm
@@ -0,0 +1,54 @@
+#import
+
+@interface RCT_EXTERN_MODULE(RNFingerprintjsPro, NSObject)
+
+RCT_EXTERN_METHOD(configure:(NSString *)apiToken
+ pluginVersion:(NSString *)pluginVersion
+ extendedResponseFormat:(BOOL)extendedResponseFormat
+ fallbackEndpointUrls:(NSArray *)fallbackEndpointUrls
+ allowUseOfLocationData:(BOOL)allowUseOfLocationData
+ locationTimeoutMillis:(double)locationTimeoutMillis
+ region:(NSString * _Nullable)region
+ endpointUrl:(NSString * _Nullable)endpointUrl
+)
+
+RCT_EXTERN_METHOD(getVisitorId:(NSDictionary *)tags
+ linkedId:(NSString *)linkedId
+ timeout:(NSNumber * _Nullable)timeout
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+)
+
+RCT_EXTERN_METHOD(getVisitorData:(NSDictionary *)tags
+ linkedId:(NSString *)linkedId
+ timeout:(NSNumber * _Nullable)timeout
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject
+)
+
+@end
+
+// Expose the Swift class as a real TurboModule backed by the Codegen-generated spec. The Swift
+// implementation already exposes the exact selectors the generated
+// `NativeRNFingerprintjsProSpecJSI` invokes.
+#import
+
+// Import the Swift-generated interface so the category below can extend the Swift class.
+#if __has_include("RNFingerprintjsPro-Swift.h")
+#import "RNFingerprintjsPro-Swift.h"
+#else
+#import
+#endif
+
+@interface RNFingerprintjsPro (TurboModule)
+- (std::shared_ptr)getTurboModule:
+ (const facebook::react::ObjCTurboModule::InitParams &)params;
+@end
+
+@implementation RNFingerprintjsPro (TurboModule)
+- (std::shared_ptr)getTurboModule:
+ (const facebook::react::ObjCTurboModule::InitParams &)params
+{
+ return std::make_shared(params);
+}
+@end
diff --git a/sdk/ios/RNFingerprintjsPro.swift b/sdk/ios/RNFingerprintjsPro.swift
index 12a4e917..d927ebeb 100644
--- a/sdk/ios/RNFingerprintjsPro.swift
+++ b/sdk/ios/RNFingerprintjsPro.swift
@@ -11,20 +11,15 @@ class RNFingerprintjsPro: NSObject {
@objc
static func requiresMainQueueSetup() -> Bool { false }
- @objc(configure:region:endpoint:endpointFallbacks:extendedResponseFormat:pluginVersion:allowUseOfLocationData:locationTimeoutMillis:)
- public func configure(_ apiToken: String, _ region: String? = "us", _ endpoint: String?, _ endpointFallbacks: [String] = [], _ extendedResponseFormat: Bool = false, _ pluginVersion: String, allowUseOfLocationData: Bool = false, locationTimeoutMillis: NSNumber?) -> Void {
- let region = RNFingerprintjsPro.parseRegion(region, endpoint: endpoint, endpointFallbacks: endpointFallbacks)
+ @objc(configure:pluginVersion:extendedResponseFormat:fallbackEndpointUrls:allowUseOfLocationData:locationTimeoutMillis:region:endpointUrl:)
+ public func configure(_ apiToken: String, _ pluginVersion: String, _ extendedResponseFormat: Bool, _ fallbackEndpointUrls: [String], _ allowUseOfLocationData: Bool, _ locationTimeoutMillis: Double, _ region: String?, _ endpointUrl: String?) -> Void {
+ let region = RNFingerprintjsPro.parseRegion(region, endpoint: endpointUrl, endpointFallbacks: fallbackEndpointUrls)
let integrationInfo = [("fingerprint-pro-react-native", pluginVersion)]
let configuration = Configuration(apiKey: apiToken, region: region, integrationInfo: integrationInfo, extendedResponseFormat: extendedResponseFormat, allowUseOfLocationData: allowUseOfLocationData)
fpjsClient = FingerprintProFactory.getInstance(configuration)
}
- @objc(getVisitorId:linkedId:resolve:rejecter:)
- public func getVisitorId(tags: [String: Any]?, linkedId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
- self.getVisitorId(tags: tags, linkedId: linkedId, timeout: nil, resolve: resolve, reject: reject)
- }
-
- @objc(getVisitorIdWithTimeout:linkedId:timeout:resolve:rejecter:)
+ @objc(getVisitorId:linkedId:timeout:resolve:reject:)
public func getVisitorId(tags: [String: Any]?, linkedId: String?, timeout: NSNumber?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
let metadata = RNFingerprintjsPro.prepareMetadata(linkedId, tags: tags)
@@ -45,25 +40,20 @@ class RNFingerprintjsPro: NSObject {
}
}
- @objc(getVisitorData:linkedId:resolve:rejecter:)
- public func getVisitorData(tags: [String: Any]?, linkedId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
- self.getVisitorData(tags: tags, linkedId: linkedId, timeout: nil, resolve: resolve, reject: reject)
- }
-
- @objc(getVisitorDataWithTimeout:linkedId:timeout:resolve:rejecter:)
+ @objc(getVisitorData:linkedId:timeout:resolve:reject:)
public func getVisitorData(tags: [String: Any]?, linkedId: String?, timeout: NSNumber?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
let metadata = RNFingerprintjsPro.prepareMetadata(linkedId, tags: tags)
let completionHandler: FingerprintPro.VisitorIdResponseBlock = { visitorIdResponseResult in
switch visitorIdResponseResult {
case let .success(visitorDataResponse):
- let tuple = [
- visitorDataResponse.requestId,
- visitorDataResponse.confidence,
- visitorDataResponse.asJSON(),
- visitorDataResponse.sealedResult,
- ] as [Any]
- resolve(tuple)
+ let visitorData: [String: Any] = [
+ "requestId": visitorDataResponse.requestId,
+ "confidenceScore": visitorDataResponse.confidence,
+ "visitorDataJson": visitorDataResponse.asJSON(),
+ "sealedResult": visitorDataResponse.sealedResult ?? "",
+ ]
+ resolve(visitorData)
case .failure(let error):
let description = error.reactDescription
reject("Error: ", description, error)
diff --git a/sdk/ios/RNFingerprintjsPro.xcodeproj/project.pbxproj b/sdk/ios/RNFingerprintjsPro.xcodeproj/project.pbxproj
index 5561528e..c67aef03 100644
--- a/sdk/ios/RNFingerprintjsPro.xcodeproj/project.pbxproj
+++ b/sdk/ios/RNFingerprintjsPro.xcodeproj/project.pbxproj
@@ -8,7 +8,7 @@
/* Begin PBXBuildFile section */
10F8301B27A9CBEC00149386 /* RNFingerprintjsPro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F8301A27A9CBEC00149386 /* RNFingerprintjsPro.swift */; };
- B3E7B58A1CC2AC0600A0062D /* RNFingerprintjsPro.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNFingerprintjsPro.m */; };
+ B3E7B58A1CC2AC0600A0062D /* RNFingerprintjsPro.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNFingerprintjsPro.mm */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -28,7 +28,7 @@
10F8301A27A9CBEC00149386 /* RNFingerprintjsPro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNFingerprintjsPro.swift; sourceTree = ""; };
134814201AA4EA6300B7C361 /* libRNFingerprintjsPro.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFingerprintjsPro.a; sourceTree = BUILT_PRODUCTS_DIR; };
7DC52BF728242E1900F78B99 /* RNFingerprintjsPro.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = RNFingerprintjsPro.podspec; path = ../RNFingerprintjsPro.podspec; sourceTree = ""; };
- B3E7B5891CC2AC0600A0062D /* RNFingerprintjsPro.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFingerprintjsPro.m; sourceTree = ""; };
+ B3E7B5891CC2AC0600A0062D /* RNFingerprintjsPro.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RNFingerprintjsPro.mm; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -54,7 +54,7 @@
isa = PBXGroup;
children = (
7DC52BF728242E1900F78B99 /* RNFingerprintjsPro.podspec */,
- B3E7B5891CC2AC0600A0062D /* RNFingerprintjsPro.m */,
+ B3E7B5891CC2AC0600A0062D /* RNFingerprintjsPro.mm */,
10F8301A27A9CBEC00149386 /* RNFingerprintjsPro.swift */,
134814211AA4EA7D00B7C361 /* Products */,
10F8301927A9CBEC00149386 /* RNFingerprintjsPro-Bridging-Header.h */,
@@ -119,7 +119,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- B3E7B58A1CC2AC0600A0062D /* RNFingerprintjsPro.m in Sources */,
+ B3E7B58A1CC2AC0600A0062D /* RNFingerprintjsPro.mm in Sources */,
10F8301B27A9CBEC00149386 /* RNFingerprintjsPro.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/sdk/package.json b/sdk/package.json
index ed75e7d5..a9a0f895 100644
--- a/sdk/package.json
+++ b/sdk/package.json
@@ -22,6 +22,7 @@
},
"files": [
"dist",
+ "src/specs",
"android",
"ios",
"RNFingerprintjsPro.podspec",
@@ -31,6 +32,14 @@
"LICENSE",
"CHANGELOG.md"
],
+ "codegenConfig": {
+ "name": "RNFingerprintjsProSpec",
+ "type": "modules",
+ "jsSrcsDir": "src/specs",
+ "android": {
+ "javaPackageName": "com.fingerprintjs.reactnative"
+ }
+ },
"keywords": [
"fraud",
"fraud detection",
@@ -75,10 +84,10 @@
},
"peerDependencies": {
"@fingerprintjs/fingerprintjs-pro-spa": "^1.3.2",
- "expo": ">=51.0.0",
+ "expo": ">=53.0.0",
"react": "*",
"react-dom": "*",
- "react-native": "*"
+ "react-native": ">=0.79.0"
},
"peerDependenciesMeta": {
"@fingerprintjs/fingerprintjs-pro-spa": {
diff --git a/sdk/src/FingerprintJsProAgent.ts b/sdk/src/FingerprintJsProAgent.ts
index e200c66a..bf4731a8 100644
--- a/sdk/src/FingerprintJsProAgent.ts
+++ b/sdk/src/FingerprintJsProAgent.ts
@@ -1,16 +1,8 @@
-import { NativeModules } from 'react-native'
import { UnknownError } from './errors'
-import type {
- FingerprintJsProAgentParams,
- NativeVisitorData,
- ProAgent,
- RequestOptions,
- Tags,
- VisitorData,
- VisitorId,
-} from './types'
+import RNFingerprintjsPro, { type NativeVisitorData } from './specs/NativeRNFingerprintjsPro'
+import type { FingerprintJsProAgentParams, ProAgent, RequestOptions, Tags, VisitorData, VisitorId } from './types'
import { unwrapError } from './unwrapError'
-import { isDefined, isTruthy } from './utils'
+import { isTruthy } from './utils'
const packageVersion = '__VERSION__'
@@ -37,15 +29,15 @@ export class FingerprintJsProAgent implements ProAgent {
locationTimeoutMillisAndroid = 5000,
}: FingerprintJsProAgentParams) {
try {
- NativeModules.RNFingerprintjsPro.configure(
+ RNFingerprintjsPro.configure(
apiKey,
- region,
- endpointUrl,
- fallbackEndpointUrls,
- extendedResponseFormat,
packageVersion,
+ extendedResponseFormat,
+ fallbackEndpointUrls,
allowUseOfLocationData,
- locationTimeoutMillisAndroid
+ locationTimeoutMillisAndroid,
+ region ?? null,
+ endpointUrl ?? null
)
this.requestOptions = requestOptions
} catch (e) {
@@ -63,11 +55,7 @@ export class FingerprintJsProAgent implements ProAgent {
public async getVisitorId(tags?: Tags, linkedId?: string, options?: RequestOptions): Promise {
try {
const timeout = options?.timeout ?? this.requestOptions.timeout
- if (isDefined(timeout)) {
- return await NativeModules.RNFingerprintjsPro.getVisitorIdWithTimeout(tags, linkedId, timeout)
- }
-
- return await NativeModules.RNFingerprintjsPro.getVisitorId(tags, linkedId)
+ return await RNFingerprintjsPro.getVisitorId(tags ?? null, linkedId ?? null, timeout ?? null)
} catch (error) {
if (error instanceof Error) {
throw unwrapError(error)
@@ -89,16 +77,15 @@ export class FingerprintJsProAgent implements ProAgent {
public async getVisitorData(tags?: Tags, linkedId?: string, options?: RequestOptions): Promise {
try {
const timeout = options?.timeout ?? this.requestOptions.timeout
- let visitorData: NativeVisitorData | null
- if (isDefined(timeout)) {
- visitorData = await NativeModules.RNFingerprintjsPro.getVisitorDataWithTimeout(tags, linkedId, timeout)
- } else {
- visitorData = await NativeModules.RNFingerprintjsPro.getVisitorData(tags, linkedId)
- }
- const [requestId, confidenceScore, visitorDataJsonString, sealedResult] = visitorData
+ const visitorData: NativeVisitorData = await RNFingerprintjsPro.getVisitorData(
+ tags ?? null,
+ linkedId ?? null,
+ timeout ?? null
+ )
+ const { requestId, confidenceScore, visitorDataJson, sealedResult } = visitorData
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const result = {
- ...JSON.parse(visitorDataJsonString),
+ ...JSON.parse(visitorDataJson),
requestId,
confidence: {
score: confidenceScore,
diff --git a/sdk/src/react-native.d.ts b/sdk/src/react-native.d.ts
deleted file mode 100644
index 7a8af599..00000000
--- a/sdk/src/react-native.d.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { Tags, VisitorId, NativeVisitorData } from './types'
-
-declare module 'react-native' {
- interface RNFingerprintjsPro {
- configure: (
- apiKey: string,
- region?: string,
- endpointUrl?: string,
- fallbackEndpointUrls?: string[],
- extendedResponseFormat: boolean,
- packageVersion: string,
- allowUseOfLocationData: boolean,
- locationTimeoutMillisAndroid: number
- ) => void
- getVisitorId: (tags?: Tags, linkedId?: string) => Promise
- getVisitorIdWithTimeout: (tags?: Tags, linkedId?: string, timeout: number) => Promise
- getVisitorData: (tags?: Tags, linkedId?: string) => Promise
- getVisitorDataWithTimeout: (tags?: Tags, linkedId?: string, timeout: number) => Promise
- }
-
- export interface NativeModulesStatic {
- RNFingerprintjsPro: RNFingerprintjsPro
- }
-}
diff --git a/sdk/src/specs/NativeRNFingerprintjsPro.ts b/sdk/src/specs/NativeRNFingerprintjsPro.ts
new file mode 100644
index 00000000..d1baea39
--- /dev/null
+++ b/sdk/src/specs/NativeRNFingerprintjsPro.ts
@@ -0,0 +1,43 @@
+import type { TurboModule } from 'react-native'
+import { TurboModuleRegistry } from 'react-native'
+import type { Double, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'
+
+/**
+ * Raw visitor data returned by the native module. The visitor payload is delivered as a JSON
+ * string (`visitorDataJson`) that the SDK parses and merges with the top-level fields.
+ *
+ * Codegen requires the numeric field to be typed as {@link Double} and every field to be
+ * non-nullable, so `sealedResult` is an empty string (rather than absent) when unavailable.
+ */
+export interface NativeVisitorData {
+ requestId: string
+ confidenceScore: Double
+ visitorDataJson: string
+ sealedResult: string
+}
+
+/**
+ * Codegen TurboModule spec for the `RNFingerprintjsPro` native module.
+ *
+ * Optional string/object/number arguments are modelled as nullable (`| null`); Codegen relies on
+ * positional arguments and native treats `null` as "not provided". Genuinely optional parameters
+ * (those without an SDK-side default: `region`, `endpointUrl`, `timeout`) come last in each signature.
+ */
+export interface Spec extends TurboModule {
+ configure(
+ apiToken: string,
+ pluginVersion: string,
+ extendedResponseFormat: boolean,
+ fallbackEndpointUrls: string[],
+ allowUseOfLocationData: boolean,
+ locationTimeoutMillis: Double,
+ region: string | null,
+ endpointUrl: string | null
+ ): void
+
+ getVisitorId(tags: UnsafeObject | null, linkedId: string | null, timeout: Double | null): Promise
+
+ getVisitorData(tags: UnsafeObject | null, linkedId: string | null, timeout: Double | null): Promise
+}
+
+export default TurboModuleRegistry.getEnforcing('RNFingerprintjsPro')
diff --git a/sdk/src/types.ts b/sdk/src/types.ts
index bb7c83ec..f5869a4f 100644
--- a/sdk/src/types.ts
+++ b/sdk/src/types.ts
@@ -406,10 +406,3 @@ export interface VisitorQueryContext extends VisitorQueryResult {
}
export type VisitorId = string
-
-export type NativeVisitorData = [
- requestId: string,
- confidenceScore: string,
- visitorDataJsonString: string,
- sealedResult?: string,
-]