Compare commits

..

3 Commits

Author SHA1 Message Date
Phillip Baker
7515eacac7 wip 2019-07-07 15:52:30 -04:00
Phillip Baker
4d17857b27 Add device type to error output.
This changes the API of the internal function `createError` to take name and biometric type. It jams the biometric type into the error message, since that was not being used and is available across platforms.
2019-07-07 10:55:48 -04:00
Phillip Baker
66440230b9 examples: Fix android caching fingerprint state in native code.
Closes https://github.com/hieuvp/react-native-fingerprint-scanner/issues/66.
2019-07-07 10:54:48 -04:00
19 changed files with 169 additions and 893 deletions

View File

@ -1,39 +0,0 @@
env:
- NODE_ENV='test'
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
matrix:
include:
- language: objective-c
os: osx
xcode_sdk: iphonesimulator12.2
osx_image: xcode10.2
sudo: true
before_install:
- brew install node@10
- cd examples && make install # install node modules to get podfiles
install:
- cd ios && pod install && cd ..
script:
- make test-ios
- language: android
android:
components:
- tools
- platform-tools
- build-tools-29.0.2
- android-29
sudo: true
before_install:
- nvm install --lts
install: true
script:
- cd examples && make test-android
- language: node_js
node_js: lts/*
sudo: false
install:
- cd examples && npm ci
script: react-native bundle --entry-file index.js --bundle-output main.jsbundle

224
README.md
View File

@ -6,14 +6,6 @@
React Native Fingerprint Scanner is a [React Native](http://facebook.github.io/react-native/) library for authenticating users with Fingerprint (TouchID).
## Table of Contents
- [Installation](#installation)
- [Compatibility](#compatibility)
- [Example](#example)
- [API](#api)
- [License](#license)
### iOS Version
The usage of the TouchID is based on a framework, named **Local Authentication**.
@ -25,10 +17,6 @@ It provides a **Default View** that prompts the user to place a finger to the iP
</div>
### Android Version
4.0.0 Prefers the new native Android BiometricPrompt lib on any Android >= v23 (M)
4.0.0 also DEPRECATES support for the legacy library that provides support for Samsung & MeiZu phones
3.0.2 and below:
Using an expandable Android Fingerprint API library, which combines [Samsung](http://developer.samsung.com/galaxy/pass#) and [MeiZu](http://open-wiki.flyme.cn/index.php?title=%E6%8C%87%E7%BA%B9%E8%AF%86%E5%88%ABAPI)'s official Fingerprint API.
Samsung and MeiZu's Fingerprint SDK supports most devices which system versions less than Android 6.0.
@ -38,26 +26,20 @@ Samsung and MeiZu's Fingerprint SDK supports most devices which system versions
<img src="https://github.com/hieuvp/react-native-fingerprint-scanner/raw/master/screenshots/android-authentication.gif" height="600">
</div>
## Table of Contents
- [Installation](#installation)
- [Example](#example)
- [API](#api)
- [License](#license)
## Installation
```sh
$ npm install react-native-fingerprint-scanner --save
```
or
```sh
$ yarn add react-native-fingerprint-scanner
```
`$ npm install react-native-fingerprint-scanner --save`
### Automatic Configuration
For RN >= 0.60
```sh
$ cd ios && pod install
```
For RN < 0.60, use react-native link to add the library to your project:
```sh
$ react-native link react-native-fingerprint-scanner
```
`$ react-native link react-native-fingerprint-scanner`
### Manual Configuration
@ -80,32 +62,19 @@ $ react-native link react-native-fingerprint-scanner
```
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
```
implementation project(':react-native-fingerprint-scanner')
compile project(':react-native-fingerprint-scanner')
```
### App Permissions
Add the following permissions to their respective files:
#### Android
In your `AndroidManifest.xml`:
API level 28+ (Uses Android native BiometricPrompt) ([Reference](https://developer.android.com/reference/android/Manifest.permission#USE_BIOMETRIC))
```xml
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
```
API level 23-28 (Uses Android native FingerprintCompat) [Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
```xml
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
```
// DEPRECATED in 4.0.0
API level <23 (Uses device-specific native fingerprinting, if available - Samsung & MeiZu only) [Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
```xml
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
```
#### iOS
In your `Info.plist`:
```xml
@ -117,37 +86,27 @@ In your `Info.plist`:
1. Make sure the following versions are all correct in `android/app/build.gradle`
```
// API v29 enables FaceId
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileSdkVersion 25
buildToolsVersion "25.0.3"
...
defaultConfig {
targetSdkVersion 29
targetSdkVersion 25
```
2. Add necessary rules to `android/app/proguard-rules.pro` if you are using proguard:
```
# MeiZu Fingerprint
// DEPRECATED in 4.0.0
-keep class com.fingerprints.service.** { *; }
-dontwarn com.fingerprints.service.**
# Samsung Fingerprint
// DEPRECATED in 4.0.0
-keep class com.samsung.android.sdk.** { *; }
-dontwarn com.samsung.android.sdk.**
```
## Compatibility
* For Gradle < 3 you MUST install react-native-fingerprint-scanner at version <= 2.5.0
* For RN >= 0.57 and/or Gradle >= 3 you MUST install react-native-fingerprint-scanner at version >= 2.6.0
* For RN >= 0.60 you MUST install react-native-fingerprint-scanner at version >= 3.0.0
* For Android native Face Unlock, MUST use >= 4.0.0
## Example
[Example Source Code](https://github.com/hieuvp/react-native-fingerprint-scanner/tree/master/examples)
@ -188,84 +147,54 @@ export default FingerprintPopup;
**Android Implementation**
```javascript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
Alert,
Image,
Text,
TouchableOpacity,
View,
ViewPropTypes,
Platform,
ViewPropTypes
} from 'react-native';
import FingerprintScanner from 'react-native-fingerprint-scanner';
import styles from './FingerprintPopup.component.styles';
import ShakingText from './ShakingText.component';
import styles from './FingerprintPopup.component.styles';
class FingerprintPopup extends Component {
// - this example component supports both the
// legacy device-specific (Android < v23) and
// current (Android >= 23) biometric APIs
// - your lib and implementation may not need both
class BiometricPopup extends Component {
constructor(props) {
super(props);
this.state = {
errorMessageLegacy: undefined,
biometricLegacy: undefined
};
this.description = null;
this.state = { errorMessage: undefined };
}
componentDidMount() {
if (this.requiresLegacyAuthentication()) {
this.authLegacy();
} else {
this.authCurrent();
}
}
componentWillUnmount = () => {
FingerprintScanner.release();
}
requiresLegacyAuthentication() {
return Platform.Version < 23;
}
authCurrent() {
FingerprintScanner
.authenticate({ title: 'Log in with Biometrics' })
.authenticate({ onAttempt: this.handleAuthenticationAttempted })
.then(() => {
this.props.onAuthenticate();
});
}
authLegacy() {
FingerprintScanner
.authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
.then(() => {
this.props.handlePopupDismissedLegacy();
this.props.handlePopupDismissed();
Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
})
.catch((error) => {
this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
this.setState({ errorMessage: error.message });
this.description.shake();
});
}
handleAuthenticationAttemptedLegacy = (error) => {
this.setState({ errorMessageLegacy: error.message });
componentWillUnmount() {
FingerprintScanner.release();
}
handleAuthenticationAttempted = (error) => {
this.setState({ errorMessage: error.message });
this.description.shake();
};
renderLegacy() {
const { errorMessageLegacy, biometricLegacy } = this.state;
const { style, handlePopupDismissedLegacy } = this.props;
render() {
const { errorMessage } = this.state;
const { style, handlePopupDismissed } = this.props;
return (
<View style={styles.container}>
@ -277,17 +206,17 @@ class BiometricPopup extends Component {
/>
<Text style={styles.heading}>
Biometric{'\n'}Authentication
Fingerprint{'\n'}Authentication
</Text>
<ShakingText
ref={(instance) => { this.description = instance; }}
style={styles.description(!!errorMessageLegacy)}>
{errorMessageLegacy || `Scan your ${biometricLegacy} on the\ndevice scanner to continue`}
style={styles.description(!!errorMessage)}>
{errorMessage || 'Scan your fingerprint on the\ndevice scanner to continue'}
</ShakingText>
<TouchableOpacity
style={styles.buttonContainer}
onPress={handlePopupDismissedLegacy}
onPress={handlePopupDismissed}
>
<Text style={styles.buttonText}>
BACK TO MAIN
@ -298,25 +227,14 @@ class BiometricPopup extends Component {
</View>
);
}
render = () => {
if (this.requiresLegacyAuthentication()) {
return this.renderLegacy();
}
// current API UI provided by native BiometricPrompt
return null;
}
}
BiometricPopup.propTypes = {
onAuthenticate: PropTypes.func.isRequired,
handlePopupDismissedLegacy: PropTypes.func,
FingerprintPopup.propTypes = {
style: ViewPropTypes.style,
handlePopupDismissed: PropTypes.func.isRequired,
};
export default BiometricPopup;
export default FingerprintPopup;
```
## API
@ -326,8 +244,6 @@ Checks if Fingerprint Scanner is able to be used by now.
- Returns a `Promise<string>`
- `biometryType: String` - The type of biometric authentication supported by the device.
- iOS: biometryType = 'Touch ID', 'Face ID'
- Android: biometryType = 'Biometrics'
- `error: FingerprintScannerError { name, message, biometric }` - The name and message of failure and the biometric type in use.
```javascript
@ -361,62 +277,29 @@ componentDidMount() {
}
```
### `authenticate({ title="Log In", subTitle, description, cancelButton="Cancel", onAttempt=() => (null) })`: (Android)
### `authenticate({ onAttempt })`: (Android)
Starts Fingerprint authentication on Android.
- Returns a `Promise`
- `title: String` the title text to display in the native Android popup
- `subTitle: String` the sub title text to display in the native Android popup
- `description: String` the description text to display in the native Android popup
- `cancelButton: String` the cancel button text to display in the native Android popup
- `onAttempt: Function` - a callback function when users are trying to scan their fingerprint but failed.
```javascript
componentDidMount() {
if (requiresLegacyAuthentication()) {
authLegacy();
} else {
authCurrent();
}
}
componentWillUnmount = () => {
FingerprintScanner.release();
}
requiresLegacyAuthentication() {
return Platform.Version < 23;
}
authCurrent() {
FingerprintScanner
.authenticate({ title: 'Log in with Biometrics' })
.authenticate({ onAttempt: this.handleAuthenticationAttempted })
.then(() => {
this.props.onAuthenticate();
});
}
authLegacy() {
FingerprintScanner
.authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
.then(() => {
this.props.handlePopupDismissedLegacy();
this.props.handlePopupDismissed();
Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
})
.catch((error) => {
this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
this.setState({ errorMessage: error.message });
this.description.shake();
});
}
handleAuthenticationAttemptedLegacy = (error) => {
this.setState({ errorMessageLegacy: error.message });
this.description.shake();
};
```
### `release()`: (Android)
Stops fingerprint scanner listener, releases cache of internal state in native code, and cancels native prompt if visible.
Stops fingerprint scanner listener, releases cache of internal state in native code.
- Returns a `Void`
@ -428,11 +311,11 @@ componentWillUnmount() {
### `Types of Biometrics`
| Value | OS | Description|
|---|---|---|
| Touch ID | iOS | |
| Face ID | iOS | |
| Biometrics | Android | Refers to the biometric set as preferred on the device |
| Value | OS |
|---|---|
| Touch ID | iOS |
| Face ID | iOS |
| Fingerprint | Android |
### `Errors`
@ -440,20 +323,15 @@ componentWillUnmount() {
|---|---|
| AuthenticationNotMatch | No match |
| AuthenticationFailed | Authentication was not successful because the user failed to provide valid credentials |
| AuthenticationTimeout | Authentication was not successful because the operation timed out |
| AuthenticationProcessFailed | 'Sensor was unable to process the image. Please try again |
| UserCancel | Authentication was canceled by the user - e.g. the user tapped Cancel in the dialog |
| UserFallback | Authentication was canceled because the user tapped the fallback button (Enter Password) |
| SystemCancel | Authentication was canceled by system - e.g. if another application came to foreground while the authentication dialog was up |
| PasscodeNotSet | Authentication could not start because the passcode is not set on the device |
| DeviceLocked | Authentication was not successful, the device currently in a lockout of 30 seconds |
| DeviceLockedPermanent | Authentication was not successful, device must be unlocked via password |
| DeviceOutOfMemory | Authentication could not proceed because there is not enough free memory on the device |
| HardwareError | A hardware error occurred |
| FingerprintScannerNotAvailable | Authentication could not start because Fingerprint Scanner is not available on the device |
| FingerprintScannerNotEnrolled | Authentication could not start because Fingerprint Scanner has no enrolled fingers |
| FingerprintScannerUnknownError | Could not authenticate for an unknown reason |
| FingerprintScannerNotSupported | Device does not support Fingerprint Scanner |
| FingerprintScannerNotEnrolled | Authentication could not start because Fingerprint Scanner has no enrolled fingers |
| FingerprintScannerNotAvailable | Authentication could not start because Fingerprint Scanner is not available on the device |
| DeviceLocked | Authentication was not successful, the device currently in a lockout of 30 seconds |
## License

View File

@ -3,30 +3,25 @@ def safeExtGet(prop, fallback) {
}
buildscript {
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
if (project == rootProject) {
repositories {
google()
jcenter()
}
repositories {
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:3.5.1")
}
dependencies {
classpath("com.android.tools.build:gradle:3.4.1")
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion safeExtGet('compileSdkVersion', 29)
buildToolsVersion safeExtGet('buildToolsVersion', '29.0.2')
compileSdkVersion safeExtGet('compileSdkVersion', 25)
buildToolsVersion safeExtGet('buildToolsVersion', '25.0.3')
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 29)
targetSdkVersion safeExtGet('targetSdkVersion', 25)
versionCode 1
versionName "1.0"
}
@ -41,13 +36,9 @@ repositories {
}
dependencies {
implementation 'com.facebook.react:react-native:+'
// androidx:biometric now supports fingerprint back to Android v23
implementation "androidx.biometric:biometric:1.0.1"
// retain fingerprintScanner lib for compat with Android v16-23 device-specific drivers (Samsung & MeiZu)
compile 'com.facebook.react:react-native:+'
// 1.2.3 is the minimum version compatible with androidx.
// See https://github.com/uccmawei/FingerprintIdentify/issues/74
// (translation https://translate.google.com/translate?sl=zh-CN&tl=en&u=https://github.com/uccmawei/FingerprintIdentify/issues/74)
implementation "com.wei.android.lib:fingerprintidentify:${safeExtGet("fingerprintidentify", "1.2.6")}"
compile "com.wei.android.lib:fingerprintidentify:${safeExtGet("fingerprintidentify", "1.2.6")}"
}

View File

@ -1,7 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hieuvp.fingerprint">
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="com.fingerprints.service.ACCESS_FINGERPRINT_MANAGER" />
<uses-permission
android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />

View File

@ -1,45 +1,23 @@
package com.hieuvp.fingerprint;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricPrompt;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt.AuthenticationCallback;
import androidx.biometric.BiometricPrompt.PromptInfo;
import androidx.fragment.app.FragmentActivity;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.bridge.UiThreadUtil;
// for Samsung/MeiZu compat, Android v16-23
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
import com.wei.android.lib.fingerprintidentify.FingerprintIdentify;
import com.wei.android.lib.fingerprintidentify.base.BaseFingerprint.ExceptionListener;
import com.wei.android.lib.fingerprintidentify.base.BaseFingerprint.IdentifyListener;
@ReactModule(name="ReactNativeFingerprintScanner")
public class ReactNativeFingerprintScannerModule
extends ReactContextBaseJavaModule
implements LifecycleEventListener
{
public class ReactNativeFingerprintScannerModule extends ReactContextBaseJavaModule
implements LifecycleEventListener {
public static final int MAX_AVAILABLE_TIMES = Integer.MAX_VALUE;
public static final String TYPE_BIOMETRICS = "Biometrics";
public static final String TYPE_FINGERPRINT_LEGACY = "Fingerprint";
public static final String TYPE_FINGERPRINT = "Fingerprint";
private final ReactApplicationContext mReactContext;
private BiometricPrompt biometricPrompt;
// for Samsung/MeiZu compat, Android v16-23
private FingerprintIdentify mFingerprintIdentify;
public ReactNativeFingerprintScannerModule(ReactApplicationContext reactContext) {
@ -65,200 +43,19 @@ public class ReactNativeFingerprintScannerModule
this.release();
}
private int currentAndroidVersion() {
return Build.VERSION.SDK_INT;
}
private boolean requiresLegacyAuthentication() {
return currentAndroidVersion() < 23;
}
public class AuthCallback extends BiometricPrompt.AuthenticationCallback {
private Promise promise;
public AuthCallback(final Promise promise) {
super();
this.promise = promise;
}
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
this.promise.reject(biometricPromptErrName(errorCode), TYPE_BIOMETRICS);
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
this.promise.resolve(true);
}
}
public BiometricPrompt getBiometricPrompt(final FragmentActivity fragmentActivity, final Promise promise) {
// memoize so can be accessed to cancel
if (biometricPrompt != null) {
return biometricPrompt;
}
// listen for onHost* methods
mReactContext.addLifecycleEventListener(this);
AuthCallback authCallback = new AuthCallback(promise);
Executor executor = Executors.newSingleThreadExecutor();
biometricPrompt = new BiometricPrompt(
fragmentActivity,
executor,
authCallback
);
return biometricPrompt;
}
private void biometricAuthenticate(final String title, final String subtitle, final String description, final String cancelButton, final Promise promise) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
FragmentActivity fragmentActivity = (FragmentActivity) mReactContext.getCurrentActivity();
if(fragmentActivity == null) return;
BiometricPrompt bioPrompt = getBiometricPrompt(fragmentActivity, promise);
PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setDeviceCredentialAllowed(false)
.setConfirmationRequired(false)
.setNegativeButtonText(cancelButton)
.setDescription(description)
.setSubtitle(subtitle)
.setTitle(title)
.build();
bioPrompt.authenticate(promptInfo);
}
});
}
// the below constants are consistent across BiometricPrompt and BiometricManager
private String biometricPromptErrName(int errCode) {
switch (errCode) {
case BiometricPrompt.ERROR_CANCELED:
return "SystemCancel";
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
return "FingerprintScannerNotSupported";
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
return "FingerprintScannerNotAvailable";
case BiometricPrompt.ERROR_LOCKOUT:
return "DeviceLocked";
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
return "DeviceLockedPermanent";
case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
return "UserCancel";
case BiometricPrompt.ERROR_NO_BIOMETRICS:
return "FingerprintScannerNotEnrolled";
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
return "PasscodeNotSet";
case BiometricPrompt.ERROR_NO_SPACE:
return "DeviceOutOfMemory";
case BiometricPrompt.ERROR_TIMEOUT:
return "AuthenticationTimeout";
case BiometricPrompt.ERROR_UNABLE_TO_PROCESS:
return "AuthenticationProcessFailed";
case BiometricPrompt.ERROR_USER_CANCELED: // actually 'user elected another auth method'
return "UserFallback";
case BiometricPrompt.ERROR_VENDOR:
// hardware-specific error codes
return "HardwareError";
default:
return "FingerprintScannerUnknownError";
}
}
private String getSensorError() {
BiometricManager biometricManager = BiometricManager.from(mReactContext);
int authResult = biometricManager.canAuthenticate();
if (authResult == BiometricManager.BIOMETRIC_SUCCESS) {
return null;
}
if (authResult == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
return "FingerprintScannerNotSupported";
} else if (authResult == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
return "FingerprintScannerNotEnrolled";
} else if (authResult == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
return "FingerprintScannerNotAvailable";
}
return null;
}
@ReactMethod
public void authenticate(String title, String subtitle, String description, String cancelButton, final Promise promise) {
if (requiresLegacyAuthentication()) {
legacyAuthenticate(promise);
}
else {
final String errorName = getSensorError();
if (errorName != null) {
promise.reject(errorName, TYPE_BIOMETRICS);
ReactNativeFingerprintScannerModule.this.release();
return;
}
biometricAuthenticate(title, subtitle, description, cancelButton, promise);
}
}
@ReactMethod
public void release() {
if (requiresLegacyAuthentication()) {
getFingerprintIdentify().cancelIdentify();
mFingerprintIdentify = null;
}
// consistent across legacy and current API
if (biometricPrompt != null) {
biometricPrompt.cancelAuthentication(); // if release called from eg React
}
biometricPrompt = null;
mReactContext.removeLifecycleEventListener(this);
}
@ReactMethod
public void isSensorAvailable(final Promise promise) {
if (requiresLegacyAuthentication()) {
String errorMessage = legacyGetErrorMessage();
if (errorMessage != null) {
promise.reject(errorMessage, TYPE_FINGERPRINT_LEGACY);
} else {
promise.resolve(TYPE_FINGERPRINT_LEGACY);
}
return;
}
// current API
String errorName = getSensorError();
if (errorName != null) {
promise.reject(errorName, TYPE_BIOMETRICS);
} else {
promise.resolve(TYPE_BIOMETRICS);
}
}
// for Samsung/MeiZu compat, Android v16-23
private FingerprintIdentify getFingerprintIdentify() {
if (mFingerprintIdentify != null) {
return mFingerprintIdentify;
}
mReactContext.addLifecycleEventListener(this);
mFingerprintIdentify = new FingerprintIdentify(mReactContext);
mFingerprintIdentify = new FingerprintIdentify(getCurrentActivity());
mFingerprintIdentify.setSupportAndroidL(true);
mFingerprintIdentify.setExceptionListener(
new ExceptionListener() {
@Override
public void onCatchException(Throwable exception) {
mReactContext.removeLifecycleEventListener(ReactNativeFingerprintScannerModule.this);
mReactContext.removeLifecycleEventListener(
ReactNativeFingerprintScannerModule.this);
}
}
);
@ -266,7 +63,7 @@ public class ReactNativeFingerprintScannerModule
return mFingerprintIdentify;
}
private String legacyGetErrorMessage() {
private String getErrorMessage() {
if (!getFingerprintIdentify().isHardwareEnable()) {
return "FingerprintScannerNotSupported";
} else if (!getFingerprintIdentify().isRegisteredFingerprint()) {
@ -274,15 +71,14 @@ public class ReactNativeFingerprintScannerModule
} else if (!getFingerprintIdentify().isFingerprintEnable()) {
return "FingerprintScannerNotAvailable";
}
return null;
}
private void legacyAuthenticate(final Promise promise) {
final String errorMessage = legacyGetErrorMessage();
@ReactMethod
public void authenticate(final Promise promise) {
final String errorMessage = getErrorMessage();
if (errorMessage != null) {
promise.reject(errorMessage, TYPE_FINGERPRINT_LEGACY);
promise.reject(errorMessage, TYPE_FINGERPRINT);
ReactNativeFingerprintScannerModule.this.release();
return;
}
@ -292,18 +88,13 @@ public class ReactNativeFingerprintScannerModule
@Override
public void onSucceed() {
promise.resolve(true);
ReactNativeFingerprintScannerModule.this.release();
}
@Override
public void onNotMatch(int availableTimes) {
if (availableTimes <= 0) {
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "DeviceLocked");
} else {
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationNotMatch");
}
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationNotMatch");
}
@Override
@ -311,7 +102,7 @@ public class ReactNativeFingerprintScannerModule
if(isDeviceLocked){
promise.reject("AuthenticationFailed", "DeviceLocked");
} else {
promise.reject("AuthenticationFailed", TYPE_FINGERPRINT_LEGACY);
promise.reject("AuthenticationFailed", TYPE_FINGERPRINT);
}
ReactNativeFingerprintScannerModule.this.release();
}
@ -323,4 +114,21 @@ public class ReactNativeFingerprintScannerModule
}
});
}
@ReactMethod
public void release() {
getFingerprintIdentify().cancelIdentify();
mFingerprintIdentify = null;
mReactContext.removeLifecycleEventListener(this);
}
@ReactMethod
public void isSensorAvailable(final Promise promise) {
String errorMessage = getErrorMessage();
if (errorMessage != null) {
promise.reject(errorMessage, TYPE_FINGERPRINT);
} else {
promise.resolve(TYPE_FINGERPRINT);
}
}
}

View File

@ -1,16 +0,0 @@
all: test-ios
install:
npm ci
clean:
cd android && ./gradlew clean
test-ios: install build-ios
build-ios:
cd ios && xcodebuild clean build -scheme examples \
-workspace examples.xcworkspace \
-configuration Debug -sdk iphonesimulator \
CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
test-android: install build-android
build-android:
cd android && chmod +x ./gradlew && TERM=dumb ./gradlew assembleDebug --stacktrace
.PHONY: all

View File

@ -5,10 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- API lvl 27- (Oreo MR1 and below) -->
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<!-- API lvl 28+ (Pie and above) -->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-sdk
android:minSdkVersion="16"

View File

@ -2,11 +2,11 @@
buildscript {
ext {
buildToolsVersion = "29.0.2"
buildToolsVersion = "28.0.3"
minSdkVersion = 16
compileSdkVersion = 29
targetSdkVersion = 29
supportLibVersion = "29.0.0"
compileSdkVersion = 28
targetSdkVersion = 28
supportLibVersion = "28.0.0"
}
repositories {
google()

View File

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -2189,8 +2189,7 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"optional": true
"bundled": true
},
"aproba": {
"version": "1.2.0",
@ -2208,13 +2207,11 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"optional": true
"bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2227,18 +2224,15 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"core-util-is": {
"version": "1.0.2",
@ -2341,8 +2335,7 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"optional": true
"bundled": true
},
"ini": {
"version": "1.3.5",
@ -2352,7 +2345,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2365,20 +2357,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"optional": true
"bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -2395,7 +2384,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2468,8 +2456,7 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"object-assign": {
"version": "4.1.1",
@ -2479,7 +2466,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2555,8 +2541,7 @@
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"optional": true
"bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@ -2586,7 +2571,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -2604,7 +2588,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -2643,13 +2626,11 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"optional": true
"bundled": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"optional": true
"bundled": true
}
}
},
@ -3258,9 +3239,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"lodash.throttle": {
"version": "4.1.1",

View File

@ -1,81 +1,50 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
Alert,
Image,
Text,
TouchableOpacity,
View,
ViewPropTypes,
Platform,
ViewPropTypes
} from 'react-native';
import FingerprintScanner from 'react-native-fingerprint-scanner';
import styles from './FingerprintPopup.component.styles';
import ShakingText from './ShakingText.component';
class FingerprintPopup extends Component {
// Based on https://github.com/hieuvp/react-native-fingerprint-scanner/blob/master/examples/src/FingerprintPopup.component.android.js
// - this example component supports both the legacy device-specific (Android < v23) and
// current (Android >= 23) biometric APIs
// - your lib and implementation may not need both
class BiometricPopup extends Component {
constructor(props) {
super(props);
this.state = {
errorMessageLegacy: undefined,
biometricLegacy: undefined
};
this.description = null;
this.state = { errorMessage: undefined, biometric: undefined };
}
componentDidMount() {
if (requiresLegacyAuthentication()) {
authLegacy();
} else {
authCurrent();
}
}
componentWillUnmount = () => {
FingerprintScanner.release();
}
requiresLegacyAuthentication() {
return Platform.Version < 23;
}
authCurrent() {
FingerprintScanner
.authenticate({ description: this.props.description || 'Log in with Biometrics' })
.authenticate({ onAttempt: this.handleAuthenticationAttempted })
.then(() => {
this.props.onAuthenticate();
});
}
authLegacy() {
FingerprintScanner
.authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
.then(() => {
this.props.handlePopupDismissedLegacy();
this.props.handlePopupDismissed();
Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
})
.catch((error) => {
this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
this.setState({ errorMessage: error.message, biometric: error.biometric });
this.description.shake();
});
}
handleAuthenticationAttemptedLegacy = (error) => {
this.setState({ errorMessageLegacy: error.message });
componentWillUnmount() {
FingerprintScanner.release();
}
handleAuthenticationAttempted = (error) => {
this.setState({ errorMessage: error.message });
this.description.shake();
};
renderLegacy() {
const { errorMessageLegacy, biometricLegacy } = this.state;
const { style, handlePopupDismissedLegacy } = this.props;
render() {
const { errorMessage, biometric } = this.state;
const { style, handlePopupDismissed } = this.props;
return (
<View style={styles.container}>
@ -91,13 +60,13 @@ class BiometricPopup extends Component {
</Text>
<ShakingText
ref={(instance) => { this.description = instance; }}
style={styles.description(!!errorMessageLegacy)}>
{errorMessageLegacy || `Scan your ${biometricLegacy} on the\ndevice scanner to continue`}
style={styles.description(!!errorMessage)}>
{errorMessage || `Scan your ${biometric} on the\ndevice scanner to continue`}
</ShakingText>
<TouchableOpacity
style={styles.buttonContainer}
onPress={handlePopupDismissedLegacy}
onPress={handlePopupDismissed}
>
<Text style={styles.buttonText}>
BACK TO MAIN
@ -108,24 +77,11 @@ class BiometricPopup extends Component {
</View>
);
}
render = () => {
if (this.requiresLegacyAuthentication()) {
return this.renderLegacy();
}
// current API UI provided by native BiometricPrompt
return null;
}
}
BiometricPopup.propTypes = {
description: PropTypes.string,
onAuthenticate: PropTypes.func.isRequired,
handlePopupDismissedLegacy: PropTypes.func,
FingerprintPopup.propTypes = {
style: ViewPropTypes.style,
handlePopupDismissed: PropTypes.func.isRequired,
};
export default BiometricPopup;
export default FingerprintPopup;

173
index.d.ts vendored
View File

@ -1,173 +0,0 @@
export type AuthenticateIOS = {
description?: string;
fallbackEnabled?: boolean;
};
export type AuthenticateAndroid = {
title?: string;
subTitle?: string;
description?: string;
cancelButton?: string;
onAttempt?: (error: FingerprintScannerError) => void;
};
export type Biometrics = 'Touch ID' | 'Face ID' | 'Biometrics';
export type Errors =
| { name: 'AuthenticationNotMatch'; message: 'No match' }
| {
name: 'AuthenticationFailed';
message: 'Authentication was not successful because the user failed to provide valid credentials';
}
| {
name: 'AuthenticationTimeout';
message: 'Authentication was not successful because the operation timed out.';
}
| {
name: 'AuthenticationProcessFailed';
message: 'Sensor was unable to process the image. Please try again.';
}
| {
name: 'UserCancel';
message: 'Authentication was canceled by the user - e.g. the user tapped Cancel in the dialog';
}
| {
name: 'UserFallback';
message: 'Authentication was canceled because the user tapped the fallback button (Enter Password)';
}
| {
name: 'SystemCancel';
message: 'Authentication was canceled by system - e.g. if another application came to foreground while the authentication dialog was up';
}
| {
name: 'PasscodeNotSet';
message: 'Authentication could not start because the passcode is not set on the device';
}
| {
name: 'FingerprintScannerNotAvailable';
message: ' Authentication could not start because Fingerprint Scanner is not available on the device';
}
| {
name: 'FingerprintScannerNotEnrolled';
message: ' Authentication could not start because Fingerprint Scanner has no enrolled fingers';
}
| {
name: 'FingerprintScannerUnknownError';
message: 'Could not authenticate for an unknown reason';
}
| {
name: 'FingerprintScannerNotSupported';
message: 'Device does not support Fingerprint Scanner';
}
| {
name: 'DeviceLocked';
message: 'Authentication was not successful, the device currently in a lockout of 30 seconds';
}
| {
name: 'DeviceLockedPermanent';
message: 'Authentication was not successful, device must be unlocked via password.';
}
| {
name: 'DeviceOutOfMemory';
message: 'Authentication could not proceed because there is not enough free memory on the device.';
}
| {
name: 'HardwareError';
message: 'A hardware error occurred.';
};
export type FingerprintScannerError = { biometric: Biometrics } & Errors;
export interface FingerPrintProps {
/**
### release(): (Android)
Stops fingerprint scanner listener, releases cache of internal state in native code.
- Returns a `void`
-------------------
Example
```
componentWillUnmount() {
FingerprintScanner.release();
}
```
*/
release: () => void;
/**
### isSensorAvailable(): (Android, iOS)
Checks if Fingerprint Scanner is able to be used by now.
- Returns a `Promise<Biometrics>`
- `biometryType`: *String* - The type of biometric authentication supported by the device.
- `error: FingerprintScannerError { name, message, biometric }` - The name and message of failure and the biometric type in use.
-------------
Example
```
FingerprintScanner
.isSensorAvailable()
.then(biometryType => this.setState({ biometryType }))
.catch(error => this.setState({ errorMessage: error.message }));
```
------------
*/
isSensorAvailable: () => Promise<Biometrics>;
/**
### authenticate({ description, fallbackEnabled }): (iOS)
- Returns a `Promise`
- `description: String` - the string to explain the request for user authentication.
- `fallbackEnabled: Boolean` - default to ***true***, whether to display fallback button (e.g. Enter Password).
----------------
- Example:
```
FingerprintScanner
.authenticate({ description: 'Scan your fingerprint on the device scanner to continue' })
.then(() => {
this.props.handlePopupDismissed();
AlertIOS.alert('Authenticated successfully');
})
.catch((error) => {
this.props.handlePopupDismissed();
AlertIOS.alert(error.message);
});
```
-----------------
### authenticate({ description: 'Log in with Biometrics', onAttempt: () => (null) }): (Android)
- Returns a `Promise`
- `description: String` - the title text to appear on the native Android prompt
- `onAttempt: Function` - a callback function when users are trying to scan their fingerprint but failed.
-----------------
- Example:
```
FingerprintScanner
.authenticate({
description: 'Log in with Biometrics',
onAttempt: this.handleAuthenticationAttempted,
})
.then(() => {
this.props.handlePopupDismissed();
Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
})
.catch((error) => {
this.setState({ errorMessage: error.message });
this.description.shake();
});
```
-----------------
*/
authenticate: (
platformProps: AuthenticateIOS | AuthenticateAndroid
) => Promise<void>;
}
declare const FingerprintScanner: FingerPrintProps;
export default FingerprintScanner;

