Files
aliasvault/apps/mobile-app/app/initialize.tsx

181 lines
5.3 KiB
TypeScript

import { useRouter } from 'expo-router';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Alert, StyleSheet } from 'react-native';
import { useVaultSync } from '@/hooks/useVaultSync';
import LoadingIndicator from '@/components/LoadingIndicator';
import { ThemedView } from '@/components/themed/ThemedView';
import { useAuth } from '@/context/AuthContext';
import { useDb } from '@/context/DbContext';
import { useWebApi } from '@/context/WebApiContext';
import NativeVaultManager from '@/specs/NativeVaultManager';
/**
* Initialize page that handles all boot logic.
*/
export default function Initialize() : React.ReactNode {
const router = useRouter();
const [status, setStatus] = useState('');
const hasInitialized = useRef(false);
const { t } = useTranslation();
const { initializeAuth } = useAuth();
const { syncVault } = useVaultSync();
const dbContext = useDb();
const webApi = useWebApi();
useEffect(() => {
// Ensure this only runs once.
if (hasInitialized.current) {
return;
}
hasInitialized.current = true;
/**
* Initialize the app.
*/
const initializeApp = async () : Promise<void> => {
/**
* Handle vault unlocking process.
*/
async function handleVaultUnlock() : Promise<void> {
const { enabledAuthMethods } = await initializeAuth();
try {
const hasEncryptedDatabase = await NativeVaultManager.hasEncryptedDatabase();
if (hasEncryptedDatabase) {
const isFaceIDEnabled = enabledAuthMethods.includes('faceid');
if (!isFaceIDEnabled) {
router.replace('/unlock');
return;
}
setStatus(t('app.status.unlockingVault'));
const isUnlocked = await dbContext.unlockVault();
if (isUnlocked) {
await new Promise(resolve => setTimeout(resolve, 750));
setStatus(t('app.status.decryptingVault'));
await new Promise(resolve => setTimeout(resolve, 750));
// Check if the vault is up to date, if not, redirect to the upgrade page.
if (await dbContext.hasPendingMigrations()) {
router.replace('/upgrade');
return;
}
router.replace('/(tabs)/credentials');
return;
}
router.replace('/unlock');
return;
} else {
router.replace('/unlock');
return;
}
} catch {
router.replace('/unlock');
return;
}
}
/**
* Initialize the app.
*/
const initialize = async () : Promise<void> => {
const { isLoggedIn } = await initializeAuth();
if (!isLoggedIn) {
router.replace('/login');
return;
}
// First perform vault sync
await syncVault({
initialSync: true,
/**
* Handle the status update.
*/
onStatus: (message) => {
setStatus(message);
},
/**
* Handle successful vault sync and continue with vault unlock flow.
*/
onSuccess: async () => {
// Continue with the rest of the flow after successful sync
handleVaultUnlock();
},
/**
* Handle offline state and prompt user for action.
*/
onOffline: async () => {
Alert.alert(
t('app.alerts.syncIssue'),
t('app.alerts.syncIssueMessage'),
[
{
text: t('app.alerts.openLocalVault'),
/**
* Handle opening vault in read-only mode.
*/
onPress: async () : Promise<void> => {
setStatus(t('app.status.openingVaultReadOnly'));
await handleVaultUnlock();
}
},
{
text: t('app.alerts.retrySync'),
/**
* Handle retrying the connection.
*/
onPress: () : void => {
setStatus(t('app.status.retryingConnection'));
initialize();
}
}
]
);
},
/**
* Handle error during vault sync.
*/
onError: async (error: string) => {
// Show modal with error message
Alert.alert(t('app.alerts.error'), error);
// The logout user and navigate to the login screen.
await webApi.logout(error);
router.replace('/login');
},
/**
* On upgrade required.
*/
onUpgradeRequired: () : void => {
router.replace('/upgrade');
},
});
};
initialize();
};
initializeApp();
}, [dbContext, syncVault, initializeAuth, webApi, router, t]);
const styles = StyleSheet.create({
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
});
return (
<ThemedView style={styles.container}>
{status ? <LoadingIndicator status={status} /> : null}
</ThemedView>
);
}