Skip to content

Commit

Permalink
Update session lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
shilgapira committed Jan 7, 2025
1 parent 935a681 commit 6f38dbb
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/sdk/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct DescopeConfig {
/// This property can be useful to test code that uses the Descope SDK without any
/// network requests actually taking place. In most other cases there shouldn't be
/// any need to use it.
public var networkClient: DescopeNetworkClient? = nil
public var networkClient: DescopeNetworkClient?
}

/// The ``DescopeLogger`` class can be used to customize logging functionality in the Descope SDK.
Expand Down
6 changes: 3 additions & 3 deletions src/sdk/SDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public class DescopeSDK {
/// override the ``DescopeSDK`` object's default networking client with one that always
/// fails, using code such as this (see ``DescopeNetworkClient``):
///
/// let descope = DescopeSDK(projectId: "") { config in
/// let descope = DescopeSDK(projectId: "test") { config in
/// config.networkClient = FailingNetworkClient()
/// }
/// testOTPNetworkError(descope)
Expand Down Expand Up @@ -143,15 +143,15 @@ public extension DescopeSDK {
static let name = "DescopeKit"

/// The Descope SDK version
static let version = "0.9.10"
static let version = "0.9.11"
}

// Internal

private extension DescopeSessionManager {
convenience init(sdk: DescopeSDK) {
let storage = SessionStorage(projectId: sdk.config.projectId)
let lifecycle = SessionLifecycle(auth: sdk.auth)
let lifecycle = SessionLifecycle(auth: sdk.auth, storage: storage, logger: sdk.config.logger)
self.init(storage: storage, lifecycle: lifecycle)
}
}
44 changes: 36 additions & 8 deletions src/session/Lifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,49 @@ public protocol DescopeSessionLifecycle: AnyObject {
/// or if it's already expired.
public class SessionLifecycle: DescopeSessionLifecycle {
public let auth: DescopeAuth
public let storage: DescopeSessionStorage
public let logger: DescopeLogger?

public init(auth: DescopeAuth) {
public init(auth: DescopeAuth, storage: DescopeSessionStorage, logger: DescopeLogger?) {
self.auth = auth
self.storage = storage
self.logger = logger
}

public var stalenessAllowedInterval: TimeInterval = 60 /* seconds */

public var stalenessCheckFrequency: TimeInterval = 30 /* seconds */ {
public var periodicCheckFrequency: TimeInterval = 30 /* seconds */ {
didSet {
if stalenessCheckFrequency != oldValue {
if periodicCheckFrequency != oldValue {
resetTimer()
}
}
}

public var shouldSaveAfterPeriodicRefresh: Bool = true

public var session: DescopeSession? {
didSet {
if session?.refreshJwt != oldValue?.refreshJwt {
resetTimer()
}
if let session, session.refreshToken.isExpired {
logger(.debug, "Session has an expired refresh token", session.refreshToken.expiresAt)
}
}
}

public func refreshSessionIfNeeded() async throws -> Bool {
guard let current = session, shouldRefresh(current) else { return false }

logger(.info, "Refreshing session that is about to expire", current.sessionToken.expiresAt.timeIntervalSinceNow)
let response = try await auth.refreshSession(refreshJwt: current.refreshJwt)

guard session?.sessionJwt == current.sessionJwt else {
logger(.info, "Skipping refresh because session has changed in the meantime")
return false
}

session?.updateTokens(with: response)
return true
}
Expand All @@ -61,7 +78,7 @@ public class SessionLifecycle: DescopeSessionLifecycle {
private var timer: Timer?

private func resetTimer() {
if stalenessCheckFrequency > 0, let refreshToken = session?.refreshToken, !refreshToken.isExpired {
if periodicCheckFrequency > 0, let refreshToken = session?.refreshToken, !refreshToken.isExpired {
startTimer()
} else {
stopTimer()
Expand All @@ -70,9 +87,9 @@ public class SessionLifecycle: DescopeSessionLifecycle {

private func startTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: stalenessCheckFrequency, repeats: true) { [weak self] timer in
timer = Timer.scheduledTimer(withTimeInterval: periodicCheckFrequency, repeats: true) { [weak self] timer in
guard let lifecycle = self else { return timer.invalidate() }
Task { @MainActor in
Task {
await lifecycle.periodicRefresh()
}
}
Expand All @@ -84,11 +101,22 @@ public class SessionLifecycle: DescopeSessionLifecycle {
}

private func periodicRefresh() async {
if let refreshToken = session?.refreshToken, refreshToken.isExpired {
logger(.debug, "Stopping periodic refresh for session with expired refresh token")
stopTimer()
return
}

do {
_ = try await refreshSessionIfNeeded()
let refreshed = try await refreshSessionIfNeeded()
if refreshed, shouldSaveAfterPeriodicRefresh, let session {
logger(.debug, "Saving refresh session after periodic refresh")
storage.saveSession(session)
}
} catch DescopeError.networkError {
// allow retries on network errors
logger(.debug, "Ignoring network error in periodic refresh")
} catch {
logger(.error, "Stopping periodic refresh after failure", error)
stopTimer()
}
}
Expand Down
21 changes: 12 additions & 9 deletions src/session/Manager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public class DescopeSessionManager {
/// each other's saved sessions.
public func manageSession(_ session: DescopeSession) {
lifecycle.session = session
storage.saveSession(session)
saveSession()
}

/// Clears any active ``DescopeSession`` from this manager and removes it
Expand All @@ -122,6 +122,17 @@ public class DescopeSessionManager {
storage.removeSession()
}

/// Saves the active ``DescopeSession`` to the storage.
///
/// - Important: There is usually no need to call this method directly.
/// The session is automatically saved when it's refreshed or updated,
/// unless you're using a session manager with custom `stroage` and
/// `lifecycle` objects.
public func saveSession() {
guard let session else { return }
storage.saveSession(session)
}

/// Ensures that the session is valid and refreshes it if needed.
///
/// The session manager checks whether there's an active ``DescopeSession`` and if
Expand Down Expand Up @@ -170,12 +181,4 @@ public class DescopeSessionManager {
lifecycle.session?.updateUser(with: user)
saveSession()
}

// Internal

/// Saves the latest session value to the storage.
private func saveSession() {
guard let session else { return }
storage.saveSession(session)
}
}

0 comments on commit 6f38dbb

Please sign in to comment.