From 39996d49670903a9292b3b4801604302cf8dd6ea Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 13 Feb 2025 08:03:30 -0300 Subject: [PATCH] drop SessionManager --- Sources/Auth/AuthClient.swift | 38 +++---- Sources/Auth/AuthMFA.swift | 7 +- ....swift => AuthClient+SessionManager.swift} | 106 +++++++----------- Tests/AuthTests/AuthClientTests.swift | 3 - 4 files changed, 58 insertions(+), 96 deletions(-) rename Sources/Auth/Internal/{SessionManager.swift => AuthClient+SessionManager.swift} (52%) diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 10693d0a..1b21e5ec 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -16,22 +16,14 @@ import Helpers public final class AuthClient: Sendable { struct MutableState { - var sessionManager: SessionManager? + var inFlightRefreshTask: Task? + var autoRefreshTokenTask: Task? } let mutableState = LockIsolated(MutableState()) let configuration: AuthClient.Configuration let http: any HTTPClientType - var sessionManager: SessionManager { - mutableState.withValue { - if $0.sessionManager == nil { - $0.sessionManager = .live(client: self) - } - return $0.sessionManager! - } - } - let eventEmitter = AuthStateChangeEventEmitter() var logger: (any SupabaseLogger)? { configuration.logger } @@ -63,7 +55,7 @@ public final class AuthClient: Sendable { /// ``` public var session: Session { get async throws { - try await sessionManager.session() + try await _session() } } @@ -326,7 +318,7 @@ public final class AuthClient: Sendable { ) if let session = response.session { - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.signedIn, session: session) } @@ -449,7 +441,7 @@ public final class AuthClient: Sendable { decoder: configuration.decoder ) - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.signedIn, session: session) return session @@ -626,7 +618,7 @@ public final class AuthClient: Sendable { storeCodeVerifier(nil) - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.signedIn, session: session) return session @@ -884,7 +876,7 @@ public final class AuthClient: Sendable { user: user ) - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.signedIn, session: session) if let type = params["type"], type == "recovery" { @@ -949,7 +941,7 @@ public final class AuthClient: Sendable { ) } - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.signedIn, session: session) return session } @@ -977,7 +969,7 @@ public final class AuthClient: Sendable { } if scope != .others { - await sessionManager.remove() + deleteSession() eventEmitter.emit(.signedOut, session: nil) } @@ -1085,7 +1077,7 @@ public final class AuthClient: Sendable { ) if let session = response.session { - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.signedIn, session: session) } @@ -1210,7 +1202,7 @@ public final class AuthClient: Sendable { user.codeChallengeMethod = codeChallengeMethod } - var session = try await sessionManager.session() + var session = try await _session() let updatedUser = try await authorizedExecute( .init( url: configuration.url.appendingPathComponent("user"), @@ -1228,7 +1220,7 @@ public final class AuthClient: Sendable { jwt: session.accessToken ).decoded(as: User.self, decoder: configuration.decoder) session.user = updatedUser - await sessionManager.update(session) + storeSession(session) eventEmitter.emit(.userUpdated, session: session) return updatedUser } @@ -1385,19 +1377,19 @@ public final class AuthClient: Sendable { throw AuthError.sessionMissing } - return try await sessionManager.refreshSession(refreshToken) + return try await _refreshSession(refreshToken) } /// Starts an auto-refresh process in the background. The session is checked every few seconds. Close to the time of expiration a process is started to refresh the session. If refreshing fails it will be retried for as long as necessary. /// /// If you set ``Configuration/autoRefreshToken`` you don't need to call this function, it will be called for you. public func startAutoRefresh() { - Task { await sessionManager.startAutoRefresh() } + _startAutoRefreshToken() } /// Stops an active auto refresh process running in the background (if any). public func stopAutoRefresh() { - Task { await sessionManager.stopAutoRefresh() } + _stopAutoRefreshToken() } private func emitInitialSession(forToken token: ObservationToken) async { diff --git a/Sources/Auth/AuthMFA.swift b/Sources/Auth/AuthMFA.swift index 38f8124d..4fae1c60 100644 --- a/Sources/Auth/AuthMFA.swift +++ b/Sources/Auth/AuthMFA.swift @@ -6,7 +6,6 @@ public struct AuthMFA: Sendable { let client: AuthClient var eventEmitter: AuthStateChangeEventEmitter { client.eventEmitter } - var sessionManager: SessionManager { client.sessionManager } var configuration: AuthClient.Configuration { client.configuration } var encoder: JSONEncoder { configuration.encoder } var decoder: JSONDecoder { configuration.decoder } @@ -70,7 +69,7 @@ public struct AuthMFA: Sendable { jwt: client.session.accessToken ).decoded(decoder: decoder) - await sessionManager.update(response) + client.storeSession(response) eventEmitter.emit(.mfaChallengeVerified, session: response, token: nil) @@ -116,7 +115,7 @@ public struct AuthMFA: Sendable { /// /// - Returns: An authentication response with the list of MFA factors. public func listFactors() async throws -> AuthMFAListFactorsResponse { - let user = try await sessionManager.session().user + let user = try await client.session.user let factors = user.factors ?? [] let totp = factors.filter { $0.factorType == "totp" && $0.status == .verified @@ -134,7 +133,7 @@ public struct AuthMFA: Sendable { -> AuthMFAGetAuthenticatorAssuranceLevelResponse { do { - let session = try await sessionManager.session() + let session = try await client.session let payload = JWT.decodePayload(session.accessToken) var currentLevel: AuthenticatorAssuranceLevels? diff --git a/Sources/Auth/Internal/SessionManager.swift b/Sources/Auth/Internal/AuthClient+SessionManager.swift similarity index 52% rename from Sources/Auth/Internal/SessionManager.swift rename to Sources/Auth/Internal/AuthClient+SessionManager.swift index 9956ebf2..494e84b9 100644 --- a/Sources/Auth/Internal/SessionManager.swift +++ b/Sources/Auth/Internal/AuthClient+SessionManager.swift @@ -1,48 +1,11 @@ import Foundation import Helpers -struct SessionManager: Sendable { - var session: @Sendable () async throws -> Session - var refreshSession: @Sendable (_ refreshToken: String) async throws -> Session - var update: @Sendable (_ session: Session) async -> Void - var remove: @Sendable () async -> Void - - var startAutoRefresh: @Sendable () async -> Void - var stopAutoRefresh: @Sendable () async -> Void -} - -extension SessionManager { - static func live(client: AuthClient) -> Self { - let instance = LiveSessionManager(client: client) - return Self( - session: { try await instance.session() }, - refreshSession: { try await instance.refreshSession($0) }, - update: { await instance.update($0) }, - remove: { await instance.remove() }, - startAutoRefresh: { await instance.startAutoRefreshToken() }, - stopAutoRefresh: { await instance.stopAutoRefreshToken() } - ) - } -} - -private actor LiveSessionManager { - - unowned var client: AuthClient - - private var configuration: AuthClient.Configuration { client.configuration } - private var eventEmitter: AuthStateChangeEventEmitter { client.eventEmitter } - private var logger: (any SupabaseLogger)? { client.logger } - - private var inFlightRefreshTask: Task? - private var startAutoRefreshTokenTask: Task? - - init(client: AuthClient) { - self.client = client - } +extension AuthClient { - func session() async throws -> Session { + func _session() async throws -> Session { try await trace(using: logger) { - guard let currentSession = client.getStoredSession() else { + guard let currentSession = getStoredSession() else { logger?.debug("session missing") throw AuthError.sessionMissing } @@ -52,11 +15,11 @@ private actor LiveSessionManager { } logger?.debug("session expired") - return try await refreshSession(currentSession.refreshToken) + return try await _refreshSession(currentSession.refreshToken) } } - func refreshSession(_ refreshToken: String) async throws -> Session { + func _refreshSession(_ refreshToken: String) async throws -> Session { try await SupabaseLoggerTaskLocal.$additionalContext.withValue( merging: [ "refresh_id": .string(UUID().uuidString), @@ -64,21 +27,23 @@ private actor LiveSessionManager { ] ) { try await trace(using: logger) { - if let inFlightRefreshTask { + if let inFlightRefreshTask = mutableState.inFlightRefreshTask { logger?.debug("Refresh already in flight") return try await inFlightRefreshTask.value } - inFlightRefreshTask = Task { + let inFlightRefreshTask = Task { logger?.debug("Refresh task started") defer { - inFlightRefreshTask = nil + mutableState.withValue { + $0.inFlightRefreshTask = nil + } logger?.debug("Refresh task ended") } do { - let session = try await client.execute( + let session = try await execute( HTTPRequest( url: configuration.url.appendingPathComponent("token"), method: .post, @@ -92,7 +57,7 @@ private actor LiveSessionManager { ) .decoded(as: Session.self, decoder: configuration.decoder) - update(session) + storeSession(session) eventEmitter.emit(.tokenRefreshed, session: session) return session @@ -110,48 +75,57 @@ private actor LiveSessionManager { } else if let error = error as? any RetryableError, error.shouldRetry { throw error } else { - remove() + deleteSession() throw error } } } - return try await inFlightRefreshTask!.value + mutableState.withValue { + $0.inFlightRefreshTask = inFlightRefreshTask + } + + return try await inFlightRefreshTask.value } } } - func update(_ session: Session) { - client.storeSession(session) - } + // func update(_ session: Session) { + // client.storeSession(session) + // } - func remove() { - client.deleteSession() - } + // func remove() { + // client.deleteSession() + // } - func startAutoRefreshToken() { + func _startAutoRefreshToken() { logger?.debug("start auto refresh token") - startAutoRefreshTokenTask?.cancel() - startAutoRefreshTokenTask = Task { - while !Task.isCancelled { - await autoRefreshTokenTick() - try? await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(autoRefreshTickDuration)) + mutableState.withValue { + $0.autoRefreshTokenTask?.cancel() + $0.autoRefreshTokenTask = Task { + while !Task.isCancelled { + await autoRefreshTokenTick() + try? await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(autoRefreshTickDuration)) + } } } } - func stopAutoRefreshToken() { + func _stopAutoRefreshToken() { logger?.debug("stop auto refresh token") - startAutoRefreshTokenTask?.cancel() - startAutoRefreshTokenTask = nil + + mutableState.withValue { + $0.autoRefreshTokenTask?.cancel() + $0.autoRefreshTokenTask = nil + } } private func autoRefreshTokenTick() async { await trace(using: logger) { let now = Date().timeIntervalSince1970 - guard let session = client.getStoredSession() else { + guard let session = getStoredSession() else { return } @@ -161,7 +135,7 @@ private actor LiveSessionManager { ) if expiresInTicks <= autoRefreshTickThreshold { - _ = try? await refreshSession(session.refreshToken) + _ = try? await _refreshSession(session.refreshToken) } } } diff --git a/Tests/AuthTests/AuthClientTests.swift b/Tests/AuthTests/AuthClientTests.swift index 546c439a..ab3876ff 100644 --- a/Tests/AuthTests/AuthClientTests.swift +++ b/Tests/AuthTests/AuthClientTests.swift @@ -20,8 +20,6 @@ import XCTest #endif final class AuthClientTests: XCTestCase { - var sessionManager: SessionManager! - var storage: InMemoryLocalStorage! var http: HTTPClientMock! @@ -52,7 +50,6 @@ final class AuthClientTests: XCTestCase { defer { completion() } sut = nil - sessionManager = nil storage = nil }