diff --git a/README.md b/README.md
index 42ff194..c2bcde6 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ It provides a **Default View** that prompts the user to place a finger to the iP
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:
+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.
@@ -74,14 +74,14 @@ $ react-native link react-native-fingerprint-scanner
- Add `import com.hieuvp.fingerprint.ReactNativeFingerprintScannerPackage;` to the imports at the top of the file
- Add `new ReactNativeFingerprintScannerPackage()` to the list returned by the `getPackages()` method
2. Append the following lines to `android/settings.gradle`:
- ```
- include ':react-native-fingerprint-scanner'
- project(':react-native-fingerprint-scanner').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fingerprint-scanner/android')
- ```
+ ```
+ include ':react-native-fingerprint-scanner'
+ project(':react-native-fingerprint-scanner').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fingerprint-scanner/android')
+ ```
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
- ```
+ ```
implementation project(':react-native-fingerprint-scanner')
- ```
+ ```
### App Permissions
@@ -95,13 +95,13 @@ API level 28+ (Uses Android native BiometricPrompt) ([Reference](https://develop
```
-API level 23-28 (Uses Android native FingerprintCompat) [Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
+API level 23-28 (Uses Android native FingerprintCompat) [Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
```xml
```
// 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))
+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
```
@@ -162,27 +162,62 @@ import FingerprintScanner from 'react-native-fingerprint-scanner';
class FingerprintPopup extends Component {
componentDidMount() {
+ this._iosTouchID()
+ }
+
+ _iosTouchID = () => {
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();
+ switch (error.biometric) {
+ case 'UserCancel':
+ AlertIOS.alert('The user clicks the cancel button')
+ break
+ case 'AuthenticationFailed':
+ AlertIOS.alert('User failed to identify 3 times')
+ break
+ case 'AuthenticationLockout':
+ // console.log('Accumulated 5 identification failures, fingerprint identification was locked')
+ AlertIOS.alert('Identify cumulative multiple failures, temporarily unavailable', [
+ {
+ text: 'cancel',
+ style: 'default',
+ onPress: () => {
+ }
+ }, {
+ text: 'To unlock',
+ style: 'default',
+ onPress: () => {
+ this._iosAuthenticateDevice()
+ }
+ }
+ ])
+ break
+ default:
+ break
+ }
AlertIOS.alert(error.message);
});
}
+ _iosAuthenticateDevice = () => {
+ FingerprintScanner.authenticateDevice().then(() => {
+ // console.log('Device unlocked')
+ this._iosTouchID()
+ }).catch((error) => {
+ // error.biometric
+ AlertIOS.alert('catch error:', error.message)
+ })
+ }
+
render() {
return false;
}
}
-FingerprintPopup.propTypes = {
- handlePopupDismissed: PropTypes.func.isRequired,
-};
-
export default FingerprintPopup;
```
@@ -222,11 +257,7 @@ class BiometricPopup extends Component {
}
componentDidMount() {
- if (this.requiresLegacyAuthentication()) {
- this.authLegacy();
- } else {
- this.authCurrent();
- }
+ this._androidTouchID();
}
componentWillUnmount = () => {
@@ -237,24 +268,28 @@ class BiometricPopup extends Component {
return Platform.Version < 23;
}
- authCurrent() {
+ _androidTouchID() {
+ FingerprintScanner.release()
FingerprintScanner
.authenticate({ title: 'Log in with Biometrics' })
.then(() => {
this.props.onAuthenticate();
- });
- }
-
- authLegacy() {
- FingerprintScanner
- .authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
- .then(() => {
- this.props.handlePopupDismissedLegacy();
- Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
})
- .catch((error) => {
- this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
- this.description.shake();
+ .catch(error => {
+ FingerprintScanner.release()
+ switch (error.biometric) {
+ case 'UserCancel':
+ AlertIOS.alert('Click the cancel button')
+ break
+ case 'DeviceLocked':
+ AlertIOS.alert('Accumulated 5 identification failures, fingerprint identification was locked')
+ break
+ case 'DeviceLockedPermanent':
+ AlertIOS.alert('Accumulates many times to recognize the failure, is locked permanently, needs to unlock')
+ break
+ default:
+ break
+ }
});
}
@@ -339,6 +374,24 @@ componentDidMount() {
}
```
+### `authenticateDevice()`: (iOS)
+Unlock with the device password.
+
+- Returns a `Promise`
+- `error: FingerprintScannerError { name, message, biometric }` - The name and message of failure and the biometric type in use.
+
+
+```javascript
+ FingerprintScanner
+ .authenticateDevice()
+ .then(() => {
+ // AlertIOS.alert('Device unlocked')
+ this._iosTouchID()
+ }).catch((error) => {
+ // AlertIOS.alert('catch error:', error.message, error.biometric)
+ })
+```
+
### `authenticate({ description, fallbackEnabled })`: (iOS)
Starts Fingerprint authentication on iOS.
@@ -438,6 +491,7 @@ componentWillUnmount() {
| Name | Message |
|---|---|
+| AuthenticationLockout | Authentication lockout |
| 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 |
@@ -450,6 +504,7 @@ componentWillUnmount() {
| 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 |
+| UserDeviceCancel | Authentication Device was canceled |
| 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 |
@@ -458,3 +513,5 @@ componentWillUnmount() {
## License
MIT
+
+
diff --git a/android/src/main/java/com/hieuvp/fingerprint/ReactNativeFingerprintScannerModule.java b/android/src/main/java/com/hieuvp/fingerprint/ReactNativeFingerprintScannerModule.java
index 2db879d..dfb4232 100644
--- a/android/src/main/java/com/hieuvp/fingerprint/ReactNativeFingerprintScannerModule.java
+++ b/android/src/main/java/com/hieuvp/fingerprint/ReactNativeFingerprintScannerModule.java
@@ -84,7 +84,7 @@ public AuthCallback(final Promise promise) {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
- this.promise.reject(biometricPromptErrName(errorCode), TYPE_BIOMETRICS);
+ this.promise.reject(biometricPromptErrName(errorCode), biometricPromptErrName(errorCode));
}
@Override
@@ -193,12 +193,10 @@ private String getSensorError() {
public void authenticate(String title, String subtitle, String description, String cancelButton, final Promise promise) {
if (requiresLegacyAuthentication()) {
legacyAuthenticate(promise);
- }
- else {
+ } else {
final String errorName = getSensorError();
if (errorName != null) {
- promise.reject(errorName, TYPE_BIOMETRICS);
- ReactNativeFingerprintScannerModule.this.release();
+ promise.reject(errorName, errorName);
return;
}
@@ -236,13 +234,12 @@ public void isSensorAvailable(final Promise promise) {
// current API
String errorName = getSensorError();
if (errorName != null) {
- promise.reject(errorName, TYPE_BIOMETRICS);
+ promise.reject(errorName, errorName);
} else {
promise.resolve(TYPE_BIOMETRICS);
}
}
-
// for Samsung/MeiZu compat, Android v16-23
private FingerprintIdentify getFingerprintIdentify() {
if (mFingerprintIdentify != null) {
@@ -305,12 +302,11 @@ public void onNotMatch(int availableTimes) {
@Override
public void onFailed(boolean isDeviceLocked) {
- if(isDeviceLocked){
+ if (isDeviceLocked) {
promise.reject("AuthenticationFailed", "DeviceLocked");
} else {
promise.reject("AuthenticationFailed", TYPE_FINGERPRINT_LEGACY);
}
- ReactNativeFingerprintScannerModule.this.release();
}
@Override
diff --git a/index.d.ts b/index.d.ts
index 6de5573..c8625e3 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -13,7 +13,8 @@ export type AuthenticateAndroid = {
export type Biometrics = 'Touch ID' | 'Face ID' | 'Biometrics';
export type Errors =
- | { name: 'AuthenticationNotMatch'; message: 'No match' }
+ | { name: 'AuthenticationLockout'; message: 'Authentication lockout'; }
+ | { name: 'AuthenticationNotMatch'; message: 'No match'; }
| {
name: 'AuthenticationFailed';
message: 'Authentication was not successful because the user failed to provide valid credentials';
@@ -44,11 +45,11 @@ export type Errors =
}
| {
name: 'FingerprintScannerNotAvailable';
- message: ' Authentication could not start because Fingerprint Scanner is not available on the device';
+ 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';
+ message: ' Authentication could not start because Fingerprint Scanner has no enrolled fingers';
}
| {
name: 'FingerprintScannerUnknownError';
@@ -73,6 +74,10 @@ export type Errors =
| {
name: 'HardwareError';
message: 'A hardware error occurred.';
+ }
+ | {
+ name: 'UserDeviceCancel';
+ message: 'Authentication Device was canceled';
};
export type FingerprintScannerError = { biometric: Biometrics } & Errors;
@@ -166,6 +171,27 @@ export interface FingerPrintProps {
authenticate: (
platformProps: AuthenticateIOS | AuthenticateAndroid
) => Promise;
+
+ /**
+ ### authenticateDevice(): (iOS)
+ Unlock with the device password.
+ - Returns a `Promise`
+ - `error: FingerprintScannerError { name, message, biometric }` - The name and message of failure and the biometric type in use.
+
+ -------------
+ Exemple
+
+ ```
+ FingerprintScanner
+ .authenticateDevice()
+ .then(() => {
+ AlertIOS.alert('Authenticated successfully');
+ })
+ .catch(error => this.setState({ errorMessage: error.message }));
+ ```
+ ------------
+ */
+ authenticateDevice: () => Promise;
}
declare const FingerprintScanner: FingerPrintProps;
diff --git a/ios/ReactNativeFingerprintScanner.m b/ios/ReactNativeFingerprintScanner.m
index e29bad7..33408a7 100644
--- a/ios/ReactNativeFingerprintScanner.m
+++ b/ios/ReactNativeFingerprintScanner.m
@@ -26,29 +26,54 @@ @implementation ReactNativeFingerprintScanner
code = @"FingerprintScannerNotAvailable";
message = [self getBiometryType:context];
break;
-
+
case LAErrorBiometryNotEnrolled:
code = @"FingerprintScannerNotEnrolled";
message = [self getBiometryType:context];
break;
+ case LAErrorTouchIDLockout:
+ code = @"AuthenticationLockout";
+ message = [self getBiometryType:context];
+ break;
+
+ case LAErrorAuthenticationFailed:
+ code = @"AuthenticationFailed";
+ message = [self getBiometryType:context];
+ break;
+
+ case LAErrorUserCancel:
+ code = @"UserCancel";
+ message = [self getBiometryType:context];
+ break;
+
+ case LAErrorUserFallback:
+ code = @"UserFallback";
+ message = [self getBiometryType:context];
+ break;
+
+ case LAErrorSystemCancel:
+ code = @"SystemCancel";
+ message = [self getBiometryType:context];
+ break;
+
case LAErrorBiometryLockout:
code = @"DeviceLockedPermanent";
message = [self getBiometryType:context];
break;
-
+
case LAErrorPasscodeNotSet:
code = @"PasscodeNotSet";
message = [self getBiometryType:context];
break;
default:
- code = @"FingerprintScannerNotSupported";
+ code = @"AuthenticationNotMatch";
message = nil;
break;
}
-
- callback(@[RCTJSErrorFromCodeMessageAndNSError(code, message, nil)]);
+ NSLog(@"Authentication failed: %@", code);
+ callback(@[RCTJSErrorFromCodeMessageAndNSError(code, code, nil)]);
return;
}
}
@@ -77,6 +102,11 @@ @implementation ReactNativeFingerprintScanner
NSString *errorReason;
switch (error.code) {
+
+ case LAErrorTouchIDLockout:
+ errorReason = @"AuthenticationLockout";
+ break;
+
case LAErrorAuthenticationFailed:
errorReason = @"AuthenticationFailed";
break;
@@ -110,7 +140,7 @@ @implementation ReactNativeFingerprintScanner
break;
default:
- errorReason = @"FingerprintScannerUnknownError";
+ errorReason = @"AuthenticationNotMatch";
break;
}
@@ -159,11 +189,65 @@ @implementation ReactNativeFingerprintScanner
return;
}
// Device does not support FingerprintScanner
- callback(@[RCTJSErrorFromCodeMessageAndNSError(@"FingerprintScannerNotSupported", @"FingerprintScannerNotSupported", nil)]);
+ // callback(@[RCTJSErrorFromCodeMessageAndNSError(@"FingerprintScannerNotSupported", @"FingerprintScannerNotSupported", nil)]);
+ NSString *errorReason;
+
+ switch (error.code) {
+ case LAErrorTouchIDNotAvailable:
+ errorReason = @"FingerprintScannerNotAvailable";
+ break;
+
+ case LAErrorTouchIDNotEnrolled:
+ errorReason = @"FingerprintScannerNotEnrolled";
+ break;
+
+ case LAErrorTouchIDLockout:
+ errorReason = @"AuthenticationLockout";
+ break;
+
+ case LAErrorAuthenticationFailed:
+ errorReason = @"AuthenticationFailed";
+ break;
+
+ case LAErrorUserCancel:
+ errorReason = @"UserCancel";
+ break;
+
+ case LAErrorUserFallback:
+ errorReason = @"UserFallback";
+ break;
+
+ case LAErrorSystemCancel:
+ errorReason = @"SystemCancel";
+ break;
+
+ case LAErrorPasscodeNotSet:
+ errorReason = @"PasscodeNotSet";
+ break;
+
+ default:
+ errorReason = @"AuthenticationNotMatch";
+ break;
+ }
+ callback(@[RCTJSErrorFromCodeMessageAndNSError(errorReason, errorReason, nil)]);
return;
}
}
+RCT_EXPORT_METHOD(authenticateDevice: (RCTResponseSenderBlock)callback)
+{
+ LAContext *context = [[LAContext alloc] init];
+ [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason: @" " reply:^(BOOL success, NSError * _Nullable error) {
+ if(error) {
+ NSString *errorReason = @"UserDeviceCancel";
+ NSLog(@"Authentication failed: %@", errorReason);
+ callback(@[RCTJSErrorFromCodeMessageAndNSError(errorReason, errorReason, nil)]);
+ } else {
+ callback(@[[NSNull null], @"Authentication unlock."]);
+ }
+ }];
+}
+
- (NSString *)getBiometryType:(LAContext *)context
{
if (@available(iOS 11, *)) {
diff --git a/src/authenticateDevice.android.js b/src/authenticateDevice.android.js
new file mode 100644
index 0000000..461f67a
--- /dev/null
+++ b/src/authenticateDevice.android.js
@@ -0,0 +1 @@
+export default () => null;
diff --git a/src/authenticateDevice.ios.js b/src/authenticateDevice.ios.js
new file mode 100644
index 0000000..a878cdd
--- /dev/null
+++ b/src/authenticateDevice.ios.js
@@ -0,0 +1,16 @@
+import { NativeModules } from 'react-native';
+import createError from 'react-native-fingerprint-scanner/src/createError';
+
+const { ReactNativeFingerprintScanner } = NativeModules;
+
+export default () => {
+ return new Promise((resolve, reject) => {
+ ReactNativeFingerprintScanner.authenticateDevice(error => {
+ if (error) {
+ return reject(createError(error.code, error.message))
+ }
+
+ return resolve(true);
+ });
+ });
+}
diff --git a/src/createError.js b/src/createError.js
index f453564..34a2c13 100644
--- a/src/createError.js
+++ b/src/createError.js
@@ -5,6 +5,7 @@ const ERRORS = {
FingerprintScannerNotAvailable: 'Authentication could not start because Fingerprint Scanner is not available on the device.',
// auth failures
+ AuthenticationLockout: 'Authentication lockout',
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.',
@@ -18,6 +19,7 @@ const ERRORS = {
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.',
+ UserDeviceCancel: 'Authentication Device was canceled'
};
class FingerprintScannerError extends Error {
diff --git a/src/index.js b/src/index.js
index 3fc84b5..4d9400e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,9 +1,11 @@
import authenticate from './authenticate';
import isSensorAvailable from './isSensorAvailable';
import release from './release';
+import authenticateDevice from './authenticateDevice';
export default {
authenticate,
release,
isSensorAvailable,
+ authenticateDevice
};