diff --git a/docs/auth.md b/docs/auth.md index 2269130..821fc1d 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -70,6 +70,11 @@ Since code should be documenting itself you can also take a look at the followin - [tests/.../AuthFixture.cs](https://github.com/TobiasBuchholz/Plugin.Firebase/blob/master/tests/Plugin.Firebase.IntegrationTests/Auth/AuthFixture.cs) - [sample/.../AuthService.cs](https://github.com/TobiasBuchholz/Plugin.Firebase/blob/master/sample/Playground/Common/Services/Auth/AuthService.cs) +## Language + +Use `SetLanguageCode("fr")` (or any BCP-47 code) before invoking an Auth flow that triggers user-facing content such as password-reset emails, email-verification emails, or phone-auth SMS. +Pass `null` or whitespace to reset to the app language (`UseAppLanguage`). + ## Error handling Most Auth operations can throw `FirebaseAuthException`. @@ -79,6 +84,9 @@ The exception contains: - `Email`: populated for account-collision cases when available ## Release notes +- Version 4.1.0 + - Add `ReloadCurrentUserAsync()` to refresh the currently signed in user from the backend. + - Add `SetLanguageCode(string?)` to control the language used for Auth-generated user-facing flows (reset/verification emails, SMS). - Version 4.0.1 - Improve `FirebaseAuthException` mapping and expose raw error details (`ErrorCode`, `Email`, and native error metadata) to support robust UI handling. - Version 4.0.0 diff --git a/src/Analytics/Analytics.csproj b/src/Analytics/Analytics.csproj index 1031eff..dd34ae6 100644 --- a/src/Analytics/Analytics.csproj +++ b/src/Analytics/Analytics.csproj @@ -22,7 +22,7 @@ Plugin.Firebase.Analytics - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Auth/Auth.csproj b/src/Auth/Auth.csproj index 382fddc..d9b0b3f 100644 --- a/src/Auth/Auth.csproj +++ b/src/Auth/Auth.csproj @@ -23,7 +23,7 @@ Plugin.Firebase.Auth - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Auth/Platforms/Android/FirebaseAuthImplementation.cs b/src/Auth/Platforms/Android/FirebaseAuthImplementation.cs index 4a5dbdd..bc6860e 100644 --- a/src/Auth/Platforms/Android/FirebaseAuthImplementation.cs +++ b/src/Auth/Platforms/Android/FirebaseAuthImplementation.cs @@ -170,6 +170,28 @@ public async Task SendPasswordResetEmailAsync(string email) await WrapAsync(_firebaseAuth.SendPasswordResetEmailAsync(email)); } + public async Task ReloadCurrentUserAsync() + { + var currentUser = _firebaseAuth.CurrentUser; + if(currentUser is null) { + throw new FirebaseException( + "CurrentUser is null. You need to be logged in to use this feature." + ); + } + + await WrapAsync(currentUser.ReloadAsync()); + } + + public void SetLanguageCode(string? languageCode) + { + if(string.IsNullOrWhiteSpace(languageCode)) { + _firebaseAuth.UseAppLanguage(); + return; + } + + _firebaseAuth.LanguageCode = languageCode; + } + public void UseEmulator(string host, int port) { _firebaseAuth.UseEmulator(host, port); diff --git a/src/Auth/Platforms/iOS/FirebaseAuthImplementation.cs b/src/Auth/Platforms/iOS/FirebaseAuthImplementation.cs index 8e87ebc..0a3d5f5 100644 --- a/src/Auth/Platforms/iOS/FirebaseAuthImplementation.cs +++ b/src/Auth/Platforms/iOS/FirebaseAuthImplementation.cs @@ -141,9 +141,11 @@ public Task SignOutAsync() { _firebaseAuth.SignOut(out var error); - return error is null - ? Task.CompletedTask - : throw GetFirebaseAuthException(new NSErrorException(error)); + if(error is null) { + return Task.CompletedTask; + } + + throw GetFirebaseAuthException(new NSErrorException(error)); } /// @@ -180,6 +182,28 @@ public async Task SendPasswordResetEmailAsync(string email) await WrapAsync(_firebaseAuth.SendPasswordResetAsync(email)); } + public async Task ReloadCurrentUserAsync() + { + var currentUser = _firebaseAuth.CurrentUser; + if(currentUser is null) { + throw new FirebaseException( + "CurrentUser is null. You need to be logged in to use this feature." + ); + } + + await WrapAsync(currentUser.ReloadAsync()); + } + + public void SetLanguageCode(string? languageCode) + { + if(string.IsNullOrWhiteSpace(languageCode)) { + _firebaseAuth.UseAppLanguage(); + return; + } + + _firebaseAuth.LanguageCode = languageCode; + } + /// public void UseEmulator(string host, int port) { diff --git a/src/Auth/Shared/IFirebaseAuth.cs b/src/Auth/Shared/IFirebaseAuth.cs index 0d0286d..117974d 100644 --- a/src/Auth/Shared/IFirebaseAuth.cs +++ b/src/Auth/Shared/IFirebaseAuth.cs @@ -105,6 +105,19 @@ Task SignInWithEmailAndPasswordAsync( /// The registered user email. Task SendPasswordResetEmailAsync(string email); + /// + /// Reloads the currently signed in user from the backend. + /// + /// Thrown when no user is signed in. + Task ReloadCurrentUserAsync(); + + /// + /// Sets the language used by Firebase Auth for user-facing flows such as Auth-generated emails/SMS (password reset, email verification, phone auth). + /// Call this before invoking an API that triggers the flow, then reset by passing null/whitespace to use the app language. + /// + /// A BCP-47 language code (e.g. "fr", "en-GB"), or null to use app language. + void SetLanguageCode(string? languageCode); + /// /// Modify this FirebaseAuth instance to communicate with the Firebase Authentication emulator. /// Note: this must be called before this instance has been used to do any operations. diff --git a/src/Bundled/Bundled.csproj b/src/Bundled/Bundled.csproj index 47fd8ae..948871e 100644 --- a/src/Bundled/Bundled.csproj +++ b/src/Bundled/Bundled.csproj @@ -22,7 +22,7 @@ Plugin.Firebase - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/CloudMessaging/CloudMessaging.csproj b/src/CloudMessaging/CloudMessaging.csproj index 406f89b..3aa1e9b 100644 --- a/src/CloudMessaging/CloudMessaging.csproj +++ b/src/CloudMessaging/CloudMessaging.csproj @@ -23,7 +23,7 @@ Plugin.Firebase.CloudMessaging - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 4ee186e..7dd09fe 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -23,7 +23,7 @@ Plugin.Firebase.Core - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Crashlytics/Crashlytics.csproj b/src/Crashlytics/Crashlytics.csproj index f6de800..c1aad96 100644 --- a/src/Crashlytics/Crashlytics.csproj +++ b/src/Crashlytics/Crashlytics.csproj @@ -22,7 +22,7 @@ Plugin.Firebase.Crashlytics - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Firestore/Firestore.csproj b/src/Firestore/Firestore.csproj index 97f9f37..035ff1e 100644 --- a/src/Firestore/Firestore.csproj +++ b/src/Firestore/Firestore.csproj @@ -22,7 +22,7 @@ Plugin.Firebase.Firestore - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Functions/Functions.csproj b/src/Functions/Functions.csproj index d42c97f..2314a0a 100644 --- a/src/Functions/Functions.csproj +++ b/src/Functions/Functions.csproj @@ -22,7 +22,7 @@ Plugin.Firebase.Functions - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/RemoteConfig/RemoteConfig.csproj b/src/RemoteConfig/RemoteConfig.csproj index 16d8cfb..872eab7 100644 --- a/src/RemoteConfig/RemoteConfig.csproj +++ b/src/RemoteConfig/RemoteConfig.csproj @@ -22,7 +22,7 @@ Plugin.Firebase.RemoteConfig - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/src/Storage/Storage.csproj b/src/Storage/Storage.csproj index 320875d..8607534 100644 --- a/src/Storage/Storage.csproj +++ b/src/Storage/Storage.csproj @@ -22,7 +22,7 @@ Plugin.Firebase.Storage - 4.0.2 + 4.1.0 MIT https://github.com/TobiasBuchholz/Plugin.Firebase diff --git a/tests/Plugin.Firebase.IntegrationTests/Auth/AuthFixture.cs b/tests/Plugin.Firebase.IntegrationTests/Auth/AuthFixture.cs index ef674ff..a1c64ee 100644 --- a/tests/Plugin.Firebase.IntegrationTests/Auth/AuthFixture.cs +++ b/tests/Plugin.Firebase.IntegrationTests/Auth/AuthFixture.cs @@ -152,6 +152,34 @@ public async Task sends_verification_email() await sut.CurrentUser.SendEmailVerificationAsync(); } + [Fact] + public async Task reloads_current_user() + { + var sut = CrossFirebaseAuth.Current; + await sut.SignInWithEmailAndPasswordAsync("reload-current-user@test.com", "123456"); + Assert.NotNull(sut.CurrentUser); + + var uid = sut.CurrentUser.Uid; + await sut.ReloadCurrentUserAsync(); + + Assert.NotNull(sut.CurrentUser); + Assert.Equal(uid, sut.CurrentUser.Uid); + } + + [Fact] + public async Task sets_language_code() + { + var sut = CrossFirebaseAuth.Current; + await sut.SignInWithEmailAndPasswordAsync("set-language-code@test.com", "123456"); + + var ex = Record.Exception(() => { + sut.SetLanguageCode("fr"); + sut.SetLanguageCode(null); + sut.SetLanguageCode(" "); + }); + Assert.Null(ex); + } + [Fact] public async Task deletes_user() { diff --git a/tests/Plugin.Firebase.UnitTests/FirebaseAuthExceptionTests.cs b/tests/Plugin.Firebase.UnitTests/FirebaseAuthExceptionTests.cs index fe47d3b..e07adbd 100644 --- a/tests/Plugin.Firebase.UnitTests/FirebaseAuthExceptionTests.cs +++ b/tests/Plugin.Firebase.UnitTests/FirebaseAuthExceptionTests.cs @@ -18,10 +18,15 @@ public sealed class FirebaseAuthExceptionTests new object[] { "ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL", FIRAuthError.AccountExistsWithDifferentCredential }, new object[] { "ERROR_CREDENTIAL_ALREADY_IN_USE", FIRAuthError.CredentialAlreadyInUse }, new object[] { "ERROR_REQUIRES_RECENT_LOGIN", FIRAuthError.RequiresRecentLogin }, + new object[] { "RequiresRecentLogin", FIRAuthError.RequiresRecentLogin }, + new object[] { "FIRAuthErrorCodeRequiresRecentLogin", FIRAuthError.RequiresRecentLogin }, + new object[] { "AuthErrorCodeRequiresRecentLogin", FIRAuthError.RequiresRecentLogin }, new object[] { "ERROR_OPERATION_NOT_ALLOWED", FIRAuthError.OperationNotAllowed }, new object[] { "ERROR_INVALID_CUSTOM_TOKEN", FIRAuthError.InvalidCustomToken }, new object[] { "ERROR_CUSTOM_TOKEN_MISMATCH", FIRAuthError.CustomTokenMismatch }, new object[] { "ERROR_INVALID_USER_TOKEN", FIRAuthError.InvalidUserToken }, + new object[] { "ERROR_USER_TOKEN_EXPIRED", FIRAuthError.UserTokenExpired }, + new object[] { "FIRAuthErrorCodeUserTokenExpired", FIRAuthError.UserTokenExpired }, new object[] { "ERROR_KEYCHAIN_ERROR", FIRAuthError.KeychainError }, new object[] { "ERROR_INTERNAL_ERROR", FIRAuthError.InternalError }, new object[] { "ERROR_TOO_MANY_REQUESTS", FIRAuthError.TooManyRequests }, @@ -60,4 +65,4 @@ public void FromErrorCode_preserves_metadata() Assert.Equal("domain", exception.NativeErrorDomain); Assert.Equal(123, exception.NativeErrorCode); } -} +} \ No newline at end of file