Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions ios/backupfile/BackupFileHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

@objc class BackupFileHelper: NSObject {

override init() {
super.init()
}

@objc func setFileProtection(filePath: String, callback: @escaping ((Bool, String?) -> Void)) {
let fileManager = FileManager.default

do {
// Set file protection attribute
try fileManager.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: filePath
)
callback(true, nil)
} catch {
callback(false, error.localizedDescription)
}
}
}
6 changes: 6 additions & 0 deletions ios/backupfile/backupfile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#import <React/RCTBridgeModule.h>
#import <Foundation/Foundation.h>

@class BackupFile;
@interface BackupFile:NSObject <RCTBridgeModule>
@end
24 changes: 24 additions & 0 deletions ios/backupfile/backupfile.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import "backupfile.h"

#import <Foundation/Foundation.h>
#import "hexa_keeper-Swift.h"
#import <React/RCTConvert.h>

@implementation BackupFile

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(setFileProtection:(NSString *)filePath
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject){
BackupFileHelper *helper = [[BackupFileHelper alloc]init];
[helper setFileProtectionWithFilePath:filePath callback:^(BOOL success, NSString * _Nullable error) {
if (success) {
resolve(@YES);
} else {
reject(@"FILE_PROTECTION_ERROR", error ?: @"Failed to set file protection", nil);
}
}];
}

@end
4 changes: 3 additions & 1 deletion src/components/Backup/BackupHealthCheckList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Text from '../KeeperText';
import { hp } from 'src/constants/responsive';
import ThemedSvg from '../ThemedSvg.tsx/ThemedSvg';
import ConfirmSeedWord from '../SeedWordBackup/ConfirmSeedWord';
import { setRecoveryKeyBackedUp } from 'src/store/reducers/account';

