diff --git a/auth/CMakeLists.txt b/auth/CMakeLists.txt index e47166bfe..a2488579e 100644 --- a/auth/CMakeLists.txt +++ b/auth/CMakeLists.txt @@ -24,6 +24,7 @@ set(firebase_auth_swig # Firebase Auth CSharp files set(firebase_auth_src src/FirebaseAccountLinkException.cs + src/FirebaseUser.cs src/IUserInfo.cs src/PhoneAuthProvider.cs ) diff --git a/auth/src/FirebaseUser.cs b/auth/src/FirebaseUser.cs new file mode 100644 index 000000000..a6a3323c9 --- /dev/null +++ b/auth/src/FirebaseUser.cs @@ -0,0 +1,407 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + using System.Threading.Tasks; + +namespace Firebase.Auth { + +/// @brief Firebase user account object. +/// +/// This class allows you to manipulate the profile of a user, link to and +/// unlink from authentication providers, and refresh authentication tokens. +public sealed class FirebaseUser : UserInfoInterface { + private FirebaseAuth authProxy; + + internal FirebaseUser(FirebaseAuth auth) { + authProxy = auth; + } + + private FirebaseUserInternal GetValidFirebaseUserInternal() { + if (authProxy == null) { + throw new System.NullReferenceException(); + } else { + FirebaseUserInternal userInternal = authProxy.CurrentUserInternal; + if (userInternal == null) { + throw new System.NullReferenceException(); + } else { + return userInternal; + } + } + } + + private void CompleteSignInResult(SignInResult signInResult) { + if (signInResult != null) { + // Cache the authProxy in the SignInResult + signInResult.authProxy = authProxy; + } + } + + private void CompleteAuthResult(AuthResult authResult) { + if (authResult != null) { + // Cache the authProxy in the AuthResult + authResult.authProxy = authProxy; + } + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use + /// @ref ReauthenticateWithProviderAsync(FederatedAuthProvider) instead. + /// + /// Reauthenticate a user via a federated auth provider. + /// + /// @note: This operation is supported only on iOS, tvOS and Android + /// platforms. On other platforms this method will return a Future with a + /// preset error code: kAuthErrorUnimplemented. + [System.Obsolete("Please use `Task ReauthenticateWithProviderAsync(FederatedAuthProvider)` instead", false)] + public async Task ReauthenticateWithProviderAsync_DEPRECATED(FederatedAuthProvider provider) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + SignInResult result = await userInternal.ReauthenticateWithProviderInternalAsync_DEPRECATED(provider); + CompleteSignInResult(result); + return result; + } + + /// Reauthenticate a user via a federated auth provider. + /// + /// @note: This operation is supported only on iOS, tvOS and Android + /// platforms. On other platforms this method will return a Future with a + /// preset error code: kAuthErrorUnimplemented. + public async Task ReauthenticateWithProviderAsync(FederatedAuthProvider provider) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + AuthResult result = await userInternal.ReauthenticateWithProviderInternalAsync(provider); + CompleteAuthResult(result); + return result; + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use + /// @ref LinkWithProviderAsync(FederatedAuthProvider) instead. + /// + /// Link a user via a federated auth provider. + /// + /// @note: This operation is supported only on iOS, tvOS and Android + /// platforms. On other platforms this method will return a Future with a + /// preset error code: kAuthErrorUnimplemented. + [System.Obsolete("Please use `Task LinkWithProviderAsync(FederatedAuthProvider)` instead", false)] + public async Task LinkWithProviderAsync_DEPRECATED(FederatedAuthProvider provider) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + SignInResult result = await userInternal.LinkWithProviderInternalAsync_DEPRECATED(provider); + CompleteSignInResult(result); + return result; + } + + /// Link a user via a federated auth provider. + /// + /// @note: This operation is supported only on iOS, tvOS and Android + /// platforms. On other platforms this method will return a Future with a + /// preset error code: kAuthErrorUnimplemented. + public async Task LinkWithProviderAsync(FederatedAuthProvider provider) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + AuthResult result = await userInternal.LinkWithProviderInternalAsync(provider); + CompleteAuthResult(result); + return result; + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use + /// @ref LinkWithCredentialAsync(Credential) instead. + /// + /// Links the user with the given 3rd party credentials. + /// + /// For example, a Facebook login access token, a Twitter token/token-secret + /// pair. + /// Status will be an error if the token is invalid, expired, or otherwise + /// not accepted by the server as well as if the given 3rd party + /// user id is already linked with another user account or if the current user + /// is already linked with another id from the same provider. + /// + /// Data from the Identity Provider used to sign-in is returned in the + /// @ref AdditionalUserInfo inside @ref SignInResult. + [System.Obsolete("Please use `Task LinkWithCredentialAsync(Credential)` instead", false)] + public async Task LinkAndRetrieveDataWithCredentialAsync(Credential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + SignInResult result = await userInternal.LinkAndRetrieveDataWithCredentialInternalAsync(credential); + CompleteSignInResult(result); + return result; + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use + /// @ref LinkWithCredentialAsync(Credential) instead. + /// + /// Associates a user account from a third-party identity provider. + [System.Obsolete("Please use `Task LinkWithCredentialAsync(Credential)` instead", false)] + public async Task LinkWithCredentialAsync_DEPRECATED(Credential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + // We don't care about the returned user, since there is currently only meant to + // be a single FirebaseUser under the hood. + await userInternal.LinkWithCredentialInternalAsync_DEPRECATED(credential); + return this; + } + + /// Associates a user account from a third-party identity provider. + public async Task LinkWithCredentialAsync(Credential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + AuthResult result = await userInternal.LinkWithCredentialInternalAsync(credential); + CompleteAuthResult(result); + return result; + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use + /// @ref ReauthenticateAndRetrieveDataAsync(Credential) instead. + /// + /// Reauthenticate using a credential. + /// + /// Data from the Identity Provider used to sign-in is returned in the + /// AdditionalUserInfo inside the returned SignInResult. + /// + /// Returns an error if the existing credential is not for this user + /// or if sign-in with that credential failed. + /// + /// @note: The current user may be signed out if this operation fails on + /// Android and desktop platforms. + [System.Obsolete("Please use `Task ReauthenticateAndRetrieveDataAsync(Credential)` instead", false)] + public async Task ReauthenticateAndRetrieveDataAsync_DEPRECATED(Credential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + SignInResult result = await userInternal.ReauthenticateAndRetrieveDataInternalAsync_DEPRECATED(credential); + CompleteSignInResult(result); + return result; + } + + /// Reauthenticate using a credential. + /// + /// Data from the Identity Provider used to sign-in is returned in the + /// AdditionalUserInfo inside the returned @ref AuthResult. + /// + /// Returns an error if the existing credential is not for this user + /// or if sign-in with that credential failed. + /// + /// @note: The current user may be signed out if this operation fails on + /// Android and desktop platforms. + public async Task ReauthenticateAndRetrieveDataAsync(Credential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + AuthResult result = await userInternal.ReauthenticateAndRetrieveDataInternalAsync(credential); + CompleteAuthResult(result); + return result; + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use @ref UnlinkAsync(string) instead. + /// + /// Unlinks the current user from the provider specified. + /// Status will be an error if the user is not linked to the given provider. + [System.Obsolete("Please use `Task UnlinkAsync(string)` instead", false)] + public async Task UnlinkAsync_DEPRECATED(string provider) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + // We don't care about the returned user, since there is currently only meant to + // be a single FirebaseUser under the hood. + await userInternal.UnlinkInternalAsync_DEPRECATED(provider); + return this; + } + + /// Unlinks the current user from the provider specified. + /// Status will be an error if the user is not linked to the given provider. + public async Task UnlinkAsync(string provider) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + AuthResult result = await userInternal.UnlinkInternalAsync(provider); + CompleteAuthResult(result); + return result; + } + + /// @deprecated This method is deprecated in favor of methods that return + /// `Task`. Please use + /// @ref UpdatePhoneNumberCredentialAsync(PhoneAuthCredential) instead. + /// + /// Updates the currently linked phone number on the user. + /// This is useful when a user wants to change their phone number. It is a + /// shortcut to calling `UnlinkAsync_DEPRECATED(phoneCredential.Provider)` + /// and then `LinkWithCredentialAsync_DEPRECATED(phoneCredential)`. + /// `phoneCredential` must have been created with @ref PhoneAuthProvider. + [System.Obsolete("Please use `Task UpdatePhoneNumberCredentialAsync(PhoneAuthCredential)` instead", false)] + public async Task UpdatePhoneNumberCredentialAsync_DEPRECATED(Credential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + // We don't care about the returned user, since there is currently only meant to + // be a single FirebaseUser under the hood. + await userInternal.UpdatePhoneNumberCredentialInternalAsync_DEPRECATED(credential); + return this; + } + + /// Updates the currently linked phone number on the user. + /// This is useful when a user wants to change their phone number. It is a + /// shortcut to calling `UnlinkAsync(phoneCredential.Provider)` + /// and then `LinkWithCredentialAsync(phoneCredential)`. + /// `phoneCredential` must have been created with @ref PhoneAuthProvider. + public async Task UpdatePhoneNumberCredentialAsync(PhoneAuthCredential credential) { + FirebaseUserInternal userInternal = GetValidFirebaseUserInternal(); + // We don't care about the returned user, since there is currently only meant to + // be a single FirebaseUser under the hood. + await userInternal.UpdatePhoneNumberCredentialInternalAsync_DEPRECATED(credential); + return this; + } + + /// Returns whether this FirebaseUser object represents a valid user. Could be + /// false on FirebaseUsers contained with AuthResult structures from failed Auth + /// operations. + public bool IsValid() { + if (authProxy != null) { + FirebaseUserInternal userInternal = authProxy.CurrentUserInternal; + if (userInternal != null) { + return userInternal.IsValid(); + } + } + return false; + } + + /// The Java Web Token (JWT) that can be used to identify the user to + /// the backend. + /// + /// If a current ID token is still believed to be valid (i.e. it has not yet + /// expired), that token will be returned immediately. + /// A developer may set the optional force_refresh flag to get a new ID token, + /// whether or not the existing token has expired. For example, a developer + /// may use this when they have discovered that the token is invalid for some + /// other reason. + public Task TokenAsync(bool forceRefresh) { + return GetValidFirebaseUserInternal().TokenAsync(forceRefresh); + } + + /// Sets the email address for the user. + /// + /// May fail if there is already an email/password-based account for the same + /// email address. + public Task UpdateEmailAsync(string email) { + return GetValidFirebaseUserInternal().UpdateEmailAsync(email); + } + + /// Attempts to change the password for the current user. + /// + /// For an account linked to an Identity Provider (IDP) with no password, + /// this will result in the account becoming an email/password-based account + /// while maintaining the IDP link. May fail if the password is invalid, + /// if there is a conflicting email/password-based account, or if the token + /// has expired. + /// To retrieve fresh tokens, call ReauthenticateAsync. + public Task UpdatePasswordAsync(string password) { + return GetValidFirebaseUserInternal().UpdatePasswordAsync(password); + } + + /// Convenience function for ReauthenticateAndRetrieveData that discards + /// the returned AdditionalUserInfo data. + public Task ReauthenticateAsync(Credential credential) { + return GetValidFirebaseUserInternal().ReauthenticateAsync(credential); + } + + /// Initiates email verification for the user. + public Task SendEmailVerificationAsync() { + return GetValidFirebaseUserInternal().SendEmailVerificationAsync(); + } + + /// Updates a subset of user profile information. + public Task UpdateUserProfileAsync(UserProfile profile) { + return GetValidFirebaseUserInternal().UpdateUserProfileAsync(profile); + } + + /// Refreshes the data for this user. + /// + /// For example, the attached providers, email address, display name, etc. + public System.Threading.Tasks.Task ReloadAsync() { + return GetValidFirebaseUserInternal().ReloadAsync(); + } + + /// Deletes the user account. + public System.Threading.Tasks.Task DeleteAsync() { + return GetValidFirebaseUserInternal().DeleteAsync(); + } + + /// Gets the display name associated with the user, if any. + public string DisplayName { + get { + return GetValidFirebaseUserInternal().DisplayName; + } + } + + /// Gets email associated with the user, if any. + public string Email { + get { + return GetValidFirebaseUserInternal().Email; + } + } + + /// Returns true if user signed in anonymously. + public bool IsAnonymous { + get { + return GetValidFirebaseUserInternal().IsAnonymous; + } + } + + /// Returns true if the email address associated with this user has been + /// verified. + public bool IsEmailVerified { + get { + return GetValidFirebaseUserInternal().IsEmailVerified; + } + } + + /// Gets the metadata for this user account. + public UserMetadata Metadata { + get { + return GetValidFirebaseUserInternal().Metadata; + } + } + + /// Gets the phone number for the user, in E.164 format. + public string PhoneNumber { + get { + return GetValidFirebaseUserInternal().PhoneNumber; + } + } + + /// The photo url associated with the user, if any. + public System.Uri PhotoUrl { + get { + return Firebase.FirebaseApp.UrlStringToUri(GetValidFirebaseUserInternal().PhotoUrlInternal); + } + } + + /// Gets the third party profile data associated with this user returned by + /// the authentication server, if any. + public System.Collections.Generic.IEnumerable ProviderData { + get { + return GetValidFirebaseUserInternal().ProviderData; + } + } + + /// Gets the provider ID for the user (For example, "Facebook"). + public string ProviderId { + get { + return GetValidFirebaseUserInternal().ProviderId; + } + } + + /// Gets the unique Firebase user ID for the user. + /// + /// @note The user's ID, unique to the Firebase project. + /// Do NOT use this value to authenticate with your backend server, if you + /// have one. + /// Use FirebaseUser.TokenAsync instead. + public string UserId { + get { + return GetValidFirebaseUserInternal().UserId; + } + } +} + +} // namespace Firebase.Auth diff --git a/auth/src/swig/auth.i b/auth/src/swig/auth.i index 79046ab13..3fc775eac 100644 --- a/auth/src/swig/auth.i +++ b/auth/src/swig/auth.i @@ -318,10 +318,10 @@ static CppInstanceManager g_auth_instances; %ignore firebase::auth::PhoneAuthOptions::ui_parent; // For deprecated Future -%SWIG_FUTURE(Future_User, FirebaseUser, internal, firebase::auth::User *, +%SWIG_FUTURE(Future_User, FirebaseUserInternal, internal, firebase::auth::User *, FirebaseException) // For Future -%SWIG_FUTURE(Future_User_Value, FirebaseUser, internal, firebase::auth::User, +%SWIG_FUTURE(Future_User_Value, FirebaseUserInternal, internal, firebase::auth::User, FirebaseException) %SWIG_FUTURE(Future_FetchProvidersResult, FetchProvidersResult, internal, firebase::auth::Auth::FetchProvidersResult, FirebaseException) @@ -513,6 +513,8 @@ static CppInstanceManager g_auth_instances; firebase::auth::Auth::SignInWithEmailAndPassword_DEPRECATED; %rename(CreateUserWithEmailAndPasswordInternalAsync_DEPRECATED) firebase::auth::Auth::CreateUserWithEmailAndPassword_DEPRECATED; +%rename(SignInWithCredentialInternalAsync_DEPRECATED) + firebase::auth::Auth::SignInWithCredential_DEPRECATED; %rename(SignInWithProviderInternalAsync_DEPRECATED) firebase::auth::Auth::SignInWithProvider_DEPRECATED; @@ -575,15 +577,12 @@ static CppInstanceManager g_auth_instances; /// platforms. On other platforms this method will return a Future with a /// preset error code: kAuthErrorUnimplemented. [System.Obsolete("Please use `Task SignInWithProviderAsync(FederatedAuthProvider)` instead", false)] - public System.Threading.Tasks.Task SignInWithProviderAsync_DEPRECATED( + public async System.Threading.Tasks.Task SignInWithProviderAsync_DEPRECATED( FederatedAuthProvider provider) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithProviderInternalAsync_DEPRECATED(provider).ContinueWith(task => { - CompleteSignInResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + SignInResult result = await SignInWithProviderInternalAsync_DEPRECATED(provider); + result.authProxy = this; + return result; } /// Sign-in a user authenticated via a federated auth provider. @@ -591,15 +590,12 @@ static CppInstanceManager g_auth_instances; /// @note: This operation is supported only on iOS, tvOS and Android /// platforms. On other platforms this method will return a Future with a /// preset error code: kAuthErrorUnimplemented. - public System.Threading.Tasks.Task SignInWithProviderAsync( + public async System.Threading.Tasks.Task SignInWithProviderAsync( FederatedAuthProvider provider) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithProviderInternalAsync(provider).ContinueWith(task => { - CompleteAuthResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + AuthResult result = await SignInWithProviderInternalAsync(provider); + result.authProxy = this; + return result; } // Holds a reference to the FirebaseApp proxy object so that it isn't @@ -863,18 +859,8 @@ static CppInstanceManager g_auth_instances; return taskCompletionSource.Task; } - // Does additional work to set up the FirebaseUser. - private FirebaseUser SetupUser(FirebaseUser user) { - if (user != null) { - // If the user isn't valid, switch to use null instead - if (!user.IsValid()) { - return null; - } - // Set the Auth object in the user - user.authProxy = this; - } - return user; - } + // Cached FirebaseUser, so that it can return the same object each time. + private FirebaseUser currentUser; /// @brief Synchronously gets the cached current user, or null if there is none. /// @note Accessing this property may block and wait until the FirebaseAuth instance finishes @@ -882,8 +868,14 @@ static CppInstanceManager g_auth_instances; /// period of time after the FirebaseAuth instance is created. public FirebaseUser CurrentUser { get { - var user = swigCPtr.Handle != System.IntPtr.Zero ? CurrentUserInternal : null; - return SetupUser(user); + // Validate the internal FirebaseUser first + FirebaseUserInternal userInternal = swigCPtr.Handle != System.IntPtr.Zero ? CurrentUserInternal : null; + if (userInternal == null || !userInternal.IsValid()) { + currentUser = null; + } else if (currentUser == null) { + currentUser = new FirebaseUser(this); + } + return currentUser; } } @@ -896,45 +888,41 @@ static CppInstanceManager g_auth_instances; /// An error is returned, if the token is invalid, expired or otherwise /// not accepted by the server. [System.Obsolete("Please use `Task SignInWithCustomTokenAsync(string)` instead", false)] - public System.Threading.Tasks.Task SignInWithCustomTokenAsync_DEPRECATED( + public async System.Threading.Tasks.Task SignInWithCustomTokenAsync_DEPRECATED( string token) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithCustomTokenInternalAsync_DEPRECATED(token).ContinueWith(task => { - CompleteFirebaseUserTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + await SignInWithCustomTokenInternalAsync_DEPRECATED(token); + return CurrentUser; } /// Asynchronously logs into Firebase with the given Auth token. /// /// An error is returned, if the token is invalid, expired or otherwise /// not accepted by the server. - public System.Threading.Tasks.Task SignInWithCustomTokenAsync( + public async System.Threading.Tasks.Task SignInWithCustomTokenAsync( string token) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithCustomTokenInternalAsync(token).ContinueWith(task => { - CompleteAuthResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + AuthResult result = await SignInWithCustomTokenInternalAsync(token); + result.authProxy = this; + return result; + } + + public async System.Threading.Tasks.Task SignInWithCredentialAsync_DEPRECATED( + Credential credential) { + ThrowIfNull(); + await SignInWithCredentialInternalAsync_DEPRECATED(credential); + return CurrentUser; } /// @brief Asynchronously logs into Firebase with the given `Auth` token. /// /// An error is returned, if the token is invalid, expired or otherwise not /// accepted by the server. - public System.Threading.Tasks.Task SignInWithCredentialAsync( + public async System.Threading.Tasks.Task SignInWithCredentialAsync( Credential credential) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithCredentialInternalAsync(credential).ContinueWith(task => { - CompleteFirebaseUserTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + await SignInWithCredentialInternalAsync(credential); + return CurrentUser; } /// @deprecated This method is deprecated in favor of methods that return @@ -954,14 +942,12 @@ static CppInstanceManager g_auth_instances; /// An error is returned if the token is invalid, expired, or otherwise not /// accepted by the server. [System.Obsolete("Please use `Task SignInAndRetrieveDataWithCredentialAsync(Credential)` instead", false)] - public System.Threading.Tasks.Task + public async System.Threading.Tasks.Task SignInAndRetrieveDataWithCredentialAsync_DEPRECATED(Credential credential) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInAndRetrieveDataWithCredentialInternalAsync_DEPRECATED(credential).ContinueWith( - task => { CompleteSignInResultTask(task, taskCompletionSource); }); - return taskCompletionSource.Task; + SignInResult result = await SignInAndRetrieveDataWithCredentialInternalAsync_DEPRECATED(credential); + result.authProxy = this; + return result; } /// Asynchronously logs into Firebase with the given credentials. @@ -975,14 +961,12 @@ static CppInstanceManager g_auth_instances; /// /// An error is returned if the token is invalid, expired, or otherwise not /// accepted by the server. - public System.Threading.Tasks.Task + public async System.Threading.Tasks.Task SignInAndRetrieveDataWithCredentialAsync(Credential credential) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInAndRetrieveDataWithCredentialInternalAsync(credential).ContinueWith( - task => { CompleteAuthResultTask(task, taskCompletionSource); }); - return taskCompletionSource.Task; + AuthResult result = await SignInAndRetrieveDataWithCredentialInternalAsync(credential); + result.authProxy = this; + return result; } /// @deprecated This method is deprecated in favor of methods that return @@ -1011,14 +995,10 @@ static CppInstanceManager g_auth_instances; /// } /// @endcode [System.Obsolete("Please use `Task SignInAnonymouslyAsync()` instead", false)] - public System.Threading.Tasks.Task SignInAnonymouslyAsync_DEPRECATED() { + public async System.Threading.Tasks.Task SignInAnonymouslyAsync_DEPRECATED() { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInAnonymouslyInternalAsync_DEPRECATED().ContinueWith(task => { - CompleteFirebaseUserTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + await SignInAnonymouslyInternalAsync_DEPRECATED(); + return CurrentUser; } /// Asynchronously creates and becomes an anonymous user. @@ -1042,14 +1022,11 @@ static CppInstanceManager g_auth_instances; /// }); /// } /// @endcode - public System.Threading.Tasks.Task SignInAnonymouslyAsync() { + public async System.Threading.Tasks.Task SignInAnonymouslyAsync() { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInAnonymouslyInternalAsync().ContinueWith(task => { - CompleteAuthResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + AuthResult result = await SignInAnonymouslyInternalAsync(); + result.authProxy = this; + return result; } /// @deprecated This method is deprecated in favor of methods that return @@ -1060,31 +1037,22 @@ static CppInstanceManager g_auth_instances; /// An error is returned if the password is wrong or otherwise not accepted /// by the server. [System.Obsolete("Please use `Task SignInWithEmailAndPasswordAsync(string, string)` instead", false)] - public System.Threading.Tasks.Task SignInWithEmailAndPasswordAsync_DEPRECATED( + public async System.Threading.Tasks.Task SignInWithEmailAndPasswordAsync_DEPRECATED( string email, string password) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithEmailAndPasswordInternalAsync_DEPRECATED(email, password).ContinueWith( - task => { - CompleteFirebaseUserTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + await SignInWithEmailAndPasswordInternalAsync_DEPRECATED(email, password); + return CurrentUser; } /// Signs in using provided email address and password. /// An error is returned if the password is wrong or otherwise not accepted /// by the server. - public System.Threading.Tasks.Task SignInWithEmailAndPasswordAsync( + public async System.Threading.Tasks.Task SignInWithEmailAndPasswordAsync( string email, string password) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - SignInWithEmailAndPasswordInternalAsync(email, password).ContinueWith( - task => { - CompleteAuthResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + AuthResult result = await SignInWithEmailAndPasswordInternalAsync(email, password); + result.authProxy = this; + return result; } /// @deprecated This method is deprecated in favor of methods that return @@ -1098,16 +1066,11 @@ static CppInstanceManager g_auth_instances; /// An error is returned when account creation is unsuccessful /// (due to another existing account, invalid password, etc.). [System.Obsolete("Please use `Task CreateUserWithEmailAndPasswordAsync(string, string)` instead", false)] - public System.Threading.Tasks.Task + public async System.Threading.Tasks.Task CreateUserWithEmailAndPasswordAsync_DEPRECATED(string email, string password) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - CreateUserWithEmailAndPasswordInternalAsync_DEPRECATED(email, password).ContinueWith( - task => { - CompleteFirebaseUserTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; + await CreateUserWithEmailAndPasswordInternalAsync_DEPRECATED(email, password); + return CurrentUser; } /// Creates, and on success, logs in a user with the given email address @@ -1115,31 +1078,12 @@ static CppInstanceManager g_auth_instances; /// /// An error is returned when account creation is unsuccessful /// (due to another existing account, invalid password, etc.). - public System.Threading.Tasks.Task + public async System.Threading.Tasks.Task CreateUserWithEmailAndPasswordAsync(string email, string password) { ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - CreateUserWithEmailAndPasswordInternalAsync(email, password).ContinueWith( - task => { - CompleteAuthResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; - } - - // Complete a task that returns a FirebaseUser. - internal void CompleteFirebaseUserTask( - System.Threading.Tasks.Task task, - System.Threading.Tasks.TaskCompletionSource - taskCompletionSource) { - if (task.IsCanceled) { - taskCompletionSource.SetCanceled(); - } else if (task.IsFaulted) { - Firebase.Internal.TaskCompletionSourceCompat.SetException( - taskCompletionSource, task.Exception); - } else { - taskCompletionSource.SetResult(SetupUser(task.Result)); - } + AuthResult result = await CreateUserWithEmailAndPasswordInternalAsync(email, password); + result.authProxy = this; + return result; } // Complete a task that returns a SignInResult. @@ -1158,25 +1102,6 @@ static CppInstanceManager g_auth_instances; taskCompletionSource.SetResult(result); } } - - // Complete a task that returns a AuthResult. - internal void CompleteAuthResultTask( - System.Threading.Tasks.Task task, - System.Threading.Tasks.TaskCompletionSource - taskCompletionSource) { - if (task.IsCanceled) { - taskCompletionSource.SetCanceled(); - } else if (task.IsFaulted) { - Firebase.Internal.TaskCompletionSourceCompat.SetException( - taskCompletionSource, task.Exception); - } else { - AuthResult result = task.Result; - // This assume all the users from AuthResult points to current users. - // TODO(AuthRewrite): Update this logic when we can have multile FirebaseUser. - result.UserInternal = SetupUser(result.User); - taskCompletionSource.SetResult(result); - } - } %} // Replace the default Dispose() method to remove references to this instance @@ -1192,22 +1117,8 @@ static CppInstanceManager g_auth_instances; } %typemap(csclassmodifiers) firebase::auth::Auth "public sealed class"; -%typemap(csclassmodifiers) firebase::auth::User "public sealed class"; -%rename(FirebaseUser) firebase::auth::User; - -// TODO(butterfield): Aren't these all redundant? -- ignored via the C scope / name -%ignore FirebaseUser::uid; -%ignore FirebaseUser::email; -%ignore FirebaseUser::display_name; -%ignore FirebaseUser::photo_url; -%ignore FirebaseUser::provider_id; -%ignore FirebaseUser::GetToken; -%ignore FirebaseUser::GetTokenThreadSafe; -%ignore FirebaseUser::GetTokenLastResult; -%ignore FirebaseUser::provider_data; -%ignore FirebaseUser::is_email_verified; -%ignore FirebaseUser::is_anonymous; -%ignore FirebaseUser::metadata; +%typemap(csclassmodifiers) firebase::auth::User "internal sealed class"; +%rename(FirebaseUserInternal) firebase::auth::User; %typemap(csclassmodifiers) firebase::auth::User::UserProfile "public sealed class"; @@ -1300,6 +1211,7 @@ static CppInstanceManager g_auth_instances; %immutable firebase::auth::AuthResult::additional_user_info; %rename(CredentialInternal) firebase::auth::AuthResult::credential; %immutable firebase::auth::AuthResult::credential; +// Hide the User, we don't want to use this one because it will be deleted with the AuthResult %rename(UserInternal) firebase::auth::AuthResult::user; %typemap(cscode) firebase::auth::AuthResult %{ /// Identity-provider specific information for the user, if the provider is @@ -1313,11 +1225,14 @@ static CppInstanceManager g_auth_instances; get { return CredentialInternal; } } + internal FirebaseAuth authProxy; + /// The currently signed-in @ref FirebaseUser, or null if there isn't one. public FirebaseUser User { - // Both FirebaseAuth.CurrentUser and AuthResult.User returns null if the - // user is invalid. - get { return (UserInternal != null && UserInternal.IsValid()) ? UserInternal : null; } + // Return the Auth's User, since there can currently only be one user per Auth. + get { + return authProxy != null ? authProxy.CurrentUser : null; + } } %} %typemap(csclassmodifiers) firebase::auth::AuthResult "public sealed class"; @@ -1349,365 +1264,6 @@ static CppInstanceManager g_auth_instances; } } - -%typemap(cscode) firebase::auth::User %{ - // Holds a reference to the FirebaseAuth proxy object so that it isn't - // garbage collected while the application holds a reference to this object. - // This is set by FirebaseAuth when a user is fetched from the object. - internal FirebaseAuth authProxy; - - /// The photo url associated with the user, if any. - public System.Uri PhotoUrl { - get { - return Firebase.FirebaseApp.UrlStringToUri(PhotoUrlInternal); - } - } - // Complete a task that returns a SignInResult. - private void CompleteSignInResultTask( - System.Threading.Tasks.Task task, - System.Threading.Tasks.TaskCompletionSource - taskCompletionSource) { - if (task.IsCanceled) { - taskCompletionSource.SetCanceled(); - } else if (task.IsFaulted) { - Firebase.Internal.TaskCompletionSourceCompat.SetException( - taskCompletionSource, task.Exception); - } else { - SignInResult result = task.Result; - result.authProxy = authProxy; - taskCompletionSource.SetResult(result); - } - } - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use - /// @ref ReauthenticateWithProviderAsync(FederatedAuthProvider) instead. - /// - /// Reauthenticate a user via a federated auth provider. - /// - /// @note: This operation is supported only on iOS, tvOS and Android - /// platforms. On other platforms this method will return a Future with a - /// preset error code: kAuthErrorUnimplemented. - [System.Obsolete("Please use `Task ReauthenticateWithProviderAsync(FederatedAuthProvider)` instead", false)] - public System.Threading.Tasks.Task - ReauthenticateWithProviderAsync_DEPRECATED(FederatedAuthProvider provider) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - ReauthenticateWithProviderInternalAsync_DEPRECATED(provider).ContinueWith(task => { - CompleteSignInResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; - } - - /// Reauthenticate a user via a federated auth provider. - /// - /// @note: This operation is supported only on iOS, tvOS and Android - /// platforms. On other platforms this method will return a Future with a - /// preset error code: kAuthErrorUnimplemented. - public System.Threading.Tasks.Task - ReauthenticateWithProviderAsync(FederatedAuthProvider provider) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - ReauthenticateWithProviderInternalAsync(provider).ContinueWith(task => { - if(authProxy != null) { - authProxy.CompleteAuthResultTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'ReauthenticateWithProviderAsync()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - // Throw a NullReferenceException if this proxy references a deleted object. - private void ThrowIfNull() { - if (swigCPtr.Handle == System.IntPtr.Zero) { - throw new System.NullReferenceException(); - } - } - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use - /// @ref LinkWithProviderAsync(FederatedAuthProvider) instead. - /// - /// Link a user via a federated auth provider. - /// - /// @note: This operation is supported only on iOS, tvOS and Android - /// platforms. On other platforms this method will return a Future with a - /// preset error code: kAuthErrorUnimplemented. - [System.Obsolete("Please use `Task LinkWithProviderAsync(FederatedAuthProvider)` instead", false)] - public System.Threading.Tasks.Task LinkWithProviderAsync_DEPRECATED( - FederatedAuthProvider provider) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - LinkWithProviderInternalAsync_DEPRECATED(provider).ContinueWith(task => { - CompleteSignInResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; - } - - /// Link a user via a federated auth provider. - /// - /// @note: This operation is supported only on iOS, tvOS and Android - /// platforms. On other platforms this method will return a Future with a - /// preset error code: kAuthErrorUnimplemented. - public System.Threading.Tasks.Task LinkWithProviderAsync( - FederatedAuthProvider provider) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - LinkWithProviderInternalAsync(provider).ContinueWith(task => { - if(authProxy != null) { - authProxy.CompleteAuthResultTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'LinkWithProviderAsync()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use - /// @ref LinkWithCredentialAsync(Credential) instead. - /// - /// Links the user with the given 3rd party credentials. - /// - /// For example, a Facebook login access token, a Twitter token/token-secret - /// pair. - /// Status will be an error if the token is invalid, expired, or otherwise - /// not accepted by the server as well as if the given 3rd party - /// user id is already linked with another user account or if the current user - /// is already linked with another id from the same provider. - /// - /// Data from the Identity Provider used to sign-in is returned in the - /// @ref AdditionalUserInfo inside @ref SignInResult. - [System.Obsolete("Please use `Task LinkWithCredentialAsync(Credential)` instead", false)] - public System.Threading.Tasks.Task LinkAndRetrieveDataWithCredentialAsync( - Credential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - LinkAndRetrieveDataWithCredentialInternalAsync(credential).ContinueWith(task => { - CompleteSignInResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; - } - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use - /// @ref LinkWithCredentialAsync(Credential) instead. - /// - /// Associates a user account from a third-party identity provider. - [System.Obsolete("Please use `Task LinkWithCredentialAsync(Credential)` instead", false)] - public System.Threading.Tasks.Task LinkWithCredentialAsync_DEPRECATED( - Credential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - LinkWithCredentialInternalAsync_DEPRECATED(credential).ContinueWith( - task => { - if(authProxy != null) { - authProxy.CompleteFirebaseUserTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'LinkWithCredentialAsync_DEPRECATED()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - /// Associates a user account from a third-party identity provider. - public System.Threading.Tasks.Task LinkWithCredentialAsync( - Credential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - LinkWithCredentialInternalAsync(credential).ContinueWith(task => { - if(authProxy != null) { - authProxy.CompleteAuthResultTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'LinkWithCredentialAsync()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; -} - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use - /// @ref ReauthenticateAndRetrieveDataAsync(Credential) instead. - /// - /// Reauthenticate using a credential. - /// - /// Data from the Identity Provider used to sign-in is returned in the - /// AdditionalUserInfo inside the returned SignInResult. - /// - /// Returns an error if the existing credential is not for this user - /// or if sign-in with that credential failed. - /// - /// @note: The current user may be signed out if this operation fails on - /// Android and desktop platforms. - [System.Obsolete("Please use `Task ReauthenticateAndRetrieveDataAsync(Credential)` instead", false)] - public System.Threading.Tasks.Task ReauthenticateAndRetrieveDataAsync_DEPRECATED( - Credential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - ReauthenticateAndRetrieveDataInternalAsync_DEPRECATED(credential).ContinueWith(task => { - CompleteSignInResultTask(task, taskCompletionSource); - }); - return taskCompletionSource.Task; - } - - /// Reauthenticate using a credential. - /// - /// Data from the Identity Provider used to sign-in is returned in the - /// AdditionalUserInfo inside the returned @ref AuthResult. - /// - /// Returns an error if the existing credential is not for this user - /// or if sign-in with that credential failed. - /// - /// @note: The current user may be signed out if this operation fails on - /// Android and desktop platforms. - public System.Threading.Tasks.Task ReauthenticateAndRetrieveDataAsync( - Credential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - ReauthenticateAndRetrieveDataInternalAsync(credential).ContinueWith(task => { - if(authProxy != null) { - authProxy.CompleteAuthResultTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'ReauthenticateAndRetrieveDataAsync()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use @ref UnlinkAsync(string) instead. - /// - /// Unlinks the current user from the provider specified. - /// Status will be an error if the user is not linked to the given provider. - [System.Obsolete("Please use `Task UnlinkAsync(string)` instead", false)] - public System.Threading.Tasks.Task UnlinkAsync_DEPRECATED( - string provider) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - UnlinkInternalAsync_DEPRECATED(provider).ContinueWith( - task => { - if(authProxy != null) { - authProxy.CompleteFirebaseUserTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'UnlinkAsync_DEPRECATED()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - /// Unlinks the current user from the provider specified. - /// Status will be an error if the user is not linked to the given provider. - public System.Threading.Tasks.Task UnlinkAsync( - string provider) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - UnlinkInternalAsync(provider).ContinueWith( - task => { - if(authProxy != null) { - authProxy.CompleteAuthResultTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'UnlinkAsync()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - /// @deprecated This method is deprecated in favor of methods that return - /// `Task`. Please use - /// @ref UpdatePhoneNumberCredentialAsync(PhoneAuthCredential) instead. - /// - /// Updates the currently linked phone number on the user. - /// This is useful when a user wants to change their phone number. It is a - /// shortcut to calling `UnlinkAsync_DEPRECATED(phoneCredential.Provider)` - /// and then `LinkWithCredentialAsync_DEPRECATED(phoneCredential)`. - /// `phoneCredential` must have been created with @ref PhoneAuthProvider. - [System.Obsolete("Please use `Task UpdatePhoneNumberCredentialAsync(PhoneAuthCredential)` instead", false)] - public System.Threading.Tasks.Task UpdatePhoneNumberCredentialAsync_DEPRECATED( - Credential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - UpdatePhoneNumberCredentialInternalAsync_DEPRECATED(credential).ContinueWith( - task => { - if(authProxy != null) { - authProxy.CompleteFirebaseUserTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'UpdatePhoneNumberCredentialAsync_DEPRECATED()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } - - /// Updates the currently linked phone number on the user. - /// This is useful when a user wants to change their phone number. It is a - /// shortcut to calling `UnlinkAsync(phoneCredential.Provider)` - /// and then `LinkWithCredentialAsync(phoneCredential)`. - /// `phoneCredential` must have been created with @ref PhoneAuthProvider. - public System.Threading.Tasks.Task UpdatePhoneNumberCredentialAsync( - PhoneAuthCredential credential) { - ThrowIfNull(); - var taskCompletionSource = - new System.Threading.Tasks.TaskCompletionSource(); - UpdatePhoneNumberCredentialInternalAsync(credential).ContinueWith( - task => { - if(authProxy != null) { - authProxy.CompleteFirebaseUserTask(task, taskCompletionSource); - } else { - // This should not happen. But if it does, throw exception and - // notify the team. - taskCompletionSource.TrySetException(new FirebaseException(0, - "Cannot complete 'UpdatePhoneNumberCredentialAsync()' " + - "because the authProxy is null.")); - } - }); - return taskCompletionSource.Task; - } -%} - %typemap(cscode) firebase::auth::UserInfoInterface %{ /// Gets the photo url associated with the user, if any. public System.Uri PhotoUrl { diff --git a/auth/testapp/Assets/Firebase/Sample/Auth/UIHandlerAutomated.cs b/auth/testapp/Assets/Firebase/Sample/Auth/UIHandlerAutomated.cs index f5c77bc84..74e891e17 100644 --- a/auth/testapp/Assets/Firebase/Sample/Auth/UIHandlerAutomated.cs +++ b/auth/testapp/Assets/Firebase/Sample/Auth/UIHandlerAutomated.cs @@ -42,6 +42,7 @@ public override void Start() { TestSignInAnonymouslyAsync_DEPRECATED, TestSignInEmailAsync_DEPRECATED, TestSignInCredentialAsync_DEPRECATED, + TestCachingUser, // TODO(b/132083720) This test is currently broken, so disable it until it is fixed. // TestSignInAnonymouslyWithExceptionsInEventHandlersAsync, // TODO(b/281153256): Add more test cases @@ -449,5 +450,37 @@ Task TestSignInAnonymouslyWithExceptionsInEventHandlersAsync_DEPRECATED() { }); return tcs.Task; } + + // Test if caching the FirebaseUser object, and then deleting some of the C++ objects, will work. + Task TestCachingUser() { + SignOut(); + TaskCompletionSource tcs = new TaskCompletionSource(); + + auth.SignInAnonymouslyAsync().ContinueWithOnMainThread(t => { + if (t.IsFaulted) { + tcs.SetException(t.Exception); + return; + } else if (!t.Result.User.IsValid()) { + tcs.SetException(new Exception("User wasn't valid after sign in")); + return; + } + + // Cache the user, and then Dispose the AuthResult, to delete the underlying + // C++ AuthResult object. + Firebase.Auth.FirebaseUser user = t.Result.User; + t.Result.Dispose(); + + // Check if the User is still valid, which is should be + if (!user.IsValid()) { + tcs.SetException(new Exception("User should still be valid after deleting the AuthResult")); + } + + user.DeleteAsync().ContinueWithOnMainThread(t2 => { + tcs.SetResult(true); + }); + }); + + return tcs.Task; + } } } diff --git a/docs/readme.md b/docs/readme.md index 87143c749..946d52ec4 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -74,6 +74,8 @@ Release Notes ### Upcoming changes - Changes - General (iOS): 32-bit iOS builds (i386 and armv7) are no longer supported. + - Auth: Fixed a potential crash with holding onto FirebaseUser objects + after the AuthResult has been disposed. ### 11.3.0 - Changes