diff options
| author | Christian Cleberg <[email protected]> | 2026-04-25 00:41:34 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-25 00:41:34 -0500 |
| commit | 535bb0ff0f64d57be1074e33ceb42f598f80205b (patch) | |
| tree | 7e8cf8c86c226c1994555b121218484c05a5bdd4 /DomainDig/DataResetService.swift | |
| parent | 1715e59287a7b48b29ed8cbf2cd45fcc92ec3126 (diff) | |
| download | domain-dig-535bb0ff0f64d57be1074e33ceb42f598f80205b.tar.gz domain-dig-535bb0ff0f64d57be1074e33ceb42f598f80205b.tar.bz2 domain-dig-535bb0ff0f64d57be1074e33ceb42f598f80205b.zip | |
DomainDig v4.1.0: Add a full local data reset flow in Data Management
- add a destructive Delete All Data action with confirmation, progress, success,
and failure handling
- centralize wipe behavior in DataResetService instead of scattering delete
logic in views
- clear local persistence, temp/export files, integration secrets,
notifications, caches, and in-memory app state
- reset sync, purchase, and integration services after wipe so the app returns
to a clean first-launch state
Polish batch and empty-state UX
- present the batch sweep summary after manual bulk searches complete, not only
from Watchlist
- align the Workflows empty state styling with other empty states by removing
the extra background card treatment
Bump the project version from 4.0.0 to 4.1.0
Diffstat (limited to 'DomainDig/DataResetService.swift')
| -rw-r--r-- | DomainDig/DataResetService.swift | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/DomainDig/DataResetService.swift b/DomainDig/DataResetService.swift new file mode 100644 index 0000000..e993eba --- /dev/null +++ b/DomainDig/DataResetService.swift @@ -0,0 +1,76 @@ +import Foundation +import Security + +enum DataResetService { + enum ResetError: LocalizedError { + case missingBundleIdentifier + + var errorDescription: String? { + switch self { + case .missingBundleIdentifier: + "DomainDig could not determine its local storage identifier." + } + } + } + + static func wipeAllLocalData(viewModel: DomainViewModel) async throws { + let secretReferences = await MainActor.run { + IntegrationService.shared.localSecretReferences() + } + + try await Task.detached(priority: .userInitiated) { + try performPersistentWipe(secretReferences: secretReferences) + }.value + + await LookupRuntime.shared.clearCache() + await LocalNotificationService.shared.clearAllNotifications() + await UsageCreditService.shared.resetForCurrentVersion() + + await MainActor.run { + IntegrationService.shared.resetAfterLocalWipe() + CloudSyncService.shared.resetLocalStateAfterWipe() + PurchaseService.shared.resetCachedStateAfterLocalWipe() + _ = DomainMonitoringScheduler.shared.syncSchedule() + } + + await viewModel.applyLocalDataReset() + } + + private nonisolated static func performPersistentWipe(secretReferences: [String]) throws { + guard let bundleIdentifier = Bundle.main.bundleIdentifier else { + throw ResetError.missingBundleIdentifier + } + + for reference in secretReferences { + deleteIntegrationSecret(reference: reference) + } + + try removeTemporaryFiles() + + let defaults = UserDefaults.standard + defaults.removePersistentDomain(forName: bundleIdentifier) + defaults.synchronize() + } + + private nonisolated static func removeTemporaryFiles() throws { + let tempDirectory = FileManager.default.temporaryDirectory + let urls = try FileManager.default.contentsOfDirectory( + at: tempDirectory, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + ) + + for url in urls { + try? FileManager.default.removeItem(at: url) + } + } + + private nonisolated static func deleteIntegrationSecret(reference: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: reference + ] + + SecItemDelete(query as CFDictionary) + } +} |
