Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces state cleanup for email-link reauthentication when the process is cancelled, dismissed, or when there is no longer a signed-in user. It adds cancellation handlers in AuthService and the corresponding SwiftUI views, along with integration tests to verify the new behavior. The feedback suggests renaming the helper method hasPendingEmailLinkReauth() to checkAndCleanPendingEmailLinkReauth() (and updating its callsites) to better reflect its side-effects, adhering to the Command-Query Separation principle and Swift API Design Guidelines.
| private func hasPendingEmailLinkReauth() -> Bool { | ||
| guard isReauthenticating else { return false } | ||
| guard let user = auth.currentUser, !user.isAnonymous else { | ||
| cancelEmailLinkReauthentication() | ||
| return false | ||
| } | ||
| return true | ||
| } |
There was a problem hiding this comment.
The method hasPendingEmailLinkReauth() violates the Command-Query Separation (CQS) principle and the Swift API Design Guidelines. It is named as a Boolean assertion (has...), which implies it is a pure query with no side-effects. However, it contains a side-effect that modifies state by calling cancelEmailLinkReauthentication() when there is no signed-in user.
To adhere to the Swift API Design Guidelines, methods with side-effects should be named using imperative verbs. Consider renaming this method to checkAndCleanPendingEmailLinkReauth() to explicitly signal that it performs cleanup, or separate the query and command logic.
| private func hasPendingEmailLinkReauth() -> Bool { | |
| guard isReauthenticating else { return false } | |
| guard let user = auth.currentUser, !user.isAnonymous else { | |
| cancelEmailLinkReauthentication() | |
| return false | |
| } | |
| return true | |
| } | |
| private func checkAndCleanPendingEmailLinkReauth() -> Bool { | |
| guard isReauthenticating else { return false } | |
| guard let user = auth.currentUser, !user.isAnonymous else { | |
| cancelEmailLinkReauthentication() | |
| return false | |
| } | |
| return true | |
| } |
References
- Swift API Design Guidelines state that Boolean methods and properties should read as assertions about the receiver when the call has no side-effects. Methods with side-effects should be named using imperative verbs. (link)
| func handleSignInLink(url: URL) async throws { | ||
| guard url != emailLinkInFlight else { return } | ||
| if isReauthenticating { | ||
| if hasPendingEmailLinkReauth() { |
| /// that shows its own screen through `withEmailLinkSignIn(onTap:)` handles links there. | ||
| internal func presentEmailLinkScreen(for url: URL) { | ||
| guard emailLinkSignInEnabled, emailLinkUsesLibraryScreen, !isReauthenticating, | ||
| guard emailLinkSignInEnabled, emailLinkUsesLibraryScreen, !hasPendingEmailLinkReauth(), |
There was a problem hiding this comment.
Update this callsite to use the renamed checkAndCleanPendingEmailLinkReauth() method to align with the Command-Query Separation principle.
| guard emailLinkSignInEnabled, emailLinkUsesLibraryScreen, !hasPendingEmailLinkReauth(), | |
| guard emailLinkSignInEnabled, emailLinkUsesLibraryScreen, !checkAndCleanPendingEmailLinkReauth(), |
| /// other links, or when that view is showing to handle the link itself. | ||
| internal func finishEmailLinkReauthentication(with url: URL) async throws { | ||
| guard isReauthenticating, !isShowingEmailLinkReauth, currentUser != nil else { return } | ||
| guard !isShowingEmailLinkReauth, hasPendingEmailLinkReauth() else { return } |
There was a problem hiding this comment.
Update this callsite to use the renamed checkAndCleanPendingEmailLinkReauth() method to align with the Command-Query Separation principle.
| guard !isShowingEmailLinkReauth, hasPendingEmailLinkReauth() else { return } | |
| guard !isShowingEmailLinkReauth, checkAndCleanPendingEmailLinkReauth() else { return } |
dd53050 to
c9bbb50
Compare
c9bbb50 to
ce97892
Compare
… no user is signed in Cancelling or dismissing EmailLinkReauthView left the stored reauthentication state set, so later sign-in links were skipped by the email-link screen and routed to reauthentication. Clear that state when the view closes, cancel the coordinator when the sheet is swiped away, and drop leftover state when no user is signed in.
Forget the reauthentication a send stores after the view has closed, only cancel the coordinator on swipe-dismiss when no new request has started, and ignore leftover reauthentication without a signed-in user instead of clearing it, so a user that can't be read yet doesn't lose a pending one.
d9d1cd9 to
98cc6ed
Compare
Clears stored email-link reauthentication state when it is abandoned, so later sign-in links are handled as sign-in again. Stacked on #1430.
Changes
EmailLinkReauthViewforgets the stored reauth email and flag, including when the close lands while the email is still sending. Before, the email-link screen skipped sign-in links andhandleSignInLink(url:)routed them to reauthentication.auth.currentUser, which is set beforecurrentUserafter a cold launch.Preview
Recorded on an erased simulator in the e2e example app (
--test-view-enabled), which signs out throughAuthon relaunch, so the session ends withoutsignOut(). Delete Account was patched locally to require a recent login, since the Auth emulator never does. Steps: sign in with an email link, Delete Account, cancel reauthentication, relaunch, then open a sign-in link for another user.Before (#1430,
518defa19e)Expected: when a user wants to sign in with an email link after a cancelled reauthentication, opening the link should sign them in.
pr1431-before.mp4
After (#1431,
98cc6ed8e0)pr1431-after.mp4