@@ -27,9 +27,10 @@ public protocol AuthProviderUI {
2727 var provider : AuthProviderSwift { get }
2828}
2929
30- public protocol PhoneAuthProviderSwift : AuthProviderSwift {
31- @MainActor func verifyPhoneNumber( phoneNumber: String ) async throws -> String
32- func setVerificationCode( verificationID: String , code: String )
30+ public protocol PhoneAuthProviderSwift : AuthProviderSwift , AnyObject {
31+ // Phone auth provider that presents its own UI flow in createAuthCredential()
32+ // Internal use only: AuthService will be injected automatically by AuthService.signIn()
33+ var authService : AuthService ? { get set }
3334}
3435
3536public enum AuthenticationState {
@@ -50,8 +51,6 @@ public enum AuthView: Hashable {
5051 case mfaEnrollment
5152 case mfaManagement
5253 case mfaResolution
53- case enterPhoneNumber
54- case enterVerificationCode( verificationID: String , fullPhoneNumber: String )
5554}
5655
5756public enum SignInOutcome : @unchecked Sendable {
@@ -144,10 +143,6 @@ public final class AuthService {
144143
145144 private var providers : [ AuthProviderUI ] = [ ]
146145
147- public var currentPhoneProvider : PhoneAuthProviderSwift ? {
148- providers. compactMap { $0. provider as? PhoneAuthProviderSwift } . first
149- }
150-
151146 public func registerProvider( providerWithButton: AuthProviderUI ) {
152147 providers. append ( providerWithButton)
153148 }
@@ -171,11 +166,17 @@ public final class AuthService {
171166
172167 public func signIn( _ provider: AuthProviderSwift ) async throws -> SignInOutcome {
173168 do {
169+ // Automatically inject AuthService for phone provider
170+ if let phoneProvider = provider as? PhoneAuthProviderSwift {
171+ phoneProvider. authService = self
172+ }
173+
174174 let credential = try await provider. createAuthCredential ( )
175175 let result = try await signIn ( credentials: credential)
176176 return result
177177 } catch {
178- updateError ( message: string. localizedErrorMessage ( for: error) )
178+ // Always pass the underlying error - view decides what to show
179+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError: error)
179180 throw error
180181 }
181182 }
@@ -206,8 +207,8 @@ public final class AuthService {
206207 currentError = nil
207208 }
208209
209- func updateError( title: String = " Error " , message: String ) {
210- currentError = AlertError ( title: title, message: message)
210+ func updateError( title: String = " Error " , message: String , underlyingError : Error ? = nil ) {
211+ currentError = AlertError ( title: title, message: message, underlyingError : underlyingError )
211212 }
212213
213214 public var shouldHandleAnonymousUpgrade : Bool {
@@ -217,9 +218,11 @@ public final class AuthService {
217218 public func signOut( ) async throws {
218219 do {
219220 try await auth. signOut ( )
221+ // Cannot wait for auth listener to change, feedback needs to be immediate
222+ currentUser = nil
220223 updateAuthenticationState ( )
221224 } catch {
222- updateError ( message: string. localizedErrorMessage ( for: error) )
225+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
223226 throw error
224227 }
225228 }
@@ -237,12 +240,12 @@ public final class AuthService {
237240 updateAuthenticationState ( )
238241 } catch {
239242 authenticationState = . unauthenticated
240- updateError ( message: string. localizedErrorMessage ( for: error) )
243+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
241244 throw error
242245 }
243246 }
244247
245- public func handleAutoUpgradeAnonymousUser( credentials: AuthCredential ) async throws
248+ private func handleAutoUpgradeAnonymousUser( credentials: AuthCredential ) async throws
246249 -> SignInOutcome {
247250 if currentUser == nil {
248251 throw AuthServiceError . noCurrentUser
@@ -252,11 +255,27 @@ public final class AuthService {
252255 updateAuthenticationState ( )
253256 return . signedIn( result)
254257 } catch let error as NSError {
258+ // Handle credentialAlreadyInUse error
259+ if error. code == AuthErrorCode . credentialAlreadyInUse. rawValue {
260+ // Extract the updated credential from the error
261+ let updatedCredential = error. userInfo [ " FIRAuthUpdatedCredentialKey " ] as? AuthCredential
262+ ?? credentials
263+
264+ let context = AccountMergeConflictContext (
265+ credential: updatedCredential,
266+ underlyingError: error,
267+ message: " Unable to merge accounts. The credential is already associated with a different account. " ,
268+ uid: currentUser? . uid
269+ )
270+ throw AuthServiceError . accountMergeConflict ( context: context)
271+ }
272+
273+ // Handle emailAlreadyInUse error
255274 if error. code == AuthErrorCode . emailAlreadyInUse. rawValue {
256275 let context = AccountMergeConflictContext (
257276 credential: credentials,
258277 underlyingError: error,
259- message: " Unable to merge accounts. Use the credential in the context to resolve the conflict . " ,
278+ message: " Unable to merge accounts. This email is already associated with a different account . " ,
260279 uid: currentUser? . uid
261280 )
262281 throw AuthServiceError . accountMergeConflict ( context: context)
@@ -285,7 +304,7 @@ public final class AuthService {
285304 }
286305 } else {
287306 // Don't want error modal on MFA error so we only update here
288- updateError ( message: string. localizedErrorMessage ( for: error) )
307+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
289308 }
290309
291310 throw error
@@ -307,7 +326,7 @@ public final class AuthService {
307326 }
308327 }
309328 } catch {
310- updateError ( message: string. localizedErrorMessage ( for: error) )
329+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
311330 throw error
312331 }
313332 }
@@ -326,7 +345,7 @@ public extension AuthService {
326345 try await user. delete ( )
327346 }
328347 } catch {
329- updateError ( message: string. localizedErrorMessage ( for: error) )
348+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
330349 throw error
331350 }
332351 }
@@ -341,7 +360,7 @@ public extension AuthService {
341360 try await user. updatePassword ( to: password)
342361 }
343362 } catch {
344- updateError ( message: string. localizedErrorMessage ( for: error) )
363+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
345364 throw error
346365 }
347366 }
@@ -374,7 +393,7 @@ public extension AuthService {
374393 }
375394 } catch {
376395 authenticationState = . unauthenticated
377- updateError ( message: string. localizedErrorMessage ( for: error) )
396+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
378397 throw error
379398 }
380399 }
@@ -383,7 +402,7 @@ public extension AuthService {
383402 do {
384403 try await auth. sendPasswordReset ( withEmail: email)
385404 } catch {
386- updateError ( message: string. localizedErrorMessage ( for: error) )
405+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
387406 throw error
388407 }
389408 }
@@ -400,7 +419,7 @@ public extension AuthService {
400419 actionCodeSettings: actionCodeSettings
401420 )
402421 } catch {
403- updateError ( message: string. localizedErrorMessage ( for: error) )
422+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
404423 throw error
405424 }
406425 }
@@ -433,7 +452,7 @@ public extension AuthService {
433452 emailLink = nil
434453 }
435454 } catch {
436- updateError ( message: string. localizedErrorMessage ( for: error) )
455+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
437456 throw error
438457 }
439458 }
@@ -502,7 +521,7 @@ public extension AuthService {
502521 changeRequest. photoURL = url
503522 try await changeRequest. commitChanges ( )
504523 } catch {
505- updateError ( message: string. localizedErrorMessage ( for: error) )
524+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
506525 throw error
507526 }
508527 }
@@ -517,7 +536,7 @@ public extension AuthService {
517536 changeRequest. displayName = name
518537 try await changeRequest. commitChanges ( )
519538 } catch {
520- updateError ( message: string. localizedErrorMessage ( for: error) )
539+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
521540 throw error
522541 }
523542 }
@@ -609,7 +628,7 @@ public extension AuthService {
609628 )
610629 }
611630 } catch {
612- updateError ( message: string. localizedErrorMessage ( for: error) )
631+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
613632 throw error
614633 }
615634 }
@@ -661,7 +680,7 @@ public extension AuthService {
661680
662681 return verificationID
663682 } catch {
664- updateError ( message: string. localizedErrorMessage ( for: error) )
683+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
665684 throw error
666685 }
667686 }
@@ -740,7 +759,7 @@ public extension AuthService {
740759 }
741760 currentUser = auth. currentUser
742761 } catch {
743- updateError ( message: string. localizedErrorMessage ( for: error) )
762+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
744763 throw error
745764 }
746765 }
@@ -829,7 +848,7 @@ public extension AuthService {
829848
830849 return freshFactors
831850 } catch {
832- updateError ( message: string. localizedErrorMessage ( for: error) )
851+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
833852 throw error
834853 }
835854 }
@@ -899,7 +918,7 @@ public extension AuthService {
899918 }
900919 }
901920 } catch {
902- updateError ( message: string. localizedErrorMessage ( for: error) )
921+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
903922 throw error
904923 }
905924 }
@@ -952,7 +971,7 @@ public extension AuthService {
952971 . multiFactorAuth ( " Failed to resolve MFA challenge: \( error. localizedDescription) " )
953972 }
954973 } catch {
955- updateError ( message: string. localizedErrorMessage ( for: error) )
974+ updateError ( message: string. localizedErrorMessage ( for: error) , underlyingError : error )
956975 throw error
957976 }
958977 }
0 commit comments