Paul Beusterien преди 1 година
родител
ревизия
d06bd8a175

+ 1 - 1
FirebaseAuth/Sources/Swift/Auth/Auth.swift

@@ -1160,7 +1160,7 @@ extension Auth: AuthInterop {
   @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
   open func revokeToken(withAuthorizationCode authorizationCode: String) async throws {
     if let currentUser {
-      let idToken = try await currentUser.internalGetTokenAsync()
+      let idToken = try await currentUser.internalGetToken()
       let request = RevokeTokenRequest(withToken: authorizationCode,
                                        idToken: idToken,
                                        requestConfiguration: requestConfiguration)

+ 138 - 13
FirebaseAuth/Sources/Swift/Auth/AuthWorker.swift

@@ -63,7 +63,7 @@ actor AuthWorker {
     guard let currentUser = auth.currentUser else {
       return nil
     }
-    return try await currentUser.internalGetTokenAsync(forceRefresh: forceRefresh)
+    return try await currentUser.internalGetToken(forceRefresh: forceRefresh)
   }
 
   /// Only for testing
@@ -342,7 +342,7 @@ actor AuthWorker {
         return
       }
       // The list of providers need to be updated for the newly added email-password provider.
-      let accessToken = try await user.internalGetTokenAsync()
+      let accessToken = try await user.internalGetToken()
       let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken,
                                                         requestConfiguration: requestConfiguration)
       do {
@@ -386,7 +386,7 @@ actor AuthWorker {
     /// operation.
     func updateOrLinkPhoneNumber(user: User, credential: PhoneAuthCredential,
                                  isLinkOperation: Bool) async throws {
-      let accessToken = try await user.internalGetTokenAsync()
+      let accessToken = try await user.internalGetToken()
 
       guard let configuration = user.auth?.requestConfiguration else {
         fatalError("Auth Internal Error: nil value for VerifyPhoneNumberRequest initializer")
@@ -436,7 +436,7 @@ actor AuthWorker {
                                                                     SetAccountInfoRequest)
                                               -> Void) async throws {
     let userAccountInfo = try await getAccountInfoRefreshingCache(user)
-    let accessToken = try await user.internalGetTokenAsync()
+    let accessToken = try await user.internalGetToken()
 
     // Mutate setAccountInfoRequest in block
     let setAccountInfoRequest = SetAccountInfoRequest(requestConfiguration: requestConfiguration)
@@ -465,7 +465,7 @@ actor AuthWorker {
   /// error has been detected. Invoked asynchronously on the auth global work queue in the future.
   func getAccountInfoRefreshingCache(_ user: User) async throws
     -> GetAccountInfoResponseUser {
-    let token = try await user.internalGetTokenAsync()
+    let token = try await user.internalGetToken()
     let request = GetAccountInfoRequest(accessToken: token,
                                         requestConfiguration: requestConfiguration)
     do {
@@ -511,7 +511,7 @@ actor AuthWorker {
 
   func getIDTokenResult(user: User,
                         forcingRefresh forceRefresh: Bool) async throws -> AuthTokenResult {
-    let token = try await user.internalGetTokenAsync(forceRefresh: forceRefresh)
+    let token = try await user.internalGetToken(forceRefresh: forceRefresh)
     let tokenResult = try AuthTokenResult.tokenResult(token: token)
     AuthLog.logDebug(code: "I-AUT000017", message: "Actual token expiration date: " +
       "\(String(describing: tokenResult.expirationDate))," +
@@ -537,7 +537,7 @@ actor AuthWorker {
       }
     #endif
 
-    let accessToken = try await user.internalGetTokenAsync()
+    let accessToken = try await user.internalGetToken()
     let request = VerifyAssertionRequest(providerID: credential.provider,
                                          requestConfiguration: requestConfiguration)
     credential.prepare(request)
@@ -572,6 +572,131 @@ actor AuthWorker {
     return try await link(user: user, with: credential)
   }
 
+  func unlink(user: User, fromProvider provider: String) async throws -> User {
+    let accessToken = try await user.internalGetToken()
+    let request = SetAccountInfoRequest(requestConfiguration: requestConfiguration)
+    request.accessToken = accessToken
+
+    if user.providerDataRaw[provider] == nil {
+      throw AuthErrorUtils.noSuchProviderError()
+    }
+    request.deleteProviders = [provider]
+    do {
+      let response = try await AuthBackend.call(with: request)
+
+      // We can't just use the provider info objects in SetAccountInfoResponse
+      // because they don't have localID and email fields. Remove the specific
+      // provider manually.
+      user.providerDataRaw.removeValue(forKey: provider)
+
+      if provider == EmailAuthProvider.id {
+        user.hasEmailPasswordCredential = false
+      }
+      #if os(iOS)
+        // After successfully unlinking a phone auth provider, remove the phone number
+        // from the cached user info.
+        if provider == PhoneAuthProvider.id {
+          user.phoneNumber = nil
+        }
+      #endif
+      if let idToken = response.idToken,
+         let refreshToken = response.refreshToken {
+        let tokenService = SecureTokenService(
+          withRequestConfiguration: requestConfiguration,
+          accessToken: idToken,
+          accessTokenExpirationDate: response.approximateExpirationDate,
+          refreshToken: refreshToken
+        )
+        try await user.setTokenService(tokenService: tokenService)
+        return user
+      }
+    } catch {
+      user.signOutIfTokenIsInvalid(withError: error)
+      throw error
+    }
+
+    if let error = user.updateKeychain() {
+      throw error
+    }
+    return user
+  }
+
+  func sendEmailVerification(user: User,
+                             with actionCodeSettings: ActionCodeSettings?) async throws {
+    let accessToken = try await user.internalGetToken()
+    let request = GetOOBConfirmationCodeRequest.verifyEmailRequest(
+      accessToken: accessToken,
+      actionCodeSettings: actionCodeSettings,
+      requestConfiguration: requestConfiguration
+    )
+    do {
+      _ = try await AuthBackend.call(with: request)
+    } catch {
+      user.signOutIfTokenIsInvalid(withError: error)
+      throw error
+    }
+  }
+
+  func sendEmailVerification(user: User,
+                             beforeUpdatingEmail newEmail: String,
+                             actionCodeSettings: ActionCodeSettings?) async throws {
+    let accessToken = try await user.internalGetToken()
+    let request = GetOOBConfirmationCodeRequest.verifyBeforeUpdateEmail(
+      accessToken: accessToken,
+      newEmail: newEmail,
+      actionCodeSettings: actionCodeSettings,
+      requestConfiguration: requestConfiguration
+    )
+    do {
+      _ = try await AuthBackend.call(with: request)
+    } catch {
+      user.signOutIfTokenIsInvalid(withError: error)
+      throw error
+    }
+  }
+
+  func delete(user: User) async throws {
+    let accessToken = try await user.internalGetToken()
+    let request = DeleteAccountRequest(localID: user.uid, accessToken: accessToken,
+                                       requestConfiguration: requestConfiguration)
+    _ = try await AuthBackend.call(with: request)
+    try user.auth?.signOutByForce(withUserID: user.uid)
+  }
+
+  func commitChanges(changeRequest: UserProfileChangeRequest) async throws {
+    if changeRequest.consumed {
+      fatalError("Internal Auth Error: commitChanges should only be called once.")
+    }
+    changeRequest.consumed = true
+
+    // Return fast if there is nothing to update:
+    if !changeRequest.photoURLWasSet, !changeRequest.displayNameWasSet {
+      return
+    }
+    let displayName = changeRequest.displayName
+    let displayNameWasSet = changeRequest.displayNameWasSet
+    let photoURL = changeRequest.photoURL
+    let photoURLWasSet = changeRequest.photoURLWasSet
+
+    try await executeUserUpdateWithChanges(user: changeRequest.user) { _, request in
+      if photoURLWasSet {
+        request.photoURL = photoURL
+      }
+      if displayNameWasSet {
+        request.displayName = displayName
+      }
+    }
+    if displayNameWasSet {
+      changeRequest.user.displayName = displayName
+    }
+    if photoURLWasSet {
+      changeRequest.user.photoURL = photoURL
+    }
+    if let error = changeRequest.user.updateKeychain() {
+      throw error
+    }
+  }
+
   private func link(user: User,
                     withEmailCredential emailCredential: EmailAuthCredential) async throws
     -> AuthDataResult {
@@ -588,7 +713,7 @@ actor AuthWorker {
         authResult: result
       )
     case let .link(link):
-      let accessToken = try? await user.internalGetTokenAsync()
+      let accessToken = try? await user.internalGetToken()
       var queryItems = AuthWebUtils.parseURL(link)
       if link.count == 0 {
         if let urlComponents = URLComponents(string: link),
@@ -622,7 +747,7 @@ actor AuthWorker {
                     withEmail email: String,
                     password: String,
                     authResult: AuthDataResult) async throws -> AuthDataResult {
-    let accessToken = try await user.internalGetTokenAsync()
+    let accessToken = try await user.internalGetToken()
     do {
       let request = SignUpNewUserRequest(email: email,
                                          password: password,
@@ -647,7 +772,7 @@ actor AuthWorker {
         refreshToken: refreshToken
       )
 
-      let accessToken = try await user.internalGetTokenAsync()
+      let accessToken = try await user.internalGetToken()
       let getAccountInfoRequest = GetAccountInfoRequest(
         accessToken: accessToken,
         requestConfiguration: requestConfiguration
@@ -670,7 +795,7 @@ actor AuthWorker {
     private func link(user: User,
                       withGameCenterCredential gameCenterCredential: GameCenterAuthCredential) async throws
       -> AuthDataResult {
-      let accessToken = try await user.internalGetTokenAsync()
+      let accessToken = try await user.internalGetToken()
       guard let publicKeyURL = gameCenterCredential.publicKeyURL,
             let signature = gameCenterCredential.signature,
             let salt = gameCenterCredential.salt else {
@@ -721,7 +846,7 @@ actor AuthWorker {
       accessTokenExpirationDate: expirationDate,
       refreshToken: refreshToken
     )
-    let accessToken = try await user.internalGetTokenAsync()
+    let accessToken = try await user.internalGetToken()
     let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken,
                                                       requestConfiguration: requestConfiguration)
     do {
@@ -821,7 +946,7 @@ actor AuthWorker {
     }
     let uid = currentUser.uid
     do {
-      _ = try await currentUser.internalGetTokenAsync(forceRefresh: true)
+      _ = try await currentUser.internalGetToken(forceRefresh: true)
       if auth.currentUser?.uid != uid {
         return
       }

+ 43 - 329
FirebaseAuth/Sources/Swift/User/User.swift

@@ -669,67 +669,15 @@ extension User: NSSecureCoding {}
   /// fails.
   @objc open func unlink(fromProvider provider: String,
                          completion: ((User?, Error?) -> Void)? = nil) {
-    taskQueue.enqueueTask { complete in
-      let completeAndCallbackWithError = { error in
-        complete()
-        User.callInMainThreadWithUserAndError(callback: completion, user: self,
-                                              error: error)
-      }
-      self.internalGetToken { accessToken, error in
-        if let error {
-          completeAndCallbackWithError(error)
-          return
-        }
-        guard let requestConfiguration = self.auth?.requestConfiguration else {
-          fatalError("Internal Error: Unexpected nil requestConfiguration.")
-        }
-        let request = SetAccountInfoRequest(requestConfiguration: requestConfiguration)
-        request.accessToken = accessToken
-
-        if self.providerDataRaw[provider] == nil {
-          completeAndCallbackWithError(AuthErrorUtils.noSuchProviderError())
-          return
+    Task {
+      do {
+        let user = try await unlink(fromProvider: provider)
+        await MainActor.run {
+          completion?(user, nil)
         }
-        request.deleteProviders = [provider]
-        Task {
-          do {
-            let response = try await AuthBackend.call(with: request)
-            // We can't just use the provider info objects in SetAccountInfoResponse
-            // because they don't have localID and email fields. Remove the specific
-            // provider manually.
-            self.providerDataRaw.removeValue(forKey: provider)
-            if provider == EmailAuthProvider.id {
-              self.hasEmailPasswordCredential = false
-            }
-            #if os(iOS)
-              // After successfully unlinking a phone auth provider, remove the phone number
-              // from the cached user info.
-              if provider == PhoneAuthProvider.id {
-                self.phoneNumber = nil
-              }
-            #endif
-            if let idToken = response.idToken,
-               let refreshToken = response.refreshToken {
-              let tokenService = SecureTokenService(withRequestConfiguration: requestConfiguration,
-                                                    accessToken: idToken,
-                                                    accessTokenExpirationDate: response
-                                                      .approximateExpirationDate,
-                                                    refreshToken: refreshToken)
-              self.setTokenService(tokenService: tokenService) { error in
-                completeAndCallbackWithError(error)
-              }
-              return
-            }
-            if let error = self.updateKeychain() {
-              completeAndCallbackWithError(error)
-              return
-            }
-            completeAndCallbackWithError(nil)
-          } catch {
-            self.signOutIfTokenIsInvalid(withError: error)
-            completeAndCallbackWithError(error)
-            return
-          }
+      } catch {
+        await MainActor.run {
+          completion?(nil, error)
         }
       }
     }
@@ -750,15 +698,7 @@ extension User: NSSecureCoding {}
   /// - Returns: The user.
   @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
   open func unlink(fromProvider provider: String) async throws -> User {
-    return try await withCheckedThrowingContinuation { continuation in
-      self.unlink(fromProvider: provider) { result, error in
-        if let result {
-          continuation.resume(returning: result)
-        } else if let error {
-          continuation.resume(throwing: error)
-        }
-      }
-    }
+    return try await auth.authWorker.unlink(user: self, fromProvider: provider)
   }
 
   /// Initiates email verification for the user.
@@ -795,31 +735,15 @@ extension User: NSSecureCoding {}
   @objc(sendEmailVerificationWithActionCodeSettings:completion:)
   open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil,
                                   completion: ((Error?) -> Void)? = nil) {
-    kAuthGlobalWorkQueue.async {
-      self.internalGetToken { accessToken, error in
-        if let error {
-          User.callInMainThreadWithError(callback: completion, error: error)
-          return
-        }
-        guard let accessToken else {
-          fatalError("Internal Error: Both error and accessToken are nil.")
-        }
-        guard let requestConfiguration = self.auth?.requestConfiguration else {
-          fatalError("Internal Error: Unexpected nil requestConfiguration.")
+    Task {
+      do {
+        try await sendEmailVerification(with: actionCodeSettings)
+        await MainActor.run {
+          completion?(nil)
         }
-        let request = GetOOBConfirmationCodeRequest.verifyEmailRequest(
-          accessToken: accessToken,
-          actionCodeSettings: actionCodeSettings,
-          requestConfiguration: requestConfiguration
-        )
-        Task {
-          do {
-            let _ = try await AuthBackend.call(with: request)
-            User.callInMainThreadWithError(callback: completion, error: nil)
-          } catch {
-            self.signOutIfTokenIsInvalid(withError: error)
-            User.callInMainThreadWithError(callback: completion, error: error)
-          }
+      } catch {
+        await MainActor.run {
+          completion?(error)
         }
       }
     }
@@ -839,15 +763,7 @@ extension User: NSSecureCoding {}
   /// handling action codes. The default value is `nil`.
   @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
   open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil) async throws {
-    return try await withCheckedThrowingContinuation { continuation in
-      self.sendEmailVerification(with: actionCodeSettings) { error in
-        if let error {
-          continuation.resume(throwing: error)
-        } else {
-          continuation.resume()
-        }
-      }
-    }
+    return try await auth.authWorker.sendEmailVerification(user: self, with: actionCodeSettings)
   }
 
   /// Deletes the user account (also signs out the user, if this was the current user).
@@ -860,28 +776,15 @@ extension User: NSSecureCoding {}
   /// - Parameter completion: Optionally; the block invoked when the request to delete the account
   /// is complete, or fails. Invoked asynchronously on the main thread in the future.
   @objc open func delete(completion: ((Error?) -> Void)? = nil) {
-    kAuthGlobalWorkQueue.async {
-      self.internalGetToken { accessToken, error in
-        if let error {
-          User.callInMainThreadWithError(callback: completion, error: error)
-          return
-        }
-        guard let accessToken else {
-          fatalError("Auth Internal Error: Both error and accessToken are nil.")
-        }
-        guard let requestConfiguration = self.auth?.requestConfiguration else {
-          fatalError("Auth Internal Error: Unexpected nil requestConfiguration.")
+    Task {
+      do {
+        try await delete()
+        await MainActor.run {
+          completion?(nil)
         }
-        let request = DeleteAccountRequest(localID: self.uid, accessToken: accessToken,
-                                           requestConfiguration: requestConfiguration)
-        Task {
-          do {
-            let _ = try await AuthBackend.call(with: request)
-            try self.auth?.signOutByForce(withUserID: self.uid)
-            User.callInMainThreadWithError(callback: completion, error: nil)
-          } catch {
-            User.callInMainThreadWithError(callback: completion, error: error)
-          }
+      } catch {
+        await MainActor.run {
+          completion?(error)
         }
       }
     }
@@ -896,15 +799,7 @@ extension User: NSSecureCoding {}
   /// `reauthenticate(with:)`.
   @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
   open func delete() async throws {
-    return try await withCheckedThrowingContinuation { continuation in
-      self.delete { error in
-        if let error {
-          continuation.resume(throwing: error)
-        } else {
-          continuation.resume()
-        }
-      }
-    }
+    return try await auth.authWorker.delete(user: self)
   }
 
   /// Send an email to verify the ownership of the account then update to the new email.
@@ -925,31 +820,16 @@ extension User: NSSecureCoding {}
   @objc open func sendEmailVerification(beforeUpdatingEmail email: String,
                                         actionCodeSettings: ActionCodeSettings? = nil,
                                         completion: ((Error?) -> Void)? = nil) {
-    kAuthGlobalWorkQueue.async {
-      self.internalGetToken { accessToken, error in
-        if let error {
-          User.callInMainThreadWithError(callback: completion, error: error)
-          return
-        }
-        guard let accessToken else {
-          fatalError("Internal Error: Both error and accessToken are nil.")
-        }
-        guard let requestConfiguration = self.auth?.requestConfiguration else {
-          fatalError("Internal Error: Unexpected nil requestConfiguration.")
+    Task {
+      do {
+        try await sendEmailVerification(beforeUpdatingEmail: email,
+                                        actionCodeSettings: actionCodeSettings)
+        await MainActor.run {
+          completion?(nil)
         }
-        let request = GetOOBConfirmationCodeRequest.verifyBeforeUpdateEmail(
-          accessToken: accessToken,
-          newEmail: email,
-          actionCodeSettings: actionCodeSettings,
-          requestConfiguration: requestConfiguration
-        )
-        Task {
-          do {
-            let _ = try await AuthBackend.call(with: request)
-            User.callInMainThreadWithError(callback: completion, error: nil)
-          } catch {
-            User.callInMainThreadWithError(callback: completion, error: error)
-          }
+      } catch {
+        await MainActor.run {
+          completion?(error)
         }
       }
     }
@@ -962,16 +842,11 @@ extension User: NSSecureCoding {}
   @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
   open func sendEmailVerification(beforeUpdatingEmail newEmail: String,
                                   actionCodeSettings: ActionCodeSettings? = nil) async throws {
-    return try await withCheckedThrowingContinuation { continuation in
-      self.sendEmailVerification(beforeUpdatingEmail: newEmail,
-                                 actionCodeSettings: actionCodeSettings) { error in
-        if let error {
-          continuation.resume(throwing: error)
-        } else {
-          continuation.resume()
-        }
-      }
-    }
+    return try await auth.authWorker.sendEmailVerification(
+      user: self,
+      beforeUpdatingEmail: newEmail,
+      actionCodeSettings: actionCodeSettings
+    )
   }
 
   // MARK: Internal implementations below
@@ -1017,7 +892,7 @@ extension User: NSSecureCoding {}
     user.auth = auth
     user.tenantID = auth.tenantID
     user.requestConfiguration = auth.requestConfiguration
-    let accessToken2 = try await user.internalGetTokenAsync()
+    let accessToken2 = try await user.internalGetToken()
     let getAccountInfoRequest = GetAccountInfoRequest(
       accessToken: accessToken2,
       requestConfiguration: user.requestConfiguration
@@ -1113,41 +988,6 @@ extension User: NSSecureCoding {}
     }
   }
 
-  /// Gets the users' account data from the server, updating our local values.
-  /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an
-  /// error has been detected. Invoked asynchronously on the auth global work queue in the future.
-  private func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponseUser?,
-                                                                  Error?) -> Void) {
-    internalGetToken { token, error in
-      if let error {
-        callback(nil, error)
-        return
-      }
-      guard let token else {
-        fatalError("Internal Error: Both error and token are nil.")
-      }
-      guard let requestConfiguration = self.auth?.requestConfiguration else {
-        fatalError("Internal Error: Unexpected nil requestConfiguration.")
-      }
-      let request = GetAccountInfoRequest(accessToken: token,
-                                          requestConfiguration: requestConfiguration)
-      Task {
-        do {
-          let accountInfoResponse = try await AuthBackend.call(with: request)
-          self.update(withGetAccountInfoResponse: accountInfoResponse)
-          if let error = self.updateKeychain() {
-            callback(nil, error)
-            return
-          }
-          callback(accountInfoResponse.users?.first, nil)
-        } catch {
-          self.signOutIfTokenIsInvalid(withError: error)
-          callback(nil, error)
-        }
-      }
-    }
-  }
-
   func update(withGetAccountInfoResponse response: GetAccountInfoResponse) {
     guard let user = response.users?.first else {
       // Silent fallthrough in ObjC code.
@@ -1183,67 +1023,6 @@ extension User: NSSecureCoding {}
     #endif
   }
 
-  // DELETE ME
-  /// Performs a setAccountInfo request by mutating the results of a getAccountInfo response,
-  /// atomically in regards to other calls to this method.
-  /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest`
-  /// - Parameter callback: A block to invoke when the change is complete. Invoked asynchronously on
-  /// the auth global work queue in the future.
-  func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponseUser,
-                                                            SetAccountInfoRequest) -> Void,
-                                    callback: @escaping (Error?) -> Void) {
-    taskQueue.enqueueTask { complete in
-      self.getAccountInfoRefreshingCache { user, error in
-        if let error {
-          complete()
-          callback(error)
-          return
-        }
-        guard let user else {
-          fatalError("Internal error: Both user and error are nil")
-        }
-        self.internalGetToken { accessToken, error in
-          if let error {
-            complete()
-            callback(error)
-            return
-          }
-          if let configuration = self.auth?.requestConfiguration {
-            // Mutate setAccountInfoRequest in block
-            let setAccountInfoRequest = SetAccountInfoRequest(requestConfiguration: configuration)
-            setAccountInfoRequest.accessToken = accessToken
-            changeBlock(user, setAccountInfoRequest)
-            Task {
-              do {
-                let accountInfoResponse = try await AuthBackend.call(with: setAccountInfoRequest)
-                if let idToken = accountInfoResponse.idToken,
-                   let refreshToken = accountInfoResponse.refreshToken {
-                  let tokenService = SecureTokenService(
-                    withRequestConfiguration: configuration,
-                    accessToken: idToken,
-                    accessTokenExpirationDate: accountInfoResponse.approximateExpirationDate,
-                    refreshToken: refreshToken
-                  )
-                  self.setTokenService(tokenService: tokenService) { error in
-                    complete()
-                    callback(error)
-                  }
-                  return
-                }
-                complete()
-                callback(nil)
-              } catch {
-                self.signOutIfTokenIsInvalid(withError: error)
-                complete()
-                callback(error)
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
   /// Signs out this user if the user or the token is invalid.
   /// - Parameter error: The error from the server.
   func signOutIfTokenIsInvalid(withError error: Error) {
@@ -1258,28 +1037,7 @@ extension User: NSSecureCoding {}
     }
   }
 
-  /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
-  /// - Parameter callback: The block to invoke when the token is available. Invoked asynchronously
-  /// on the  global work thread in the future.
-  func internalGetToken(forceRefresh: Bool = false,
-                        callback: @escaping (String?, Error?) -> Void) {
-    tokenService.fetchAccessToken(forcingRefresh: forceRefresh) { token, error, tokenUpdated in
-      if let error {
-        self.signOutIfTokenIsInvalid(withError: error)
-        callback(nil, error)
-        return
-      }
-      if tokenUpdated {
-        if let error = self.updateKeychain() {
-          callback(nil, error)
-          return
-        }
-      }
-      callback(token, nil)
-    }
-  }
-
-  func internalGetTokenAsync(forceRefresh: Bool = false) async throws -> String {
+  func internalGetToken(forceRefresh: Bool = false) async throws -> String {
     do {
       let (token, tokenUpdated) = try await tokenService.fetchAccessToken(
         user: self,
@@ -1303,50 +1061,6 @@ extension User: NSSecureCoding {}
     return auth?.updateKeychain(withUser: self)
   }
 
-  /// Calls a callback in main thread with error.
-  /// - Parameter callback: The callback to be called in main thread.
-  /// - Parameter error: The error to pass to callback.
-
-  class func callInMainThreadWithError(callback: ((Error?) -> Void)?, error: Error?) {
-    if let callback {
-      DispatchQueue.main.async {
-        callback(error)
-      }
-    }
-  }
-
-  /// Calls a callback in main thread with user and error.
-  /// - Parameter callback: The callback to be called in main thread.
-  /// - Parameter user: The user to pass to callback if there is no error.
-  /// - Parameter error: The error to pass to callback.
-  private class func callInMainThreadWithUserAndError(callback: ((User?, Error?) -> Void)?,
-                                                      user: User,
-                                                      error: Error?) {
-    if let callback {
-      DispatchQueue.main.async {
-        callback((error != nil) ? nil : user, error)
-      }
-    }
-  }
-
-  /// Calls a callback in main thread with user and error.
-  /// - Parameter callback: The callback to be called in main thread.
-  private class func callInMainThreadWithAuthDataResultAndError(callback: (
-    (AuthDataResult?, Error?) -> Void
-  )?,
-  complete: AuthSerialTaskCompletionBlock? = nil,
-  result: AuthDataResult? = nil,
-  error: Error? = nil) {
-    if let callback {
-      DispatchQueue.main.async {
-        if let complete {
-          complete()
-        }
-        callback(result, error)
-      }
-    }
-  }
-
   // MARK: NSSecureCoding
 
   private let kUserIDCodingKey = "userID"

+ 22 - 61
FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift

@@ -24,13 +24,11 @@ import Foundation
   @objc open var displayName: String? {
     get { return _displayName }
     set(newDisplayName) {
-      kAuthGlobalWorkQueue.async {
-        if self.consumed {
-          fatalError("Internal Auth Error: Invalid call to setDisplayName after commitChanges.")
-        }
-        self.displayNameWasSet = true
-        self._displayName = newDisplayName
+      if consumed {
+        fatalError("Internal Auth Error: Invalid call to setDisplayName after commitChanges.")
       }
+      displayNameWasSet = true
+      _displayName = newDisplayName
     }
   }
 
@@ -40,13 +38,11 @@ import Foundation
   @objc open var photoURL: URL? {
     get { return _photoURL }
     set(newPhotoURL) {
-      kAuthGlobalWorkQueue.async {
-        if self.consumed {
-          fatalError("Internal Auth Error: Invalid call to setPhotoURL after commitChanges.")
-        }
-        self.photoURLWasSet = true
-        self._photoURL = newPhotoURL
+      if consumed {
+        fatalError("Internal Auth Error: Invalid call to setPhotoURL after commitChanges.")
       }
+      photoURLWasSet = true
+      _photoURL = newPhotoURL
     }
   }
 
@@ -56,47 +52,20 @@ import Foundation
   ///
   /// Invoked asynchronously on the main thread in the future.
   ///
-  /// This method should only be called once.Once called, property values should not be changed.
+  /// This method should only be called once. Once called, property values should not be changed.
   /// - Parameter completion: Optionally; the block invoked when the user profile change has been
   /// applied.
   @objc open func commitChanges(completion: ((Error?) -> Void)? = nil) {
-    kAuthGlobalWorkQueue.async {
-      if self.consumed {
-        fatalError("Internal Auth Error: commitChanges should only be called once.")
-      }
-      self.consumed = true
-      // Return fast if there is nothing to update:
-      if !self.photoURLWasSet, !self.displayNameWasSet {
-        User.callInMainThreadWithError(callback: completion, error: nil)
-        return
-      }
-      let displayName = self.displayName
-      let displayNameWasSet = self.displayNameWasSet
-      let photoURL = self.photoURL
-      let photoURLWasSet = self.photoURLWasSet
-
-      self.user.executeUserUpdateWithChanges(changeBlock: { user, request in
-        if photoURLWasSet {
-          request.photoURL = photoURL
-        }
-        if displayNameWasSet {
-          request.displayName = displayName
+    Task {
+      do {
+        try await self.commitChanges()
+        await MainActor.run {
+          completion?(nil)
         }
-      }) { error in
-        if let error {
-          User.callInMainThreadWithError(callback: completion, error: error)
-          return
+      } catch {
+        await MainActor.run {
+          completion?(error)
         }
-        if displayNameWasSet {
-          self.user.displayName = displayName
-        }
-        if photoURLWasSet {
-          self.user.photoURL = photoURL
-        }
-        if let error = self.user.updateKeychain() {
-          User.callInMainThreadWithError(callback: completion, error: error)
-        }
-        User.callInMainThreadWithError(callback: completion, error: nil)
       }
     }
   }
@@ -106,23 +75,15 @@ import Foundation
   /// This method should only be called once. Once called, property values should not be changed.
   @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
   open func commitChanges() async throws {
-    return try await withCheckedThrowingContinuation { continuation in
-      self.commitChanges { error in
-        if let error {
-          continuation.resume(throwing: error)
-        } else {
-          continuation.resume()
-        }
-      }
-    }
+    try await user.auth.authWorker.commitChanges(changeRequest: self)
   }
 
   init(_ user: User) {
     self.user = user
   }
 
-  private let user: User
-  private var consumed = false
-  private var displayNameWasSet = false
-  private var photoURLWasSet = false
+  let user: User
+  var consumed = false
+  var displayNameWasSet = false
+  var photoURLWasSet = false
 }