import Foundation // 控件专用设备会话:主 app 经 /api/auth/device-session 给控制中心控件代铸的独立委派会话 // (独立 refresh,不与引擎抢轮换)。存 App Group 共享文件,控件读取后自行调 /api/clipboard、 // 401 即自刷并写回;refresh 被拒则清空,待主 app 下次启动重铸。 struct WidgetSession: Codable { var accessToken: String var refreshToken: String let userId: String } enum WidgetSessionStore { private static func fileURL() -> URL? { guard let base = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppGroup.identifier) else { return nil } return base.appendingPathComponent("widget_session.json") } static func load() -> WidgetSession? { guard let url = fileURL(), let data = try? Data(contentsOf: url) else { return nil } return try? JSONDecoder().decode(WidgetSession.self, from: data) } static func save(_ session: WidgetSession) { guard let url = fileURL(), let data = try? JSONEncoder().encode(session) else { return } // 首次解锁后即可读(含再次锁屏后):控制中心控件可从锁屏触发,用 .completeFileProtection // 会在锁屏时读不到 → 控件静默失效。UntilFirstUserAuthentication 仍加密、又覆盖锁屏调用。 try? data.write(to: url, options: [ .atomic, .completeFileProtectionUntilFirstUserAuthentication ]) } static func clear() { guard let url = fileURL() else { return } try? FileManager.default.removeItem(at: url) } }