import Foundation import Security /// 保存登录令牌的地方:正式环境是钥匙串,测试用内存 public protocol TokenStore: Sendable { func read() -> String? func write(_ token: String) throws func delete() } /// 登录钥匙串里的通用密码项。用文件式钥匙串而不是数据保护钥匙串:后者要求应用有 application-identifier, /// 本地 ad-hoc 签名的开发构建没有 public struct KeychainTokenStore: TokenStore { public let service: String public let account: String public init(service: String = "com.openstats.sync", account: String = "session") { self.service = service self.account = account } private var query: [String: Any] { [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account] } public func read() -> String? { var query = query query[kSecReturnData as String] = true query[kSecMatchLimit as String] = kSecMatchLimitOne var result: AnyObject? guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } public func write(_ token: String) throws { let data = Data(token.utf8) let update = [kSecValueData as String: data] var status = SecItemUpdate(query as CFDictionary, update as CFDictionary) if status == errSecItemNotFound { var item = query item[kSecValueData as String] = data item[kSecAttrLabel as String] = "OpenStats 同步" status = SecItemAdd(item as CFDictionary, nil) } guard status == errSecSuccess else { throw KeychainError(status: status) } } public func delete() { SecItemDelete(query as CFDictionary) } } public struct KeychainError: Error, LocalizedError { public let status: OSStatus public var errorDescription: String? { (SecCopyErrorMessageString(status, nil) as String?) ?? "Keychain error \(status)" } } /// 测试与截图用的内存实现 public final class MemoryTokenStore: TokenStore, @unchecked Sendable { private let lock = NSLock() private var token: String? public init(token: String? = nil) { self.token = token } public func read() -> String? { lock.withLock { token } } public func write(_ token: String) { lock.withLock { self.token = token } } public func delete() { lock.withLock { token = nil } } }