REF: useBiometrics

This commit is contained in:
Marcos Rodriguez Velez 2024-05-17 18:34:39 -04:00 committed by Overtorment
parent 3b835e3dfe
commit 60d6abf08b
16 changed files with 437 additions and 264 deletions

View File

@ -2,7 +2,6 @@ import 'react-native-gesture-handler'; // should be on top
import React, { Suspense, lazy } from 'react';
import MainRoot from './navigation';
import { useStorage } from './blue_modules/storage-context';
import Biometric from './class/biometrics';
const CompanionDelegates = lazy(() => import('./components/CompanionDelegates'));
const MasterView = () => {
@ -10,7 +9,6 @@ const MasterView = () => {
return (
<>
<Biometric />
<MainRoot />
{walletsInitialized && (
<Suspense>

View File

@ -1,8 +1,8 @@
import { Platform } from 'react-native';
import Biometric from '../class/biometrics';
import prompt from '../helpers/prompt';
import loc from '../loc';
import { BlueApp as BlueAppClass } from '../class/';
import { showKeychainWipeAlert } from '../class/biometrics';
const BlueApp = BlueAppClass.getInstance();
// If attempt reaches 10, a wipe keychain option will be provided to the user.
@ -55,7 +55,7 @@ export const startAndDecrypt = async (retry?: boolean): Promise<boolean> => {
return startAndDecrypt(true);
} else {
unlockAttempt = 0;
Biometric.showKeychainWipeAlert();
showKeychainWipeAlert();
// We want to return false to let the UnlockWith screen that it is NOT ok to proceed.
return false;
}

View File

@ -1,186 +1,174 @@
import { useContext } from 'react';
import { Alert, Platform } from 'react-native';
import ReactNativeBiometrics, { BiometryTypes as RNBiometryTypes } from 'react-native-biometrics';
import PasscodeAuth from 'react-native-passcode-auth';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
import loc from '../loc';
import * as NavigationService from '../NavigationService';
import { BlueStorageContext } from '../blue_modules/storage-context';
import presentAlert from '../components/Alert';
import { BlueApp } from './blue-app';
const STORAGEKEY = 'Biometrics';
const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true });
// Define a function type with properties
type DescribableFunction = {
(): null; // Call signature
FaceID: 'Face ID';
TouchID: 'Touch ID';
Biometrics: 'Biometrics';
isBiometricUseCapableAndEnabled: () => Promise<boolean>;
isDeviceBiometricCapable: () => Promise<boolean>;
setBiometricUseEnabled: (arg: boolean) => Promise<void>;
biometricType: () => Promise<keyof typeof RNBiometryTypes | undefined>;
isBiometricUseEnabled: () => Promise<boolean>;
unlockWithBiometrics: () => Promise<boolean>;
showKeychainWipeAlert: () => void;
const FaceID = 'Face ID';
const TouchID = 'Touch ID';
const Biometrics = 'Biometrics';
const isDeviceBiometricCapable = async () => {
try {
const { available } = await rnBiometrics.isSensorAvailable();
return available;
} catch (e) {
console.log('Biometrics isDeviceBiometricCapable failed');
console.log(e);
setBiometricUseEnabled(false);
}
return false;
};
// Bastard component/module. All properties are added in runtime
const Biometric = function () {
const { getItem, setItem } = useContext(BlueStorageContext);
Biometric.FaceID = 'Face ID';
Biometric.TouchID = 'Touch ID';
Biometric.Biometrics = 'Biometrics';
Biometric.isDeviceBiometricCapable = async () => {
try {
const { available } = await rnBiometrics.isSensorAvailable();
return available;
} catch (e) {
console.log('Biometrics isDeviceBiometricCapable failed');
console.log(e);
Biometric.setBiometricUseEnabled(false);
const biometricType = async () => {
try {
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
if (!available) {
return undefined;
}
return false;
};
Biometric.biometricType = async () => {
try {
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
if (!available) {
return undefined;
}
return biometryType;
} catch (e) {
console.log('Biometrics biometricType failed');
console.log(e);
return undefined;
}
};
return biometryType;
} catch (e) {
console.log('Biometrics biometricType failed');
console.log(e);
return undefined; // Explicitly return false in case of an error
}
};
const isBiometricUseEnabled = async () => {
try {
const enabledBiometrics = await BlueApp.getInstance().getItem(STORAGEKEY);
return !!enabledBiometrics;
} catch (_) {}
Biometric.isBiometricUseEnabled = async () => {
try {
const enabledBiometrics = await getItem(STORAGEKEY);
return !!enabledBiometrics;
} catch (_) {}
return false;
};
return false;
};
const isBiometricUseCapableAndEnabled = async () => {
const isEnabled = await isBiometricUseEnabled();
const isCapable = await isDeviceBiometricCapable();
return isEnabled && isCapable;
};
Biometric.isBiometricUseCapableAndEnabled = async () => {
const isBiometricUseEnabled = await Biometric.isBiometricUseEnabled();
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
return isBiometricUseEnabled && isDeviceBiometricCapable;
};
const setBiometricUseEnabled = async (value: boolean) => {
await BlueApp.getInstance().setItem(STORAGEKEY, value === true ? '1' : '');
};
Biometric.setBiometricUseEnabled = async value => {
await setItem(STORAGEKEY, value === true ? '1' : '');
};
Biometric.unlockWithBiometrics = async () => {
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
if (isDeviceBiometricCapable) {
return new Promise(resolve => {
rnBiometrics
.simplePrompt({ promptMessage: loc.settings.biom_conf_identity })
.then((result: { success: any }) => {
if (result.success) {
resolve(true);
} else {
console.log('Biometrics authentication failed');
resolve(false);
}
})
.catch((error: Error) => {
console.log('Biometrics authentication error');
presentAlert({ message: error.message });
const unlockWithBiometrics = async () => {
const isCapable = await isDeviceBiometricCapable();
if (isCapable) {
return new Promise(resolve => {
rnBiometrics
.simplePrompt({ promptMessage: loc.settings.biom_conf_identity })
.then((result: { success: any }) => {
if (result.success) {
resolve(true);
} else {
console.log('Biometrics authentication failed');
resolve(false);
});
});
}
return false;
};
}
})
.catch((error: Error) => {
console.log('Biometrics authentication error');
presentAlert({ message: error.message });
resolve(false);
});
});
}
return false;
};
const clearKeychain = async () => {
try {
console.log('Wiping keychain');
console.log('Wiping key: data');
await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), {
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
console.log('Wiped key: data');
console.log('Wiping key: data_encrypted');
await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
console.log('Wiped key: data_encrypted');
console.log('Wiping key: STORAGEKEY');
await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
console.log('Wiped key: STORAGEKEY');
NavigationService.reset();
} catch (error: any) {
console.warn(error);
presentAlert({ message: error.message });
}
};
const clearKeychain = async () => {
try {
console.log('Wiping keychain');
console.log('Wiping key: data');
await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), {
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
console.log('Wiped key: data');
console.log('Wiping key: data_encrypted');
await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
console.log('Wiped key: data_encrypted');
console.log('Wiping key: STORAGEKEY');
await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
console.log('Wiped key: STORAGEKEY');
NavigationService.reset();
} catch (error: any) {
console.warn(error);
presentAlert({ message: error.message });
}
};
const requestDevicePasscode = async () => {
let isDevicePasscodeSupported: boolean | undefined = false;
try {
isDevicePasscodeSupported = await PasscodeAuth.isSupported();
if (isDevicePasscodeSupported) {
const isAuthenticated = await PasscodeAuth.authenticate();
if (isAuthenticated) {
Alert.alert(
loc.settings.encrypt_tstorage,
loc.settings.biom_remove_decrypt,
[
{ text: loc._.cancel, style: 'cancel' },
{
text: loc._.ok,
style: 'destructive',
onPress: async () => await clearKeychain(),
},
],
{ cancelable: false },
);
}
}
} catch {
isDevicePasscodeSupported = undefined;
}
if (isDevicePasscodeSupported === false) {
presentAlert({ message: loc.settings.biom_no_passcode });
}
};
Biometric.showKeychainWipeAlert = () => {
if (Platform.OS === 'ios') {
Alert.alert(
loc.settings.encrypt_tstorage,
loc.settings.biom_10times,
[
{
text: loc._.cancel,
onPress: () => {
console.log('Cancel Pressed');
const requestDevicePasscode = async () => {
let isDevicePasscodeSupported: boolean | undefined = false;
try {
isDevicePasscodeSupported = await PasscodeAuth.isSupported();
if (isDevicePasscodeSupported) {
const isAuthenticated = await PasscodeAuth.authenticate();
if (isAuthenticated) {
Alert.alert(
loc.settings.encrypt_tstorage,
loc.settings.biom_remove_decrypt,
[
{ text: loc._.cancel, style: 'cancel' },
{
text: loc._.ok,
style: 'destructive',
onPress: async () => await clearKeychain(),
},
style: 'cancel',
},
{
text: loc._.ok,
onPress: () => requestDevicePasscode(),
style: 'default',
},
],
{ cancelable: false },
);
],
{ cancelable: false },
);
}
}
};
} catch {
isDevicePasscodeSupported = undefined;
}
if (isDevicePasscodeSupported === false) {
presentAlert({ message: loc.settings.biom_no_passcode });
}
};
return null;
} as DescribableFunction;
export const showKeychainWipeAlert = () => {
if (Platform.OS === 'ios') {
Alert.alert(
loc.settings.encrypt_tstorage,
loc.settings.biom_10times,
[
{
text: loc._.cancel,
onPress: () => {
console.log('Cancel Pressed');
},
style: 'cancel',
},
{
text: loc._.ok,
onPress: () => requestDevicePasscode(),
style: 'default',
},
],
{ cancelable: false },
);
}
};
export {
FaceID,
TouchID,
Biometrics,
isDeviceBiometricCapable,
biometricType,
isBiometricUseEnabled,
isBiometricUseCapableAndEnabled,
setBiometricUseEnabled,
unlockWithBiometrics,
};
export default Biometric;
export { RNBiometryTypes as BiometricType };

View File

@ -1,4 +1,4 @@
import React, { useContext, useRef } from 'react';
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { ListItem } from 'react-native-elements';
@ -10,12 +10,12 @@ import Clipboard from '@react-native-clipboard/clipboard';
import Share from 'react-native-share';
import { useTheme } from '../themes';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Biometric from '../../class/biometrics';
import { useStorage } from '../../blue_modules/storage-context';
import presentAlert from '../Alert';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import QRCodeComponent from '../QRCodeComponent';
import confirm from '../../helpers/confirm';
import { useBiometrics } from '../../hooks/useBiometrics';
interface AddressItemProps {
// todo: fix `any` after addresses.js is converted to the church of holy typescript
@ -26,8 +26,9 @@ interface AddressItemProps {
}
const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: AddressItemProps) => {
const { wallets } = useContext(BlueStorageContext);
const { wallets } = useStorage();
const { colors } = useTheme();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const hasTransactions = item.transactions > 0;
@ -118,8 +119,8 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
navigateToSignVerify();
} else if (id === AddressItem.actionKeys.ExportPrivateKey) {
if (await confirm(loc.addresses.sensitive_private_key)) {
if (await Biometric.isBiometricUseCapableAndEnabled()) {
if (!(await Biometric.unlockWithBiometrics())) {
if (await isBiometricUseCapableAndEnabled()) {
if (!(await unlockWithBiometrics())) {
return;
}
}

196
hooks/useBiometrics.ts Normal file
View File

@ -0,0 +1,196 @@
import { useState, useEffect } from 'react';
import { Alert, Platform } from 'react-native';
import ReactNativeBiometrics, { BiometryTypes as RNBiometryTypes } from 'react-native-biometrics';
import PasscodeAuth from 'react-native-passcode-auth';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
import loc from '../loc';
import * as NavigationService from '../NavigationService';
import { useStorage } from '../blue_modules/storage-context';
import presentAlert from '../components/Alert';
const STORAGEKEY = 'Biometrics';
const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true });
const FaceID = 'Face ID';
const TouchID = 'Touch ID';
const Biometrics = 'Biometrics';
const clearKeychain = async () => {
try {
console.log('Wiping keychain');
console.log('Wiping key: data');
await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), {
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
console.log('Wiped key: data');
console.log('Wiping key: data_encrypted');
await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
console.log('Wiped key: data_encrypted');
console.log('Wiping key: STORAGEKEY');
await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
console.log('Wiped key: STORAGEKEY');
NavigationService.reset();
} catch (error: any) {
console.warn(error);
presentAlert({ message: error.message });
}
};
const requestDevicePasscode = async () => {
let isDevicePasscodeSupported: boolean | undefined = false;
try {
isDevicePasscodeSupported = await PasscodeAuth.isSupported();
if (isDevicePasscodeSupported) {
const isAuthenticated = await PasscodeAuth.authenticate();
if (isAuthenticated) {
Alert.alert(
loc.settings.encrypt_tstorage,
loc.settings.biom_remove_decrypt,
[
{ text: loc._.cancel, style: 'cancel' },
{
text: loc._.ok,
style: 'destructive',
onPress: async () => await clearKeychain(),
},
],
{ cancelable: false },
);
}
}
} catch {
isDevicePasscodeSupported = undefined;
}
if (isDevicePasscodeSupported === false) {
presentAlert({ message: loc.settings.biom_no_passcode });
}
};
const showKeychainWipeAlert = () => {
if (Platform.OS === 'ios') {
Alert.alert(
loc.settings.encrypt_tstorage,
loc.settings.biom_10times,
[
{
text: loc._.cancel,
onPress: () => {
console.log('Cancel Pressed');
},
style: 'cancel',
},
{
text: loc._.ok,
onPress: () => requestDevicePasscode(),
style: 'default',
},
],
{ cancelable: false },
);
}
};
const useBiometrics = () => {
const { getItem, setItem } = useStorage();
const [biometricEnabled, setBiometricEnabled] = useState(false);
const [deviceBiometricType, setDeviceBiometricType] = useState<'TouchID' | 'FaceID' | 'Biometrics' | undefined>(undefined);
useEffect(() => {
const fetchBiometricEnabledStatus = async () => {
const enabled = await isBiometricUseEnabled();
setBiometricEnabled(enabled);
const biometricType = await type();
setDeviceBiometricType(biometricType);
};
fetchBiometricEnabledStatus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isDeviceBiometricCapable = async () => {
try {
const { available } = await rnBiometrics.isSensorAvailable();
return available;
} catch (e) {
console.log('Biometrics isDeviceBiometricCapable failed');
console.log(e);
setBiometricUseEnabled(false);
}
return false;
};
const type = async () => {
try {
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
if (!available) {
return undefined;
}
return biometryType;
} catch (e) {
console.log('Biometrics biometricType failed');
console.log(e);
return undefined;
}
};
const isBiometricUseEnabled = async () => {
try {
const enabledBiometrics = await getItem(STORAGEKEY);
return !!enabledBiometrics;
} catch (_) {}
return false;
};
const isBiometricUseCapableAndEnabled = async () => {
const isEnabled = await isBiometricUseEnabled();
const isCapable = await isDeviceBiometricCapable();
return isEnabled && isCapable;
};
const setBiometricUseEnabled = async (value: boolean) => {
await setItem(STORAGEKEY, value === true ? '1' : '');
setBiometricEnabled(value);
};
const unlockWithBiometrics = async () => {
const isCapable = await isDeviceBiometricCapable();
if (isCapable) {
return new Promise(resolve => {
rnBiometrics
.simplePrompt({ promptMessage: loc.settings.biom_conf_identity })
.then((result: { success: any }) => {
if (result.success) {
resolve(true);
} else {
console.log('Biometrics authentication failed');
resolve(false);
}
})
.catch((error: Error) => {
console.log('Biometrics authentication error');
presentAlert({ message: error.message });
resolve(false);
});
});
}
return false;
};
return {
isDeviceBiometricCapable,
deviceBiometricType,
isBiometricUseEnabled,
isBiometricUseCapableAndEnabled,
setBiometricUseEnabled,
unlockWithBiometrics,
clearKeychain,
requestDevicePasscode,
showKeychainWipeAlert,
biometricEnabled,
};
};
export { FaceID, TouchID, Biometrics, RNBiometryTypes as BiometricType, useBiometrics };

View File

@ -1,9 +1,8 @@
import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native';
import Biometric from '../class/biometrics';
import { navigationRef } from '../NavigationService';
import { BlueStorageContext } from '../blue_modules/storage-context';
import { useContext } from 'react';
import { useStorage } from '../blue_modules/storage-context';
import { presentWalletExportReminder } from '../helpers/presentWalletExportReminder';
import { useBiometrics } from './useBiometrics';
// List of screens that require biometrics
@ -15,7 +14,8 @@ const requiresWalletExportIsSaved = ['ReceiveDetailsRoot', 'WalletAddresses'];
export const useExtendedNavigation = (): NavigationProp<ParamListBase> => {
const originalNavigation = useNavigation<NavigationProp<ParamListBase>>();
const { wallets, saveToDisk } = useContext(BlueStorageContext);
const { wallets, saveToDisk } = useStorage();
const { isBiometricUseEnabled, unlockWithBiometrics } = useBiometrics();
const enhancedNavigate: NavigationProp<ParamListBase>['navigate'] = (screenOrOptions: any, params?: any) => {
let screenName: string;
@ -42,9 +42,9 @@ export const useExtendedNavigation = (): NavigationProp<ParamListBase> => {
(async () => {
if (isRequiresBiometrics) {
const isBiometricsEnabled = await Biometric.isBiometricUseEnabled();
const isBiometricsEnabled = await isBiometricUseEnabled();
if (isBiometricsEnabled) {
const isAuthenticated = await Biometric.unlockWithBiometrics();
const isAuthenticated = await unlockWithBiometrics();
if (isAuthenticated) {
proceedWithNavigation();
return; // Ensure the function exits if this path is taken

View File

@ -1,12 +1,12 @@
import React, { useCallback, useContext, useEffect, useReducer, useRef } from 'react';
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
import { View, Image, ActivityIndicator, StyleSheet } from 'react-native';
import Biometric, { BiometricType } from '../class/biometrics';
import { BlueStorageContext } from '../blue_modules/storage-context';
import { useStorage } from '../blue_modules/storage-context';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
import SafeArea from '../components/SafeArea';
import { BlueTextCentered } from '../BlueComponents';
import loc from '../loc';
import Button from '../components/Button';
import { BiometricType, useBiometrics } from '../hooks/useBiometrics';
enum AuthType {
Encrypted,
@ -52,7 +52,8 @@ function reducer(state: State, action: Action): State {
const UnlockWith: React.FC = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const isUnlockingWallets = useRef(false);
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useContext(BlueStorageContext);
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useStorage();
const { deviceBiometricType, unlockWithBiometrics, isBiometricUseCapableAndEnabled, isBiometricUseEnabled } = useBiometrics();
const successfullyAuthenticated = useCallback(() => {
setWalletsInitialized(true);
@ -60,19 +61,20 @@ const UnlockWith: React.FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const unlockWithBiometrics = useCallback(async () => {
const unlockUsingBiometrics = useCallback(async () => {
if (isUnlockingWallets.current || state.isAuthenticating) return;
isUnlockingWallets.current = true;
dispatch({ type: SET_IS_AUTHENTICATING, payload: true });
if (await Biometric.unlockWithBiometrics()) {
if (await unlockWithBiometrics()) {
await startAndDecrypt();
successfullyAuthenticated();
}
dispatch({ type: SET_IS_AUTHENTICATING, payload: false });
isUnlockingWallets.current = false;
}, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.isAuthenticating]);
const unlockWithKey = useCallback(async () => {
if (isUnlockingWallets.current || state.isAuthenticating) return;
@ -91,14 +93,14 @@ const UnlockWith: React.FC = () => {
useEffect(() => {
const startUnlock = async () => {
const storageIsEncrypted = await isStorageEncrypted();
const isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const biometricType = isBiometricUseCapableAndEnabled ? await Biometric.biometricType() : undefined;
const biometricsUseEnabled = await Biometric.isBiometricUseEnabled();
const biometricUseCapableAndEnabled = await isBiometricUseCapableAndEnabled();
const biometricsUseEnabled = await isBiometricUseEnabled();
const biometricType = biometricUseCapableAndEnabled ? deviceBiometricType : undefined;
if (storageIsEncrypted) {
dispatch({ type: SET_AUTH, payload: { type: AuthType.Encrypted, detail: undefined } });
unlockWithKey();
} else if (isBiometricUseCapableAndEnabled) {
} else if (biometricUseCapableAndEnabled) {
dispatch({ type: SET_AUTH, payload: { type: AuthType.Biometrics, detail: biometricType } });
unlockWithBiometrics();
} else if (biometricsUseEnabled && biometricType === undefined) {
@ -116,7 +118,7 @@ const UnlockWith: React.FC = () => {
const onUnlockPressed = () => {
if (state.auth.type === AuthType.Biometrics) {
unlockWithBiometrics();
unlockUsingBiometrics();
} else {
unlockWithKey();
}

View File

@ -1,8 +1,8 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { BlueLoading, BlueDismissKeyboardInputAccessory, BlueSpacing20, BlueText } from '../../BlueComponents';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useStorage } from '../../blue_modules/storage-context';
import BigNumber from 'bignumber.js';
import AddressInput from '../../components/AddressInput';
import AmountInput from '../../components/AmountInput';
@ -11,13 +11,13 @@ import loc from '../../loc';
import { HDSegwitBech32Wallet, LightningLdkWallet } from '../../class';
import { ArrowPicker } from '../../components/ArrowPicker';
import { Psbt } from 'bitcoinjs-lib';
import Biometric from '../../class/biometrics';
import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import Button from '../../components/Button';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import SafeArea from '../../components/SafeArea';
import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency';
import { useBiometrics } from '../../hooks/useBiometrics';
type LdkOpenChannelProps = RouteProp<
{
@ -33,8 +33,8 @@ type LdkOpenChannelProps = RouteProp<
>;
const LdkOpenChannel = (props: any) => {
const { wallets, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
const [isBiometricUseCapableAndEnabled, setIsBiometricUseCapableAndEnabled] = useState(false);
const { wallets, fetchAndSaveWalletTransactions } = useStorage();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const { colors }: { colors: any } = useTheme();
const { navigate, setParams } = useNavigation();
const {
@ -75,14 +75,10 @@ const LdkOpenChannel = (props: any) => {
})();
}, [psbt]);
useEffect(() => {
Biometric.isBiometricUseCapableAndEnabled().then(setIsBiometricUseCapableAndEnabled);
}, []);
const finalizeOpenChannel = async () => {
setIsLoading(true);
if (isBiometricUseCapableAndEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (await isBiometricUseCapableAndEnabled()) {
if (!(await unlockWithBiometrics())) {
setIsLoading(false);
return;
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext } from 'react';
import React, { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { I18nManager, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useNavigation, useRoute } from '@react-navigation/native';
@ -8,8 +8,7 @@ import AmountInput from '../../components/AmountInput';
import Lnurl from '../../class/lnurl';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import loc, { formatBalanceWithoutSuffix, formatBalance } from '../../loc';
import Biometric from '../../class/biometrics';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useStorage } from '../../blue_modules/storage-context';
import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import Button from '../../components/Button';
@ -17,6 +16,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
import SafeArea from '../../components/SafeArea';
import { btcToSatoshi, fiatToBTC, satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency';
import prompt from '../../helpers/prompt';
import { useBiometrics } from '../../hooks/useBiometrics';
/**
* if user has default currency - fiat, attempting to pay will trigger conversion from entered in input field fiat value
@ -26,7 +26,8 @@ import prompt from '../../helpers/prompt';
const _cacheFiatToSat = {};
const LnurlPay = () => {
const { wallets } = useContext(BlueStorageContext);
const { wallets } = useStorage();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const { walletID, lnurl } = useRoute().params;
/** @type {LightningCustodianWallet} */
const wallet = wallets.find(w => w.getID() === walletID);
@ -105,9 +106,9 @@ const LnurlPay = () => {
/** @type {Lnurl} */
const LN = _LN;
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (!(await unlockWithBiometrics())) {
return;
}
}

View File

@ -1,4 +1,4 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
Text,
ActivityIndicator,
@ -12,24 +12,24 @@ import {
} from 'react-native';
import { Icon } from 'react-native-elements';
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
import { BlueCard, BlueDismissKeyboardInputAccessory, BlueLoading } from '../../BlueComponents';
import AddressInput from '../../components/AddressInput';
import AmountInput from '../../components/AmountInput';
import Lnurl from '../../class/lnurl';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import Biometric from '../../class/biometrics';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useStorage } from '../../blue_modules/storage-context';
import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import Button from '../../components/Button';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import SafeArea from '../../components/SafeArea';
import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency';
import { useBiometrics } from '../../hooks/useBiometrics';
const ScanLndInvoice = () => {
const { wallets, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
const { wallets, fetchAndSaveWalletTransactions } = useStorage();
const { unlockWithBiometrics, isBiometricUseCapableAndEnabled } = useBiometrics();
const { colors } = useTheme();
const { walletID, uri, invoice } = useRoute().params;
const name = useRoute().name;
@ -167,10 +167,10 @@ const ScanLndInvoice = () => {
return null;
}
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (!(await unlockWithBiometrics())) {
return;
}
}

View File

@ -5,11 +5,9 @@ import { PayjoinClient } from 'payjoin-client';
import PropTypes from 'prop-types';
import BigNumber from 'bignumber.js';
import * as bitcoin from 'bitcoinjs-lib';
import PayjoinTransaction from '../../class/payjoin-transaction';
import { BlueText, BlueCard } from '../../BlueComponents';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import Biometric from '../../class/biometrics';
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
import Notifications from '../../blue_modules/notifications';
import { BlueStorageContext } from '../../blue_modules/storage-context';
@ -21,10 +19,11 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
import SafeArea from '../../components/SafeArea';
import { satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import { useBiometrics } from '../../hooks/useBiometrics';
const Confirm = () => {
const { wallets, fetchAndSaveWalletTransactions, isElectrumDisabled } = useContext(BlueStorageContext);
const [isBiometricUseCapableAndEnabled, setIsBiometricUseCapableAndEnabled] = useState(false);
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const { params } = useRoute();
const { recipients = [], walletID, fee, memo, tx, satoshiPerByte, psbt } = params;
const [isLoading, setIsLoading] = useState(false);
@ -64,7 +63,6 @@ const Confirm = () => {
useEffect(() => {
console.log('send/confirm - useEffect');
console.log('address = ', recipients);
Biometric.isBiometricUseCapableAndEnabled().then(setIsBiometricUseCapableAndEnabled);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -77,8 +75,8 @@ const Confirm = () => {
testID="TransactionDetailsButton"
style={[styles.txDetails, stylesHook.txDetails]}
onPress={async () => {
if (isBiometricUseCapableAndEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (await isBiometricUseCapableAndEnabled()) {
if (!(await unlockWithBiometrics())) {
return;
}
}
@ -99,7 +97,7 @@ const Confirm = () => {
),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors, fee, feeSatoshi, isBiometricUseCapableAndEnabled, memo, recipients, satoshiPerByte, tx, wallet]);
}, [colors, fee, feeSatoshi, memo, recipients, satoshiPerByte, tx, wallet]);
/**
* we need to look into `recipients`, find destination address and return its outputScript
@ -163,8 +161,8 @@ const Confirm = () => {
await BlueElectrum.ping();
await BlueElectrum.waitTillConnected();
if (isBiometricUseCapableAndEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (await isBiometricUseCapableAndEnabled()) {
if (!(await unlockWithBiometrics())) {
return;
}
}

View File

@ -1,16 +1,14 @@
import React, { useEffect, useRef, useState } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import { useIsFocused, useNavigation, useRoute } from '@react-navigation/native';
import * as bitcoin from 'bitcoinjs-lib';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, Linking, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
import DocumentPicker from 'react-native-document-picker';
import RNFS from 'react-native-fs';
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import Notifications from '../../blue_modules/notifications';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Biometric from '../../class/biometrics';
import { useStorage } from '../../blue_modules/storage-context';
import presentAlert from '../../components/Alert';
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
import { DynamicQRCode } from '../../components/DynamicQRCode';
@ -20,9 +18,12 @@ import { requestCameraAuthorization } from '../../helpers/scan-qr';
import loc from '../../loc';
import SaveFileButton from '../../components/SaveFileButton';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import { useBiometrics } from '../../hooks/useBiometrics';
const PsbtWithHardwareWallet = () => {
const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled } = useContext(BlueStorageContext);
const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled } = useStorage();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const navigation = useNavigation();
const route = useRoute();
const { fromWallet, memo, psbt, deepLinkPSBT, launchedBy } = route.params;
@ -116,10 +117,10 @@ const PsbtWithHardwareWallet = () => {
const broadcast = async () => {
setIsLoading(true);
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (!(await unlockWithBiometrics())) {
setIsLoading(false);
return;
}

View File

@ -1,20 +1,20 @@
import React, { useEffect, useState, useCallback, useContext } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import { View, ScrollView, Alert, TouchableOpacity, TouchableWithoutFeedback, Text, StyleSheet, Platform } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { BlueLoading, BlueSpacing20, BlueCard, BlueText } from '../../BlueComponents';
import Biometric from '../../class/biometrics';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useStorage } from '../../blue_modules/storage-context';
import presentAlert from '../../components/Alert';
import ListItem from '../../components/ListItem';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { useTheme } from '../../components/themes';
import prompt from '../../helpers/prompt';
import { useBiometrics } from '../../hooks/useBiometrics';
const EncryptStorage = () => {
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useContext(BlueStorageContext);
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useStorage();
const [isLoading, setIsLoading] = useState(true);
const [biometrics, setBiometrics] = useState({ isDeviceBiometricCapable: false, isBiometricsEnabled: false, biometricsType: '' });
const { isDeviceBiometricCapable, biometricEnabled, setBiometricUseEnabled, deviceBiometricType, unlockWithBiometrics } = useBiometrics();
const [storageIsEncryptedSwitchEnabled, setStorageIsEncryptedSwitchEnabled] = useState(false);
const { navigate, popToTop } = useNavigation();
const { colors } = useTheme();
@ -28,12 +28,8 @@ const EncryptStorage = () => {
});
const initialState = useCallback(async () => {
const isBiometricsEnabled = await Biometric.isBiometricUseEnabled();
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
const biometricsType = (await Biometric.biometricType()) || loc.settings.biometrics;
const isStorageEncryptedSwitchEnabled = await isStorageEncrypted();
setStorageIsEncryptedSwitchEnabled(isStorageEncryptedSwitchEnabled);
setBiometrics({ isBiometricsEnabled, isDeviceBiometricCapable, biometricsType });
setIsLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -107,15 +103,8 @@ const EncryptStorage = () => {
};
const onUseBiometricSwitch = async value => {
const isBiometricsEnabled = {
isDeviceBiometricCapable: biometrics.isDeviceBiometricCapable,
isBiometricsEnabled: biometrics.isBiometricsEnabled,
biometricsType: biometrics.biometricsType,
};
if (await Biometric.unlockWithBiometrics()) {
isBiometricsEnabled.isBiometricsEnabled = value;
await Biometric.setBiometricUseEnabled(value);
setBiometrics(isBiometricsEnabled);
if (await unlockWithBiometrics()) {
setBiometricUseEnabled(value);
}
};
@ -135,7 +124,7 @@ const EncryptStorage = () => {
return isCapable ? (
<>
<BlueText />
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: biometrics.biometricsType })}</BlueText>
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType })}</BlueText>
</>
) : null;
};
@ -147,18 +136,18 @@ const EncryptStorage = () => {
) : (
<ScrollView contentContainerStyle={styles.root} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
<View style={styles.paddingTop} />
{biometrics.isDeviceBiometricCapable && (
{isDeviceBiometricCapable && (
<>
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
{loc.settings.biometrics}
</Text>
<ListItem
title={loc.formatString(loc.settings.encrypt_use, { type: biometrics.biometricsType })}
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType })}
Component={TouchableWithoutFeedback}
switch={{ value: biometrics.isBiometricsEnabled, onValueChange: onUseBiometricSwitch }}
switch={{ value: biometricEnabled, onValueChange: onUseBiometricSwitch }}
/>
<BlueCard>
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: biometrics.biometricsType })}</BlueText>
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType })}</BlueText>
{renderPasscodeExplanation()}
</BlueCard>
<BlueSpacing20 />

View File

@ -31,7 +31,6 @@ import * as NavigationService from '../../NavigationService';
import { useStorage } from '../../blue_modules/storage-context';
import { encodeUR } from '../../blue_modules/ur';
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
import Biometric from '../../class/biometrics';
import presentAlert from '../../components/Alert';
import BottomModal from '../../components/BottomModal';
import Button from '../../components/Button';
@ -52,11 +51,13 @@ import SaveFileButton from '../../components/SaveFileButton';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import prompt from '../../helpers/prompt';
import { useSettings } from '../../components/Context/SettingsContext';
import { useBiometrics } from '../../hooks/useBiometrics';
const ViewEditMultisigCosigners: React.FC = () => {
const hasLoaded = useRef(false);
const { colors } = useTheme();
const { wallets, setWalletsWithNewOrder, isElectrumDisabled } = useStorage();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const { isAdvancedModeEnabled } = useSettings();
const { navigate, dispatch, addListener } = useExtendedNavigation();
const openScannerButtonRef = useRef();
@ -173,10 +174,10 @@ const ViewEditMultisigCosigners: React.FC = () => {
}
setIsLoading(true);
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (!(await unlockWithBiometrics())) {
setIsLoading(false);
return;
}

View File

@ -1,5 +1,5 @@
import { useRoute } from '@react-navigation/native';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
ActivityIndicator,
Alert,
@ -20,7 +20,7 @@ import {
import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import Notifications from '../../blue_modules/notifications';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useStorage } from '../../blue_modules/storage-context';
import {
HDAezeedWallet,
HDSegwitBech32Wallet,
@ -31,7 +31,6 @@ import {
SegwitP2SHWallet,
WatchOnlyWallet,
} from '../../class';
import Biometric from '../../class/biometrics';
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import presentAlert from '../../components/Alert';
@ -47,6 +46,7 @@ import SaveFileButton from '../../components/SaveFileButton';
import { useSettings } from '../../components/Context/SettingsContext';
import HeaderRightButton from '../../components/HeaderRightButton';
import { writeFileAndExport } from '../../blue_modules/fs';
import { useBiometrics } from '../../hooks/useBiometrics';
const styles = StyleSheet.create({
scrollViewContent: {
@ -107,7 +107,8 @@ const styles = StyleSheet.create({
});
const WalletDetails = () => {
const { saveToDisk, wallets, deleteWallet, setSelectedWalletID, txMetadata } = useContext(BlueStorageContext);
const { saveToDisk, wallets, deleteWallet, setSelectedWalletID, txMetadata } = useStorage();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const { walletID } = useRoute().params;
const [isLoading, setIsLoading] = useState(false);
const [backdoorPressed, setBackdoorPressed] = useState(0);
@ -402,10 +403,10 @@ const WalletDetails = () => {
{
text: loc.wallets.details_yes_delete,
onPress: async () => {
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
if (!(await unlockWithBiometrics())) {
return;
}
}

View File

@ -1,6 +1,6 @@
import { useFocusEffect, useRoute } from '@react-navigation/native';
import PropTypes from 'prop-types';
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Alert,
@ -24,9 +24,8 @@ import BlueClipboard from '../../blue_modules/clipboard';
import { isDesktop } from '../../blue_modules/environment';
import * as fs from '../../blue_modules/fs';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueStorageContext, WalletTransactionsStatus } from '../../blue_modules/storage-context';
import { WalletTransactionsStatus, useStorage } from '../../blue_modules/storage-context';
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
import Biometric from '../../class/biometrics';
import WalletGradient from '../../class/wallet-gradient';
import presentAlert from '../../components/Alert';
import { FButton, FContainer } from '../../components/FloatButtons';
@ -41,6 +40,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc from '../../loc';
import { Chain } from '../../models/bitcoinUnits';
import ActionSheet from '../ActionSheet';
import { useBiometrics } from '../../hooks/useBiometrics';
const buttonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
@ -55,7 +55,8 @@ const WalletTransactions = ({ navigation }) => {
walletTransactionUpdateStatus,
isElectrumDisabled,
setReloadTransactionsMenuActionFunction,
} = useContext(BlueStorageContext);
} = useStorage();
const { isBiometricUseCapableAndEnabled, unlockWithBiometrics } = useBiometrics();
const [isLoading, setIsLoading] = useState(false);
const { walletID } = useRoute().params;
const { name } = useRoute();
@ -485,10 +486,10 @@ const WalletTransactions = ({ navigation }) => {
})
}
onWalletBalanceVisibilityChange={async isShouldBeVisible => {
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (wallet.hideBalance && isBiometricsEnabled) {
const unlocked = await Biometric.unlockWithBiometrics();
const unlocked = await unlockWithBiometrics();
if (!unlocked) {
throw new Error('Biometrics failed');
}