import Foundation import Security // 🤖 The full local reset behind SIGN OUT: wipes everything the app persists — // Keychain identities, thread keys, the home session, UserDefaults, and the // on-disk stores — so the next launch is a true first run. Signing out of // Eleven MEANS clearing this device: the spaces live in the member's Eleven // account, and signing back in restores them (the caller pushes a final sync // first). Also the reason a plain uninstall isn't a reset: Keychain items // survive app deletion on iOS. public enum AppReset { public static func wipeAll() { // Whole-service deletes (no account = every item), with and without // the app-family access group — matching how the stores write. for service in ["com.lchat.client", "com.lchat.client.threadkeys"] { for withGroup in [true, false] { var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecUseDataProtectionKeychain as String: true, ] if withGroup { // Same group constant as Keychain/SharedThreadKeys. query[kSecAttrAccessGroup as String] = "J5NDF5PCD8.com.elevenmessenger.messenger.shared" } SecItemDelete(query as CFDictionary) } } if let bundleID = Bundle.main.bundleIdentifier { UserDefaults.standard.removePersistentDomain(forName: bundleID) } let fm = FileManager.default #if os(macOS) // NOT sandboxed on the Mac: the search paths are the user's REAL // Library folders, so only paths that are provably OURS may go — the // app's data dir (MessageStore/MacChatsStore both live in // "Application Support/lchat"), its WebKit store (the webviews' // localStorage identities), and its caches. Never the whole folder. for base in fm.urls(for: .applicationSupportDirectory, in: .userDomainMask) { try? fm.removeItem(at: base.appendingPathComponent("lchat")) } if let bundleID = Bundle.main.bundleIdentifier { for base in fm.urls(for: .cachesDirectory, in: .userDomainMask) { try? fm.removeItem(at: base.appendingPathComponent(bundleID)) } for base in fm.urls(for: .libraryDirectory, in: .userDomainMask) { try? fm.removeItem(at: base.appendingPathComponent("WebKit/\(bundleID)")) try? fm.removeItem(at: base.appendingPathComponent("HTTPStorages/\(bundleID)")) } } #else // Sandboxed: the search paths are the app's own container — wipe wholesale. for dir: FileManager.SearchPathDirectory in [.applicationSupportDirectory, .cachesDirectory, .documentDirectory] { for url in fm.urls(for: dir, in: .userDomainMask) { for item in (try? fm.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)) ?? [] { try? fm.removeItem(at: item) } } } try? fm.removeItem(at: fm.temporaryDirectory) #endif } }