From f06cf511ebb3cf31027597343563747569def978 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 4 May 2025 13:48:24 +0200 Subject: [PATCH] Linting refactor context (#771) --- apps/mobile-app/context/AuthContext.tsx | 33 +++--- apps/mobile-app/context/DbContext.tsx | 60 +++++----- apps/mobile-app/context/ThemeContext.tsx | 134 ---------------------- apps/mobile-app/context/WebApiContext.tsx | 3 +- 4 files changed, 51 insertions(+), 179 deletions(-) delete mode 100644 apps/mobile-app/context/ThemeContext.tsx diff --git a/apps/mobile-app/context/AuthContext.tsx b/apps/mobile-app/context/AuthContext.tsx index e3afea957..811accf3a 100644 --- a/apps/mobile-app/context/AuthContext.tsx +++ b/apps/mobile-app/context/AuthContext.tsx @@ -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>(); @@ -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 => { + const getAutoLockTimeout = useCallback(async (): Promise => { 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 => { try { await NativeVaultManager.setAutoLockTimeout(timeout); } catch (error) { console.error('Failed to update iOS auto-lock timeout:', error); } - }; + }, []); - const isVaultUnlocked = async (): Promise => { + /** + * Check if the vault is unlocked. + */ + const isVaultUnlocked = useCallback(async (): Promise => { 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 diff --git a/apps/mobile-app/context/DbContext.tsx b/apps/mobile-app/context/DbContext.tsx index 79a2ff196..21394ad71 100644 --- a/apps/mobile-app/context/DbContext.tsx +++ b/apps/mobile-app/context/DbContext.tsx @@ -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 => { + 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 => { - 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 => { 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, diff --git a/apps/mobile-app/context/ThemeContext.tsx b/apps/mobile-app/context/ThemeContext.tsx deleted file mode 100644 index f16683bb7..000000000 --- a/apps/mobile-app/context/ThemeContext.tsx +++ /dev/null @@ -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(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('system'); - - /** - * Tracks whether dark mode is active (based on theme or system preference). - */ - const [isDarkMode, setIsDarkMode] = useState(false); - - useEffect(() => { - /** - * Load theme setting from storage. - */ - const loadTheme = async () : Promise => { - 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 => { - return (await AsyncStorage.getItem(THEME_PREFERENCE_KEY) as Theme) || 'system'; - }; - - /** - * Set the theme in storage. - */ - const setStoredTheme = async (theme: Theme): Promise => { - 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 ( - - {children} - - ); -}; - -/** - * 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; -}; \ No newline at end of file diff --git a/apps/mobile-app/context/WebApiContext.tsx b/apps/mobile-app/context/WebApiContext.tsx index 4daff274a..813c27fe7 100644 --- a/apps/mobile-app/context/WebApiContext.tsx +++ b/apps/mobile-app/context/WebApiContext.tsx @@ -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(null);