From 09f61bd7a2bfb4e61e1e49e8e9c9c1a849794a32 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 10 Oct 2025 16:35:46 +0200 Subject: [PATCH] Cleanup RN AsyncStorage calls (#520) --- apps/mobile-app/app/login-settings.tsx | 13 +---- apps/mobile-app/context/AuthContext.tsx | 67 +++++++++++++------------ apps/mobile-app/utils/ApiUrlUtility.ts | 11 +--- apps/mobile-app/utils/WebApiService.ts | 47 ----------------- 4 files changed, 36 insertions(+), 102 deletions(-) diff --git a/apps/mobile-app/app/login-settings.tsx b/apps/mobile-app/app/login-settings.tsx index 9b16b28e5..4c74267bf 100644 --- a/apps/mobile-app/app/login-settings.tsx +++ b/apps/mobile-app/app/login-settings.tsx @@ -47,17 +47,8 @@ export default function SettingsScreen() : React.ReactNode { */ const loadStoredSettings = useCallback(async () : Promise => { try { - // Try to get API URL from native layer first, fallback to AsyncStorage - let apiUrl: string | null = null; - try { - apiUrl = await NativeVaultManager.getApiUrl(); - } catch (nativeError) { - console.warn('Failed to get API URL from native layer, falling back to AsyncStorage:', nativeError); - apiUrl = await AsyncStorage.getItem('apiUrl'); - } - + const apiUrl = await NativeVaultManager.getApiUrl(); const matchingOption = DEFAULT_OPTIONS.find(opt => opt.value === apiUrl); - if (matchingOption) { setSelectedOption(matchingOption.value); } else if (apiUrl) { @@ -81,8 +72,6 @@ export default function SettingsScreen() : React.ReactNode { const handleOptionChange = async (value: string) : Promise => { setSelectedOption(value); if (value !== 'custom') { - // Sync to both AsyncStorage and native layer - await AsyncStorage.setItem('apiUrl', value); try { await NativeVaultManager.setApiUrl(value); } catch (error) { diff --git a/apps/mobile-app/context/AuthContext.tsx b/apps/mobile-app/context/AuthContext.tsx index 69de2d157..8c056cac2 100644 --- a/apps/mobile-app/context/AuthContext.tsx +++ b/apps/mobile-app/context/AuthContext.tsx @@ -126,22 +126,10 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children * Set auth tokens in storage as part of the login process. After db is initialized, the login method should be called as well. */ const setAuthTokens = useCallback(async (username: string, accessToken: string, refreshToken: string): Promise => { - // Store username in native layer (new approach) await NativeVaultManager.setUsername(username); + await NativeVaultManager.setAuthTokens(accessToken, refreshToken); - // Keep AsyncStorage for backward compatibility / migration - // TODO: Remove AsyncStorage username storage in future version - await AsyncStorage.setItem('username', username); - await AsyncStorage.setItem('accessToken', accessToken); - await AsyncStorage.setItem('refreshToken', refreshToken); - - // Sync tokens to native layer - try { - await NativeVaultManager.setAuthTokens(accessToken, refreshToken); - } catch (error) { - console.error('Failed to sync auth tokens to native layer:', error); - } - + // Update React state setUsername(username); }, []); @@ -150,38 +138,51 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children * @returns object containing whether the user is logged in and enabled auth methods */ const initializeAuth = useCallback(async (): Promise<{ isLoggedIn: boolean; enabledAuthMethods: AuthMethod[] }> => { - const accessToken = await AsyncStorage.getItem('accessToken') as string; - const refreshToken = await AsyncStorage.getItem('refreshToken') as string; + // Sync legacy config to native layer (can be removed in future version 0.25.0+) + syncLegacyConfigToNative(); - // Try to get username from native layer first (new approach) - let username = await NativeVaultManager.getUsername(); - - // Fallback to AsyncStorage for migration - if (!username) { - username = await AsyncStorage.getItem('username'); - // Migrate to native storage - if (username) { - await NativeVaultManager.setUsername(username); - } - } - - // Load offline mode from native layer - const offline = await NativeVaultManager.getOfflineMode(); - setIsOffline(offline); + const accessToken = await NativeVaultManager.getAccessToken(); + const username = await NativeVaultManager.getUsername(); + // Update local React state let isAuthenticated = false; let methods: AuthMethod[] = ['password']; - if (accessToken && refreshToken && username) { + // Check if user is logged in (has both access token and username) + if (accessToken && username) { setUsername(username); setIsLoggedIn(true); isAuthenticated = true; methods = await getEnabledAuthMethods(); } + + const offline = await NativeVaultManager.getOfflineMode(); + setIsInitialized(true); + setIsOffline(offline); return { isLoggedIn: isAuthenticated, enabledAuthMethods: methods }; }, [getEnabledAuthMethods]); + /** + * Sync legacy config to native layer + */ + const syncLegacyConfigToNative = useCallback(async (): Promise => { + // Migrate tokens from AsyncStorage to native on first launch, then remove to prevent repeated syncs + const accessToken = await AsyncStorage.getItem('accessToken'); + const refreshToken = await AsyncStorage.getItem('refreshToken'); + + if (accessToken && refreshToken) { + await NativeVaultManager.setAuthTokens(accessToken, refreshToken); + await AsyncStorage.multiRemove(['accessToken', 'refreshToken']); + } + + const username = await AsyncStorage.getItem('username'); + if (username) { + await NativeVaultManager.setUsername(username); + await AsyncStorage.removeItem('username'); + } + }, []); + /** * Set logged in status to true which refreshes the app. */ @@ -199,7 +200,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children await NativeVaultManager.clearAuthTokens(); // Clear from AsyncStorage (for backward compatibility) - // TODO: Remove AsyncStorage cleanup in future version + // TODO: Remove AsyncStorage cleanup in future version 0.25.0+ await AsyncStorage.removeItem('username'); await AsyncStorage.removeItem('accessToken'); await AsyncStorage.removeItem('refreshToken'); diff --git a/apps/mobile-app/utils/ApiUrlUtility.ts b/apps/mobile-app/utils/ApiUrlUtility.ts index 93607b99b..b783c4e39 100644 --- a/apps/mobile-app/utils/ApiUrlUtility.ts +++ b/apps/mobile-app/utils/ApiUrlUtility.ts @@ -1,4 +1,3 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; import { useState } from 'react'; import { AppInfo } from '@/utils/AppInfo'; @@ -21,7 +20,6 @@ export const useApiUrl = (): { */ const loadApiUrl = async (): Promise => { try { - // Try to get from native layer first const storedUrl = await NativeVaultManager.getApiUrl(); if (storedUrl && storedUrl.length > 0) { setApiUrl(storedUrl); @@ -29,14 +27,7 @@ export const useApiUrl = (): { setApiUrl(AppInfo.DEFAULT_API_URL); } } catch (error) { - console.warn('Failed to get API URL from native layer, falling back to AsyncStorage:', error); - // Fallback to AsyncStorage - const storedUrl = await AsyncStorage.getItem('apiUrl'); - if (storedUrl && storedUrl.length > 0) { - setApiUrl(storedUrl); - } else { - setApiUrl(AppInfo.DEFAULT_API_URL); - } + console.warn('Failed to get API URL from native layer:', error); } }; diff --git a/apps/mobile-app/utils/WebApiService.ts b/apps/mobile-app/utils/WebApiService.ts index 892001c49..371a1c79b 100644 --- a/apps/mobile-app/utils/WebApiService.ts +++ b/apps/mobile-app/utils/WebApiService.ts @@ -1,5 +1,3 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; - import { AppInfo } from '@/utils/AppInfo'; import type { StatusResponse, VaultResponse, AuthLogModel, RefreshToken } from '@/utils/dist/shared/models/webapi'; @@ -25,14 +23,6 @@ type NativeWebApiResponse = { * This class now acts as a proxy to the native layer, where all WebAPI calls are executed. */ export class WebApiService { - /** - * Constructor for the WebApiService class. - */ - public constructor() { - // Initialize API URL and tokens from AsyncStorage if they exist - this.syncLegacyConfigToNative(); - } - /** * Get the base URL for the API from settings. */ @@ -352,44 +342,7 @@ export class WebApiService { return apiUrl || AppInfo.DEFAULT_API_URL; } catch (error) { console.error('Failed to get API URL from native layer:', error); - // Fallback to AsyncStorage - const result = await AsyncStorage.getItem('apiUrl') as string; - if (result && result.length > 0) { - return result; - } return AppInfo.DEFAULT_API_URL; } } - - /** - * Sync configuration from AsyncStorage to native layer on initialization. - * - * This is primarily for backward compatibility / migration purposes: - * - Existing users who upgraded from a version without native WebAPI will have tokens in AsyncStorage - * - This ensures those tokens are migrated to the native layer on first launch after upgrade - * - For new installations, all tokens/config go directly to native layer, so this becomes a no-op - * - * TODO: This can be removed in a future version (e.g., after some time that 0.24.0 is released) - * and once most if not all active users have migrated. - */ - private async syncLegacyConfigToNative(): Promise { - try { - // Migrate API URL from AsyncStorage to native on first launch, then remove to prevent repeated syncs - const apiUrl = await AsyncStorage.getItem('apiUrl'); - if (apiUrl) { - await NativeVaultManager.setApiUrl(apiUrl); - await AsyncStorage.removeItem('apiUrl'); - } - - // Migrate tokens from AsyncStorage to native on first launch, then remove to prevent repeated syncs - const accessToken = await AsyncStorage.getItem('accessToken'); - const refreshToken = await AsyncStorage.getItem('refreshToken'); - if (accessToken && refreshToken) { - await NativeVaultManager.setAuthTokens(accessToken, refreshToken); - await AsyncStorage.multiRemove(['accessToken', 'refreshToken']); - } - } catch (error) { - console.error('Failed to sync config to native layer:', error); - } - } }