diff --git a/lib/runtime/secret_store.dart b/lib/runtime/secret_store.dart index eb786255..1e459d70 100644 --- a/lib/runtime/secret_store.dart +++ b/lib/runtime/secret_store.dart @@ -1,10 +1,3 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:path_provider/path_provider.dart'; - import 'runtime_models.dart'; abstract class SecureStorageClient { @@ -15,74 +8,6 @@ abstract class SecureStorageClient { Future delete({required String key}); } -class FlutterSecureStorageClient implements SecureStorageClient { - const FlutterSecureStorageClient(this._storage); - - final FlutterSecureStorage _storage; - - @override - Future read({required String key}) { - return _storage.read(key: key); - } - - @override - Future write({required String key, required String value}) { - return _storage.write(key: key, value: value); - } - - @override - Future delete({required String key}) { - return _storage.delete(key: key); - } -} - -class FileSecureStorageClient implements SecureStorageClient { - FileSecureStorageClient(this._directoryResolver); - - final Future Function() _directoryResolver; - - @override - Future delete({required String key}) async { - final file = await _fileForKey(key); - if (file == null || !await file.exists()) { - return; - } - await file.delete(); - } - - @override - Future read({required String key}) async { - final file = await _fileForKey(key); - if (file == null || !await file.exists()) { - return null; - } - final value = (await file.readAsString()).trim(); - return value.isEmpty ? null : value; - } - - @override - Future write({required String key, required String value}) async { - final file = await _fileForKey(key); - if (file == null) { - throw StateError('Secure storage directory unavailable for $key'); - } - await file.writeAsString(value, flush: true); - } - - Future _fileForKey(String key) async { - final directory = await _directoryResolver(); - if (directory == null) { - return null; - } - final secureDirectory = Directory('${directory.path}/secure-storage'); - if (!await secureDirectory.exists()) { - await secureDirectory.create(recursive: true); - } - final safeKey = base64Url.encode(utf8.encode(key)).replaceAll('=', ''); - return File('${secureDirectory.path}/$safeKey.txt'); - } -} - class SecretStore { SecretStore({ Future Function()? fallbackDirectoryPathResolver, @@ -90,222 +15,94 @@ class SecretStore { Future Function()? defaultSupportDirectoryPathResolver, SecureStorageClient? secureStorage, bool enableSecureStorage = true, - }) : _fallbackDirectoryPathResolver = fallbackDirectoryPathResolver, - _databasePathResolver = databasePathResolver, - _defaultSupportDirectoryPathResolver = - defaultSupportDirectoryPathResolver, - _secureStorageOverride = secureStorage, - _enableSecureStorage = enableSecureStorage; + }); - static const Duration _secureStorageTimeout = Duration(seconds: 5); static const String legacyLocalStateKey = 'xworkmate.local_state.key'; - static const String _legacyGatewayTokenKey = 'xworkmate.gateway.token'; - static const String _legacyGatewayPasswordKey = 'xworkmate.gateway.password'; - static const String _gatewayDeviceIdKey = 'xworkmate.gateway.device.id'; - static const String _gatewayDevicePublicKeyKey = - 'xworkmate.gateway.device.public_key'; - static const String _gatewayDevicePrivateKeyKey = - 'xworkmate.gateway.device.private_key'; - static const String _ollamaCloudApiKeyKey = 'xworkmate.ollama.cloud.api_key'; - static const String _vaultTokenKey = 'xworkmate.vault.token'; - static const String _aiGatewayApiKeyKey = 'xworkmate.ai_gateway.api_key'; - static const Map _legacyFallbackFileNames = { - _legacyGatewayTokenKey: 'gateway-token.txt', - _legacyGatewayPasswordKey: 'gateway-password.txt', - _ollamaCloudApiKeyKey: 'ollama-cloud-api-key.txt', - _vaultTokenKey: 'vault-token.txt', - _aiGatewayApiKeyKey: 'ai-gateway-api-key.txt', - }; + Future initialize() async {} - final Map _memorySecure = {}; - final Future Function()? _fallbackDirectoryPathResolver; - final Future Function()? _databasePathResolver; - final Future Function()? _defaultSupportDirectoryPathResolver; - final SecureStorageClient? _secureStorageOverride; - final bool _enableSecureStorage; - SecureStorageClient? _secureStorage; - bool _initialized = false; - - Future initialize() async { - if (_initialized) { - return; - } - await _ensureDurableStorageLayout(); - if (_secureStorageOverride != null) { - _secureStorage = _secureStorageOverride; - } else if (_enableSecureStorage) { - try { - _secureStorage = FlutterSecureStorageClient( - const FlutterSecureStorage(), - ); - } catch (_) { - _secureStorage = FileSecureStorageClient( - () => _resolveFallbackDirectory(), - ); - } - } else { - _secureStorage = FileSecureStorageClient( - () => _resolveFallbackDirectory(), - ); - } - _initialized = true; - } - - Future _ensureDurableStorageLayout() async { - final fallbackDirectory = await _resolveFallbackDirectory(); - if (fallbackDirectory == null) { - throw StateError( - 'Durable secret storage layout unavailable: cannot resolve fallback directory.', - ); - } - final secureStorageDirectory = Directory( - '${fallbackDirectory.path}/secure-storage', + Future loadGatewayToken({int? profileIndex}) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', ); - if (!await secureStorageDirectory.exists()) { - await secureStorageDirectory.create(recursive: true); - } } - Future loadGatewayToken({int? profileIndex}) async { - if (profileIndex != null) { - final scopedValue = await _readSecure( - _gatewayTokenKeyForProfile(profileIndex), - ); - if ((scopedValue ?? '').trim().isNotEmpty) { - return scopedValue; - } - return _readSecure(_legacyGatewayTokenKey); - } - final legacyValue = await _readSecure(_legacyGatewayTokenKey); - if ((legacyValue ?? '').trim().isNotEmpty) { - return legacyValue; - } - for (final index in _gatewayProfileFallbackOrder) { - final scopedValue = await _readSecure(_gatewayTokenKeyForProfile(index)); - if ((scopedValue ?? '').trim().isNotEmpty) { - return scopedValue; - } - } - return null; + Future saveGatewayToken(String value, {int? profileIndex}) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } - Future saveGatewayToken(String value, {int? profileIndex}) => - _writeSecure( - profileIndex == null - ? _legacyGatewayTokenKey - : _gatewayTokenKeyForProfile(profileIndex), - value, - ); - - Future clearGatewayToken({int? profileIndex}) => _deleteSecure( - profileIndex == null - ? _legacyGatewayTokenKey - : _gatewayTokenKeyForProfile(profileIndex), - ); - - Future loadGatewayPassword({int? profileIndex}) async { - if (profileIndex != null) { - final scopedValue = await _readSecure( - _gatewayPasswordKeyForProfile(profileIndex), - ); - if ((scopedValue ?? '').trim().isNotEmpty) { - return scopedValue; - } - return _readSecure(_legacyGatewayPasswordKey); - } - final legacyValue = await _readSecure(_legacyGatewayPasswordKey); - if ((legacyValue ?? '').trim().isNotEmpty) { - return legacyValue; - } - for (final index in _gatewayProfileFallbackOrder) { - final scopedValue = await _readSecure( - _gatewayPasswordKeyForProfile(index), - ); - if ((scopedValue ?? '').trim().isNotEmpty) { - return scopedValue; - } - } - return null; + Future clearGatewayToken({int? profileIndex}) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } - Future saveGatewayPassword(String value, {int? profileIndex}) => - _writeSecure( - profileIndex == null - ? _legacyGatewayPasswordKey - : _gatewayPasswordKeyForProfile(profileIndex), - value, - ); + Future loadGatewayPassword({int? profileIndex}) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future clearGatewayPassword({int? profileIndex}) => _deleteSecure( - profileIndex == null - ? _legacyGatewayPasswordKey - : _gatewayPasswordKeyForProfile(profileIndex), - ); + Future saveGatewayPassword(String value, {int? profileIndex}) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future loadOllamaCloudApiKey() => _readSecure(_ollamaCloudApiKeyKey); + Future clearGatewayPassword({int? profileIndex}) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future saveOllamaCloudApiKey(String value) => - _writeSecure(_ollamaCloudApiKeyKey, value); + Future loadOllamaCloudApiKey() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future loadVaultToken() => _readSecure(_vaultTokenKey); + Future saveOllamaCloudApiKey(String value) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future saveVaultToken(String value) => - _writeSecure(_vaultTokenKey, value); + Future loadVaultToken() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future loadAiGatewayApiKey() => _readSecure(_aiGatewayApiKeyKey); + Future saveVaultToken(String value) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future saveAiGatewayApiKey(String value) => - _writeSecure(_aiGatewayApiKeyKey, value); + Future loadAiGatewayApiKey() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future clearAiGatewayApiKey() => _deleteSecure(_aiGatewayApiKeyKey); + Future saveAiGatewayApiKey(String value) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } - Future> loadSecureRefs() async { - await initialize(); - final legacyGatewayToken = await _readSecure(_legacyGatewayTokenKey); - final legacyGatewayPassword = await _readSecure(_legacyGatewayPasswordKey); - final deviceIdentity = await loadDeviceIdentity(); - final deviceToken = deviceIdentity == null - ? null - : await loadDeviceToken( - deviceId: deviceIdentity.deviceId, - role: 'operator', - ); - final ollamaKey = await loadOllamaCloudApiKey(); - final vaultToken = await loadVaultToken(); - final aiGatewayApiKey = await loadAiGatewayApiKey(); - final secureRefs = {}; - if (legacyGatewayToken case final value?) { - secureRefs['gateway_token'] = value; - } - if (legacyGatewayPassword case final value?) { - secureRefs['gateway_password'] = value; - } - for (var index = 0; index < kGatewayProfileListLength; index += 1) { - final scopedToken = await _readSecure(_gatewayTokenKeyForProfile(index)); - final scopedPassword = await _readSecure( - _gatewayPasswordKeyForProfile(index), - ); - if (scopedToken case final value?) { - secureRefs[_gatewayTokenRefKey(index)] = value; - } - if (scopedPassword case final value?) { - secureRefs[_gatewayPasswordRefKey(index)] = value; - } - } - if (deviceToken case final value?) { - secureRefs['gateway_device_token_operator'] = value; - } - if (ollamaKey case final value?) { - secureRefs['ollama_cloud_api_key'] = value; - } - if (vaultToken case final value?) { - secureRefs['vault_token'] = value; - } - if (aiGatewayApiKey case final value?) { - secureRefs['ai_gateway_api_key'] = value; - } - return secureRefs; + Future clearAiGatewayApiKey() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); + } + + Future> loadSecureRefs() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } static String gatewayTokenRefKey(int profileIndex) => @@ -314,87 +111,53 @@ class SecretStore { static String gatewayPasswordRefKey(int profileIndex) => _gatewayPasswordRefKey(profileIndex); - Future loadDeviceIdentity() async { - await initialize(); - final deviceId = await _readSecure(_gatewayDeviceIdKey); - final publicKey = await _readSecure(_gatewayDevicePublicKeyKey); - final privateKey = await _readSecure(_gatewayDevicePrivateKeyKey); - if (deviceId == null || publicKey == null || privateKey == null) { - return null; - } - return LocalDeviceIdentity( - deviceId: deviceId, - publicKeyBase64Url: publicKey, - privateKeyBase64Url: privateKey, - createdAtMs: DateTime.now().millisecondsSinceEpoch, + Future loadDeviceIdentity() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', ); } - Future saveDeviceIdentity(LocalDeviceIdentity identity) async { - await initialize(); - await _writeSecure(_gatewayDeviceIdKey, identity.deviceId); - await _writeSecure(_gatewayDevicePublicKeyKey, identity.publicKeyBase64Url); - await _writeSecure( - _gatewayDevicePrivateKeyKey, - identity.privateKeyBase64Url, + Future saveDeviceIdentity(LocalDeviceIdentity identity) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', ); } Future loadDeviceToken({ required String deviceId, required String role, - }) async { - await initialize(); - return _readSecure(_deviceTokenKey(deviceId, role)); + }) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } Future saveDeviceToken({ required String deviceId, required String role, required String token, - }) async { - await initialize(); - await _writeSecure(_deviceTokenKey(deviceId, role), token); + }) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } Future clearDeviceToken({ required String deviceId, required String role, - }) async { - await initialize(); - await _deleteSecure(_deviceTokenKey(deviceId, role)); + }) { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } - Future?> loadLegacyLocalStateKeyBytes() async { - await initialize(); - final current = (await _readSecureRaw(legacyLocalStateKey))?.trim() ?? ''; - if (current.isNotEmpty) { - return _base64UrlDecode(current); - } - final file = await _legacyLocalStateKeyFile(); - if (file == null || !await file.exists()) { - return null; - } - final value = (await file.readAsString()).trim(); - if (value.isEmpty) { - return null; - } - if (_secureStorage != null) { - try { - await _writeSecureValue(_secureStorage!, legacyLocalStateKey, value); - await file.delete(); - } catch (_) { - // Keep the fallback file available for future recovery attempts. - } - } - return _base64UrlDecode(value); + Future?> loadLegacyLocalStateKeyBytes() { + throw StateError( + 'Legacy secret persistence removed. New secret-path store is pending implementation.', + ); } - Future dispose() async { - _secureStorage = null; - _initialized = false; - _memorySecure.clear(); - } + Future dispose() async {} static String maskValue(String value) { final trimmed = value.trim(); @@ -407,267 +170,9 @@ class SecretStore { return '${trimmed.substring(0, 3)}••••${trimmed.substring(trimmed.length - 3)}'; } - Future _readSecure(String key) async { - await initialize(); - final direct = await _readSecureRaw(key); - if (direct != null && direct.trim().isNotEmpty) { - return direct.trim(); - } - final migrated = await _migrateLegacyFallbackFile(key); - if (migrated != null && migrated.trim().isNotEmpty) { - return migrated.trim(); - } - return null; - } - - Future _readSecureRaw(String key) async { - final client = await _ensureSecureStorageClient(); - try { - final value = await _readSecureValue(client, key); - if (value == null || value.trim().isEmpty) { - return null; - } - final trimmed = value.trim(); - _memorySecure[key] = trimmed; - return trimmed; - } catch (_) { - final promoted = await _promoteToFileSecureStorageFallback(); - if (!promoted || _secureStorage == null) { - throw StateError( - 'Durable secret storage unavailable for $key: failed to read secure value.', - ); - } - final value = await _readSecureValue(_secureStorage!, key); - if (value == null || value.trim().isEmpty) { - return null; - } - final trimmed = value.trim(); - _memorySecure[key] = trimmed; - return trimmed; - } - } - - Future _writeSecure(String key, String value) async { - await initialize(); - final trimmed = value.trim(); - if (trimmed.isEmpty) { - return; - } - final client = await _ensureSecureStorageClient(); - try { - await _writeSecureValue(client, key, trimmed); - _memorySecure[key] = trimmed; - final file = await _legacyFallbackFile(key); - if (file != null && await file.exists()) { - await file.delete(); - } - } catch (_) { - final promoted = await _promoteToFileSecureStorageFallback(); - if (promoted && _secureStorage != null) { - await _writeSecureValue(_secureStorage!, key, trimmed); - _memorySecure[key] = trimmed; - final file = await _legacyFallbackFile(key); - if (file != null && await file.exists()) { - await file.delete(); - } - return; - } - throw StateError( - 'Durable secret storage unavailable for $key: failed to write secure value.', - ); - } - } - - Future _deleteSecure(String key) async { - await initialize(); - final client = await _ensureSecureStorageClient(); - try { - await _deleteSecureValue(client, key); - } catch (_) { - final promoted = await _promoteToFileSecureStorageFallback(); - if (!promoted || _secureStorage == null) { - throw StateError( - 'Durable secret storage unavailable for $key: failed to delete secure value.', - ); - } - await _deleteSecureValue(_secureStorage!, key); - } - _memorySecure.remove(key); - final file = await _legacyFallbackFile(key); - if (file != null && await file.exists()) { - await file.delete(); - } - } - - Future _migrateLegacyFallbackFile(String key) async { - final file = await _legacyFallbackFile(key); - if (file == null || !await file.exists()) { - return null; - } - final value = (await file.readAsString()).trim(); - if (value.isEmpty) { - return null; - } - if (_secureStorage != null) { - try { - await _writeSecureValue(_secureStorage!, key, value); - await file.delete(); - } catch (_) { - // Leave the fallback file in place if migration fails. - } - } - _memorySecure[key] = value; - return value; - } - - Future _legacyFallbackFile(String key) async { - final fileName = _legacyFallbackFileNames[key]; - if (fileName == null) { - return null; - } - final directory = await _resolveFallbackDirectory(); - if (directory == null) { - return null; - } - return File('${directory.path}/$fileName'); - } - - Future _legacyLocalStateKeyFile() async { - final directory = await _resolveFallbackDirectory(); - if (directory == null) { - return null; - } - return File('${directory.path}/local-state-key.txt'); - } - - Future _resolveFallbackDirectory() async { - final fallbackRoot = await _resolvePath(_fallbackDirectoryPathResolver); - if (fallbackRoot != null) { - return _ensureDirectory(fallbackRoot); - } - final databasePath = await _resolvePath(_databasePathResolver); - if (databasePath != null) { - return _ensureDirectory(File(databasePath).parent.path); - } - final defaultSupportRoot = await _resolvePath( - _defaultSupportDirectoryPathResolver, - ); - if (defaultSupportRoot != null) { - return _ensureDirectory('$defaultSupportRoot/gateway-auth'); - } - try { - final supportDirectory = await getApplicationSupportDirectory(); - return _ensureDirectory( - '${supportDirectory.path}/xworkmate/gateway-auth', - ); - } catch (_) { - return null; - } - } - - Future _ensureDirectory(String path) async { - final directory = Directory(path); - if (!await directory.exists()) { - await directory.create(recursive: true); - } - return directory; - } - - Future _promoteToFileSecureStorageFallback() async { - if (_secureStorageOverride != null) { - return false; - } - final directory = await _resolveFallbackDirectory(); - if (directory == null) { - return false; - } - _secureStorage = FileSecureStorageClient(() async => directory); - return true; - } - - Future _readSecureValue(SecureStorageClient client, String key) { - final future = client.read(key: key); - if (client is FlutterSecureStorageClient) { - return future.timeout(_secureStorageTimeout); - } - return future; - } - - Future _writeSecureValue( - SecureStorageClient client, - String key, - String value, - ) { - final future = client.write(key: key, value: value); - if (client is FlutterSecureStorageClient) { - return future.timeout(_secureStorageTimeout); - } - return future; - } - - Future _deleteSecureValue(SecureStorageClient client, String key) { - final future = client.delete(key: key); - if (client is FlutterSecureStorageClient) { - return future.timeout(_secureStorageTimeout); - } - return future; - } - - static String _deviceTokenKey(String deviceId, String role) { - final safeRole = role.trim().isEmpty ? 'operator' : role.trim(); - return 'xworkmate.gateway.device_token.$deviceId.$safeRole'; - } - - static String _gatewayTokenKeyForProfile(int profileIndex) => - 'xworkmate.gateway.profile.$profileIndex.token'; - - static String _gatewayPasswordKeyForProfile(int profileIndex) => - 'xworkmate.gateway.profile.$profileIndex.password'; - static String _gatewayTokenRefKey(int profileIndex) => 'gateway_token_$profileIndex'; static String _gatewayPasswordRefKey(int profileIndex) => 'gateway_password_$profileIndex'; - - static const List _gatewayProfileFallbackOrder = [ - kGatewayRemoteProfileIndex, - kGatewayLocalProfileIndex, - 2, - 3, - 4, - ]; - - static List _base64UrlDecode(String value) { - final normalized = value.replaceAll('-', '+').replaceAll('_', '/'); - final padded = normalized + '=' * ((4 - normalized.length % 4) % 4); - return base64.decode(padded); - } - - Future _ensureSecureStorageClient() async { - final client = _secureStorage; - if (client != null) { - return client; - } - final promoted = await _promoteToFileSecureStorageFallback(); - if (promoted && _secureStorage != null) { - return _secureStorage!; - } - throw StateError( - 'Durable secret storage unavailable: no persistent secure storage client.', - ); - } - - Future _resolvePath(Future Function()? resolver) async { - if (resolver == null) { - return null; - } - try { - final resolved = await resolver(); - final trimmed = resolved?.trim() ?? ''; - return trimmed.isEmpty ? null : trimmed; - } catch (_) { - return null; - } - } } diff --git a/lib/runtime/secure_config_store.dart b/lib/runtime/secure_config_store.dart index 8416b251..32bc8d2e 100644 --- a/lib/runtime/secure_config_store.dart +++ b/lib/runtime/secure_config_store.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - export 'secret_store.dart'; export 'settings_store.dart'; @@ -16,14 +14,11 @@ class SecureConfigStore { SecureStorageClient? secureStorage, bool enableSecureStorage = true, }) { - final resolvedDefaultSupportDirectoryPathResolver = - defaultSupportDirectoryPathResolver ?? - _resolveDefaultSupportDirectoryPath; _secretStore = SecretStore( fallbackDirectoryPathResolver: fallbackDirectoryPathResolver, databasePathResolver: databasePathResolver, defaultSupportDirectoryPathResolver: - resolvedDefaultSupportDirectoryPathResolver, + defaultSupportDirectoryPathResolver, secureStorage: secureStorage, enableSecureStorage: enableSecureStorage, ); @@ -31,7 +26,7 @@ class SecureConfigStore { fallbackDirectoryPathResolver: fallbackDirectoryPathResolver, databasePathResolver: databasePathResolver, defaultSupportDirectoryPathResolver: - resolvedDefaultSupportDirectoryPathResolver, + defaultSupportDirectoryPathResolver, databaseOpener: databaseOpener, ); } @@ -155,34 +150,3 @@ class SecureConfigStore { return SecretStore.maskValue(value); } } - -const String _defaultBundleIdentifier = 'plus.svc.xworkmate'; - -Future _resolveDefaultSupportDirectoryPath() async { - final home = Platform.environment['HOME']?.trim() ?? ''; - if (home.isNotEmpty) { - if (Platform.isMacOS) { - return '$home/Library/Application Support/$_defaultBundleIdentifier/xworkmate'; - } - if (Platform.isLinux) { - final xdgStateHome = Platform.environment['XDG_STATE_HOME']?.trim() ?? ''; - if (xdgStateHome.isNotEmpty) { - return '$xdgStateHome/$_defaultBundleIdentifier/xworkmate'; - } - return '$home/.local/state/$_defaultBundleIdentifier/xworkmate'; - } - } - - if (Platform.isWindows) { - final appData = Platform.environment['APPDATA']?.trim() ?? ''; - if (appData.isNotEmpty) { - return '$appData\\$_defaultBundleIdentifier\\xworkmate'; - } - final localAppData = Platform.environment['LOCALAPPDATA']?.trim() ?? ''; - if (localAppData.isNotEmpty) { - return '$localAppData\\$_defaultBundleIdentifier\\xworkmate'; - } - } - - return null; -} diff --git a/lib/runtime/settings_store.dart b/lib/runtime/settings_store.dart index c7a0a334..cab0d27e 100644 --- a/lib/runtime/settings_store.dart +++ b/lib/runtime/settings_store.dart @@ -1,14 +1,10 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:path_provider/path_provider.dart'; -import 'package:sqlite3/sqlite3.dart' as sqlite; import 'runtime_models.dart'; -typedef SecureConfigDatabaseOpener = - FutureOr Function(String resolvedPath); +typedef SecureConfigDatabaseOpener = FutureOr Function( + String resolvedPath, +); class SettingsStore { SettingsStore({ @@ -16,11 +12,7 @@ class SettingsStore { Future Function()? databasePathResolver, Future Function()? defaultSupportDirectoryPathResolver, SecureConfigDatabaseOpener? databaseOpener, - }) : _fallbackDirectoryPathResolver = fallbackDirectoryPathResolver, - _databasePathResolver = databasePathResolver, - _defaultSupportDirectoryPathResolver = - defaultSupportDirectoryPathResolver, - _databaseOpener = databaseOpener; + }); static const String settingsKey = 'xworkmate.settings.snapshot'; static const String auditKey = 'xworkmate.secrets.audit'; @@ -28,301 +20,51 @@ class SettingsStore { static const String databaseFileName = 'config-store.sqlite3'; static const String databaseTableName = 'config_entries'; - final Future Function()? _fallbackDirectoryPathResolver; - final Future Function()? _databasePathResolver; - final Future Function()? _defaultSupportDirectoryPathResolver; - final SecureConfigDatabaseOpener? _databaseOpener; - sqlite.Database? _database; - String? _resolvedDatabasePath; - bool _initialized = false; + Future initialize() async {} - Future initialize() async { - if (_initialized) { - return; - } - await _initializeDatabase(); - _initialized = true; + Future loadSettingsSnapshot() { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', + ); } - Future loadSettingsSnapshot() async { - await initialize(); - final raw = await _readStoredString(settingsKey); - return _decodeSettingsSnapshot(raw) ?? SettingsSnapshot.defaults(); + Future saveSettingsSnapshot(SettingsSnapshot snapshot) { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', + ); } - Future saveSettingsSnapshot(SettingsSnapshot snapshot) async { - await initialize(); - final encoded = snapshot.toJsonString(); - await _writeStoredString(settingsKey, encoded); - } - - Future> loadAssistantThreadRecords() async { - await initialize(); - final raw = await _readStoredString(assistantThreadsKey); - return _decodeAssistantThreadRecords(raw) ?? - const []; + Future> loadAssistantThreadRecords() { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', + ); } Future saveAssistantThreadRecords( List records, - ) async { - await initialize(); - final encoded = jsonEncode( - records.map((item) => item.toJson()).toList(growable: false), - ); - await _writeStoredString(assistantThreadsKey, encoded); - } - - Future clearAssistantLocalState() async { - await initialize(); - await _deleteStoredString(settingsKey); - await _deleteStoredString(assistantThreadsKey); - } - - Future> loadAuditTrail() async { - await initialize(); - final raw = await _readStoredString(auditKey); - if (raw == null || raw.trim().isEmpty) { - return const []; - } - try { - final decoded = jsonDecode(raw) as List; - return decoded - .map( - (item) => SecretAuditEntry.fromJson( - (item as Map).cast(), - ), - ) - .toList(growable: false); - } catch (_) { - return const []; - } - } - - Future appendAudit(SecretAuditEntry entry) async { - final items = (await loadAuditTrail()).toList(growable: true); - items.insert(0, entry); - if (items.length > 40) { - items.removeRange(40, items.length); - } - await _writeStoredString( - auditKey, - jsonEncode(items.map((item) => item.toJson()).toList(growable: false)), + ) { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', ); } - void dispose() { - final database = _database; - _database = null; - if (database != null) { - try { - database.dispose(); - } catch (_) { - // Ignore close errors during teardown. - } - } - _initialized = false; - _resolvedDatabasePath = null; - } - - Future _initializeDatabase() async { - final resolvedPath = await _resolveDatabasePath(); - try { - _database = await _openDatabase(resolvedPath); - _resolvedDatabasePath = resolvedPath; - } catch (error) { - throw StateError( - 'Durable settings storage unavailable: failed to open $resolvedPath. Cause: $error', - ); - } - } - - Future _openDatabase(String resolvedPath) async { - if (_databaseOpener != null) { - final database = await _databaseOpener(resolvedPath); - if (database == null) { - throw StateError( - 'Durable settings storage unavailable: database opener returned null for $resolvedPath.', - ); - } - _configureDatabase(database); - return database; - } - final file = File(resolvedPath); - if (!await file.parent.exists()) { - await file.parent.create(recursive: true); - } - final database = sqlite.sqlite3.open(file.path); - _configureDatabase(database); - return database; - } - - void _configureDatabase(sqlite.Database database) { - database.execute(''' - CREATE TABLE IF NOT EXISTS $databaseTableName ( - storage_key TEXT PRIMARY KEY, - value TEXT NOT NULL, - updated_at_ms INTEGER NOT NULL - ) - '''); - } - - Future _resolveDatabasePath() async { - final resolved = _resolvedDatabasePath?.trim() ?? ''; - if (resolved.isNotEmpty) { - return resolved; - } - final explicitDatabasePath = await _resolvePath(_databasePathResolver); - if (explicitDatabasePath != null) { - return explicitDatabasePath; - } - final fallbackRoot = await _resolvePath(_fallbackDirectoryPathResolver); - if (fallbackRoot != null) { - return '$fallbackRoot/$databaseFileName'; - } - final defaultSupportRoot = await _resolvePath( - _defaultSupportDirectoryPathResolver, + Future clearAssistantLocalState() { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', ); - if (defaultSupportRoot != null) { - return '$defaultSupportRoot/$databaseFileName'; - } - try { - final supportDirectory = await getApplicationSupportDirectory(); - return '${supportDirectory.path}/xworkmate/$databaseFileName'; - } catch (_) { - throw StateError( - 'Durable settings storage unavailable: cannot resolve $databaseFileName.', - ); - } } - Future _readStoredString(String key) async { - if (_database == null) { - throw StateError( - 'Durable settings storage unavailable: database not initialized.', - ); - } - try { - final result = _database!.select( - 'SELECT value FROM $databaseTableName WHERE storage_key = ? LIMIT 1', - [key], - ); - if (result.isEmpty) { - return null; - } - final value = result.first['value']; - return value is String && value.trim().isNotEmpty ? value : null; - } catch (_) { - throw StateError( - 'Durable settings storage unavailable: failed to read $key from $_resolvedDatabasePath.', - ); - } + Future> loadAuditTrail() { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', + ); } - Future _writeStoredString(String key, String value) async { - final trimmed = value.trim(); - if (trimmed.isEmpty) { - return; - } - if (_database == null) { - throw StateError( - 'Durable settings storage unavailable: database not initialized.', - ); - } - try { - _database!.execute( - ''' - INSERT INTO $databaseTableName (storage_key, value, updated_at_ms) - VALUES (?, ?, ?) - ON CONFLICT(storage_key) DO UPDATE SET - value = excluded.value, - updated_at_ms = excluded.updated_at_ms - ''', - [key, trimmed, DateTime.now().millisecondsSinceEpoch], - ); - } catch (_) { - throw StateError( - 'Durable settings storage unavailable: failed to write $key to $_resolvedDatabasePath.', - ); - } + Future appendAudit(SecretAuditEntry entry) { + throw StateError( + 'Legacy settings persistence removed. New file-based settings store is pending implementation.', + ); } - Future _deleteStoredString(String key) async { - if (_database == null) { - throw StateError( - 'Durable settings storage unavailable: database not initialized.', - ); - } - try { - _database!.execute( - 'DELETE FROM $databaseTableName WHERE storage_key = ?', - [key], - ); - } catch (_) { - throw StateError( - 'Durable settings storage unavailable: failed to delete $key from $_resolvedDatabasePath.', - ); - } - } - - SettingsSnapshot? _decodeSettingsSnapshot(String? raw) { - final trimmed = raw?.trim() ?? ''; - if (trimmed.isEmpty) { - return null; - } - try { - final decodedValue = jsonDecode(trimmed); - if (decodedValue is! Map) { - return null; - } - final decoded = decodedValue.cast(); - if (!_looksLikeSettingsSnapshot(decoded)) { - return null; - } - return SettingsSnapshot.fromJson(decoded); - } catch (_) { - return null; - } - } - - List? _decodeAssistantThreadRecords(String? raw) { - final trimmed = raw?.trim() ?? ''; - if (trimmed.isEmpty) { - return null; - } - try { - final decoded = jsonDecode(trimmed) as List; - return decoded - .whereType() - .map( - (item) => - AssistantThreadRecord.fromJson(item.cast()), - ) - .toList(growable: false); - } catch (_) { - return null; - } - } - - bool _looksLikeSettingsSnapshot(Map json) { - return json.containsKey('appLanguage') || - json.containsKey('gateway') || - json.containsKey('gatewayProfiles') || - json.containsKey('aiGateway') || - json.containsKey('accountUsername') || - json.containsKey('assistantExecutionTarget'); - } - - Future _resolvePath(Future Function()? resolver) async { - if (resolver == null) { - return null; - } - try { - final resolved = await resolver(); - final trimmed = resolved?.trim() ?? ''; - return trimmed.isEmpty ? null : trimmed; - } catch (_) { - return null; - } - } + void dispose() {} }