Linting refactor context (#771)

This commit is contained in:
Leendert de Borst
2025-05-04 13:48:24 +02:00
parent 15d707615a
commit f06cf511eb
4 changed files with 51 additions and 179 deletions

View File

@@ -1,11 +1,12 @@
import React, { createContext, useContext, useState, useEffect, useMemo, useCallback, useRef } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useDb } from './DbContext';
import { AppState, Platform, Linking } from 'react-native';
import { AppState, Platform } from 'react-native';
import { router, usePathname } from 'expo-router';
import { NavigationContainerRef, ParamListBase } from '@react-navigation/native';
import * as LocalAuthentication from 'expo-local-authentication';
import NativeVaultManager from '../specs/NativeVaultManager';
import { useDb } from '@/context/DbContext';
import NativeVaultManager from '@/specs/NativeVaultManager';
// Create a navigation reference
export const navigationRef = React.createRef<NavigationContainerRef<ParamListBase>>();
@@ -214,55 +215,55 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
/**
* Get the auto-lock timeout from the iOS credentials manager
*/
const getAutoLockTimeout = async (): Promise<number> => {
const getAutoLockTimeout = useCallback(async (): Promise<number> => {
try {
return await NativeVaultManager.getAutoLockTimeout();
} catch (error) {
console.error('Failed to get auto-lock timeout:', error);
return 0;
}
}
}, []);
/**
* Set the auto-lock timeout in the iOS credentials manager
*/
const setAutoLockTimeout = async (timeout: number) => {
const setAutoLockTimeout = useCallback(async (timeout: number): Promise<void> => {
try {
await NativeVaultManager.setAutoLockTimeout(timeout);
} catch (error) {
console.error('Failed to update iOS auto-lock timeout:', error);
}
};
}, []);
const isVaultUnlocked = async (): Promise<boolean> => {
/**
* Check if the vault is unlocked.
*/
const isVaultUnlocked = useCallback(async (): Promise<boolean> => {
try {
return await NativeVaultManager.isVaultUnlocked();
} catch (error) {
console.error('Failed to check vault status:', error);
return false;
}
};
}, []);
// Handle app state changes
useEffect(() => {
const subscription = AppState.addEventListener('change', async (nextAppState) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
// App coming to foreground
console.log('App coming to foreground in AuthContext');
if (!pathname?.includes('unlock') && !pathname?.includes('login')) {
try {
// Check if vault is unlocked.
const isUnlocked = await isVaultUnlocked();
if (!isUnlocked) {
// Database connection failed, navigate to unlock flow
console.log('Vault is not unlocked anymore, navigating to unlock flow');
router.replace('/sync');
} else {
console.log('Vault is still unlocked, staying on current screen');
// Vault is still unlocked, staying on current screen
}
} catch (error) {
} catch {
// Database query failed, navigate to unlock flow
console.log('Failed to check vault status, navigating to unlock flow:', error);
router.replace('/sync');
}
}
@@ -270,10 +271,10 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
appState.current = nextAppState;
});
return () => {
return (): void => {
subscription.remove();
};
}, [isVaultUnlocked]);
}, [isVaultUnlocked, pathname]);
/**
* Load iOS Autofill state from storage

View File

@@ -1,8 +1,9 @@
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
import NativeVaultManager from '@/specs/NativeVaultManager';
import SqliteClient from '@/utils/SqliteClient';
import { VaultResponse } from '@/utils/types/webapi/VaultResponse';
import { VaultMetadata } from '@/utils/types/messaging/VaultMetadata';
import NativeVaultManager from '../specs/NativeVaultManager';
type DbContextType = {
sqliteClient: SqliteClient | null;
@@ -24,7 +25,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
/**
* SQLite client is initialized in constructor as it passes SQL queries to the native module.
*/
const sqliteClient = new SqliteClient();
const sqliteClient = useMemo(() => new SqliteClient(), []);
/**
* Database initialization state. If true, the database has been initialized and the dbAvailable state is correct.
@@ -36,9 +37,25 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
*/
const [dbAvailable, setDbAvailable] = useState(false);
/**
* Unlock the vault in the native module which will decrypt the database using the stored encryption key
* and load it into memory.
*/
const unlockVault = useCallback(async () : Promise<boolean> => {
try {
await NativeVaultManager.unlockVault();
return true;
} catch (error) {
console.error('Failed to unlock vault:', error);
return false;
}
}, []);
const initializeDatabase = useCallback(async (vaultResponse: VaultResponse, derivedKey: string | null = null) => {
// If the derived key is provided, store it in the keychain.
// Otherwise we assume the encryption key is already stored in the keychain.
/*
* If the derived key is provided, store it in the keychain.
* Otherwise we assume the encryption key is already stored in the keychain.
*/
if (derivedKey) {
await sqliteClient.storeEncryptionKey(derivedKey);
}
@@ -58,7 +75,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
setDbInitialized(true);
setDbAvailable(true);
}, []);
}, [sqliteClient, unlockVault]);
const checkStoredVault = useCallback(async () => {
try {
@@ -67,25 +84,25 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
// Get metadata from SQLite client
const metadata = await sqliteClient.getVaultMetadata();
if (metadata) {
console.log('Vault metadata found, setting dbInitialized and dbAvailable to true');
// Vault metadata found, set database initialization state
setDbInitialized(true);
setDbAvailable(true);
} else {
console.log('Vault metadata not found, setting dbInitialized and dbAvailable to false');
// Vault metadata not found, set database initialization state
setDbInitialized(true);
setDbAvailable(false);
}
} else {
console.log('Vault not initialized, setting dbInitialized and dbAvailable to false');
// Vault not initialized, set database initialization state
setDbInitialized(true);
setDbAvailable(false);
}
} catch (error) {
console.error('Error checking vault initialization:', error);
} catch {
// Error checking vault initialization, set database initialization state
setDbInitialized(true);
setDbAvailable(false);
}
}, []);
}, [sqliteClient]);
/**
* Check if database is initialized and try to retrieve vault from background
@@ -104,26 +121,12 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
NativeVaultManager.clearVault();
}, []);
/**
* Unlock the vault in the native module which will decrypt the database using the stored encryption key
* and load it into memory.
*/
const unlockVault = useCallback(async () : Promise<boolean> => {
try {
await NativeVaultManager.unlockVault();
return true;
} catch (error) {
console.error('Failed to unlock vault:', error);
return false;
}
}, []);
/**
* Get the current vault metadata directly from SQLite client
*/
const getVaultMetadata = useCallback(async () : Promise<VaultMetadata | null> => {
return await sqliteClient.getVaultMetadata();
}, []);
}, [sqliteClient]);
/**
* Test if the database is working with the provided (to be stored) encryption key by performing a simple query
@@ -146,10 +149,11 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
}
return false;
} catch (error) {
} catch {
// Error testing database connection, return false
return false;
}
}, []);
}, [sqliteClient, unlockVault]);
const contextValue = useMemo(() => ({
sqliteClient,

View File

@@ -1,134 +0,0 @@
import React, { createContext, useContext, useState, useMemo, useEffect, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
/**
* Theme type.
*/
type Theme = 'light' | 'dark' | 'system';
/**
* Theme preference key in storage.
*/
const THEME_PREFERENCE_KEY = 'local:theme';
/**
* Theme context type.
*/
type ThemeContextType = {
theme: Theme;
setTheme: (theme: Theme) => void;
isDarkMode: boolean;
}
/**
* Theme context.
*/
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
/**
* Theme provider
*/
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
/**
* Theme state that can be 'light', 'dark', or 'system'.
*/
const [theme, setTheme] = useState<Theme>('system');
/**
* Tracks whether dark mode is active (based on theme or system preference).
*/
const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
useEffect(() => {
/**
* Load theme setting from storage.
*/
const loadTheme = async () : Promise<void> => {
const savedTheme = await getTheme();
setTheme(savedTheme);
};
loadTheme();
}, []);
/**
* Set the theme and save to storage.
*/
const updateTheme = useCallback((newTheme: Theme): void => {
setTheme(newTheme);
setStoredTheme(newTheme);
}, []);
/**
* Get the theme from storage.
*/
const getTheme = async (): Promise<Theme> => {
return (await AsyncStorage.getItem(THEME_PREFERENCE_KEY) as Theme) || 'system';
};
/**
* Set the theme in storage.
*/
const setStoredTheme = async (theme: Theme): Promise<void> => {
await AsyncStorage.setItem(THEME_PREFERENCE_KEY, theme);
};
/**
* Effect to apply theme to document and handle system preference changes
*/
useEffect(() => {
/**
* Update the dark mode status.
*/
const updateDarkMode = (): void => {
if (theme === 'system') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setIsDarkMode(prefersDark);
document.documentElement.classList.toggle('dark', prefersDark);
} else {
const isDark = theme === 'dark';
setIsDarkMode(isDark);
document.documentElement.classList.toggle('dark', isDark);
}
};
// Initial update
updateDarkMode();
// Listen for system preference changes if using 'system' theme
if (theme === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
/**
* Update the dark mode status when the system preference changes.
*/
const handler = () : void => updateDarkMode();
mediaQuery.addEventListener('change', handler);
return () : void => mediaQuery.removeEventListener('change', handler);
}
}, [theme]);
const value = useMemo(
() => ({
theme,
setTheme: updateTheme,
isDarkMode,
}),
[theme, isDarkMode, updateTheme]
);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};
/**
* Hook to use theme state
*/
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};

View File

@@ -1,6 +1,7 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { useAuth } from '@/context/AuthContext';
import { WebApiService } from '@/utils/WebApiService';
import { useAuth } from './AuthContext';
const WebApiContext = createContext<WebApiService | null>(null);