Skip to content

Commit

Permalink
drop SessionManager
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev committed Feb 13, 2025
1 parent aab8020 commit 39996d4
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 96 deletions.
38 changes: 15 additions & 23 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,14 @@ import Helpers

public final class AuthClient: Sendable {
struct MutableState {
var sessionManager: SessionManager?
var inFlightRefreshTask: Task<Session, any Error>?
var autoRefreshTokenTask: Task<Void, Never>?
}

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 }
Expand Down Expand Up @@ -63,7 +55,7 @@ public final class AuthClient: Sendable {
/// ```
public var session: Session {
get async throws {
try await sessionManager.session()
try await _session()
}
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -626,7 +618,7 @@ public final class AuthClient: Sendable {

storeCodeVerifier(nil)

await sessionManager.update(session)
storeSession(session)
eventEmitter.emit(.signedIn, session: session)

return session
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -949,7 +941,7 @@ public final class AuthClient: Sendable {
)
}

await sessionManager.update(session)
storeSession(session)
eventEmitter.emit(.signedIn, session: session)
return session
}
Expand Down Expand Up @@ -977,7 +969,7 @@ public final class AuthClient: Sendable {
}

if scope != .others {
await sessionManager.remove()
deleteSession()
eventEmitter.emit(.signedOut, session: nil)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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"),
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 3 additions & 4 deletions Sources/Auth/AuthMFA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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?
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Session, any Error>?
private var startAutoRefreshTokenTask: Task<Void, Never>?

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
}
Expand All @@ -52,33 +15,35 @@ 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),
"refresh_token": .string(refreshToken),
]
) {
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,
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -161,7 +135,7 @@ private actor LiveSessionManager {
)

if expiresInTicks <= autoRefreshTickThreshold {
_ = try? await refreshSession(session.refreshToken)
_ = try? await _refreshSession(session.refreshToken)
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions Tests/AuthTests/AuthClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import XCTest
#endif

final class AuthClientTests: XCTestCase {
var sessionManager: SessionManager!

var storage: InMemoryLocalStorage!

var http: HTTPClientMock!
Expand Down Expand Up @@ -52,7 +50,6 @@ final class AuthClientTests: XCTestCase {
defer { completion() }

sut = nil
sessionManager = nil
storage = nil
}

Expand Down

0 comments on commit 39996d4

Please sign in to comment.