RadrootsAppLocalStateReset.swift (2766B)
1 import Foundation 2 import Security 3 4 public struct RadrootsAppLocalStateResetRequest: Sendable, Equatable { 5 public let appIdentifier: String 6 public let keychainServiceNames: [String] 7 8 public init(appIdentifier: String, keychainServiceNames: [String] = []) { 9 self.appIdentifier = appIdentifier 10 self.keychainServiceNames = keychainServiceNames 11 } 12 } 13 14 public enum RadrootsAppLocalStateReset { 15 public static func reset(_ request: RadrootsAppLocalStateResetRequest) throws { 16 try clearApplicationSupport(appIdentifier: request.appIdentifier) 17 for serviceName in request.keychainServiceNames { 18 try clearKeychainService(serviceName) 19 } 20 } 21 22 public static func clearApplicationSupport( 23 appIdentifier: String, 24 fileManager: FileManager = .default 25 ) throws { 26 let trimmed = appIdentifier.trimmingCharacters(in: .whitespacesAndNewlines) 27 guard !trimmed.isEmpty else { 28 throw RadrootsAppLocalStateResetError.invalidRequest 29 } 30 do { 31 let root = try fileManager.url( 32 for: .applicationSupportDirectory, 33 in: .userDomainMask, 34 appropriateFor: nil, 35 create: true 36 ).appendingPathComponent(trimmed, isDirectory: true) 37 if fileManager.fileExists(atPath: root.path) { 38 try fileManager.removeItem(at: root) 39 } 40 } catch let error as RadrootsAppLocalStateResetError { 41 throw error 42 } catch { 43 throw RadrootsAppLocalStateResetError.fileSystemFailure 44 } 45 } 46 47 public static func clearKeychainService(_ serviceName: String) throws { 48 let trimmed = serviceName.trimmingCharacters(in: .whitespacesAndNewlines) 49 guard !trimmed.isEmpty else { 50 throw RadrootsAppLocalStateResetError.invalidRequest 51 } 52 let query: [String: Any] = [ 53 kSecClass as String: kSecClassGenericPassword, 54 kSecAttrService as String: trimmed, 55 ] 56 let status = SecItemDelete(query as CFDictionary) 57 guard status == errSecSuccess || status == errSecItemNotFound else { 58 throw RadrootsAppLocalStateResetError.keychainFailure 59 } 60 } 61 } 62 63 public enum RadrootsAppLocalStateResetError: Error, Equatable, Sendable { 64 case invalidRequest 65 case fileSystemFailure 66 case keychainFailure 67 } 68 69 extension RadrootsAppLocalStateResetError: LocalizedError { 70 public var errorDescription: String? { 71 switch self { 72 case .invalidRequest: "The local-state reset request is invalid." 73 case .fileSystemFailure: "The local application state could not be reset." 74 case .keychainFailure: "The secure local state could not be reset." 75 } 76 } 77 }