Skip to content

Commit b87e17e

Browse files
fix: silent error on cancel
1 parent 7820b56 commit b87e17e

3 files changed

Lines changed: 63 additions & 47 deletions

File tree

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,13 @@ public final class AuthService {
170170
if let phoneProvider = provider as? PhoneAuthProviderSwift {
171171
phoneProvider.authService = self
172172
}
173-
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 {
@@ -221,7 +222,7 @@ public final class AuthService {
221222
currentUser = nil
222223
updateAuthenticationState()
223224
} catch {
224-
updateError(message: string.localizedErrorMessage(for: error))
225+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
225226
throw error
226227
}
227228
}
@@ -239,7 +240,7 @@ public final class AuthService {
239240
updateAuthenticationState()
240241
} catch {
241242
authenticationState = .unauthenticated
242-
updateError(message: string.localizedErrorMessage(for: error))
243+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
243244
throw error
244245
}
245246
}
@@ -303,7 +304,7 @@ public final class AuthService {
303304
}
304305
} else {
305306
// Don't want error modal on MFA error so we only update here
306-
updateError(message: string.localizedErrorMessage(for: error))
307+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
307308
}
308309

309310
throw error
@@ -325,7 +326,7 @@ public final class AuthService {
325326
}
326327
}
327328
} catch {
328-
updateError(message: string.localizedErrorMessage(for: error))
329+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
329330
throw error
330331
}
331332
}
@@ -344,7 +345,7 @@ public extension AuthService {
344345
try await user.delete()
345346
}
346347
} catch {
347-
updateError(message: string.localizedErrorMessage(for: error))
348+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
348349
throw error
349350
}
350351
}
@@ -359,7 +360,7 @@ public extension AuthService {
359360
try await user.updatePassword(to: password)
360361
}
361362
} catch {
362-
updateError(message: string.localizedErrorMessage(for: error))
363+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
363364
throw error
364365
}
365366
}
@@ -392,7 +393,7 @@ public extension AuthService {
392393
}
393394
} catch {
394395
authenticationState = .unauthenticated
395-
updateError(message: string.localizedErrorMessage(for: error))
396+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
396397
throw error
397398
}
398399
}
@@ -401,7 +402,7 @@ public extension AuthService {
401402
do {
402403
try await auth.sendPasswordReset(withEmail: email)
403404
} catch {
404-
updateError(message: string.localizedErrorMessage(for: error))
405+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
405406
throw error
406407
}
407408
}
@@ -418,7 +419,7 @@ public extension AuthService {
418419
actionCodeSettings: actionCodeSettings
419420
)
420421
} catch {
421-
updateError(message: string.localizedErrorMessage(for: error))
422+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
422423
throw error
423424
}
424425
}
@@ -451,7 +452,7 @@ public extension AuthService {
451452
emailLink = nil
452453
}
453454
} catch {
454-
updateError(message: string.localizedErrorMessage(for: error))
455+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
455456
throw error
456457
}
457458
}
@@ -520,7 +521,7 @@ public extension AuthService {
520521
changeRequest.photoURL = url
521522
try await changeRequest.commitChanges()
522523
} catch {
523-
updateError(message: string.localizedErrorMessage(for: error))
524+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
524525
throw error
525526
}
526527
}
@@ -535,7 +536,7 @@ public extension AuthService {
535536
changeRequest.displayName = name
536537
try await changeRequest.commitChanges()
537538
} catch {
538-
updateError(message: string.localizedErrorMessage(for: error))
539+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
539540
throw error
540541
}
541542
}
@@ -627,7 +628,7 @@ public extension AuthService {
627628
)
628629
}
629630
} catch {
630-
updateError(message: string.localizedErrorMessage(for: error))
631+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
631632
throw error
632633
}
633634
}
@@ -679,7 +680,7 @@ public extension AuthService {
679680

680681
return verificationID
681682
} catch {
682-
updateError(message: string.localizedErrorMessage(for: error))
683+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
683684
throw error
684685
}
685686
}
@@ -758,7 +759,7 @@ public extension AuthService {
758759
}
759760
currentUser = auth.currentUser
760761
} catch {
761-
updateError(message: string.localizedErrorMessage(for: error))
762+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
762763
throw error
763764
}
764765
}
@@ -847,7 +848,7 @@ public extension AuthService {
847848

848849
return freshFactors
849850
} catch {
850-
updateError(message: string.localizedErrorMessage(for: error))
851+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
851852
throw error
852853
}
853854
}
@@ -917,7 +918,7 @@ public extension AuthService {
917918
}
918919
}
919920
} catch {
920-
updateError(message: string.localizedErrorMessage(for: error))
921+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
921922
throw error
922923
}
923924
}
@@ -970,7 +971,7 @@ public extension AuthService {
970971
.multiFactorAuth("Failed to resolve MFA challenge: \(error.localizedDescription)")
971972
}
972973
} catch {
973-
updateError(message: string.localizedErrorMessage(for: error))
974+
updateError(message: string.localizedErrorMessage(for: error), underlyingError: error)
974975
throw error
975976
}
976977
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/ErrorAlertView.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ struct ErrorAlertModifier: ViewModifier {
2222
func body(content: Content) -> some View {
2323
content
2424
.alert(isPresented: Binding<Bool>(
25-
get: { error != nil },
25+
get: {
26+
// View layer decides: Don't show alert for CancellationError
27+
guard let error = error else { return false }
28+
return !(error.underlyingError is CancellationError)
29+
},
2630
set: { if !$0 { error = nil } }
2731
)) {
2832
Alert(
@@ -48,9 +52,11 @@ public struct AlertError: Identifiable {
4852
public let id = UUID()
4953
public let title: String
5054
public let message: String
55+
public let underlyingError: Error?
5156

52-
public init(title: String = "Error", message: String) {
57+
public init(title: String = "Error", message: String, underlyingError: Error? = nil) {
5358
self.title = title
5459
self.message = message
60+
self.underlyingError = underlyingError
5561
}
5662
}

FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ private class PhoneAuthCoordinator: ObservableObject {
3434
@Published var verificationCode = ""
3535
@Published var currentError: AlertError?
3636
@Published var isProcessing = false
37-
37+
3838
var continuation: CheckedContinuation<AuthCredential, Error>?
39-
39+
4040
enum Step {
4141
case enterPhoneNumber
4242
case enterVerificationCode
4343
}
44-
44+
4545
func sendVerificationCode() async {
4646
isProcessing = true
4747
do {
@@ -63,13 +63,13 @@ private class PhoneAuthCoordinator: ObservableObject {
6363
}
6464
isProcessing = false
6565
}
66-
66+
6767
func verifyCodeAndComplete() async {
6868
isProcessing = true
6969
do {
7070
let credential = PhoneAuthProvider.provider()
7171
.credential(withVerificationID: verificationID, verificationCode: verificationCode)
72-
72+
7373
isPresented = false
7474
continuation?.resume(returning: credential)
7575
continuation = nil
@@ -78,10 +78,19 @@ private class PhoneAuthCoordinator: ObservableObject {
7878
isProcessing = false
7979
}
8080
}
81-
81+
8282
func cancel() {
8383
isPresented = false
84-
continuation?.resume(throwing: AuthServiceError.signInCancelled("Phone authentication was cancelled"))
84+
85+
// Only throw error if user has started the flow (sent verification code)
86+
// If they cancel before entering/sending phone number, dismiss silently
87+
if !verificationID.isEmpty {
88+
continuation?
89+
.resume(throwing: AuthServiceError.signInCancelled("Phone authentication was cancelled"))
90+
} else {
91+
continuation?.resume(throwing: CancellationError())
92+
}
93+
8594
continuation = nil
8695
}
8796
}
@@ -92,7 +101,7 @@ private class PhoneAuthCoordinator: ObservableObject {
92101
private struct PhoneAuthFlowView: View {
93102
@StateObject var coordinator: PhoneAuthCoordinator
94103
@Environment(AuthService.self) private var authService
95-
104+
96105
var body: some View {
97106
NavigationStack {
98107
Group {
@@ -109,7 +118,7 @@ private struct PhoneAuthFlowView: View {
109118
}
110119
.interactiveDismissDisabled(authService.configuration.interactiveDismissEnabled)
111120
}
112-
121+
113122
@ToolbarContentBuilder
114123
var toolbar: some ToolbarContent {
115124
ToolbarItem(placement: .topBarTrailing) {
@@ -122,9 +131,9 @@ private struct PhoneAuthFlowView: View {
122131
}
123132
}
124133
}
125-
134+
126135
// MARK: - Phone Number View
127-
136+
128137
var phoneNumberView: some View {
129138
VStack(spacing: 16) {
130139
Text(authService.string.enterPhoneNumberPlaceholder)
@@ -173,9 +182,9 @@ private struct PhoneAuthFlowView: View {
173182
.padding(.horizontal)
174183
.errorAlert(error: $coordinator.currentError, okButtonLabel: authService.string.okButtonLabel)
175184
}
176-
185+
177186
// MARK: - Verification Code View
178-
187+
179188
var verificationCodeView: some View {
180189
VStack(spacing: 32) {
181190
VStack(spacing: 16) {
@@ -236,10 +245,10 @@ private struct PhoneAuthFlowView: View {
236245

237246
public class PhoneProviderSwift: PhoneAuthProviderSwift {
238247
private var cancellables = Set<AnyCancellable>()
239-
248+
240249
// Internal use only: Injected automatically by AuthService.signIn()
241250
public weak var authService: AuthService?
242-
251+
243252
public init() {}
244253

245254
@MainActor public func createAuthCredential() async throws -> AuthCredential {
@@ -248,41 +257,41 @@ public class PhoneProviderSwift: PhoneAuthProviderSwift {
248257
"AuthService not injected. This should be set automatically by AuthService.signIn()."
249258
)
250259
}
251-
260+
252261
// Get the root view controller to present from
253262
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
254263
let rootViewController = windowScene.windows.first?.rootViewController else {
255264
throw AuthServiceError.rootViewControllerNotFound(
256265
"Root view controller not available to present phone auth flow"
257266
)
258267
}
259-
268+
260269
// Find the topmost view controller
261270
var topViewController = rootViewController
262271
while let presented = topViewController.presentedViewController {
263272
topViewController = presented
264273
}
265-
274+
266275
// Create coordinator
267276
let coordinator = PhoneAuthCoordinator()
268-
277+
269278
// Present the flow and wait for result
270279
return try await withCheckedThrowingContinuation { continuation in
271280
coordinator.continuation = continuation
272-
281+
273282
// Create SwiftUI view with environment
274283
let flowView = PhoneAuthFlowView(coordinator: coordinator)
275284
.environment(authService)
276-
285+
277286
let hostingController = UIHostingController(rootView: flowView)
278-
287+
279288
// Dismiss handler - watch for presentation state changes
280289
coordinator.$isPresented.sink { [weak hostingController] isPresented in
281290
if !isPresented {
282291
hostingController?.dismiss(animated: true)
283292
}
284293
}.store(in: &cancellables)
285-
294+
286295
// Present modally
287296
topViewController.present(hostingController, animated: true)
288297
}

0 commit comments

Comments
 (0)