Files
libp2p-native-bridge/implementations/SettingsService.ts
Chris Daßler 6f1d6ec37b Initial commit: libp2p-native-bridge package
- Extracted libp2p component from main app
- Created modular package structure with interfaces and implementations
- Added dependency injection for NativeModules
- Configured for IOR loading from Gitea
- Added comprehensive README and documentation
2025-08-29 11:18:37 +02:00

298 lines
8.1 KiB
TypeScript

/**
* 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<AppSettings> {
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<AppSettings> {
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<void> {
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<void> {
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<K extends keyof AppSettings>(key: K): AppSettings[K] {
return this.currentSettings[key];
}
/**
* Update specific setting
*/
async updateSetting<K extends keyof AppSettings>(key: K, value: AppSettings[K]): Promise<void> {
const newSettings = { ...this.currentSettings, [key]: value };
await this.saveSettings(newSettings);
}
/**
* Reset all application data
*/
async resetAllData(): Promise<void> {
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<void> {
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<object> {
try {
const allKeys = await AsyncStorage.getAllKeys();
const allData: Record<string, unknown> = {};
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<boolean> {
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);