Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1b6cc83
feat: migrate to strict react-native API
TheUnderScorer Sep 4, 2026
7af274f
feat: add experimental SPM support
TheUnderScorer Sep 4, 2026
7a6fbb6
feat(spm): update Fingerprint iOS SDK to new repository URL
TheUnderScorer Sep 4, 2026
353c612
feat(spm): fix Fingerprint iOS SDK import and Swift language mode
TheUnderScorer Sep 7, 2026
9438500
feat(ios): make RNFingerprintjsPro class public
TheUnderScorer Sep 7, 2026
85bc10a
fix(ios): update import logic for Swift compatibility
TheUnderScorer Sep 7, 2026
7ca5591
feat(spm): rename module to `RNFingerprintjsPro` for consistency
TheUnderScorer Sep 7, 2026
1ee6900
docs(spm): add clarification for local path dependencies
TheUnderScorer Sep 7, 2026
5447f8a
feat: add bare rn app to test spm support
TheUnderScorer Sep 7, 2026
7646dc9
fix(ios): make RNFingerprintjsPro class non-public
TheUnderScorer Sep 7, 2026
072a7de
chore: integrate react-native-dotenv
TheUnderScorer Sep 7, 2026
1e61482
chore: fix linter
TheUnderScorer Sep 8, 2026
b61e973
chore: update linter comment in bare rn app
TheUnderScorer Sep 8, 2026
4688c7d
fix(ios): simplify Fingerprint-iOS dependency definition
TheUnderScorer Sep 8, 2026
7955c0a
fix(ios): simplify import logic in RNFingerprintjsPro
TheUnderScorer Sep 8, 2026
c69a78e
chore: remove bare rn app
TheUnderScorer Sep 8, 2026
ee5f04b
chore: loosen Fingerprint-ios constraint
TheUnderScorer Sep 8, 2026
111ba96
docs: fix casing
TheUnderScorer Sep 8, 2026
0fc355b
docs: improve wording
TheUnderScorer Sep 8, 2026
5ead202
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 10, 2026
ce3ac4f
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 10, 2026
5c2530a
Merge remote-tracking branch 'origin/feature/INTER-2190-v4-support' i…
TheUnderScorer Sep 11, 2026
ad69ce5
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 15, 2026
af67e14
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 15, 2026
e4884eb
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 16, 2026
a6a9dbe
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 17, 2026
369c6b6
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 18, 2026
19b20ca
feat(sdk): add tvOS support and update iOS to 15.1
TheUnderScorer Sep 21, 2026
971224d
fix(sdk): align tvOS version with iOS at 15.1
TheUnderScorer Sep 21, 2026
56c8820
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 21, 2026
6f19b44
Merge branch 'feature/INTER-2190-v4-support' into feature/INTER-2397-…
TheUnderScorer Sep 21, 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
4 changes: 3 additions & 1 deletion .changeset/new-architecture-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

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.
Migrated the project to [Strict TypeScript API](https://reactnative.dev/docs/strict-typescript-api).

This is a breaking change: the SDK now requires React Native >= 0.80 (Expo SDK >= 54) with the [New Architecture](https://reactnative.dev/architecture/landing-page) enabled.
13 changes: 13 additions & 0 deletions .changeset/spm-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@fingerprintjs/fingerprintjs-pro-react-native': minor
---

Added experimental support for [Swift Package Manager](https://swift.org/package-manager/). The library now ships with its own `Package.swift`.

If you are on React Native >= 0.87, you can run:
```bash
cd ios
# deintegrate will remove CocoaPods from your project
npx react-native spm --deintegrate
```
to consume the library via Swift Package Manager.
2 changes: 1 addition & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- id: set-matrix
run: |
echo "matrix=['local', '0.79', '0.81', '0.83', '0.85']" >> $GITHUB_OUTPUT
echo "matrix=['local', '0.81', '0.83', '0.85']" >> $GITHUB_OUTPUT

build-android:
strategy:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ application to call the native Fingerprint Pro libraries (Android and iOS) and i

## Requirements and limitations

- React Native 0.79 or higher is supported (New Architecture only)
- Expo 53.0.0 or higher is supported
- React Native 0.80 or higher is supported (New Architecture only)
- Expo 54.0.0 or higher is supported
- Android 7.0 (API level 24+) or higher
- iOS 15.1+/tvOS 15.1+, Swift 5.9 or higher (stable releases)

Expand Down
2 changes: 1 addition & 1 deletion contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ With that being said, it would be possible to test these versions without Expo,

Below is the list of React Native versions that are **not** tested in CI:

```0.78, 0.80, 0.82, 0.84```
```0.80, 0.82, 0.84```

## How to publish

Expand Down
3 changes: 0 additions & 3 deletions e2e-app/scripts/compatibility.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import pkg from '../package.json' with { type: 'json' }
const devPackages = Object.keys(pkg.devDependencies)

const reactNativeMetadata = {
0.79: {
packages: ['expo@53', 'detox@20.37.0', '@config-plugins/detox@11', 'react@19.0.0', 'react-dom@19.0.0'],
},
0.81: {
packages: ['expo@54', 'detox@20.51.0', '@config-plugins/detox@11', 'react@19.1.0', 'react-dom@19.1.0'],
},
Expand Down
11 changes: 11 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ const config = [
},
},
},
{
// SDK source consumes React Native's Strict TypeScript API, which resolves only under
// bundler module resolution (see sdk/tsconfig.sdk.json).
files: ['sdk/**/*.{ts,tsx}'],
languageOptions: {
parserOptions: {
project: 'sdk/tsconfig.sdk.json',
tsconfigRootDir: __dirname,
},
},
},
{
files: ['sdk/__tests__/**/*.{ts,tsx}'],
languageOptions: {
Expand Down
1,594 changes: 1,200 additions & 394 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions sdk/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// swift-tools-version: 6.0
//
// Swift Package Manager manifest for
// @fingerprintjs/fingerprintjs-pro-react-native, consumed by React Native
// 0.87's experimental SwiftPM autolinking (`npx react-native spm`).
//
// ─────────────────────────────────────────────────────────────────────────────
// WHY THIS IS HAND-WRITTEN (not produced by `react-native spm scaffold`)
// ─────────────────────────────────────────────────────────────────────────────
// The scaffolder SKIPS this library ("skipped-mixed-language"): ios/ mixes Swift
// and Objective-C++, and a single SwiftPM target cannot contain both languages
// (SE-0403 would lift this but is not shipped). The ObjC++ file is unavoidable —
// RN 0.87 has no Swift path for the TurboModule `getTurboModule:` step, which
// must return the codegen'd C++ JSI spec. The supported workaround is the
// two-target split below: a pure-Swift target plus a thin ObjC++ glue target
// that depends on it.
//
// ─────────────────────────────────────────────────────────────────────────────
// ⚠️ NOT YET VALIDATED BY A REAL BUILD
// ─────────────────────────────────────────────────────────────────────────────
// RN's SwiftPM support is experimental ("do not use in production yet"). This
// manifest evaluates cleanly (`swift package dump-package`) but has NOT been
// exercised by an Xcode SwiftPM build in a consuming app. Confirm on a machine
Comment thread
TheUnderScorer marked this conversation as resolved.
// with Xcode + the e2e-app (via `npx react-native spm --deintegrate` then a
// detox build):
// 1. The Swift target resolves RCTPromise* via `import React` (RCT_SPM flag).
// 2. The Fingerprint v4 SwiftPM product/module identity below is correct.
// 3. The ObjC++ glue picks up RNFingerprintjsProSwift-Swift.h.
//
// The package/product name is pinned to `RNFingerprintjsPro` (matching the pod /
// native module) via `spm.name` in react-native.config.js. The autolinker uses
// that name for its libs/<name> symlink and the product it references, so the
// product below must match it.
//
// The React / React-GeneratedCode packages use RELATIVE paths that assume this
// manifest is reached through the autolinker's libs/RNFingerprintjsPro symlink —
// the same fixed depth `react-native spm scaffold` targets. They are resolved
// from the CONSUMING APP, not this repo; do not repoint them here.

import PackageDescription

let package = Package(
name: "RNFingerprintjsPro",
platforms: [.iOS("15.1"), .tvOS("15.1")],
products: [
.library(
name: "RNFingerprintjsPro",
targets: ["RNFingerprintjsPro"]
),
],
dependencies: [
// These two are parts of the CONSUMING APP, not registry packages, so
// they're referenced by local path. `ReactNative` is RN's prebuilt
// xcframeworks + header products; `React-GeneratedCode` is the app's
// per-app codegen output (the `ReactAppHeaders` product). The relative
// paths resolve from the autolinker's libs/RNFingerprintjsPro symlink
// (a fixed depth in <app>/ios/build/generated/autolinking/), NOT from
// this repo, so `../../../../xcframeworks` lands on <app>/ios/build/
// xcframeworks and `../../../ios` on the generated codegen package.
// They look wrong relative to sdk/ but are correct in every app; do not
// repoint them. (This mirrors what `react-native spm scaffold` emits.)
.package(name: "ReactNative", path: "../../../../xcframeworks"),
.package(name: "React-GeneratedCode", path: "../../../ios"),
.package(
url: "https://github.com/fingerprintjs/fingerprint-ios",
from: "4.0.0"
),
],
targets: [
// Pure-Swift implementation: all business logic + the Fingerprint SDK
// calls. Kept single-language so SwiftPM accepts the target.
.target(
name: "RNFingerprintjsProSwift",
dependencies: [
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),
.product(name: "Fingerprint", package: "fingerprint-ios"),
],
path: "ios",
exclude: [
"turbomodule",
"RNFingerprintjsPro-Bridging-Header.h",
"RNFingerprintjsPro.xcodeproj",
"build",
],
sources: [
"FPJSError+React.swift",
"JSONTypeConvertor.swift",
"RNFingerprintjsPro.swift",
],
// swift-tools-version 6.0 defaults targets to Swift 6 language mode,
// whose strict concurrency rejects capturing the non-Sendable ObjC
// block typedefs RCTPromiseResolveBlock/RCTPromiseRejectBlock inside
// the Fingerprint SDK's @Sendable completion closure. CocoaPods builds
// this pod in Swift 5 mode; match that so behavior is identical.
swiftSettings: [.define("RCT_SPM"), .swiftLanguageMode(.v5)]
),
// Objective-C++ TurboModule glue: registers the native module and returns
// the codegen'd C++ JSI spec (`NativeRNFingerprintjsProSpecJSI`). This
// cannot be Swift. Depends on the Swift target for its generated header.
.target(
name: "RNFingerprintjsPro",
dependencies: [
"RNFingerprintjsProSwift",
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),
.product(name: "ReactAppHeaders", package: "React-GeneratedCode"),
],
path: "ios/turbomodule",
publicHeadersPath: ".",
cSettings: [
.headerSearchPath("."),
.unsafeFlags(["-include", "react-native-spm-prefix.h"]),
],
cxxSettings: [
.headerSearchPath("."),
.unsafeFlags(["-include", "react-native-spm-prefix.h"]),
// Match the prebuilt React.framework's config-gated C++ ABI
// (NDEBUG in Release), mirroring what `spm scaffold` emits.
.define("DEBUG", .when(configuration: .debug)),
.define("NDEBUG", .when(configuration: .release)),
],
linkerSettings: [
.linkedFramework("UIKit"),
.linkedFramework("Foundation"),
.linkedFramework("CoreGraphics"),
]
),
],
cxxLanguageStandard: .cxx20
)
4 changes: 2 additions & 2 deletions sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ application to call the native Fingerprint Pro libraries (Android and iOS) and i

## Requirements and limitations

- React Native 0.79 or higher is supported (New Architecture only)
- Expo 53.0.0 or higher is supported
- React Native 0.80 or higher is supported (New Architecture only)
- Expo 54.0.0 or higher is supported
- Android 7.0 (API level 24+) or higher
- iOS 15.1+/tvOS 15.1+, Swift 5.9 or higher (stable releases)

Expand Down
10 changes: 5 additions & 5 deletions sdk/RNFingerprintjsPro.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ Pod::Spec.new do |s|
s.source = { :git => "https://github.com/fingerprintjs/fingerprintjs-pro-react-native.git", :tag => "main" }
s.ios.deployment_target = "15.1"
s.tvos.deployment_target = "15.1"

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/**/*"
# react-native-spm-prefix.h is a SwiftPM-only shim (force-included via Package.swift);
# CocoaPods provides its own prefix header, so keep it out of the pod sources.
s.exclude_files = "ios/build/**/*", "ios/**/react-native-spm-prefix.h"
# s.requires_arc = true

s.dependency "React-Core"
s.dependency "Fingerprint-iOS", '~> 4.0'
s.dependency "Fingerprint-iOS", "~> 4.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.
# `RCT_NEW_ARCH_ENABLED` for the pod when the app is built with the New Architecture.
install_modules_dependencies(s)
end
9 changes: 9 additions & 0 deletions sdk/ios/RNFingerprintjsPro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
import Fingerprint
import Foundation

// Under the SwiftPM two-target split (Package.swift) this Swift target has no
// bridging header, so RCTPromiseResolveBlock/RCTPromiseRejectBlock must come from
// the React module. RCT_SPM is defined only by the SwiftPM Swift target; under
// CocoaPods these types arrive via the pod's Objective-C interop and this import
// is skipped.
#if RCT_SPM
import React
#endif

@objc(RNFingerprintjsPro)
class RNFingerprintjsPro: NSObject {
private var fpjsClient: FingerprintClientProviding?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@ @interface RCT_EXTERN_MODULE(RNFingerprintjsPro, NSObject)
// `NativeRNFingerprintjsProSpecJSI` invokes.
#import <RNFingerprintjsProSpec/RNFingerprintjsProSpec.h>

// 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 <RNFingerprintjsPro/RNFingerprintjsPro-Swift.h>
#endif
Comment thread
kevinmia marked this conversation as resolved.

@interface RNFingerprintjsPro (TurboModule) <NativeRNFingerprintjsProSpec>
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params;
Expand Down
8 changes: 8 additions & 0 deletions sdk/ios/turbomodule/react-native-spm-prefix.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Mirrors CocoaPods' default prefix header so this Objective-C++ source, which
// relies on an implicit Foundation import, compiles under SwiftPM (which
// has no prefix-header mechanism). Force-included via `-include` from the
// FingerprintjsProReactNative target in Package.swift. Inert for CocoaPods
// (excluded from the podspec's source_files).
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif
10 changes: 6 additions & 4 deletions sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"android",
"ios",
"RNFingerprintjsPro.podspec",
"Package.swift",
"react-native.config.js",
"plugin/dist",
"app.plugin.js",
"README.md",
Expand Down Expand Up @@ -66,16 +68,16 @@
"@react-native/jest-preset": "^0.86.0",
"@testing-library/react": "^16.3.2",
"@types/jest": "^29.5.12",
"@types/node": "^25.9.5",
"@types/react": "19.2.17",
"@types/react-native": "0.73.0",
"esbuild": "^0.28.1",
"esbuild-plugin-license": "1.2.3",
"expo": "^57.0.4",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "^0.86.0",
"react-native": "^0.87.1",
"react-test-renderer": "18.2.0",
"ts-jest": "^29.1.2",
"tslib": "^2.6.2",
Expand All @@ -85,10 +87,10 @@
},
"peerDependencies": {
"@fingerprint/agent": "^4.1.3",
"expo": ">=53.0.0",
"expo": ">=54.0.0",
"react": "*",
"react-dom": "*",
"react-native": ">=0.79.0"
"react-native": ">=0.80.0"
},
"peerDependenciesMeta": {
"@fingerprint/agent": {
Expand Down
9 changes: 9 additions & 0 deletions sdk/react-native.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Pin this library's Swift Package Manager module name for RN 0.87's SPM
// autolinking. Without an override the autolinker derives the name from the npm
// package (`FingerprintjsProReactNative`); we pin it to `RNFingerprintjsPro` to
// match the CocoaPods pod and the native module name. The autolinker uses this
// for its libs/<name> symlink and the product it references, so it must equal
// the product/package name declared in Package.swift.
module.exports = {
spm: { name: 'RNFingerprintjsPro' },
}
5 changes: 2 additions & 3 deletions sdk/src/FingerprintClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Platform } from 'react-native'
import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'
import { Platform, type CodegenTypes } from 'react-native'
import RNFingerprintjsPro, { type NativeVisitorData } from './specs/NativeRNFingerprintjsPro'
import type { FingerprintClient, FingerprintResponse, GetOptions, StartOptions, TagsValue } from './types'
import { unwrapError } from './unwrapError'
Expand All @@ -26,7 +25,7 @@ function normalizeEndpoints(endpoints: StartOptions['endpoints']): { endpointUrl
* 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): UnsafeObject | null {
function toNativeTag(tag: TagsValue | undefined): CodegenTypes.UnsafeObject | null {
if (!isDefined(tag)) {
return null
}
Expand Down
13 changes: 8 additions & 5 deletions sdk/src/specs/NativeRNFingerprintjsPro.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { TurboModule } from 'react-native'
import type { CodegenTypes, 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.
Expand All @@ -13,7 +12,7 @@ import type { Double, UnsafeObject } from 'react-native/Libraries/Types/CodegenT
export interface NativeVisitorData {
visitorId: string
eventId: string
suspectScore: Double
suspectScore: CodegenTypes.Double
sealedResult: string
}

Expand All @@ -30,12 +29,16 @@ export interface Spec extends TurboModule {
pluginVersion: string,
fallbackEndpointUrls: string[],
allowUseOfLocationData: boolean,
locationTimeoutMillis: Double,
locationTimeoutMillis: CodegenTypes.Double,
region: string | null,
endpointUrl: string | null
): void

getVisitorData(tag: UnsafeObject | null, linkedId: string | null, timeout: Double | null): Promise<NativeVisitorData>
getVisitorData(
tag: CodegenTypes.UnsafeObject | null,
linkedId: string | null,
timeout: CodegenTypes.Double | null
): Promise<NativeVisitorData>
}

export default TurboModuleRegistry.getEnforcing<Spec>('RNFingerprintjsPro')
Loading
Loading