Skip to content

Commit e0bdfe8

Browse files
grdsdevclaude
andauthored
fix(auth): replace trait with runtime configuration flag (#844)
* fix(auth): replace trait with runtime configuration flag Replaces the EmitLocalSessionAsInitialSession trait with a runtime configuration flag to resolve compatibility issues with Xcode projects and align with Apple's trait guidelines. ## Changes - Added `emitLocalSessionAsInitialSession: Bool` property to `AuthClient.Configuration` - Defaults to `false` for backward compatibility - Will change to `true` in next major release - Replaced conditional compilation (`#if EmitLocalSessionAsInitialSession`) with runtime checks - Updated deprecation warning to reference the new configuration option - Added tests for both behaviors (old and new) - Removed `EmitLocalSessionAsInitialSession` trait from Package@swift-6.1.swift ## Benefits - Works in all project types (Xcode, SPM, CocoaPods) - No recompilation required to change behavior - Better discoverability through autocomplete and docs - Complies with Apple's trait guidelines (traits must be strictly additive) ## Migration Users who want the new behavior can now set: ```swift AuthClient( // ... other config emitLocalSessionAsInitialSession: true ) ``` Resolves compatibility issues where Xcode projects cannot enable package traits. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(auth): add emitLocalSessionAsInitialSession to SupabaseClientOptions Adds support for the emitLocalSessionAsInitialSession flag in SupabaseClientOptions.AuthOptions so users can configure this behavior when creating a SupabaseClient. - Added emitLocalSessionAsInitialSession property to SupabaseClientOptions.AuthOptions - Updated both initializers to include the new parameter - Pass the flag through to AuthClient initialization in SupabaseClient This ensures users can configure the flag whether they create an AuthClient directly or use SupabaseClient. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a83512b commit e0bdfe8

File tree

6 files changed

+108
-236
lines changed

6 files changed

+108
-236
lines changed

Package@swift-6.1.swift

Lines changed: 0 additions & 215 deletions
This file was deleted.

Sources/Auth/AuthClient.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,7 @@ public actor AuthClient {
13931393
}
13941394

13951395
private func emitInitialSession(forToken token: ObservationToken) async {
1396-
#if EmitLocalSessionAsInitialSession
1396+
if configuration.emitLocalSessionAsInitialSession {
13971397
guard let currentSession else {
13981398
eventEmitter.emit(.initialSession, session: nil, token: token)
13991399
return
@@ -1407,7 +1407,7 @@ public actor AuthClient {
14071407
// No need to emit `tokenRefreshed` nor `signOut` event since the `refreshSession` does it already.
14081408
}
14091409
}
1410-
#else
1410+
} else {
14111411
let session = try? await session
14121412
eventEmitter.emit(.initialSession, session: session, token: token)
14131413

@@ -1417,16 +1417,16 @@ public actor AuthClient {
14171417
reportIssue(
14181418
"""
14191419
Initial session emitted after attempting to refresh the local stored session.
1420-
This is incorrect behavior and will be fixed in the next major release since its a breaking change.
1421-
For now, if you want to opt-in to the new behavior, add the trait `EmitLocalSessionAsInitialSession` to your Package.swift file when importing the Supabase dependency.
1420+
This is incorrect behavior and will be fixed in the next major release since it's a breaking change.
1421+
To opt-in to the new behavior now, set `emitLocalSessionAsInitialSession: true` in your AuthClient configuration.
14221422
The new behavior ensures that the locally stored session is always emitted, regardless of its validity or expiration.
14231423
If you rely on the initial session to opt users in, you need to add an additional check for `session.isExpired` in the session.
14241424
14251425
Check https://github.com/supabase/supabase-swift/pull/822 for more information.
14261426
"""
14271427
)
14281428
}
1429-
#endif
1429+
}
14301430
}
14311431

14321432
nonisolated private func prepareForPKCE() -> (

Sources/Auth/AuthClientConfiguration.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ extension AuthClient {
4646
/// Set to `true` if you want to automatically refresh the token before expiring.
4747
public let autoRefreshToken: Bool
4848

49+
/// When `true`, emits the locally stored session immediately as the initial session,
50+
/// regardless of its validity or expiration. When `false`, emits the initial session
51+
/// after attempting to refresh the local stored session (legacy behavior).
52+
///
53+
/// Default is `false` for backward compatibility. This will change to `true` in the next major release.
54+
///
55+
/// - Note: If you rely on the initial session to opt users in, you need to add an additional
56+
/// check for `session.isExpired` when this is set to `true`.
57+
public let emitLocalSessionAsInitialSession: Bool
58+
4959
/// Initializes a AuthClient Configuration with optional parameters.
5060
///
5161
/// - Parameters:
@@ -60,6 +70,7 @@ extension AuthClient {
6070
/// - decoder: The JSON decoder to use for decoding responses.
6171
/// - fetch: The asynchronous fetch handler for network requests.
6272
/// - autoRefreshToken: Set to `true` if you want to automatically refresh the token before expiring.
73+
/// - emitLocalSessionAsInitialSession: When `true`, emits the locally stored session immediately as the initial session.
6374
public init(
6475
url: URL? = nil,
6576
headers: [String: String] = [:],
@@ -71,7 +82,8 @@ extension AuthClient {
7182
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
7283
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
7384
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) },
74-
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken
85+
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken,
86+
emitLocalSessionAsInitialSession: Bool = false
7587
) {
7688
let headers = headers.merging(Configuration.defaultHeaders) { l, _ in l }
7789

@@ -86,6 +98,7 @@ extension AuthClient {
8698
self.decoder = decoder
8799
self.fetch = fetch
88100
self.autoRefreshToken = autoRefreshToken
101+
self.emitLocalSessionAsInitialSession = emitLocalSessionAsInitialSession
89102
}
90103
}
91104

@@ -103,6 +116,7 @@ extension AuthClient {
103116
/// - decoder: The JSON decoder to use for decoding responses.
104117
/// - fetch: The asynchronous fetch handler for network requests.
105118
/// - autoRefreshToken: Set to `true` if you want to automatically refresh the token before expiring.
119+
/// - emitLocalSessionAsInitialSession: When `true`, emits the locally stored session immediately as the initial session.
106120
public init(
107121
url: URL? = nil,
108122
headers: [String: String] = [:],
@@ -114,7 +128,8 @@ extension AuthClient {
114128
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
115129
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
116130
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) },
117-
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken
131+
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken,
132+
emitLocalSessionAsInitialSession: Bool = false
118133
) {
119134
self.init(
120135
configuration: Configuration(
@@ -128,7 +143,8 @@ extension AuthClient {
128143
encoder: encoder,
129144
decoder: decoder,
130145
fetch: fetch,
131-
autoRefreshToken: autoRefreshToken
146+
autoRefreshToken: autoRefreshToken,
147+
emitLocalSessionAsInitialSession: emitLocalSessionAsInitialSession
132148
)
133149
)
134150
}

Sources/Supabase/SupabaseClient.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ public final class SupabaseClient: Sendable {
181181
// DON'T use `fetchWithAuth` method within the AuthClient as it may cause a deadlock.
182182
try await options.global.session.data(for: $0)
183183
},
184-
autoRefreshToken: options.auth.autoRefreshToken
184+
autoRefreshToken: options.auth.autoRefreshToken,
185+
emitLocalSessionAsInitialSession: options.auth.emitLocalSessionAsInitialSession
185186
)
186187

187188
_realtime = UncheckedSendable(

Sources/Supabase/Types.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ public struct SupabaseClientOptions: Sendable {
5757
/// Set to `true` if you want to automatically refresh the token before expiring.
5858
public let autoRefreshToken: Bool
5959

60+
/// When `true`, emits the locally stored session immediately as the initial session,
61+
/// regardless of its validity or expiration. When `false`, emits the initial session
62+
/// after attempting to refresh the local stored session (legacy behavior).
63+
///
64+
/// Default is `false` for backward compatibility. This will change to `true` in the next major release.
65+
public let emitLocalSessionAsInitialSession: Bool
66+
6067
/// Optional function for using a third-party authentication system with Supabase. The function should return an access token or ID token (JWT) by obtaining it from the third-party auth client library.
6168
/// Note that this function may be called concurrently and many times. Use memoization and locking techniques if this is not supported by the client libraries.
6269
/// When set, the `auth` namespace of the Supabase client cannot be used.
@@ -71,6 +78,7 @@ public struct SupabaseClientOptions: Sendable {
7178
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
7279
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
7380
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken,
81+
emitLocalSessionAsInitialSession: Bool = false,
7482
accessToken: (@Sendable () async throws -> String?)? = nil
7583
) {
7684
self.storage = storage
@@ -80,6 +88,7 @@ public struct SupabaseClientOptions: Sendable {
8088
self.encoder = encoder
8189
self.decoder = decoder
8290
self.autoRefreshToken = autoRefreshToken
91+
self.emitLocalSessionAsInitialSession = emitLocalSessionAsInitialSession
8392
self.accessToken = accessToken
8493
}
8594
}
@@ -173,6 +182,7 @@ extension SupabaseClientOptions.AuthOptions {
173182
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
174183
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
175184
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken,
185+
emitLocalSessionAsInitialSession: Bool = false,
176186
accessToken: (@Sendable () async throws -> String?)? = nil
177187
) {
178188
self.init(
@@ -183,6 +193,7 @@ extension SupabaseClientOptions.AuthOptions {
183193
encoder: encoder,
184194
decoder: decoder,
185195
autoRefreshToken: autoRefreshToken,
196+
emitLocalSessionAsInitialSession: emitLocalSessionAsInitialSession,
186197
accessToken: accessToken
187198
)
188199
}

0 commit comments

Comments
 (0)