View File

@ -22,23 +22,13 @@ RCT_EXPORT_METHOD(isSensorAvailable: (RCTResponseSenderBlock)callback)
NSString *message;
switch (error.code) {
case LAErrorBiometryNotAvailable:
case LAErrorTouchIDNotAvailable:
code = @"FingerprintScannerNotAvailable";
message = [self getBiometryType:context];
break;
case LAErrorBiometryNotEnrolled:
code = @"FingerprintScannerNotEnrolled";
message = [self getBiometryType:context];
break;
case LAErrorBiometryLockout:
code = @"DeviceLockedPermanent";
message = [self getBiometryType:context];
break;
case LAErrorPasscodeNotSet:
code = @"PasscodeNotSet";
case LAErrorTouchIDNotEnrolled:
code = @"FingerprintScannerNotEnrolled";
message = [self getBiometryType:context];
break;
@ -64,12 +54,11 @@ RCT_EXPORT_METHOD(authenticate: (NSString *)reason
if (!fallbackEnabled) {
context.localizedFallbackTitle = @"";
}
__auto_type policy = fallbackEnabled ? LAPolicyDeviceOwnerAuthentication : LAPolicyDeviceOwnerAuthenticationWithBiometrics;
// Device has FingerprintScanner
if ([context canEvaluatePolicy:policy error:&error]) {
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// Attempt Authentication
[context evaluatePolicy:policy
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:reason
reply:^(BOOL success, NSError *error)
{
@ -98,18 +87,14 @@ RCT_EXPORT_METHOD(authenticate: (NSString *)reason
errorReason = @"PasscodeNotSet";
break;
case LAErrorBiometryNotAvailable:
case LAErrorTouchIDNotAvailable:
errorReason = @"FingerprintScannerNotAvailable";
break;
case LAErrorBiometryNotEnrolled:
case LAErrorTouchIDNotEnrolled:
errorReason = @"FingerprintScannerNotEnrolled";
break;
case LAErrorBiometryLockout:
errorReason = @"DeviceLockedPermanent";
break;
default:
errorReason = @"FingerprintScannerUnknownError";
break;
@ -130,35 +115,6 @@ RCT_EXPORT_METHOD(authenticate: (NSString *)reason
}];
} else {
if (error) {
NSString *errorReason;
switch (error.code) {
case LAErrorBiometryNotAvailable:
errorReason = @"FingerprintScannerNotAvailable";
break;
case LAErrorBiometryNotEnrolled:
errorReason = @"FingerprintScannerNotEnrolled";
break;
case LAErrorBiometryLockout:
errorReason = @"DeviceLockedPermanent";
break;
case LAErrorPasscodeNotSet:
errorReason = @"PasscodeNotSet";
break;
default:
errorReason = @"FingerprintScannerNotSupported";
break;
}
NSLog(@"Authentication failed: %@", errorReason);
callback(@[RCTJSErrorFromCodeMessageAndNSError(errorReason, errorReason, nil)]);
return;
}
// Device does not support FingerprintScanner
callback(@[RCTJSErrorFromCodeMessageAndNSError(@"FingerprintScannerNotSupported", @"FingerprintScannerNotSupported", nil)]);
return;

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "react-native-fingerprint-scanner",
"version": "5.0.0",
"version": "2.6.1",
"lockfileVersion": 1
}

View File

@ -1,7 +1,7 @@
{
"name": "react-native-fingerprint-scanner",
"version": "6.0.0",
"description": "React Native Biometrics Scanner for Android and iOS",
"version": "2.6.1",
"description": "React Native Fingerprint Scanner for Android and iOS",
"main": "index.js",
"keywords": [
"react-native",
@ -16,10 +16,7 @@
"fingerprint-scanner",
"authentication",
"authenticate",
"auth",
"face-id",
"faceid",
"biometrics"
"auth"
],
"peerDependencies": {
"react-native": ">=0.60 <1.0.0"
@ -28,7 +25,6 @@
"repository": "https://github.com/hieuvp/react-native-fingerprint-scanner.git",
"author": "Hieu Van (https://github.com/hieuvp)",
"license": "MIT",
"types": "index.d.ts",
"scripts": {
"android": "cd examples && react-native run-android",
"ios": "cd examples && react-native run-ios",

View File

@ -1,66 +1,24 @@
import {
DeviceEventEmitter,
NativeModules,
Platform,
} from 'react-native';
import { DeviceEventEmitter, NativeModules } from 'react-native';
import createError from './createError';
const { ReactNativeFingerprintScanner } = NativeModules;
const authCurrent = (title, subTitle, description, cancelButton, resolve, reject) => {
ReactNativeFingerprintScanner.authenticate(title, subTitle, description, cancelButton)
.then(() => {
resolve(true);
})
.catch((error) => {
// translate errors
reject(createError(error.code, error.message));
});
}
const authLegacy = (onAttempt, resolve, reject) => {
DeviceEventEmitter.addListener('FINGERPRINT_SCANNER_AUTHENTICATION', (name) => {
if (name === 'AuthenticationNotMatch' && typeof onAttempt === 'function') {
onAttempt(createError(name));
}
});
ReactNativeFingerprintScanner.authenticate()
.then(() => {
DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
resolve(true);
})
.catch((error) => {
DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
reject(createError(error.code, error.message));
});
}
const nullOnAttempt = () => null;
export default ({ title, subTitle, description, cancelButton, onAttempt }) => {
export default ({ onAttempt }) => {
return new Promise((resolve, reject) => {
if (!title) {
title = description ? description : "Log In";
description = ""
}
if (!subTitle) {
subTitle = "";
}
if (!description) {
description = "";
}
if (!cancelButton) {
cancelButton = "Cancel";
}
if (!onAttempt) {
onAttempt = nullOnAttempt;
}
DeviceEventEmitter.addListener('FINGERPRINT_SCANNER_AUTHENTICATION', (name) => {
if (name === 'AuthenticationNotMatch' && typeof onAttempt === 'function') {
onAttempt(createError(name));
}
});
if (Platform.Version < 23) {
return authLegacy(onAttempt, resolve, reject);
}
return authCurrent(title, subTitle, description, cancelButton, resolve, reject);
ReactNativeFingerprintScanner.authenticate()
.then(() => {
DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
resolve(true);
})
.catch((error) => {
DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
reject(createError(error.code, error.message));
});
});
}

View File

@ -1,23 +1,15 @@
const ERRORS = {
// sensor availability
FingerprintScannerNotSupported: 'Device does not support Fingerprint Scanner.',
FingerprintScannerNotEnrolled: 'Authentication could not start because Fingerprint Scanner has no enrolled fingers.',
FingerprintScannerNotAvailable: 'Authentication could not start because Fingerprint Scanner is not available on the device.',
// auth failures
AuthenticationNotMatch: 'No match.',
AuthenticationFailed: 'Authentication was not successful because the user failed to provide valid credentials.',
AuthenticationTimeout: 'Authentication was not successful because the operation timed out.',
AuthenticationProcessFailed: 'Sensor was unable to process the image. Please try again.',
UserCancel: 'Authentication was canceled by the user - e.g. the user tapped Cancel in the dialog.',
UserFallback: 'Authentication was canceled because the user tapped the fallback button (Enter Password).',
SystemCancel: 'Authentication was canceled by system - e.g. if another application came to foreground while the authentication dialog was up.',
PasscodeNotSet: 'Authentication could not start because the passcode is not set on the device.',
FingerprintScannerNotAvailable: 'Authentication could not start because Fingerprint Scanner is not available on the device.',
FingerprintScannerNotEnrolled: 'Authentication could not start because Fingerprint Scanner has no enrolled fingers.',
FingerprintScannerUnknownError: 'Could not authenticate for an unknown reason.',
DeviceLocked: 'Authentication was not successful, the device currently in a lockout of 30 seconds.',
DeviceLockedPermanent: 'Authentication was not successful, device must be unlocked via password.',
DeviceOutOfMemory: 'Authentication could not proceed because there is not enough free memory on the device.',
HardwareError: 'A hardware error occurred.',
FingerprintScannerNotSupported: 'Device does not support Fingerprint Scanner.',
DeviceLocked: 'Authentication was not successful, the device currently in a lockout of 30 seconds'
};
class FingerprintScannerError extends Error {

View File

@ -1,11 +1,8 @@
import { DeviceEventEmitter, NativeModules, Platform } from 'react-native';
import { DeviceEventEmitter, NativeModules } from 'react-native';
const { ReactNativeFingerprintScanner } = NativeModules;
export default () => {
if (Platform.Version < 23) {
DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
}
DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
ReactNativeFingerprintScanner.release();
}