@exodus/react-native-biometrics 0.2.1 → 0.4.0
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.
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
package org.exodusmovement.rn.biometrics;
|
|
2
2
|
|
|
3
|
-
import java.util.concurrent.
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import com.facebook.react.module.annotations.ReactModule;
|
|
3
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
4
|
+
|
|
5
|
+
import android.app.Activity;
|
|
6
|
+
import android.os.Handler;
|
|
7
|
+
import android.os.Looper;
|
|
9
8
|
|
|
9
|
+
import androidx.annotation.Nullable;
|
|
10
10
|
import androidx.biometric.BiometricPrompt;
|
|
11
11
|
import androidx.biometric.BiometricManager;
|
|
12
12
|
import androidx.core.content.ContextCompat;
|
|
13
13
|
import androidx.fragment.app.FragmentActivity;
|
|
14
14
|
|
|
15
|
+
import com.facebook.react.bridge.Promise;
|
|
16
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
17
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
18
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
19
|
+
import com.facebook.react.module.annotations.ReactModule;
|
|
20
|
+
|
|
15
21
|
@ReactModule(name = RNBiometricsModule.NAME)
|
|
16
22
|
public final class RNBiometricsModule extends ReactContextBaseJavaModule {
|
|
17
23
|
public static final String NAME = "Biometrics";
|
|
18
24
|
|
|
19
25
|
private final ReactApplicationContext context;
|
|
20
26
|
|
|
27
|
+
@Nullable private BiometricPrompt activePrompt;
|
|
28
|
+
@Nullable private Promise activePromise;
|
|
29
|
+
@Nullable private AtomicBoolean activeSettled;
|
|
30
|
+
|
|
21
31
|
public RNBiometricsModule(ReactApplicationContext context) {
|
|
22
32
|
super(context);
|
|
23
33
|
this.context = context;
|
|
@@ -40,40 +50,120 @@ public final class RNBiometricsModule extends ReactContextBaseJavaModule {
|
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
@ReactMethod
|
|
43
|
-
public void authenticate(String title, String subtitle, String cancelButtonText, Promise promise) {
|
|
44
|
-
context.getCurrentActivity()
|
|
53
|
+
public void authenticate(final String title, final String subtitle, final String cancelButtonText, final boolean fallbackToPasscode, final Promise promise) {
|
|
54
|
+
final Activity activity = context.getCurrentActivity();
|
|
55
|
+
if (!(activity instanceof FragmentActivity)) {
|
|
56
|
+
promise.reject("E_NO_ACTIVITY", "No FragmentActivity available to host BiometricPrompt");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
final FragmentActivity fragmentActivity = (FragmentActivity) activity;
|
|
61
|
+
|
|
62
|
+
fragmentActivity.runOnUiThread(new Runnable() {
|
|
45
63
|
@Override
|
|
46
64
|
public void run() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
cancelInternal();
|
|
66
|
+
|
|
67
|
+
final AtomicBoolean settled = new AtomicBoolean(false);
|
|
68
|
+
|
|
69
|
+
BiometricPrompt.AuthenticationCallback callback = new BiometricPrompt.AuthenticationCallback() {
|
|
70
|
+
@Override
|
|
71
|
+
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
|
72
|
+
super.onAuthenticationError(errorCode, errString);
|
|
73
|
+
if (settled.compareAndSet(false, true)) {
|
|
74
|
+
clearSlotIfMatches(promise);
|
|
75
|
+
promise.resolve(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
|
|
81
|
+
super.onAuthenticationSucceeded(result);
|
|
82
|
+
if (settled.compareAndSet(false, true)) {
|
|
83
|
+
clearSlotIfMatches(promise);
|
|
84
|
+
promise.resolve(true);
|
|
63
85
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
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)
|
|
67
96
|
.setTitle(title)
|
|
68
|
-
.setSubtitle(subtitle)
|
|
69
|
-
|
|
70
|
-
|
|
97
|
+
.setSubtitle(subtitle);
|
|
98
|
+
|
|
99
|
+
if (!fallbackToPasscode) {
|
|
100
|
+
promptInfoBuilder.setNegativeButtonText(cancelButtonText);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
BiometricPrompt prompt = new BiometricPrompt(
|
|
104
|
+
fragmentActivity,
|
|
105
|
+
ContextCompat.getMainExecutor(context),
|
|
106
|
+
callback
|
|
71
107
|
);
|
|
72
108
|
|
|
109
|
+
activePrompt = prompt;
|
|
110
|
+
activePromise = promise;
|
|
111
|
+
activeSettled = settled;
|
|
112
|
+
|
|
113
|
+
prompt.authenticate(promptInfoBuilder.build());
|
|
73
114
|
} catch (Exception e) {
|
|
74
|
-
|
|
115
|
+
if (settled.compareAndSet(false, true)) {
|
|
116
|
+
clearSlotIfMatches(promise);
|
|
117
|
+
promise.reject(e);
|
|
118
|
+
}
|
|
75
119
|
}
|
|
76
120
|
}
|
|
77
121
|
});
|
|
78
122
|
}
|
|
79
|
-
|
|
123
|
+
|
|
124
|
+
@ReactMethod
|
|
125
|
+
public void cancel() {
|
|
126
|
+
final Runnable runnable = new Runnable() {
|
|
127
|
+
@Override
|
|
128
|
+
public void run() {
|
|
129
|
+
cancelInternal();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
final Activity activity = context.getCurrentActivity();
|
|
134
|
+
if (activity != null) {
|
|
135
|
+
activity.runOnUiThread(runnable);
|
|
136
|
+
} else {
|
|
137
|
+
new Handler(Looper.getMainLooper()).post(runnable);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private void cancelInternal() {
|
|
142
|
+
BiometricPrompt prompt = activePrompt;
|
|
143
|
+
Promise promise = activePromise;
|
|
144
|
+
AtomicBoolean settled = activeSettled;
|
|
145
|
+
|
|
146
|
+
activePrompt = null;
|
|
147
|
+
activePromise = null;
|
|
148
|
+
activeSettled = null;
|
|
149
|
+
|
|
150
|
+
if (prompt != null) {
|
|
151
|
+
try {
|
|
152
|
+
prompt.cancelAuthentication();
|
|
153
|
+
} catch (Exception ignored) {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (promise != null && settled != null && settled.compareAndSet(false, true)) {
|
|
158
|
+
promise.resolve(false);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private void clearSlotIfMatches(Promise promise) {
|
|
163
|
+
if (activePromise == promise) {
|
|
164
|
+
activePrompt = null;
|
|
165
|
+
activePromise = null;
|
|
166
|
+
activeSettled = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,12 @@ export default {
|
|
|
6
6
|
isSupported: async () => {
|
|
7
7
|
return AndroidBiometrics.isSupported();
|
|
8
8
|
},
|
|
9
|
-
authenticate: async ({ title, subtitle, cancelButtonText }) => {
|
|
10
|
-
return AndroidBiometrics.authenticate(title, subtitle, cancelButtonText);
|
|
11
|
-
}
|
|
9
|
+
authenticate: async ({ title, subtitle, cancelButtonText, fallbackToPasscode = false }) => {
|
|
10
|
+
return AndroidBiometrics.authenticate(title, subtitle, cancelButtonText, fallbackToPasscode);
|
|
11
|
+
},
|
|
12
|
+
cancel: () => {
|
|
13
|
+
if (typeof AndroidBiometrics?.cancel === 'function') {
|
|
14
|
+
AndroidBiometrics.cancel();
|
|
15
|
+
}
|
|
16
|
+
},
|
|
12
17
|
}
|