Skip to content

Commit 25e93f1

Browse files
committed
delete session only when error is non-retryable
1 parent 0e8b43a commit 25e93f1

File tree

4 files changed

+68
-30
lines changed

4 files changed

+68
-30
lines changed

Sources/Auth/AuthError.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Helpers
23

34
#if canImport(FoundationNetworking)
45
import FoundationNetworking
@@ -47,7 +48,8 @@ extension ErrorCode {
4748
public static let oauthProviderNotSupported = ErrorCode("oauth_provider_not_supported")
4849
public static let unexpectedAudience = ErrorCode("unexpected_audience")
4950
public static let singleIdentityNotDeletable = ErrorCode("single_identity_not_deletable")
50-
public static let emailConflictIdentityNotDeletable = ErrorCode("email_conflict_identity_not_deletable")
51+
public static let emailConflictIdentityNotDeletable = ErrorCode(
52+
"email_conflict_identity_not_deletable")
5153
public static let identityAlreadyExists = ErrorCode("identity_already_exists")
5254
public static let emailProviderDisabled = ErrorCode("email_provider_disabled")
5355
public static let phoneProviderDisabled = ErrorCode("phone_provider_disabled")
@@ -108,14 +110,16 @@ public enum AuthError: LocalizedError, Equatable {
108110
@available(
109111
*,
110112
deprecated,
111-
message: "Error used to be thrown when no exp claim was found in JWT during setSession(accessToken:refreshToken:) method."
113+
message:
114+
"Error used to be thrown when no exp claim was found in JWT during setSession(accessToken:refreshToken:) method."
112115
)
113116
case missingExpClaim
114117

115118
@available(
116119
*,
117120
deprecated,
118-
message: "Error used to be thrown when provided JWT wasn't valid during setSession(accessToken:refreshToken:) method."
121+
message:
122+
"Error used to be thrown when provided JWT wasn't valid during setSession(accessToken:refreshToken:) method."
119123
)
120124
case malformedJWT
121125

@@ -155,14 +159,16 @@ public enum AuthError: LocalizedError, Equatable {
155159
@available(
156160
*,
157161
deprecated,
158-
message: "This error is never thrown, if you depend on it, you can remove the logic as it never happens."
162+
message:
163+
"This error is never thrown, if you depend on it, you can remove the logic as it never happens."
159164
)
160165
case missingURL
161166

162167
@available(
163168
*,
164169
deprecated,
165-
message: "Error used to be thrown on methods which required a valid redirect scheme, such as signInWithOAuth. This is now considered a programming error an a assertion is triggered in case redirect scheme isn't provided."
170+
message:
171+
"Error used to be thrown on methods which required a valid redirect scheme, such as signInWithOAuth. This is now considered a programming error an a assertion is triggered in case redirect scheme isn't provided."
166172
)
167173
case invalidRedirectScheme
168174

@@ -249,9 +255,9 @@ public enum AuthError: LocalizedError, Equatable {
249255
switch self {
250256
case .sessionMissing: "Auth session missing."
251257
case let .weakPassword(message, _),
252-
let .api(message, _, _, _),
253-
let .pkceGrantCodeExchange(message, _, _),
254-
let .implicitGrantRedirect(message):
258+
let .api(message, _, _, _),
259+
let .pkceGrantCodeExchange(message, _, _),
260+
let .implicitGrantRedirect(message):
255261
message
256262
// Deprecated cases
257263
case .missingExpClaim: "Missing expiration claim in the access token."
@@ -281,3 +287,13 @@ public enum AuthError: LocalizedError, Equatable {
281287
return lhs == rhs
282288
}
283289
}
290+
291+
extension AuthError: RetryableError {
292+
package var shouldRetry: Bool {
293+
switch self {
294+
case .api(_, _, _, let response):
295+
defaultRetryableHTTPStatusCodes.contains(response.statusCode)
296+
default: false
297+
}
298+
}
299+
}

Sources/Auth/Internal/SessionManager.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private actor LiveSessionManager {
8484
url: configuration.url.appendingPathComponent("token"),
8585
method: .post,
8686
query: [
87-
URLQueryItem(name: "grant_type", value: "refresh_token"),
87+
URLQueryItem(name: "grant_type", value: "refresh_token")
8888
],
8989
body: configuration.encoder.encode(
9090
UserCredentials(refreshToken: refreshToken)
@@ -97,10 +97,13 @@ private actor LiveSessionManager {
9797
eventEmitter.emit(.tokenRefreshed, session: session)
9898

9999
return session
100+
} catch let error as RetryableError {
101+
logger?.debug("Refresh token failed with a retryable error: \(error)")
102+
throw error
100103
} catch {
101-
logger?.debug("Failed to refresh token: \(error)")
104+
logger?.debug("Refresh token failed with a non-retryable error: \(error)")
105+
102106
remove()
103-
eventEmitter.emit(.signedOut, session: nil)
104107

105108
throw error
106109
}
@@ -146,7 +149,9 @@ private actor LiveSessionManager {
146149
}
147150

148151
let expiresInTicks = Int((session.expiresAt - now) / autoRefreshTickDuration)
149-
logger?.debug("access token expires in \(expiresInTicks) ticks, a tick lasts \(autoRefreshTickDuration)s, refresh threshold is \(autoRefreshTickThreshold) ticks")
152+
logger?.debug(
153+
"access token expires in \(expiresInTicks) ticks, a tick lasts \(autoRefreshTickDuration)s, refresh threshold is \(autoRefreshTickThreshold) ticks"
154+
)
150155

151156
if expiresInTicks <= autoRefreshTickThreshold {
152157
_ = try? await refreshSession(session.refreshToken)

Sources/Helpers/HTTP/RetryRequestInterceptor.swift

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,6 @@ package actor RetryRequestInterceptor: HTTPClientInterceptor {
3030
.delete, .get, .head, .options, .put, .trace,
3131
]
3232

33-
/// The default set of retryable HTTP status codes.
34-
package static let defaultRetryableHTTPStatusCodes: Set<Int> = [
35-
408, 500, 502, 503, 504,
36-
]
37-
38-
/// The default set of retryable URL error codes.
39-
package static let defaultRetryableURLErrorCodes: Set<URLError.Code> = [
40-
.backgroundSessionInUseByAnotherProcess, .backgroundSessionWasDisconnected,
41-
.badServerResponse, .callIsActive, .cannotConnectToHost, .cannotFindHost,
42-
.cannotLoadFromNetwork, .dataNotAllowed, .dnsLookupFailed,
43-
.downloadDecodingFailedMidStream, .downloadDecodingFailedToComplete,
44-
.internationalRoamingOff, .networkConnectionLost, .notConnectedToInternet,
45-
.secureConnectionFailed, .serverCertificateHasBadDate,
46-
.serverCertificateNotYetValid, .timedOut,
47-
]
48-
4933
/// The maximum number of retries.
5034
package let retryLimit: Int
5135
/// The base value for exponential backoff.
@@ -73,8 +57,8 @@ package actor RetryRequestInterceptor: HTTPClientInterceptor {
7357
exponentialBackoffBase: UInt = RetryRequestInterceptor.defaultExponentialBackoffBase,
7458
exponentialBackoffScale: Double = RetryRequestInterceptor.defaultExponentialBackoffScale,
7559
retryableHTTPMethods: Set<HTTPTypes.HTTPRequest.Method> = RetryRequestInterceptor.defaultRetryableHTTPMethods,
76-
retryableHTTPStatusCodes: Set<Int> = RetryRequestInterceptor.defaultRetryableHTTPStatusCodes,
77-
retryableErrorCodes: Set<URLError.Code> = RetryRequestInterceptor.defaultRetryableURLErrorCodes
60+
retryableHTTPStatusCodes: Set<Int> = defaultRetryableHTTPStatusCodes,
61+
retryableErrorCodes: Set<URLError.Code> = defaultRetryableURLErrorCodes
7862
) {
7963
precondition(
8064
exponentialBackoffBase >= 2, "The `exponentialBackoffBase` must be a minimum of 2."
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// RetryableError.swift
3+
// Supabase
4+
//
5+
// Created by Guilherme Souza on 15/10/24.
6+
//
7+
import Foundation
8+
9+
package protocol RetryableError: Error {
10+
var shouldRetry: Bool { get }
11+
}
12+
13+
extension URLError: RetryableError {
14+
package var shouldRetry: Bool {
15+
defaultRetryableURLErrorCodes.contains(code)
16+
}
17+
}
18+
19+
/// The default set of retryable URL error codes.
20+
package let defaultRetryableURLErrorCodes: Set<URLError.Code> = [
21+
.backgroundSessionInUseByAnotherProcess, .backgroundSessionWasDisconnected,
22+
.badServerResponse, .callIsActive, .cannotConnectToHost, .cannotFindHost,
23+
.cannotLoadFromNetwork, .dataNotAllowed, .dnsLookupFailed,
24+
.downloadDecodingFailedMidStream, .downloadDecodingFailedToComplete,
25+
.internationalRoamingOff, .networkConnectionLost, .notConnectedToInternet,
26+
.secureConnectionFailed, .serverCertificateHasBadDate,
27+
.serverCertificateNotYetValid, .timedOut,
28+
]
29+
30+
/// The default set of retryable HTTP status codes.
31+
package let defaultRetryableHTTPStatusCodes: Set<Int> = [
32+
408, 500, 502, 503, 504,
33+
]

0 commit comments

Comments
 (0)