From 699c134983bf5d07cadddf0504a117cb87454322 Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Tue, 29 Apr 2025 13:31:55 +0100
Subject: [PATCH 01/11] chore: anonymous account upgrade

---
 .../Sources/Services/AuthService.swift        | 20 +++++++++----------
 .../FirebaseSwiftUIExampleApp.swift           |  2 +-
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index c5ecdc1643..070710d211 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -169,19 +169,19 @@ public final class AuthService {
 
   public func signIn(credentials credentials: AuthCredential) async throws {
     authenticationState = .authenticating
-    if currentUser?.isAnonymous == true, configuration.shouldAutoUpgradeAnonymousUsers {
-      try await linkAccounts(credentials: credentials)
-    } else {
-      do {
+    do {
+      if currentUser?.isAnonymous == true, configuration.shouldAutoUpgradeAnonymousUsers {
+        try await linkAccounts(credentials: credentials)
+      } else {
         try await auth.signIn(with: credentials)
         updateAuthenticationState()
-      } catch {
-        authenticationState = .unauthenticated
-        errorMessage = string.localizedErrorMessage(
-          for: error
-        )
-        throw error
       }
+    } catch {
+      authenticationState = .unauthenticated
+      errorMessage = string.localizedErrorMessage(
+        for: error
+      )
+      throw error
     }
   }
 
diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift
index c5f85d500f..da7ae917db 100644
--- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift
+++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift
@@ -78,7 +78,7 @@ struct ContentView: View {
   let authService: AuthService
 
   init() {
-    // Auth.auth().signInAnonymously()
+    Auth.auth().signInAnonymously()
 
     let actionCodeSettings = ActionCodeSettings()
     actionCodeSettings.handleCodeInApp = true

From 52b92fb08b2d734f025e5c0595fb3492f78a7fdd Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Fri, 2 May 2025 14:25:45 +0100
Subject: [PATCH 02/11] implementation for handling errors when auto upgrading
 anonymous user

---
 .../Sources/AuthServiceError.swift            | 14 ++++++++
 .../Sources/Services/AuthService.swift        | 36 ++++++++++++++++---
 2 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
index 7302beeda7..31ee5b20f4 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
@@ -1,6 +1,17 @@
 
+import FirebaseAuth
 import SwiftUI
 
+public struct AccountMergeConflictContext: LocalizedError {
+  public let credential: AuthCredential
+  public let underlyingError: Error
+  public let message: String
+
+  public var errorDescription: String? {
+    return message
+  }
+}
+
 public enum AuthServiceError: LocalizedError {
   case invalidEmailLink
   case notConfiguredProvider(String)
@@ -9,6 +20,7 @@ public enum AuthServiceError: LocalizedError {
   case reauthenticationRequired(String)
   case invalidCredentials(String)
   case signInFailed(underlying: Error)
+  case accountMergeConflict(context: AccountMergeConflictContext)
 
   public var errorDescription: String? {
     switch self {
@@ -26,6 +38,8 @@ public enum AuthServiceError: LocalizedError {
       return description
     case let .signInFailed(underlying: error):
       return "Failed to sign in: \(error.localizedDescription)"
+    case let .accountMergeConflict(context):
+      return context.errorDescription
     }
   }
 }
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 070710d211..019a3601fa 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -141,6 +141,10 @@ public final class AuthService {
     errorMessage = ""
   }
 
+  public var shouldHandleAnonymousUpgrade: Bool {
+    currentUser?.isAnonymous == true && configuration.shouldAutoUpgradeAnonymousUsers
+  }
+
   public func signOut() async throws {
     do {
       try await auth.signOut()
@@ -167,15 +171,31 @@ public final class AuthService {
     }
   }
 
+  public func handleAutoUpgradeAnonymousUser(credentials credentials: AuthCredential) async throws {
+    do {
+      try await currentUser?.link(with: credentials)
+    } catch let error as NSError {
+      if error.code == AuthErrorCode.emailAlreadyInUse.rawValue {
+        let context = AccountMergeConflictContext(
+          credential: credentials,
+          underlyingError: error,
+          message: "Unable to merge accounts. Use the credential in the context to resolve the conflict."
+        )
+        throw AuthServiceError.accountMergeConflict(context: context)
+      }
+      throw error
+    }
+  }
+
   public func signIn(credentials credentials: AuthCredential) async throws {
     authenticationState = .authenticating
     do {
-      if currentUser?.isAnonymous == true, configuration.shouldAutoUpgradeAnonymousUsers {
-        try await linkAccounts(credentials: credentials)
+      if shouldHandleAnonymousUpgrade {
+        try await handleAutoUpgradeAnonymousUser(credentials: credentials)
       } else {
         try await auth.signIn(with: credentials)
-        updateAuthenticationState()
       }
+      updateAuthenticationState()
     } catch {
       authenticationState = .unauthenticated
       errorMessage = string.localizedErrorMessage(
@@ -231,7 +251,13 @@ public extension AuthService {
     authenticationState = .authenticating
 
     do {
-      try await auth.createUser(withEmail: email, password: password)
+      if shouldHandleAnonymousUpgrade {
+        // TODO: - check this works. This is how it is done in previous implementation, but I wonder if this would fail
+        let credential = EmailAuthProvider.credential(withEmail: email, password: password)
+        try await handleAutoUpgradeAnonymousUser(credentials: credential)
+      } else {
+        try await auth.createUser(withEmail: email, password: password)
+      }
       updateAuthenticationState()
     } catch {
       authenticationState = .unauthenticated
@@ -278,6 +304,8 @@ public extension AuthService {
         throw AuthServiceError.invalidEmailLink
       }
       let link = url.absoluteString
+      // TODO: - get anonymous id here and check against current user before linking accounts
+      // put anonymous uid on link and get it back: https://github.com/firebase/FirebaseUI-iOS/blob/main/FirebaseEmailAuthUI/Sources/FUIEmailAuth.m#L822
       if auth.isSignIn(withEmailLink: link) {
         let result = try await auth.signIn(withEmail: email, link: link)
         updateAuthenticationState()

From 5614990402f30e3a530dfd83232f08e39b65b0be Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Tue, 6 May 2025 10:07:29 +0100
Subject: [PATCH 03/11] email link sign-in

---
 .../Sources/AuthServiceError.swift            |  6 +--
 .../Sources/Services/AuthService.swift        | 49 +++++++++++++++++--
 .../Sources/Utils/CommonUtils.swift           |  8 +++
 3 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
index 31ee5b20f4..23daf6242b 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
@@ -16,7 +16,7 @@ public enum AuthServiceError: LocalizedError {
   case invalidEmailLink
   case notConfiguredProvider(String)
   case clientIdNotFound(String)
-  case notConfiguredActionCodeSettings
+  case notConfiguredActionCodeSettings(String)
   case reauthenticationRequired(String)
   case invalidCredentials(String)
   case signInFailed(underlying: Error)
@@ -30,8 +30,8 @@ public enum AuthServiceError: LocalizedError {
       return description
     case let .clientIdNotFound(description):
       return description
-    case .notConfiguredActionCodeSettings:
-      return "ActionCodeSettings has not been configured for `AuthConfiguration.emailLinkSignInActionCodeSettings`"
+    case let .notConfiguredActionCodeSettings(description):
+      return description
     case let .reauthenticationRequired(description):
       return description
     case let .invalidCredentials(description):
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 019a3601fa..623574b31e 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -124,7 +124,9 @@ public final class AuthService {
     guard let actionCodeSettings = configuration
       .emailLinkSignInActionCodeSettings else {
       throw AuthServiceError
-        .notConfiguredActionCodeSettings
+        .notConfiguredActionCodeSettings(
+          "ActionCodeSettings has not been configured for `AuthConfiguration.emailLinkSignInActionCodeSettings`"
+        )
     }
     return actionCodeSettings
   }
@@ -285,7 +287,7 @@ public extension AuthService {
 public extension AuthService {
   func sendEmailSignInLink(to email: String) async throws {
     do {
-      let actionCodeSettings = try safeActionCodeSettings()
+      let actionCodeSettings = try updateActionCodeSettings()
       try await auth.sendSignInLink(
         toEmail: email,
         actionCodeSettings: actionCodeSettings
@@ -304,10 +306,15 @@ public extension AuthService {
         throw AuthServiceError.invalidEmailLink
       }
       let link = url.absoluteString
-      // TODO: - get anonymous id here and check against current user before linking accounts
-      // put anonymous uid on link and get it back: https://github.com/firebase/FirebaseUI-iOS/blob/main/FirebaseEmailAuthUI/Sources/FUIEmailAuth.m#L822
+
       if auth.isSignIn(withEmailLink: link) {
-        let result = try await auth.signIn(withEmail: email, link: link)
+        let anonymousUserID = CommonUtils.getQueryParamValue(from: link, paramName: "ui_auid")
+        if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser?.uid {
+          let credential = EmailAuthProvider.credential(withEmail: email, link: link)
+          try await handleAutoUpgradeAnonymousUser(credentials: credential)
+        } else {
+          let result = try await auth.signIn(withEmail: email, link: link)
+        }
         updateAuthenticationState()
         emailLink = nil
       }
@@ -318,6 +325,38 @@ public extension AuthService {
       throw error
     }
   }
+
+  private func updateActionCodeSettings() throws -> ActionCodeSettings {
+    let actionCodeSettings = try safeActionCodeSettings()
+    guard var urlComponents = URLComponents(string: actionCodeSettings.url!.absoluteString) else {
+      throw AuthServiceError
+        .notConfiguredActionCodeSettings(
+          "ActionCodeSettings.url has not been configured for `AuthConfiguration.emailLinkSignInActionCodeSettings`"
+        )
+    }
+
+    var queryItems: [URLQueryItem] = []
+
+    if shouldHandleAnonymousUpgrade {
+      if let currentUser = currentUser {
+        let anonymousUID = currentUser.uid
+        let auidItem = URLQueryItem(name: "ui_auid", value: anonymousUID)
+        queryItems.append(auidItem)
+      }
+    }
+
+    // We don't have config for forceSameDevice so it is set as default
+    let forceSameDevice = "1"
+    let sameDeviceItem = URLQueryItem(name: "ui_sd", value: forceSameDevice)
+    queryItems.append(sameDeviceItem)
+
+    urlComponents.queryItems = queryItems
+    if let finalURL = urlComponents.url {
+      actionCodeSettings.url = finalURL
+    }
+
+    return actionCodeSettings
+  }
 }
 
 // MARK: - Google Sign In
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
index 308b12bd77..c8bd78409f 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
@@ -46,4 +46,12 @@ public class CommonUtils {
     }
     return hash.map { String(format: "%02x", $0) }.joined()
   }
+
+  public static func getQueryParamValue(from urlString: String, paramName: String) -> String? {
+    guard let urlComponents = URLComponents(string: urlString) else {
+      return nil
+    }
+
+    return urlComponents.queryItems?.first(where: { $0.name == paramName })?.value
+  }
 }

From 89db7b0afe382be7a7d242d440229ef84b0503aa Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Tue, 6 May 2025 10:33:10 +0100
Subject: [PATCH 04/11] checked - it does work

---
 .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift       | 1 -
 1 file changed, 1 deletion(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 623574b31e..ce2954e0fc 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -254,7 +254,6 @@ public extension AuthService {
 
     do {
       if shouldHandleAnonymousUpgrade {
-        // TODO: - check this works. This is how it is done in previous implementation, but I wonder if this would fail
         let credential = EmailAuthProvider.credential(withEmail: email, password: password)
         try await handleAutoUpgradeAnonymousUser(credentials: credential)
       } else {

From 7d75ca2ec89130b07a1fa811d61ee6a0f36a68ba Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Tue, 6 May 2025 11:43:51 +0100
Subject: [PATCH 05/11] get anonymousId from continueUrl

---
 .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift     | 3 ++-
 .../FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift      | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index ce2954e0fc..9f7846eb23 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -307,7 +307,8 @@ public extension AuthService {
       let link = url.absoluteString
 
       if auth.isSignIn(withEmailLink: link) {
-        let anonymousUserID = CommonUtils.getQueryParamValue(from: link, paramName: "ui_auid")
+        let anonymousUserID = CommonUtils.getQueryParamValue(from: link, paramName: "continueUrl")
+          .flatMap { CommonUtils.getQueryParamValue(from: $0, paramName: "ui_auid") }
         if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser?.uid {
           let credential = EmailAuthProvider.credential(withEmail: email, link: link)
           try await handleAutoUpgradeAnonymousUser(credentials: credential)
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift
index f09a043226..99b405fe25 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift
@@ -125,7 +125,7 @@ extension EmailAuthView: View {
       .frame(maxWidth: .infinity)
       .buttonStyle(.borderedProminent)
       Button(action: {
-        authService.authView = .passwordRecovery
+        authService.authView = .emailLink
       }) {
         Text("Prefer Email link sign-in?")
       }

From 89e3b6fab5bc756c37fd6a3f3ca4d29aca6f80ee Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Wed, 7 May 2025 08:22:35 +0100
Subject: [PATCH 06/11] create function for getting anonymous ID

---
 .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift   | 3 +--
 .../FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift      | 5 +++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 9f7846eb23..8c33c2e106 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -307,8 +307,7 @@ public extension AuthService {
       let link = url.absoluteString
 
       if auth.isSignIn(withEmailLink: link) {
-        let anonymousUserID = CommonUtils.getQueryParamValue(from: link, paramName: "continueUrl")
-          .flatMap { CommonUtils.getQueryParamValue(from: $0, paramName: "ui_auid") }
+        let anonymousUserID = CommonUtils.getAnonymousUserIdFromUrl(from: link)
         if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser?.uid {
           let credential = EmailAuthProvider.credential(withEmail: email, link: link)
           try await handleAutoUpgradeAnonymousUser(credentials: credential)
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
index c8bd78409f..18e6daca6c 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
@@ -54,4 +54,9 @@ public class CommonUtils {
 
     return urlComponents.queryItems?.first(where: { $0.name == paramName })?.value
   }
+
+  public static func getAnonymousUserIdFromUrl(from urlString: String) -> String? {
+    getQueryParamValue(from: urlString, paramName: "continueUrl")
+      .flatMap { getQueryParamValue(from: $0, paramName: "ui_auid") }
+  }
 }

From 32d82c23d32089225dd26a61bac5bbc08f3c8af0 Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Wed, 7 May 2025 08:35:34 +0100
Subject: [PATCH 07/11] chore: check continurUrl is present before continuing

---
 .../Sources/AuthServiceError.swift                   |  6 +++---
 .../Sources/Services/AuthService.swift               | 12 ++++++++++--
 .../Sources/Utils/CommonUtils.swift                  |  5 -----
 3 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
index 23daf6242b..e020420288 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
@@ -13,7 +13,7 @@ public struct AccountMergeConflictContext: LocalizedError {
 }
 
 public enum AuthServiceError: LocalizedError {
-  case invalidEmailLink
+  case invalidEmailLink(String)
   case notConfiguredProvider(String)
   case clientIdNotFound(String)
   case notConfiguredActionCodeSettings(String)
@@ -24,8 +24,8 @@ public enum AuthServiceError: LocalizedError {
 
   public var errorDescription: String? {
     switch self {
-    case .invalidEmailLink:
-      return "Invalid sign in link. Most likely, the link you used has expired. Try signing in again."
+    case let .invalidEmailLink(description):
+      return description
     case let .notConfiguredProvider(description):
       return description
     case let .clientIdNotFound(description):
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 8c33c2e106..e2f1de41fd 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -302,12 +302,20 @@ public extension AuthService {
   func handleSignInLink(url url: URL) async throws {
     do {
       guard let email = emailLink else {
-        throw AuthServiceError.invalidEmailLink
+        throw AuthServiceError.invalidEmailLink("email address is missing from local storage")
       }
       let link = url.absoluteString
+      guard let continueUrl = CommonUtils.getQueryParamValue(from: link, paramName: "continueUrl")
+      else {
+        throw AuthServiceError
+          .invalidEmailLink("`continueUrl` parameter is missing from the email link URL")
+      }
 
       if auth.isSignIn(withEmailLink: link) {
-        let anonymousUserID = CommonUtils.getAnonymousUserIdFromUrl(from: link)
+        let anonymousUserID = CommonUtils.getQueryParamValue(
+          from: continueUrl,
+          paramName: "ui_auid"
+        )
         if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser?.uid {
           let credential = EmailAuthProvider.credential(withEmail: email, link: link)
           try await handleAutoUpgradeAnonymousUser(credentials: credential)
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
index 18e6daca6c..c8bd78409f 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift
@@ -54,9 +54,4 @@ public class CommonUtils {
 
     return urlComponents.queryItems?.first(where: { $0.name == paramName })?.value
   }
-
-  public static func getAnonymousUserIdFromUrl(from urlString: String) -> String? {
-    getQueryParamValue(from: urlString, paramName: "continueUrl")
-      .flatMap { getQueryParamValue(from: $0, paramName: "ui_auid") }
-  }
 }

From 3f796c6cb8cee2726a4a660aac13d204bb6ddba9 Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Wed, 7 May 2025 08:38:00 +0100
Subject: [PATCH 08/11] chore: update message

---
 .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index e2f1de41fd..82227370fa 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -302,7 +302,7 @@ public extension AuthService {
   func handleSignInLink(url url: URL) async throws {
     do {
       guard let email = emailLink else {
-        throw AuthServiceError.invalidEmailLink("email address is missing from local storage")
+        throw AuthServiceError.invalidEmailLink("email address is missing from app storage. Is this the same device?")
       }
       let link = url.absoluteString
       guard let continueUrl = CommonUtils.getQueryParamValue(from: link, paramName: "continueUrl")

From fbd0d4f939c5fce21f5a4e5d6e2101fd6105ed92 Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Wed, 7 May 2025 09:19:26 +0100
Subject: [PATCH 09/11] removed unused logic for force same device for now

---
 .../Sources/Services/AuthService.swift                    | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 82227370fa..fb90733c1b 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -302,7 +302,8 @@ public extension AuthService {
   func handleSignInLink(url url: URL) async throws {
     do {
       guard let email = emailLink else {
-        throw AuthServiceError.invalidEmailLink("email address is missing from app storage. Is this the same device?")
+        throw AuthServiceError
+          .invalidEmailLink("email address is missing from app storage. Is this the same device?")
       }
       let link = url.absoluteString
       guard let continueUrl = CommonUtils.getQueryParamValue(from: link, paramName: "continueUrl")
@@ -352,11 +353,6 @@ public extension AuthService {
       }
     }
 
-    // We don't have config for forceSameDevice so it is set as default
-    let forceSameDevice = "1"
-    let sameDeviceItem = URLQueryItem(name: "ui_sd", value: forceSameDevice)
-    queryItems.append(sameDeviceItem)
-
     urlComponents.queryItems = queryItems
     if let finalURL = urlComponents.url {
       actionCodeSettings.url = finalURL

From b26351cc9364d7ca230a2c582076b014f4202a6d Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Thu, 8 May 2025 09:44:32 +0100
Subject: [PATCH 10/11] throw exception if there is no currentUser. rm
 duplicate arg labels

---
 .../FirebaseAuthSwiftUI/Sources/AuthServiceError.swift     | 3 +++
 .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift | 7 +++++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
index e020420288..052e1e07c9 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
@@ -13,6 +13,7 @@ public struct AccountMergeConflictContext: LocalizedError {
 }
 
 public enum AuthServiceError: LocalizedError {
+  case noCurrentUser
   case invalidEmailLink(String)
   case notConfiguredProvider(String)
   case clientIdNotFound(String)
@@ -24,6 +25,8 @@ public enum AuthServiceError: LocalizedError {
 
   public var errorDescription: String? {
     switch self {
+    case .noCurrentUser:
+      return "No user is currently signed in."
     case let .invalidEmailLink(description):
       return description
     case let .notConfiguredProvider(description):
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 24e93a556b..696964ad99 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -180,7 +180,10 @@ public final class AuthService {
     }
   }
 
-  public func handleAutoUpgradeAnonymousUser(credentials credentials: AuthCredential) async throws {
+  public func handleAutoUpgradeAnonymousUser(credentials: AuthCredential) async throws {
+    if currentUser == nil {
+      throw AuthServiceError.noCurrentUser
+    }
     do {
       try await currentUser?.link(with: credentials)
     } catch let error as NSError {
@@ -196,7 +199,7 @@ public final class AuthService {
     }
   }
 
-  public func signIn(credentials credentials: AuthCredential) async throws {
+  public func signIn(credentials: AuthCredential) async throws {
     authenticationState = .authenticating
     do {
       if shouldHandleAnonymousUpgrade {

From a014c7a548f11031ee086877c1e37f9e43790d2d Mon Sep 17 00:00:00 2001
From: russellwheatley <russellwheatley85@gmail.com>
Date: Wed, 14 May 2025 10:58:40 +0100
Subject: [PATCH 11/11] chore: pass in uid with note to upgrade to User when
 possible

---
 .../FirebaseAuthSwiftUI/Sources/AuthServiceError.swift         | 2 ++
 .../FirebaseAuthSwiftUI/Sources/Services/AuthService.swift     | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
index 052e1e07c9..58e72fde12 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift
@@ -6,6 +6,8 @@ public struct AccountMergeConflictContext: LocalizedError {
   public let credential: AuthCredential
   public let underlyingError: Error
   public let message: String
+  // TODO: - should make this User type once fixed upstream in firebase-ios-sdk. See: https://github.com/firebase/FirebaseUI-iOS/pull/1247#discussion_r2085455355
+  public let uid: String?
 
   public var errorDescription: String? {
     return message
diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
index 696964ad99..ac9051783b 100644
--- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
+++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift
@@ -191,7 +191,8 @@ public final class AuthService {
         let context = AccountMergeConflictContext(
           credential: credentials,
           underlyingError: error,
-          message: "Unable to merge accounts. Use the credential in the context to resolve the conflict."
+          message: "Unable to merge accounts. Use the credential in the context to resolve the conflict.",
+          uid: currentUser?.uid
         )
         throw AuthServiceError.accountMergeConflict(context: context)
       }