Skip to content
123 changes: 90 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -95,13 +95,13 @@ API level 28+ (Uses Android native BiometricPrompt) ([Reference](https://develop
<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))
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))
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" />
```
Expand Down Expand Up @@ -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;
```

Expand Down Expand Up @@ -222,11 +257,7 @@ class BiometricPopup extends Component {
}

componentDidMount() {
if (this.requiresLegacyAuthentication()) {
this.authLegacy();
} else {
this.authCurrent();
}
this._androidTouchID();
}

componentWillUnmount = () => {
Expand All @@ -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
}
});
}

Expand Down Expand Up @@ -339,6 +374,24 @@ componentDidMount() {
}
```

### `authenticateDevice()`: (iOS)
Unlock with the device password.

- Returns a `Promise<string>`
- `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.

Expand Down Expand Up @@ -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 |
Expand All @@ -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 |
Expand All @@ -458,3 +513,5 @@ componentWillUnmount() {
## License

MIT


Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
32 changes: 29 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -166,6 +171,27 @@ export interface FingerPrintProps {
authenticate: (
platformProps: AuthenticateIOS | AuthenticateAndroid
) => Promise<void>;

/**
### authenticateDevice(): (iOS)
Unlock with the device password.
- Returns a `Promise<Biometrics>`
- `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<Biometrics>;
}

declare const FingerprintScanner: FingerPrintProps;
Expand Down
Loading