diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift index fe8970c91a..0a05bde4fa 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift @@ -36,9 +36,11 @@ public enum AuthServiceError: LocalizedError { case invalidCredentials(String) case signInFailed(underlying: Error) case accountMergeConflict(context: AccountMergeConflictContext) - case invalidPhoneAuthenticationArguments(String) case providerNotFound(String) case multiFactorAuth(String) + case rootViewControllerNotFound(String) + case providerAuthenticationFailed(String) + case signInCancelled(String) public var errorDescription: String? { switch self { @@ -54,16 +56,22 @@ public enum AuthServiceError: LocalizedError { return description case let .invalidCredentials(description): return description + // Use when failed to sign-in with Firebase case let .signInFailed(underlying: error): return "Failed to sign in: \(error.localizedDescription)" + // Use when failed to sign-in with provider (e.g. Google, Facebook, etc.) + case let .providerAuthenticationFailed(description): + return description + case let .signInCancelled(description): + return description case let .accountMergeConflict(context): return context.errorDescription case let .providerNotFound(description): return description - case let .invalidPhoneAuthenticationArguments(description): - return description case let .multiFactorAuth(description): return description + case let .rootViewControllerNotFound(description): + return description } } } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift index 59bcb0d594..78aee6b7d6 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift @@ -96,7 +96,8 @@ public final class PasswordPromptCoordinator { func cancel() { continuation? - .resume(throwing: AuthServiceError.reauthenticationRequired("Password entry cancelled")) + .resume(throwing: AuthServiceError + .signInCancelled("Password entry cancelled for Email provider")) cleanup() } diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift index fa76d8ed3b..c4db630898 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift @@ -19,14 +19,6 @@ import FirebaseAuth import FirebaseAuthSwiftUI import SwiftUI -public enum FacebookProviderError: Error { - case signInCancelled(String) - case configurationInvalid(String) - case limitedLoginNonce(String) - case accessToken(String) - case authenticationToken(String) -} - public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { let scopes: [String] let providerId = "facebook.com" @@ -60,8 +52,8 @@ public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { ) } }() else { - throw FacebookProviderError - .configurationInvalid("Failed to create Facebook login configuration") + throw AuthServiceError + .providerAuthenticationFailed("Failed to create Facebook login configuration") } let result = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation< @@ -74,7 +66,8 @@ public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { switch result { case .cancelled: continuation - .resume(throwing: FacebookProviderError.signInCancelled("User cancelled sign-in")) + .resume(throwing: AuthServiceError + .signInCancelled("User cancelled sign-in for Facebook")) case let .failed(error): continuation.resume(throwing: error) case .success: @@ -97,8 +90,8 @@ public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { return credential } else { - throw FacebookProviderError - .accessToken( + throw AuthServiceError + .providerAuthenticationFailed( "Access token has expired or not available. Please sign-in with Facebook before attempting to create a Facebook provider credential" ) } @@ -107,16 +100,18 @@ public class FacebookProviderSwift: AuthProviderSwift, DeleteUserSwift { private func limitedLogin() throws -> AuthCredential { if let idToken = AuthenticationToken.current { guard let nonce = rawNonce else { - throw FacebookProviderError - .limitedLoginNonce("`rawNonce` has not been generated for Facebook limited login") + throw AuthServiceError + .providerAuthenticationFailed( + "`rawNonce` has not been generated for Facebook limited login" + ) } let credential = OAuthProvider.credential(withProviderID: providerId, idToken: idToken.tokenString, rawNonce: nonce) return credential } else { - throw FacebookProviderError - .authenticationToken( + throw AuthServiceError + .providerAuthenticationFailed( "Authentication is not available. Please sign-in with Facebook before attempting to create a Facebook provider credential" ) } diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/SignInWithFacebookButton.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/SignInWithFacebookButton.swift index 96d7763422..10e74c0339 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/SignInWithFacebookButton.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/SignInWithFacebookButton.swift @@ -66,17 +66,7 @@ extension SignInWithFacebookButton: View { VStack { Button(action: { Task { - do { - try await authService.signIn(facebookProvider) - } catch { - switch error { - case FacebookProviderError.signInCancelled: - showCanceledAlert = true - default: - // Error already handled by AuthService - break - } - } + try? await authService.signIn(facebookProvider) } }) { HStack { @@ -113,12 +103,6 @@ extension SignInWithFacebookButton: View { .toggleStyle(SwitchToggleStyle(tint: .green)) } } - .alert(isPresented: $showCanceledAlert) { - Alert( - title: Text(authService.string.facebookLoginCancelledLabel), - dismissButton: .default(Text(authService.string.okButtonLabel)) - ) - } .alert(isPresented: $showUserTrackingAlert) { Alert( title: Text(authService.string.authorizeUserTrackingLabel), diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift index d785ce5f07..06ad5ca39f 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift @@ -19,12 +19,6 @@ import GoogleSignIn import GoogleSignInSwift import SwiftUI -public enum GoogleProviderError: Error { - case rootViewControllerNotFound(String) - case authenticationToken(String) - case user(String) -} - public class GoogleProviderSwift: AuthProviderSwift, DeleteUserSwift { let scopes: [String] let clientID: String @@ -42,7 +36,7 @@ public class GoogleProviderSwift: AuthProviderSwift, DeleteUserSwift { @MainActor public func createAuthCredential() async throws -> AuthCredential { guard let presentingViewController = await (UIApplication.shared.connectedScenes .first as? UIWindowScene)?.windows.first?.rootViewController else { - throw GoogleProviderError + throw AuthServiceError .rootViewControllerNotFound( "Root View controller is not available to present Google sign-in View." ) @@ -63,7 +57,8 @@ public class GoogleProviderSwift: AuthProviderSwift, DeleteUserSwift { guard let user = result?.user, let idToken = user.idToken?.tokenString else { continuation - .resume(throwing: GoogleProviderError.user("Failed to retrieve user or idToken.")) + .resume(throwing: AuthServiceError + .providerAuthenticationFailed("Failed to retrieve user or idToken.")) return } diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift index 1f71aba5ef..feb3f9062c 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift @@ -51,6 +51,7 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { self.buttonBackgroundColor = buttonBackgroundColor self.buttonForegroundColor = buttonForegroundColor } + /// Convenience initializer using SF Symbol /// - Parameters: /// - providerId: The OAuth provider ID (e.g., "github.com", "microsoft.com") @@ -132,6 +133,7 @@ public class OAuthProviderAuthUI: AuthProviderUI { } return oauthProvider.providerId } + @MainActor public func authButton() -> AnyView { AnyView(GenericOAuthButton(provider: provider)) } diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift index be555bdc92..c1c3d89695 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift @@ -39,7 +39,7 @@ public class PhoneProviderSwift: PhoneAuthProviderSwift { guard let presentingViewController = await (UIApplication.shared.connectedScenes .first as? UIWindowScene)?.windows.first?.rootViewController else { throw AuthServiceError - .invalidPhoneAuthenticationArguments( + .rootViewControllerNotFound( "Root View controller is not available to present Phone auth View." ) } diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Views/PhoneAuthView.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Views/PhoneAuthView.swift index 7d76df72c4..539130415f 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Views/PhoneAuthView.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Views/PhoneAuthView.swift @@ -51,11 +51,8 @@ extension PhoneAuthView: View { HStack { Spacer() Button(action: { - completion(.failure(NSError( - domain: "PhoneAuthError", - code: -1, - userInfo: [NSLocalizedDescriptionKey: "User cancelled"] - ))) + completion(.failure(AuthServiceError + .signInCancelled("User cancelled sign-in for Phone"))) dismiss() }) { Image(systemName: "xmark.circle.fill")