@@ -130,7 +130,7 @@ public final class AuthService {
130130 public var currentUser : User ?
131131 public var authenticationState : AuthenticationState = . unauthenticated
132132 public var authenticationFlow : AuthenticationFlow = . signIn
133- public var currentError : AlertError ?
133+ public private ( set ) var currentError : AlertError ?
134134 public let passwordPrompt : PasswordPromptCoordinator = . init( )
135135 public var currentMFARequired : MFARequired ?
136136 private var currentMFAResolver : MultiFactorResolver ?
@@ -243,9 +243,12 @@ public final class AuthService {
243243 }
244244 updateAuthenticationState ( )
245245 } catch {
246+ // Possible conflicts from user.link():
247+ // - credentialAlreadyInUse: credential is already linked to another account
248+ // - emailAlreadyInUse: email from credential is already used by another account
249+ // - accountExistsWithDifferentCredential: account exists with different sign-in method
246250 authenticationState = . unauthenticated
247- updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError: error)
248- throw error
251+ try handleErrorWithConflictCheck ( error: error, credential: credentials)
249252 }
250253 }
251254
@@ -259,7 +262,6 @@ public final class AuthService {
259262 updateAuthenticationState ( )
260263 return . signedIn( result)
261264 } catch {
262- // Always throw errors - let view layer decide what to do
263265 throw error
264266 }
265267 }
@@ -284,30 +286,11 @@ public final class AuthService {
284286 return handleMFARequiredError ( resolver: resolver)
285287 }
286288 }
287- // Check for account conflict errors
288- else if let conflictType = determineConflictType ( from: error) {
289- let context = createConflictContext (
290- from: error,
291- conflictType: conflictType,
292- credential: credentials
293- )
294-
295- // Store it for consumers to observe
296- currentAccountConflict = context
297-
298- // Only set error alert if we're NOT auto-handling it
299- if conflictType != . anonymousUpgradeConflict {
300- updateError ( message: context. message, underlyingError: error)
301- }
302-
303- // Throw the specific error with context
304- throw AuthServiceError . accountConflict ( context)
305- } else {
306- // Don't want error modal on MFA error so we only update here
307- updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError: error)
308- }
309-
310- throw error
289+
290+ // Possible conflicts from auth.signIn(with:):
291+ // - accountExistsWithDifferentCredential: account exists with different provider
292+ // - credentialAlreadyInUse: credential is already linked to another account
293+ try handleErrorWithConflictCheck ( error: error, credential: credentials)
311294 }
312295 }
313296
@@ -381,22 +364,21 @@ public extension AuthService {
381364
382365 func createUser( email email: String , password: String ) async throws -> SignInOutcome {
383366 authenticationState = . authenticating
367+ let credential = EmailAuthProvider . credential ( withEmail: email, password: password)
384368
385369 do {
386370 if shouldHandleAnonymousUpgrade {
387- let credential = EmailAuthProvider . credential ( withEmail: email, password: password)
388371 return try await handleAutoUpgradeAnonymousUser ( credentials: credential)
389372 } else {
390373 let result = try await auth. createUser ( withEmail: email, password: password)
391374 updateAuthenticationState ( )
392375 return . signedIn( result)
393376 }
394377 } catch {
378+ // Possible conflicts from auth.createUser():
379+ // - emailAlreadyInUse: email is already registered with another account
395380 authenticationState = . unauthenticated
396- // store error if consumer wants to handle it
397- updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError: error)
398- // throw error to view layer
399- throw error
381+ try handleErrorWithConflictCheck ( error: error, credential: credential)
400382 }
401383 }
402384
@@ -454,8 +436,18 @@ public extension AuthService {
454436 emailLink = nil
455437 }
456438 } catch {
457- updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError: error)
458- throw error
439+ // Reconstruct credential for conflict handling
440+ let link = url. absoluteString
441+ guard let email = emailLink else {
442+ throw AuthServiceError
443+ . invalidEmailLink ( " email address is missing from app storage. Is this the same device? " )
444+ }
445+ let credential = EmailAuthProvider . credential ( withEmail: email, link: link)
446+
447+ // Possible conflicts from auth.signIn(withEmail:link:):
448+ // - accountExistsWithDifferentCredential: account exists with different provider
449+ // - credentialAlreadyInUse: credential is already linked to another account
450+ try handleErrorWithConflictCheck ( error: error, credential: credential)
459451 }
460452 }
461453
@@ -883,12 +875,41 @@ public extension AuthService {
883875 credential: updatedCredential,
884876 underlyingError: error,
885877 message: string. localizedErrorMessage ( for: error) ,
886- email: email,
887- existingProviderIds: nil ,
888- isAnonymousUpgrade: shouldHandleAnonymousUpgrade
878+ email: email
889879 )
890880 }
891881
882+ /// Handles account conflict errors by creating context, storing it, and throwing structured error
883+ /// - Parameters:
884+ /// - error: The error to check and handle
885+ /// - credential: The credential that caused the conflict
886+ /// - Throws: AuthServiceError.accountConflict if it's a conflict error, otherwise rethrows the original error
887+ private func handleErrorWithConflictCheck( error: Error , credential: AuthCredential ) throws -> Never {
888+ // Check for account conflict errors
889+ if let error = error as NSError ? ,
890+ let conflictType = determineConflictType ( from: error) {
891+ let context = createConflictContext (
892+ from: error,
893+ conflictType: conflictType,
894+ credential: credential
895+ )
896+
897+ // Store it for consumers to observe
898+ currentAccountConflict = context
899+
900+ // Only set error alert if we're NOT auto-handling it
901+ if conflictType != . anonymousUpgradeConflict {
902+ updateError ( message: context. message, underlyingError: error)
903+ }
904+
905+ // Throw the specific error with context
906+ throw AuthServiceError . accountConflict ( context)
907+ } else {
908+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError: error)
909+ throw error
910+ }
911+ }
912+
892913 // MARK: - MFA Helper Methods
893914
894915 private func extractMFAHints( from resolver: MultiFactorResolver ) -> [ MFAHint ] {
0 commit comments