## Code Observable class with user subscription: ```swift func setupUserSubscription() { guard let clerkUser = clerkUser else { print("No clerk user available") currentUser = nil isLoadingUser = false return } guard !subscriptionsInitialized else { print("User subscription already initialized, skipping") return } // subscriptionsInitialized = true // isLoadingUser = true //TODO: race condition with subsequent subscriptions with the same clerkId // curb by holding on to last clerk id and last subscription and only cancel // if the clerkId is different print("UserManager setupUserSubscription: subscribing to user: \(clerkUser.id)") convex.subscribe( to: "users:getUserByClerkId", with: ["clerkId": clerkUser.id] ) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { [weak self] completion in print("UserManager setupUserSubscription: receiveCompletion: \(completion)") if case .failure(let error) = completion { print("Error subscribing to user: \(error)") self?.error = error.localizedDescription self?.currentUser = nil self?.isLoadingUser = false } }, receiveValue: { [weak self] (user: Account?) in print("UserManager setupUserSubscription: received user: \(String(describing: user?.id))") if var user = user { // Mark as current user for UI indicators user.isCurrentUser = true self?.currentUser = user } else { self?.currentUser = nil } self?.isLoadingUser = false } ) .store(in: &cancellables) } ``` Clerk AuthProvider class: ```swift public func login() async throws -> ClerkAuthResult { guard let session = await Clerk.shared.session else { print("ClerkProvider login: no active session") throw ClerkError.noActiveSession } // Fetch the token - Clerk handles caching automatically guard let token = try await session.getToken(.init(template: "convex")) else { print("ClerkProvider login: no token") throw ClerkError.noToken } print("ClerkProvider login: token: \(token.jwt.count)") return ClerkAuthResult(session: session, idToken: token.jwt) } public func loginFromCache() async throws -> ClerkAuthResult { // Since Clerk handles token caching internally with 1-minute TTL, // loginFromCache can be identical to login() print("ClerkProvider loginFromCache") return try await login() } public func extractIdToken(from authResult: ClerkAuthResult) -> String { print("ClerkProvider extractIdToken: \(authResult.idToken.count)") return authResult.idToken } public typealias T = ClerkAuthResult public func logout() async throws { print("ClerkProvider logout") try await Clerk.shared.signOut() } ``` View with task that sets up subscription when clerk has authenticated: ```swift .task(id: clerk.user?.id) { print("AuthCheckView task got a clerk.user: \(String(describing: clerk.user?.id))") userManager.clerkUser = clerk.user // runs on every login/logout // :door: Logout path ─ clerk.user is now nil guard clerk.user != nil else { // TODO: why do i set this here rather than the didSet of userManager.clerkUser? userManager.isLoadingUser = false return } // :wave: Login / fresh session path userManager.isLoadingUser = true // _ = await ConvexClientManager.shared.client.login() // Monitor auth state and setup subscriptions only after authentication var bag = Set() ConvexClientManager.shared.client.authState .sink { authState in switch authState { case .authenticated(_): print("Convex authenticated, setting up subscriptions") userManager.setupUserSubscription() case .unauthenticated: print("Convex unauthenticated") case .loading: print("Convex authentication loading") } } .store(in: &bag) } ```