const ContentType = {
verifying: 'verifying',
Expand Down Expand Up @@ -67,7 +68,7 @@ function BackupHealthCheckList({ isUaiFlow }) {
const { translations } = useContext(LocalizationContext);
const { BackupWallet, vault: vaultText, common } = translations;
const dispatch = useAppDispatch();
const { primaryMnemonic, backup } = useQuery(RealmSchema.KeeperApp).map(
const { primaryMnemonic, backup, id } = useQuery(RealmSchema.KeeperApp).map(
getJSONFromRealmObject
)[0];
const {
Expand Down Expand Up @@ -95,6 +96,7 @@ function BackupHealthCheckList({ isUaiFlow }) {
useEffect(() => {
if (seedConfirmed) {
setShowConfirmSeedModal(false);
dispatch(setRecoveryKeyBackedUp({ appId: id as string, status: true }));
setTimeout(() => {
setHealthCheckModal(true);
}, 100);
Expand Down
16 changes: 14 additions & 2 deletions src/context/Localization/language/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
"Createpasscode": "Create a passcode",
"Confirmyourpasscode": "Confirm your passcode",
"CreateApp": "Create a fresh app or recover an exisiting one",
"CreateAppOnly": "Create a fresh app",
"passcode": "passcode",
"Incorrect": "Incorrect Passcode, Try Again!",
"newKeeperAppDesc": "Create a new Keeper app",
Expand All @@ -222,7 +223,11 @@
"checkConnection": "Please check your internet connection and try again. If you continue offline, some features may not be available.",
"incorrectPassword": "Incorrect Password",
"youEnteredIncorrectPasscode": "You have entered an incorrect passcode. Please, try again. If you don’t remember your passcode, you will have to recover your wallet through the recovery flow",
"checkinternetConnection": "Please check your internet connection and try again."
"checkinternetConnection": "Please check your internet connection and try again.",
"forgotPasscode": "Forgot passcode?",
"resetPasscodeTitle": "Reset Passcode",
"resetPasscodeDec1": "For your security, passcode resets are verified with your recovery key.",
"resetPasscodeDec2": "Please have your recovery key ready before continuing."
},
"home": {
"wallet": "Wallet",
Expand All @@ -243,7 +248,14 @@
"incommingAndOutgoing": "All incoming and outgoing transactions",
"viewAll": " View All",
"securityTip": "Security Tip",
"securityTipDesc": "Recreate the multisig on more coordinators. Receive a small amount and send a part of it. Check the balances are appropriately reflected across all the coordinators after each step."
"securityTipDesc": "Recreate the multisig on more coordinators. Receive a small amount and send a part of it. Check the balances are appropriately reflected across all the coordinators after each step.",
"backupModalTitle": "Set Up Wallet Recovery",
"backupModalSubTitle": "To ensure you can always recover your wallet, please secure your Recovery Key and enable Assisted Server Backup.",
"backupModalDesc": "This ensure that your encrypted data is backed up and can be decrypted by your Recovery key",
"backupFileFailTitle": "Setup Adjustment Required",
"backupFileFailSubTitle": "We’ll guide you through it",
"backupFileFailDesc1": "It looks like some app information couldn't be loaded, which can happen after an update or when certain files aren’t available.",
"backupFileFailDesc2": "Please set a new passcode and restore using your recovery key to continue."
},
"transactions": {
"Fees": "Fees",
Expand Down
16 changes: 14 additions & 2 deletions src/context/Localization/language/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
"Createpasscode": "Create a passcode",
"Confirmyourpasscode": "Confirm your passcode",
"CreateApp": "Create a fresh app or recover an exisiting one",
"CreateAppOnly": "Create a fresh app",
"passcode": "passcode",
"Incorrect": "Incorrect Passcode, Try Again!",
"newKeeperAppDesc": "Create a new Keeper app",
Expand All @@ -222,7 +223,11 @@
"checkConnection": "Please check your internet connection and try again. If you continue offline, some features may not be available.",
"incorrectPassword": "Incorrect Password",
"youEnteredIncorrectPasscode": "You have entered an incorrect passcode. Please, try again. If you don’t remember your passcode, you will have to recover your wallet through the recovery flow",
"checkinternetConnection": "Please check your internet connection and try again."
"checkinternetConnection": "Please check your internet connection and try again.",
"forgotPasscode": "Forgot passcode?",
"resetPasscodeTitle": "Reset Passcode",
"resetPasscodeDec1": "For your security, passcode resets are verified with your recovery key.",
"resetPasscodeDec2": "Please have your recovery key ready before continuing."
},
"home": {
"wallet": "Wallet",
Expand All @@ -243,7 +248,14 @@
"incommingAndOutgoing": "All incoming and outgoing transactions",
"viewAll": " View All",
"securityTip": "Security Tip",
"securityTipDesc": "Recreate the multisig on more coordinators. Receive a small amount and send a part of it. Check the balances are appropriately reflected across all the coordinators after each step."
"securityTipDesc": "Recreate the multisig on more coordinators. Receive a small amount and send a part of it. Check the balances are appropriately reflected across all the coordinators after each step.",
"backupModalTitle": "Set Up Wallet Recovery",
"backupModalSubTitle": "To ensure you can always recover your wallet, please secure your Recovery Key and enable Assisted Server Backup.",
"backupModalDesc": "This ensure that your encrypted data is backed up and can be decrypted by your Recovery key",
"backupFileFailTitle": "Setup Adjustment Required",
"backupFileFailSubTitle": "We’ll guide you through it",
"backupFileFailDesc1": "It looks like some app information couldn't be loaded, which can happen after an update or when certain files aren’t available.",
"backupFileFailDesc2": "Please set a new passcode and restore using your recovery key to continue."
},
"transactions": {
"Fees": "Fees",
Expand Down
13 changes: 13 additions & 0 deletions src/nativemodules/BackupFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NativeModules, Platform } from 'react-native';

const { BackupFile } = NativeModules;

export default class BackupFileModule {
static setFileProtection = async (filePath: string): Promise<boolean> => {
if (Platform.OS === 'ios' && BackupFile) {
return await BackupFile.setFileProtection(filePath);
}
// Android doesn't need this, return true
return true;
};
}
72 changes: 71 additions & 1 deletion src/screens/Home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ import MenuFooter from 'src/components/MenuFooter';
import HomeWallet from './components/Wallet/HomeWallet';
import ManageKeys from './components/Keys/ManageKeys';
import KeeperSettings from './components/Settings/keeperSettings';
import { useNavigation } from '@react-navigation/native';
import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
import TickIcon from 'src/assets/images/icon_tick.svg';
import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg';
import ThemedColor from 'src/components/ThemedColor/ThemedColor';
import BuyBtc from './components/buyBtc/BuyBtc';
import ConciergeComponent from './components/ConciergeComponent';
import KeeperModal from 'src/components/KeeperModal';
import Text from 'src/components/KeeperText';
import { useQuery } from '@realm/react';
import { RealmSchema } from 'src/storage/realm/enum';
import dbManager from 'src/storage/realm/dbManager';

function NewHomeScreen({ route }) {
const { colorMode } = useColorMode();
Expand All @@ -31,6 +36,7 @@ function NewHomeScreen({ route }) {
const { addedSigner, selectedOption: selectedOptionFromRoute } = route.params || {};
const { wallets } = useWallets({ getAll: true });
const [electrumErrorVisible, setElectrumErrorVisible] = useState(false);
const [backupModalVisible, setBackupModalVisible] = useState(true);
const home_header_circle_background = ThemedColor({ name: 'home_header_circle_background' });

const { relayWalletUpdate, relayWalletError, realyWalletErrorMessage, homeToastMessage } =
Expand All @@ -41,13 +47,34 @@ function NewHomeScreen({ route }) {
const [selectedOption, setSelectedOption] = useState(
selectedOptionFromRoute || walletText.homeWallets
);
const backupHistory = useQuery(RealmSchema.BackupHistory);
const { recoveryKeyBackedUpByAppId } = useAppSelector((state) => state.account);
const { id } = dbManager.getObjectByIndex(RealmSchema.KeeperApp) as any;
const shouldShowBackupModal = !(recoveryKeyBackedUpByAppId?.[id] ?? false);

useEffect(() => {
if (selectedOptionFromRoute && selectedOptionFromRoute !== selectedOption) {
setSelectedOption(selectedOptionFromRoute);
}
}, [selectedOptionFromRoute]);

useEffect(() => {
if (shouldShowBackupModal) {
setBackupModalVisible(true);
}
}, [shouldShowBackupModal]);

useFocusEffect(
React.useCallback(() => {
if (shouldShowBackupModal && selectedOption !== walletText.more) {
const timer = setTimeout(() => {
setBackupModalVisible(true);
}, 300);
return () => clearTimeout(timer);
}
}, [shouldShowBackupModal, selectedOption, walletText.more])
);

const getContent = () => {
switch (selectedOption) {
case walletText.homeWallets:
Expand Down Expand Up @@ -159,6 +186,16 @@ function NewHomeScreen({ route }) {
}
}, [homeToastMessage]);

const BackupModalContent = () => {
return (
<Box style={{ gap: hp(10) }}>
<Text color={`${colorMode}.primaryText`} style={{ fontSize: 14, letterSpacing: 0.13 }}>
{homeTranslation.backupModalDesc}
</Text>
</Box>
);
};

return (
<Box backgroundColor={`${colorMode}.primaryBackground`} style={styles.container}>
<InititalAppController
Expand All @@ -175,6 +212,39 @@ function NewHomeScreen({ route }) {
navigation.navigate('Home', { selectedOption: option });
}}
/>
<KeeperModal
visible={shouldShowBackupModal && backupModalVisible}
close={() => {}}
title={homeTranslation.backupModalTitle}
subTitle={homeTranslation.backupModalSubTitle}
modalBackground={`${colorMode}.modalWhiteBackground`}
textColor={`${colorMode}.textGreen`}
subTitleColor={`${colorMode}.modalSubtitleBlack`}
buttonBackground={`${colorMode}.pantoneGreen`}
showCloseIcon={false}
buttonText={'Backup Recovery Key'}
buttonCallback={() => {
setBackupModalVisible(false);
setTimeout(() => {
if (backupHistory.length === 0) {
navigation.dispatch(
CommonActions.navigate('Home', {
selectedOption: 'More',
isUaiFlow: true,
})
);
} else {
navigation.dispatch(
CommonActions.navigate('WalletBackHistory', {
isUaiFlow: true,
})
);
}
}, 300);
}}
buttonTextColor={`${colorMode}.buttonText`}
Content={BackupModalContent}
/>
</Box>
);
}
Expand Down
55 changes: 54 additions & 1 deletion src/screens/Home/components/Settings/AppSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, useColorMode } from 'native-base';
import React, { useContext, useState } from 'react';
import { Alert, Pressable, StyleSheet } from 'react-native';
import { Alert, Platform, Pressable, StyleSheet } from 'react-native';
import ScreenWrapper from 'src/components/ScreenWrapper';
import WalletHeader from 'src/components/WalletHeader';
import { LocalizationContext } from 'src/context/Localization/LocContext';
Expand All @@ -21,6 +21,10 @@ import useToastMessage from 'src/hooks/useToastMessage';
import TickIcon from 'src/assets/images/tick_icon.svg';
import ToastErrorIcon from 'src/assets/images/toast_error.svg';
import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg';
import * as Keychain from 'react-native-keychain';
import * as SecureStore from 'src/storage/secure-store';
import { deleteBackupFile, restoreBackupKey } from 'src/services/backupfile';
import RNFS from 'react-native-fs';

const SettingsApp = () => {
const { colorMode } = useColorMode();
Expand All @@ -31,6 +35,7 @@ const SettingsApp = () => {
const [networkModeModal, setNetworkModeModal] = useState(false);
const [selectedNetwork, setSelectedNetwork] = useState(bitcoinNetworkType);
const [loading, setLoading] = useState(false);
const { allAccounts } = useAppSelector((state) => state.account);

let appSetting = [
...useSettingKeeper().appSetting,
Expand Down Expand Up @@ -135,6 +140,54 @@ const SettingsApp = () => {
</Box>
)}
/>
{/* // ! Remove this later | Only for reproducing condition for tester */}
{Platform.OS == 'ios' && (
<>
<Buttons
primaryText="Clear All Passcode"
primaryCallback={async () => {
let count = 0;
for (const acc of allAccounts) {
const service = acc.accountIdentifier == '' ? undefined : acc.accountIdentifier;
const res = await Keychain.resetGenericPassword({ service: service });
count++;
console.log('🚀 ~ Delete keychain for :', service, res);
}
showToast(`Clear passcode for ${count} accounts`);
}}
secondaryText="Has Passcode"
secondaryCallback={async () => {
const hasCreds = await SecureStore.hasPin();
console.log('🚀 ~ attemptLogin ~ hasCreds:', hasCreds);
showToast(`Passcode is set: ${hasCreds}`);
}}
/>
<Buttons
primaryText="Delete backup file"
primaryCallback={async () => {
const res = await deleteBackupFile('backup.txt');
showToast(`Backup file deleted: ${res}`);
}}
secondaryText="Try Restore"
secondaryCallback={async () => {
const res = await restoreBackupKey(false);
showToast(`Restore status: ${res}`);
}}
/>
{/* */}
<Buttons
primaryText="Create corrupted file"
primaryCallback={async () => {
const FILE_NAME = 'backup.txt';
const filePath = `${RNFS.DocumentDirectoryPath}/${FILE_NAME}`;
const gibberishData =
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lore";
await RNFS.writeFile(filePath, gibberishData, 'utf8');
}}
/>
</>
)}
{/* // ! Remove this later | Only for reproducing condition for tester */}
<ActivityIndicatorView visible={loading} showLoader />
</Box>
</ScreenWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CommonActions, useNavigation } from '@react-navigation/native';
import { useQuery } from '@realm/react';
import { Box, useColorMode } from 'native-base';
import React, { useContext, useEffect, useState } from 'react';
import { Platform } from 'react-native';
import KeeperModal from 'src/components/KeeperModal';
import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify';
import { wp } from 'src/constants/responsive';
Expand All @@ -26,7 +27,10 @@ const SettingModal = ({ isUaiFlow, confirmPass, setConfirmPass }) => {
useEffect(() => {
if (confirmPass || isUaiFlow) {
navigation.setParams({ isUaiFlow: false });
setConfirmPassVisible(true);
const delay = Platform.OS === 'ios' ? 400 : 0;
setTimeout(() => {
setConfirmPassVisible(true);
}, delay);
}
}, [confirmPass, isUaiFlow]);

Expand Down
Loading
Loading