@exodus/react-native-biometrics 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,3 +2,7 @@
2
2
 
3
3
  All notable changes to this projected will be documented in this file.
4
4
  See [Conventional Commits](https://www.conventionalcommits.org) for commit guidelines.
5
+
6
+ ## 0.4.1 (2026-09-11)
7
+
8
+ - Fix authentication failing before the prompt appears on Android 9–10 when device passcode fallback is enabled ([#9](https://github.com/ExodusMovement/react-native-biometrics/pull/9)).
package/README.md CHANGED
@@ -30,7 +30,24 @@ const valid = await Biometrics.authenticate(
30
30
  | title | string | The title of the biometric prompt |
31
31
  | subtitle | string | The subtitle of the biometric prompt |
32
32
  | cancelButtonText | string | The text of the cancel button |
33
+ | fallbackToPasscode | boolean | Allow the device PIN, pattern, or password as an alternative to biometrics (default: `false`) |
33
34
 
35
+ With passcode fallback enabled, Android 9–10 (API 28–29) accepts Class 2 (weak) or
36
+ Class 3 (strong) biometrics alongside device credentials. AndroidX does not support
37
+ `BIOMETRIC_STRONG | DEVICE_CREDENTIAL` on these versions. Other Android versions,
38
+ and biometric-only authentication on all versions, continue to require Class 3
39
+ biometrics. Authentication does not use a `CryptoObject`.
40
+
41
+ ## Android tests
42
+
43
+ With JDK 17, Gradle 8.12, Android SDK 36, and build tools 36.0.0 installed:
44
+
45
+ ```sh
46
+ gradle -p test/android :biometrics:testDebugUnitTest
47
+ ```
48
+
49
+ The tests use the library's AndroidX dependency and Robolectric to validate prompt
50
+ creation on API 27, 28, 29, and 30, including the previously rejected combination.
34
51
 
35
52
  ## License
36
53
 
@@ -3,6 +3,7 @@ package org.exodusmovement.rn.biometrics;
3
3
  import java.util.concurrent.atomic.AtomicBoolean;
4
4
 
5
5
  import android.app.Activity;
6
+ import android.os.Build;
6
7
  import android.os.Handler;
7
8
  import android.os.Looper;
8
9
 
@@ -38,6 +39,28 @@ public final class RNBiometricsModule extends ReactContextBaseJavaModule {
38
39
  return NAME;
39
40
  }
40
41
 
42
+ static BiometricPrompt.PromptInfo createPromptInfo(String title, String subtitle, String cancelButtonText, boolean fallbackToPasscode) {
43
+ int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
44
+ if (fallbackToPasscode) {
45
+ // AndroidX cannot combine strong biometrics with device credentials on API 28-29.
46
+ if (Build.VERSION.SDK_INT >= 28 && Build.VERSION.SDK_INT <= 29) {
47
+ authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK;
48
+ }
49
+ authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
50
+ }
51
+
52
+ BiometricPrompt.PromptInfo.Builder builder = new BiometricPrompt.PromptInfo.Builder()
53
+ .setAllowedAuthenticators(authenticators)
54
+ .setTitle(title)
55
+ .setSubtitle(subtitle);
56
+
57
+ if (!fallbackToPasscode) {
58
+ builder.setNegativeButtonText(cancelButtonText);
59
+ }
60
+
61
+ return builder.build();
62
+ }
63
+
41
64
  @ReactMethod
42
65
  public void isSupported(Promise promise) {
43
66
  try {
@@ -87,19 +110,6 @@ public final class RNBiometricsModule extends ReactContextBaseJavaModule {
87
110
  };
88
111
 
89
112
  try {
90
- int authenticators = fallbackToPasscode
91
- ? BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL
92
- : BiometricManager.Authenticators.BIOMETRIC_STRONG;
93
-
94
- BiometricPrompt.PromptInfo.Builder promptInfoBuilder = new BiometricPrompt.PromptInfo.Builder()
95
- .setAllowedAuthenticators(authenticators)
96
- .setTitle(title)
97
- .setSubtitle(subtitle);
98
-
99
- if (!fallbackToPasscode) {
100
- promptInfoBuilder.setNegativeButtonText(cancelButtonText);
101
- }
102
-
103
113
  BiometricPrompt prompt = new BiometricPrompt(
104
114
  fragmentActivity,
105
115
  ContextCompat.getMainExecutor(context),
@@ -110,7 +120,7 @@ public final class RNBiometricsModule extends ReactContextBaseJavaModule {
110
120
  activePromise = promise;
111
121
  activeSettled = settled;
112
122
 
113
- prompt.authenticate(promptInfoBuilder.build());
123
+ prompt.authenticate(createPromptInfo(title, subtitle, cancelButtonText, fallbackToPasscode));
114
124
  } catch (Exception e) {
115
125
  if (settled.compareAndSet(false, true)) {
116
126
  clearSlotIfMatches(promise);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/react-native-biometrics",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "React Native Biometrics",
5
5
  "main": "index.js",
6
6
  "files": [