Aggiunge app iOS nativa con parità multi-sport e test unitari.
Porting SwiftUI da Android (auth, hub, wizard, broadcast RTMP, overlay, scoring board-aware), reload hub al ritorno da diretta, decodifica score tollerante e documentazione allineata. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b798e0ea06
commit
585332e32e
@@ -0,0 +1,111 @@
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
struct StoredSession: Equatable, Sendable {
|
||||
let user: User
|
||||
let accessToken: String
|
||||
let refreshToken: String
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class TokenStore: ObservableObject {
|
||||
@Published private(set) var session: StoredSession?
|
||||
@Published private(set) var activeTeamId: String?
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
private let service = "com.matchlivetv.match-live-tv.auth"
|
||||
|
||||
init() {
|
||||
session = loadSession()
|
||||
activeTeamId = defaults.string(forKey: Keys.activeTeamId)
|
||||
}
|
||||
|
||||
func saveSession(_ session: StoredSession) {
|
||||
self.session = session
|
||||
saveKeychain(key: Keys.accessToken, value: session.accessToken)
|
||||
saveKeychain(key: Keys.refreshToken, value: session.refreshToken)
|
||||
defaults.set(session.user.id, forKey: Keys.userId)
|
||||
defaults.set(session.user.email, forKey: Keys.userEmail)
|
||||
defaults.set(session.user.name, forKey: Keys.userName)
|
||||
defaults.set(session.user.role, forKey: Keys.userRole)
|
||||
}
|
||||
|
||||
func saveActiveTeamId(_ teamId: String) {
|
||||
activeTeamId = teamId
|
||||
defaults.set(teamId, forKey: Keys.activeTeamId)
|
||||
}
|
||||
|
||||
func clear() {
|
||||
session = nil
|
||||
activeTeamId = nil
|
||||
deleteKeychain(key: Keys.accessToken)
|
||||
deleteKeychain(key: Keys.refreshToken)
|
||||
defaults.removeObject(forKey: Keys.userId)
|
||||
defaults.removeObject(forKey: Keys.userEmail)
|
||||
defaults.removeObject(forKey: Keys.userName)
|
||||
defaults.removeObject(forKey: Keys.userRole)
|
||||
defaults.removeObject(forKey: Keys.activeTeamId)
|
||||
}
|
||||
|
||||
private func loadSession() -> StoredSession? {
|
||||
guard let access = loadKeychain(key: Keys.accessToken),
|
||||
let refresh = loadKeychain(key: Keys.refreshToken),
|
||||
let id = defaults.string(forKey: Keys.userId) else { return nil }
|
||||
return StoredSession(
|
||||
user: User(
|
||||
id: id,
|
||||
email: defaults.string(forKey: Keys.userEmail) ?? "",
|
||||
name: defaults.string(forKey: Keys.userName) ?? "",
|
||||
role: defaults.string(forKey: Keys.userRole) ?? "coach"
|
||||
),
|
||||
accessToken: access,
|
||||
refreshToken: refresh
|
||||
)
|
||||
}
|
||||
|
||||
private enum Keys {
|
||||
static let accessToken = "access_token"
|
||||
static let refreshToken = "refresh_token"
|
||||
static let userId = "user_id"
|
||||
static let userEmail = "user_email"
|
||||
static let userName = "user_name"
|
||||
static let userRole = "user_role"
|
||||
static let activeTeamId = "active_team_id"
|
||||
}
|
||||
|
||||
private func saveKeychain(key: String, value: String) {
|
||||
let data = Data(value.utf8)
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: key,
|
||||
]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
var add = query
|
||||
add[kSecValueData as String] = data
|
||||
SecItemAdd(add as CFDictionary, nil)
|
||||
}
|
||||
|
||||
private func loadKeychain(key: String) -> String? {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecReturnData as String: true,
|
||||
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)
|
||||
}
|
||||
|
||||
private func deleteKeychain(key: String) {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: key,
|
||||
]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user