Replaced standalone implementations with thin wrappers over shared SDK: - KeychainHelper → delegates to BLKeychain (53→27 lines) - TelemetryService → delegates to BLTelemetryClient (139→55 lines) - FeatureFlagService → delegates to BLFeatureFlagClient (72→39 lines) - AuthService → delegates to BLAuthClient (359→171 lines) - project.yml → added ByteLystPlatformSDK local package dependency Total: 623 lines of duplicated code → 292 lines of thin wrappers. PlatformSyncManager kept as-is (product-specific timer DTOs). All existing call-site APIs preserved — zero breaking changes.
171 lines
4.9 KiB
Swift
171 lines
4.9 KiB
Swift
// ── Auth Service ──────────────────────────────────────────────
|
|
// Thin wrapper over ByteLystPlatformSDK's BLAuthClient.
|
|
// Keeps existing call-site API + ChronoMind-specific sync wiring.
|
|
// AuthUser = BLAuthUser (re-exported from SDK).
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
import ByteLystPlatformSDK
|
|
|
|
typealias AuthUser = BLAuthUser
|
|
|
|
enum CMAuthState {
|
|
case loading
|
|
case loggedOut
|
|
case loggedIn(AuthUser)
|
|
case error(String)
|
|
}
|
|
|
|
@MainActor
|
|
final class CMAuthService: ObservableObject {
|
|
static let shared = CMAuthService()
|
|
|
|
@Published var state: CMAuthState = .loading
|
|
@AppStorage("cm_user_email") private var userEmail = ""
|
|
@AppStorage("cm_user_name") private var userName = ""
|
|
@AppStorage("cm_user_plan") private var userPlan = "free"
|
|
|
|
private let authClient: BLAuthClient
|
|
private let platformClient: BLPlatformClient
|
|
|
|
private init() {
|
|
let config = BLPlatformConfig.fromInfoPlist(
|
|
productId: "chronomind",
|
|
defaultBaseURL: "https://api.chronomind.app",
|
|
bundleId: "com.saravana.chronomind",
|
|
appGroupId: "group.com.chronomind.shared"
|
|
)
|
|
platformClient = BLPlatformClient(config: config)
|
|
authClient = BLAuthClient(config: config, client: platformClient)
|
|
|
|
// Wire token updates → sync manager
|
|
authClient.onTokensUpdated = { [weak self] token in
|
|
Task { @MainActor in
|
|
self?.wireSyncToken(token)
|
|
}
|
|
}
|
|
|
|
checkExistingSession()
|
|
}
|
|
|
|
private func checkExistingSession() {
|
|
if authClient.isAuthenticated, !userEmail.isEmpty {
|
|
state = .loggedIn(AuthUser(id: "", email: userEmail, displayName: userName, plan: userPlan))
|
|
wireSyncToken(authClient.accessToken)
|
|
Task { await fetchCurrentUser() }
|
|
} else if authClient.isAuthenticated {
|
|
Task { await fetchCurrentUser() }
|
|
} else {
|
|
state = .loggedOut
|
|
}
|
|
}
|
|
|
|
// MARK: - Public API
|
|
|
|
func login(email: String, password: String) async {
|
|
state = .loading
|
|
do {
|
|
let user = try await authClient.login(email: email, password: password)
|
|
saveUserInfo(user)
|
|
state = .loggedIn(user)
|
|
} catch {
|
|
state = .error("Invalid email or password")
|
|
}
|
|
}
|
|
|
|
func register(name: String, email: String, password: String) async {
|
|
state = .loading
|
|
do {
|
|
let user = try await authClient.register(displayName: name, email: email, password: password)
|
|
saveUserInfo(user)
|
|
state = .loggedIn(user)
|
|
} catch {
|
|
state = .error("Registration failed")
|
|
}
|
|
}
|
|
|
|
func logout() {
|
|
authClient.logout()
|
|
userEmail = ""
|
|
userName = ""
|
|
state = .loggedOut
|
|
}
|
|
|
|
func forgotPassword(email: String) async -> String? {
|
|
do {
|
|
try await authClient.forgotPassword(email: email)
|
|
return nil
|
|
} catch {
|
|
return error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func changePassword(currentPassword: String, newPassword: String) async -> String? {
|
|
do {
|
|
try await authClient.changePassword(currentPassword: currentPassword, newPassword: newPassword)
|
|
return nil
|
|
} catch {
|
|
return error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func deleteAccount(password: String) async -> String? {
|
|
do {
|
|
try await authClient.deleteAccount(password: password)
|
|
userEmail = ""
|
|
userName = ""
|
|
state = .loggedOut
|
|
return nil
|
|
} catch {
|
|
return error.localizedDescription
|
|
}
|
|
}
|
|
|
|
var isLoggedIn: Bool {
|
|
if case .loggedIn = state { return true }
|
|
return false
|
|
}
|
|
|
|
var currentUser: AuthUser? {
|
|
if case .loggedIn(let user) = state { return user }
|
|
return nil
|
|
}
|
|
|
|
func getAccessToken() -> String? {
|
|
authClient.accessToken
|
|
}
|
|
|
|
// MARK: - Token Refresh
|
|
|
|
func refreshAccessToken() async -> Bool {
|
|
let ok = await authClient.refreshAccessToken()
|
|
if !ok { logout() }
|
|
return ok
|
|
}
|
|
|
|
func fetchCurrentUser() async {
|
|
do {
|
|
let user = try await authClient.getMe()
|
|
saveUserInfo(user)
|
|
state = .loggedIn(user)
|
|
} catch {
|
|
if let netErr = error as? BLNetworkError, netErr.statusCode == 401 {
|
|
let ok = await refreshAccessToken()
|
|
if ok { await fetchCurrentUser() } else { state = .loggedOut }
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private func wireSyncToken(_ token: String?) {
|
|
PlatformSyncManager.shared.setAuthToken(token)
|
|
}
|
|
|
|
private func saveUserInfo(_ user: AuthUser) {
|
|
userEmail = user.email
|
|
userName = user.displayName
|
|
userPlan = user.plan
|
|
}
|
|
}
|