Skip to content

Commit f796218

Browse files
refactor: create method for throwing error with context
1 parent ee4784c commit f796218

File tree

2 files changed

+60
-43
lines changed

2 files changed

+60
-43
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ public struct AccountConflictContext: LocalizedError, Identifiable, Equatable {
4242
public let underlyingError: Error
4343
public let message: String
4444
public let email: String?
45-
public let existingProviderIds: [String]?
46-
47-
/// Indicates if this conflict occurred during anonymous user upgrade
48-
public let isAnonymousUpgrade: Bool
49-
45+
5046
/// Human-readable description of the conflict type
5147
public var conflictDescription: String {
5248
switch conflictType {

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)