apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README

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 }