Skip to content
122 changes: 91 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
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 Down Expand Up @@ -162,27 +162,61 @@ 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':
console.log('The user clicks the cancel button')
break
case 'AuthenticationFailed':
console.log('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) => {
console.log('catch error:', error.message, error.biometric)
})
}

render() {
return false;
}
}

FingerprintPopup.propTypes = {
handlePopupDismissed: PropTypes.func.isRequired,
};

export default FingerprintPopup;
```

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

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

componentWillUnmount = () => {
Expand All @@ -237,24 +267,34 @@ class BiometricPopup extends Component {
return Platform.Version < 23;
}

authCurrent() {
_androidTouchID() {
FingerprintScanner.release()
FingerprintScanner
.authenticate({ description: 'Log in with Biometrics' })
.authenticate({
description: 'Scan your fingerprint on the device scanner to continue',
cancelButton: 'cancel',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the android implementation using a different signature than iOS before? It is inconvenient to have separate APIs for the platforms, but maybe that is unavoidable

onAttempt: this.handleAuthenticationAttemptedLegacy
})
.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 => {
console.log('_androidTouchID:', error.message)
console.log('_androidTouchID:', error.biometric)
FingerprintScanner.release()
switch (error.biometric) {
case 'UserCancel':
console.log('Click the cancel button')
break
case 'DeviceLocked':
console.log('Accumulated 5 identification failures, fingerprint identification was locked')
break
case 'DeviceLockedPermanent':
console.log('Accumulates many times to recognize the failure, is locked permanently, needs to unlock')
break
default:
break
}
});
}

Expand Down Expand Up @@ -339,6 +379,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(() => {
console.log('Device unlocked')
this._iosTouchID()
}).catch((error) => {
console.log('catch error:', error.message, error.biometric)
})
```

### `authenticate({ description, fallbackEnabled })`: (iOS)
Starts Fingerprint authentication on iOS.

Expand Down Expand Up @@ -435,6 +493,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 @@ -447,6 +506,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 Down
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 @@ -115,7 +115,7 @@ public BiometricPrompt getBiometricPrompt(final Promise promise) {
return biometricPrompt;
}

private void biometricAuthenticate(final String description, final Promise promise) {
private void biometricAuthenticate(final String description, final String cancelButton, final Promise promise) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
Expand All @@ -125,7 +125,7 @@ public void run() {
PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setDeviceCredentialAllowed(false)
.setConfirmationRequired(false)
.setNegativeButtonText("Cancel")
.setNegativeButtonText(cancelButton)
.setTitle(description)
.build();

Expand All @@ -146,7 +146,7 @@ private String biometricPromptErrName(int errCode) {
case BiometricPrompt.ERROR_LOCKOUT:
return "DeviceLocked";
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
return "DeviceLocked";
return "DeviceLockedPermanent";
case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
return "UserCancel";
case BiometricPrompt.ERROR_NO_BIOMETRICS:
Expand Down Expand Up @@ -188,19 +188,18 @@ private String getSensorError() {
}

@ReactMethod
public void authenticate(String description, final Promise promise) {
public void authenticate(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();
promise.reject(errorName, errorName);
return;
}

biometricAuthenticate(description, promise);
biometricAuthenticate(description, cancelButton, promise);
}
}

Expand Down Expand Up @@ -234,13 +233,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 @@ -287,13 +285,17 @@ private void legacyAuthenticate(final Promise promise) {
@Override
public void onSucceed() {
promise.resolve(true);
ReactNativeFingerprintScannerModule.this.release();
}

@Override
public void onNotMatch(int availableTimes) {
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationNotMatch");
if( availableTimes <= 0 ){
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationLockout");
}else{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if( availableTimes <= 0 ){
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationLockout");
}else{
if (availableTimes <= 0) {
mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationLockout");
} else {

I think general whitespace style (the majority at least) is like this in the file

mReactContext.getJSModule(RCTDeviceEventEmitter.class)
.emit("FINGERPRINT_SCANNER_AUTHENTICATION", "AuthenticationNotMatch");
}
}

@Override
Expand All @@ -303,7 +305,6 @@ public void onFailed(boolean isDeviceLocked) {
} 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 @@ -7,7 +7,8 @@ export type AuthenticateAndroid = { onAttempt: (error: FingerprintScannerError)
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 @@ -38,11 +39,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 @@ -67,6 +68,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 @@ -160,6 +165,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