// ── 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 } }