diff --git a/CHANGELOG.md b/CHANGELOG.md index b8e5812..dcbf811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,34 @@ +## 1.0.5 + +- Added the ability to use a generic type for the login method in ArcaneAuthenticationService +- Added the ability to reset the ArcaneAuthenticationService, which will unregister the current interface and clear the authentication state +- Removed unused testing tooling (e.g., `@visibleForTesting`) from the codebase + - Migration guide: Remove usages of `setMocked` in your tests + ## 1.0.4 -* Resolved an issue with authentication using the ArcaneAuthenticationService when logging in with an email and password +- Resolved an issue with authentication using the ArcaneAuthenticationService when logging in with an email and password ## 1.0.3+1 -* Added example project +- Added example project ## 1.0.3 -* Added the ability to switch back to the normal environment from the debug environment in ArcaneEnvironment -* (breaking) Made the optional `onLoggedOut` callback a Future instead of a void function in ArcaneAuthenticationService -* Added additional error handling to the login method in ArcaneAuthenticationService -* Added support for following the system's theme in ArcaneTheme -* Removed the BuildContext parameter from the `switchTheme` method in ArcaneTheme +- Added the ability to switch back to the normal environment from the debug environment in ArcaneEnvironment +- (breaking) Made the optional `onLoggedOut` callback a Future instead of a void function in ArcaneAuthenticationService +- Added additional error handling to the login method in ArcaneAuthenticationService +- Added support for following the system's theme in ArcaneTheme +- Removed the BuildContext parameter from the `switchTheme` method in ArcaneTheme ## 1.0.2 -* Migrated ArcaneAuthenticationService's isSignedIn to a ValueListenable +- Migrated ArcaneAuthenticationService's isSignedIn to a ValueListenable ## 1.0.1+1 -* Removed ID and secure storage services to improve platform compatibility +- Removed ID and secure storage services to improve platform compatibility ## 1.0.0 -* Initial release +- Initial release diff --git a/example/lib/interfaces/debug_auth_interface.dart b/example/lib/interfaces/debug_auth_interface.dart index 4278c35..60434ec 100644 --- a/example/lib/interfaces/debug_auth_interface.dart +++ b/example/lib/interfaces/debug_auth_interface.dart @@ -45,6 +45,22 @@ class DebugAuthInterface implements ArcaneAuthInterface { return Result.ok(null); } + @override + Future> login({ + T? input, + Future Function()? onLoggedIn, + }) async { + final bool alreadyLoggedIn = await isSignedIn; + + if (alreadyLoggedIn) return Result.ok(null); + + Arcane.log("Logging: $input"); + + _isSignedIn = true; + + return Result.ok(null); + } + @override Future> resendVerificationCode(String email) async { Arcane.log("Re-sending verification code to $email"); diff --git a/lib/src/services/authentication/authentication_interface.dart b/lib/src/services/authentication/authentication_interface.dart index aa2b9c0..6745a13 100644 --- a/lib/src/services/authentication/authentication_interface.dart +++ b/lib/src/services/authentication/authentication_interface.dart @@ -51,6 +51,27 @@ abstract class ArcaneAuthInterface { required String password, }); + /// Logs the user in using an optional, generic `T` type of input. + /// This login method is a generic method that can be used to login with any + /// type of input. It is useful for login methods that do not require an email + /// and password. Any type of input can be passed in, and it will be handled + /// by the implementation of the method wihin the specific authentication + /// service. + /// + /// Example: + /// ```dart + /// await authInterface.login>( + /// input: { + /// "username": "hello@world.com", + /// "password": "password", + /// }, + /// ); + /// ``` + Future> login({ + T? input, + Future Function()? onLoggedIn, + }); + /// Re-sends a verification code to the user's email address. /// /// This method is typically used when the user hasn't received or has lost their initial diff --git a/lib/src/services/authentication/authentication_service.dart b/lib/src/services/authentication/authentication_service.dart index 703fdd7..4b5fd64 100644 --- a/lib/src/services/authentication/authentication_service.dart +++ b/lib/src/services/authentication/authentication_service.dart @@ -15,7 +15,6 @@ part "authentication_interface.dart"; class ArcaneAuthenticationService extends ArcaneService { ArcaneAuthenticationService._internal(); - static bool _mocked = false; static final ArcaneAuthenticationService _instance = ArcaneAuthenticationService._internal(); @@ -32,11 +31,17 @@ class ArcaneAuthenticationService extends ArcaneService { /// - `debug`: Debug mode has been enabled, enabling development features. AuthenticationStatus get status => _status; - static late ArcaneAuthInterface _authInterface; + static ArcaneAuthInterface? _authInterface; /// Provides direct access to the registered `ArcaneAuthInterface`, if one has /// been registered. - ArcaneAuthInterface get authInterface => _authInterface; + ArcaneAuthInterface get authInterface { + assert( + _authInterface != null, + "No ArcaneAuthInterface has been registered", + ); + return _authInterface!; + } /// A shortcut to `status != AuthenticationStatus.unauthenticated`. bool get isAuthenticated => status != AuthenticationStatus.unauthenticated; @@ -53,8 +58,20 @@ class ArcaneAuthenticationService extends ArcaneService { /// provides one. Future get refreshToken => authInterface.refreshToken; + /// Removes any registered `ArcaneAuthInterface` and resets all values to + /// default. + Future reset() async { + _authInterface = null; + _status = AuthenticationStatus.unauthenticated; + notifyListeners(); + } + /// Registers an `ArcaneAuthInterface` within the `ArcaneAuthenticationService`. Future registerInterface(ArcaneAuthInterface authInterface) async { + if (_authInterface != null) { + throw Exception("ArcaneAuthInterface has already been registered"); + } + _authInterface = authInterface; await authInterface.init(); } @@ -66,8 +83,6 @@ class ArcaneAuthenticationService extends ArcaneService { BuildContext context, { Future Function()? onDebugModeSet, }) async { - if (_mocked) return; - ArcaneEnvironment? environment; try { @@ -98,8 +113,6 @@ class ArcaneAuthenticationService extends ArcaneService { BuildContext context, { Future Function()? onDebugModeUnset, }) async { - if (_mocked) return; - ArcaneEnvironment? environment; try { @@ -125,21 +138,15 @@ class ArcaneAuthenticationService extends ArcaneService { /// Sets `status` to `AuthenticationStatus.authenticated`. void setAuthenticated() { - if (_mocked) return; - _setStatus(AuthenticationStatus.authenticated); } /// Sets `status` to `AuthenticationStatus.unauthenticated`. void setUnauthenticated() { - if (_mocked) return; - _setStatus(AuthenticationStatus.unauthenticated); } void _setStatus(AuthenticationStatus newStatus) { - if (_mocked) return; - _status = newStatus; notifyListeners(); } @@ -147,9 +154,13 @@ class ArcaneAuthenticationService extends ArcaneService { /// Logs the current user out. Upon successful logout, `status` will be set to /// `AuthenticationStatus.unauthenticated`. Future logOut({Future Function()? onLoggedOut}) async { - if (_mocked) return; if (!isAuthenticated) return; + assert( + _authInterface != null, + "No ArcaneAuthInterface has been registered", + ); + final Result loggedOut = await authInterface.logout(); await loggedOut.fold( @@ -174,8 +185,6 @@ class ArcaneAuthenticationService extends ArcaneService { required String password, Future Function()? onLoggedIn, }) async { - if (_mocked) return Result.ok(null); - if (status != AuthenticationStatus.unauthenticated) { return Result.error("Cannot sign in. Status is already ${status.name}."); } @@ -194,6 +203,27 @@ class ArcaneAuthenticationService extends ArcaneService { return result; } + /// Logs the user in using an optional, generic `T` type of input. + Future> login({ + T? input, + Future Function()? onLoggedIn, + }) async { + if (_authInterface == null) { + return Result.error("No ArcaneAuthInterface has been registered"); + } + + final Result result = await authInterface.login( + input: input, + ); + + if (result.isSuccess) { + setAuthenticated(); + if (onLoggedIn != null) await onLoggedIn(); + } + + return result; + } + /// Attempts to register a new account using the provided `email` and /// `password`. Upon success, returns a `SignUpStep` indicating the next step /// in the signup process as a `SignUpStep`. @@ -201,7 +231,10 @@ class ArcaneAuthenticationService extends ArcaneService { required String email, required String password, }) async { - if (_mocked) return Result.ok(SignUpStep.done); + if (_authInterface == null) { + return Result.error("No ArcaneAuthInterface has been registered"); + } + final Result result = await authInterface.signup( email: email, password: password, @@ -216,7 +249,10 @@ class ArcaneAuthenticationService extends ArcaneService { required String email, required String confirmationCode, }) async { - if (_mocked) return Result.ok(false); + if (_authInterface == null) { + return Result.error("No ArcaneAuthInterface has been registered"); + } + final Result result = await authInterface.confirmSignup( username: email, confirmationCode: confirmationCode, @@ -228,7 +264,10 @@ class ArcaneAuthenticationService extends ArcaneService { /// Re-sends a verification code to be used when confirming the user's /// registration. Future> resendVerificationCode(String email) async { - if (_mocked) return Result.ok(""); + if (_authInterface == null) { + return Result.error("No ArcaneAuthInterface has been registered"); + } + return authInterface.resendVerificationCode(email); } @@ -243,7 +282,10 @@ class ArcaneAuthenticationService extends ArcaneService { String? newPassword, String? confirmationCode, }) async { - if (_mocked) return Result.ok(false); + if (_authInterface == null) { + return Result.error("No ArcaneAuthInterface has been registered"); + } + final Result result = await authInterface.resetPassword( email: email, newPassword: newPassword, @@ -252,12 +294,4 @@ class ArcaneAuthenticationService extends ArcaneService { return result; } - - /// Marks the service as mocked for testing purposes. - /// - /// If the service is mocked, no method will be executed. - @visibleForTesting - static void setMocked() { - _mocked = true; - } } diff --git a/lib/src/services/feature_flags/feature_flags_service.dart b/lib/src/services/feature_flags/feature_flags_service.dart index 4445ce5..c926d49 100644 --- a/lib/src/services/feature_flags/feature_flags_service.dart +++ b/lib/src/services/feature_flags/feature_flags_service.dart @@ -1,5 +1,4 @@ import "package:arcane_framework/arcane_framework.dart"; -import "package:flutter/foundation.dart"; part "feature_flags_extensions.dart"; @@ -41,15 +40,6 @@ class ArcaneFeatureFlags extends ArcaneService { /// to access the instance. static bool get initialized => I._initialized; - bool _mocked = false; - - /// Marks the feature flags as mocked for testing purposes. - /// - /// When the feature flags are mocked, they bypass certain initializations, making - /// them easier to work with in unit tests. - @visibleForTesting - void setMocked() => _mocked = true; - /// Checks if a specific [feature] is enabled. /// /// Returns `true` if the [feature] is in the list of enabled features, otherwise returns `false`. @@ -125,8 +115,6 @@ class ArcaneFeatureFlags extends ArcaneService { /// It is called automatically when enabling or disabling features if they haven't /// already been initialized. void _init() { - if (_mocked) return; - _enabledFeatures.clear(); I._initialized = true; diff --git a/lib/src/services/logging/logging_service.dart b/lib/src/services/logging/logging_service.dart index f32e21a..850db9e 100644 --- a/lib/src/services/logging/logging_service.dart +++ b/lib/src/services/logging/logging_service.dart @@ -35,22 +35,12 @@ class ArcaneLogger { /// Whether the logger has been initialized. bool get initialized => I._initialized; - /// Marks the logger as mocked for testing purposes. - /// - /// If the logger is mocked, platform-specific features (such as tracking - /// status) will not be initialized. - @visibleForTesting - void setMocked() => _mocked = true; - bool _mocked = false; - /// Initializes the logger. /// /// Sets up error handling for both Flutter and platform-specific errors. /// Also, retrieves the tracking authorization status if running on iOS or /// macOS. Future _init() async { - if (_mocked) return; - additionalMetadata.clear(); // Handles unhandled Flutter errors by logging them. @@ -146,8 +136,6 @@ class ArcaneLogger { StackTrace? stackTrace, Map? metadata, }) { - if (I._mocked) return; - if (!I._initialized) { throw Exception("ArcaneLogger has not yet been initialized."); } diff --git a/pubspec.yaml b/pubspec.yaml index 5f23a24..94c9651 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: arcane_framework description: "Agnostic Reusable Component Architecture for New Ecosystems: a modern framework for bootstrapping new applications" -version: 1.0.4 +version: 1.0.5 repository: https://github.com/hanskokx/arcane_framework issue_tracker: https://github.com/hanskokx/arcane_framework/issues