98b7600797
对标安卓正式版、对接 app-server(8770)的 SwiftUI 客户端。iOS 无障碍受限、无比价核心,做自成一体:美团 CPS 导购 + 福利(签到/任务/金币)+ 查看(比价记录/省钱战绩)。 功能完整 M0-M5:脚手架、短信登录(JWT+Keychain+401 自动刷新)、首页券 feed 导购、比价记录+省钱战绩、福利、我的(资料/反馈/设置/注销)。 单 target、0 三方依赖、XcodeGen 生成工程;Debug/Release 双 plist 分流 ATS。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
1.6 KiB
Swift
46 lines
1.6 KiB
Swift
import Foundation
|
|
|
|
/// Token 中枢:读写 Keychain + 串行刷新(对应安卓 RefreshAuthenticator 的 @Synchronized 去重)。
|
|
/// actor 保证并发安全;`inFlight` 让同时撞 401 的多个请求复用同一次刷新,只刷一次。
|
|
actor AuthTokens {
|
|
static let shared = AuthTokens()
|
|
|
|
private var inFlight: Task<Bool, Never>?
|
|
|
|
func accessToken() -> String? { KeychainStore.get(.accessToken) }
|
|
func refreshToken() -> String? { KeychainStore.get(.refreshToken) }
|
|
|
|
func setTokens(access: String, refresh: String) {
|
|
KeychainStore.set(access, .accessToken)
|
|
KeychainStore.set(refresh, .refreshToken)
|
|
}
|
|
|
|
func clear() {
|
|
KeychainStore.delete(.accessToken)
|
|
KeychainStore.delete(.refreshToken)
|
|
}
|
|
|
|
/// 用 refresh_token 换新一对。并发调用复用同一 Task。成功写回 Keychain 返 true。
|
|
func refresh() async -> Bool {
|
|
if let inFlight { return await inFlight.value }
|
|
let task = Task<Bool, Never> { await Self.performRefresh() }
|
|
inFlight = task
|
|
let ok = await task.value
|
|
inFlight = nil
|
|
return ok
|
|
}
|
|
|
|
/// 裸刷新:不带鉴权头、不会再触发刷新(避免递归),对应安卓"用裸 OkHttp 调 /refresh"。
|
|
private static func performRefresh() async -> Bool {
|
|
guard let rt = KeychainStore.get(.refreshToken) else { return false }
|
|
do {
|
|
let pair = try await APIClient.shared.rawRefresh(refreshToken: rt)
|
|
KeychainStore.set(pair.accessToken, .accessToken)
|
|
KeychainStore.set(pair.refreshToken, .refreshToken)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|