/** * Settings Service * * Manages application settings and provides methods to: * - Load/save settings * - Apply settings to native modules * - Reset application data */ import { LoggerComponent } from 'ior:gitea:gitea.metatrom.net:universal-components/logger@1.0.0'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { NativeModules, Platform } from 'react-native'; // Storage keys const SETTINGS_KEY = '@libp2p_settings'; // Default settings export const DEFAULT_SETTINGS = { tcpPort: 10000, wsPort: 10005, discoveryTimeout: 30000, enableDebugLogs: false, autoDiscovery: false, useCustomPorts: false, dhtServerUrl: 'ws://192.168.188.40:3000/ws', // This is the default/fallback DHT server URL }; export interface AppSettings { tcpPort: number; wsPort: number; discoveryTimeout: number; enableDebugLogs: boolean; autoDiscovery: boolean; useCustomPorts: boolean; dhtServerUrl: string; } export interface INativeModules { Libp2pModule?: any; DiscoveryModule?: any; SecureStorageModule?: any; } export class SettingsService { private static instance: SettingsService | null = null; private currentSettings: AppSettings = DEFAULT_SETTINGS; private logger = new LoggerComponent('SettingsService'); private nativeModules: INativeModules; private constructor(nativeModules?: INativeModules) { // Allow dependency injection of native modules this.nativeModules = nativeModules || NativeModules; } public static getInstance(nativeModules?: INativeModules): SettingsService { if (!SettingsService.instance) { SettingsService.instance = new SettingsService(nativeModules); } return SettingsService.instance; } /** * Reset the singleton instance (useful for testing) */ public static resetInstance(): void { SettingsService.instance = null; } /** * Initialize settings service */ async initialize(): Promise { try { const settings = await this.loadSettings(); this.currentSettings = settings; return settings; } catch (error) { this.logger.error('[SettingsService] Failed to initialize:', error); return DEFAULT_SETTINGS; } } /** * Load settings from storage */ async loadSettings(): Promise { try { const stored = await AsyncStorage.getItem(SETTINGS_KEY); if (stored) { const parsed = JSON.parse(stored); return { ...DEFAULT_SETTINGS, ...parsed }; } } catch (error) { this.logger.error('[SettingsService] Failed to load settings:', error); } return DEFAULT_SETTINGS; } /** * Save settings to storage */ async saveSettings(settings: AppSettings): Promise { try { await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); this.currentSettings = settings; // Apply settings to native modules if needed await this.applySettings(settings); } catch (error) { this.logger.error('[SettingsService] Failed to save settings:', error); throw error; } } /** * Apply settings to native modules */ private async applySettings(settings: AppSettings): Promise { const { Libp2pModule, DiscoveryModule } = this.nativeModules; // Apply port settings if custom ports are enabled if (settings.useCustomPorts) { // This would need to be passed to the native modules // when starting the libp2p node this.logger.debug('[SettingsService] Custom ports:', { tcp: settings.tcpPort, ws: settings.wsPort, }); } // Apply debug logging if (Libp2pModule?.setDebugConsoleOutput) { Libp2pModule.setDebugConsoleOutput(settings.enableDebugLogs); } // Apply discovery timeout if (DiscoveryModule?.setDiscoveryTimeout) { DiscoveryModule.setDiscoveryTimeout(settings.discoveryTimeout); } } /** * Get current settings */ getSettings(): AppSettings { return this.currentSettings; } /** * Get specific setting value */ getSetting(key: K): AppSettings[K] { return this.currentSettings[key]; } /** * Update specific setting */ async updateSetting(key: K, value: AppSettings[K]): Promise { const newSettings = { ...this.currentSettings, [key]: value }; await this.saveSettings(newSettings); } /** * Reset all application data */ async resetAllData(): Promise { try { this.logger.info('[SettingsService] Starting app reset...'); // Clear all AsyncStorage (this includes dht_discovered_users) const allKeys = await AsyncStorage.getAllKeys(); this.logger.debug('[SettingsService] Clearing AsyncStorage keys:', allKeys); await AsyncStorage.multiRemove(allKeys); // Clear native module data await this.clearNativeData(); // Reset settings to defaults this.currentSettings = DEFAULT_SETTINGS; this.logger.info('[SettingsService] App reset complete'); } catch (error) { this.logger.error('[SettingsService] Failed to reset app data:', error); throw error; } } /** * Clear native module stored data */ private async clearNativeData(): Promise { const { Libp2pModule, DiscoveryModule, SecureStorageModule } = this.nativeModules; try { if (Platform.OS === 'ios') { // Clear iOS UserDefaults if (Libp2pModule?.clearStoredData) { await Libp2pModule.clearStoredData(); } if (DiscoveryModule?.clearStoredData) { await DiscoveryModule.clearStoredData(); } if (SecureStorageModule?.clearAll) { await SecureStorageModule.clearAll(); } } else if (Platform.OS === 'android') { // Clear Android SharedPreferences if (Libp2pModule?.clearStoredData) { await Libp2pModule.clearStoredData(); } if (DiscoveryModule?.clearStoredData) { await DiscoveryModule.clearStoredData(); } if (SecureStorageModule?.clearAll) { await SecureStorageModule.clearAll(); } } } catch (error) { this.logger.error('[SettingsService] Failed to clear native data:', error); // Continue even if native clear fails } } /** * Export all app data for debugging */ async exportDebugData(): Promise { try { const allKeys = await AsyncStorage.getAllKeys(); const allData: Record = {}; for (const key of allKeys) { try { const value = await AsyncStorage.getItem(key); allData[key] = value ? JSON.parse(value) : null; } catch { // If JSON parse fails, store as string allData[key] = await AsyncStorage.getItem(key); } } return { settings: this.currentSettings, storedData: allData, platform: Platform.OS, timestamp: new Date().toISOString(), }; } catch (error) { this.logger.error('[SettingsService] Failed to export debug data:', error); throw error; } } /** * Check if this is first app launch */ async isFirstLaunch(): Promise { try { const hasSettings = await AsyncStorage.getItem(SETTINGS_KEY); return !hasSettings; } catch { return true; } } /** * Get storage info */ async getStorageInfo(): Promise<{ keys: string[]; totalSize: number; }> { try { const allKeys = await AsyncStorage.getAllKeys(); let totalSize = 0; for (const key of allKeys) { const value = await AsyncStorage.getItem(key); if (value) { totalSize += value.length; } } return { keys: [...allKeys], // Convert readonly array to mutable array totalSize, }; } catch (error) { this.logger.error('[SettingsService] Failed to get storage info:', error); return { keys: [], totalSize: 0 }; } } } // Export singleton getter function instead of instance export const getSettingsService = (nativeModules?: INativeModules) => SettingsService.getInstance(nativeModules);