RadrootsAppLocalStateReset.swift (2302B)
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("app identifier cannot be empty") 29 } 30 let root = try fileManager.url( 31 for: .applicationSupportDirectory, 32 in: .userDomainMask, 33 appropriateFor: nil, 34 create: true 35 ).appendingPathComponent(trimmed, isDirectory: true) 36 if fileManager.fileExists(atPath: root.path) { 37 try fileManager.removeItem(at: root) 38 } 39 } 40 41 public static func clearKeychainService(_ serviceName: String) throws { 42 let trimmed = serviceName.trimmingCharacters(in: .whitespacesAndNewlines) 43 guard !trimmed.isEmpty else { 44 throw RadrootsAppLocalStateResetError.invalidRequest("keychain service name cannot be empty") 45 } 46 let query: [String: Any] = [ 47 kSecClass as String: kSecClassGenericPassword, 48 kSecAttrService as String: trimmed 49 ] 50 let status = SecItemDelete(query as CFDictionary) 51 guard status == errSecSuccess || status == errSecItemNotFound else { 52 throw RadrootsAppLocalStateResetError.keychainStatus(status, "keychain service reset failed") 53 } 54 } 55 } 56 57 public enum RadrootsAppLocalStateResetError: Error, Equatable, Sendable { 58 case invalidRequest(String) 59 case keychainStatus(Int32, String) 60 }