- 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

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2024-09-20 15:10:00 +02:00
parent 03e3a0bcbd
commit 05624263fb
7 changed files with 117 additions and 63 deletions
+17 -10
View File
@@ -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 ## 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 ## 1.0.3+1
* Added example project - Added example project
## 1.0.3 ## 1.0.3
* Added the ability to switch back to the normal environment from the debug environment in ArcaneEnvironment - 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 - (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 additional error handling to the login method in ArcaneAuthenticationService
* Added support for following the system's theme in ArcaneTheme - Added support for following the system's theme in ArcaneTheme
* Removed the BuildContext parameter from the `switchTheme` method in ArcaneTheme - Removed the BuildContext parameter from the `switchTheme` method in ArcaneTheme
## 1.0.2 ## 1.0.2
* Migrated ArcaneAuthenticationService's isSignedIn to a ValueListenable - Migrated ArcaneAuthenticationService's isSignedIn to a ValueListenable
## 1.0.1+1 ## 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 ## 1.0.0
* Initial release - Initial release
@@ -45,6 +45,22 @@ class DebugAuthInterface implements ArcaneAuthInterface {
return Result.ok(null); return Result.ok(null);
} }
@override
Future<Result<void, String>> login<T>({
T? input,
Future<void> 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 @override
Future<Result<String, String>> resendVerificationCode(String email) async { Future<Result<String, String>> resendVerificationCode(String email) async {
Arcane.log("Re-sending verification code to $email"); Arcane.log("Re-sending verification code to $email");
@@ -51,6 +51,27 @@ abstract class ArcaneAuthInterface {
required String password, 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<Map<String, String>>(
/// input: {
/// "username": "hello@world.com",
/// "password": "password",
/// },
/// );
/// ```
Future<Result<void, String>> login<T>({
T? input,
Future<void> Function()? onLoggedIn,
});
/// Re-sends a verification code to the user's email address. /// 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 /// This method is typically used when the user hasn't received or has lost their initial
@@ -15,7 +15,6 @@ part "authentication_interface.dart";
class ArcaneAuthenticationService extends ArcaneService { class ArcaneAuthenticationService extends ArcaneService {
ArcaneAuthenticationService._internal(); ArcaneAuthenticationService._internal();
static bool _mocked = false;
static final ArcaneAuthenticationService _instance = static final ArcaneAuthenticationService _instance =
ArcaneAuthenticationService._internal(); ArcaneAuthenticationService._internal();
@@ -32,11 +31,17 @@ class ArcaneAuthenticationService extends ArcaneService {
/// - `debug`: Debug mode has been enabled, enabling development features. /// - `debug`: Debug mode has been enabled, enabling development features.
AuthenticationStatus get status => _status; AuthenticationStatus get status => _status;
static late ArcaneAuthInterface _authInterface; static ArcaneAuthInterface? _authInterface;
/// Provides direct access to the registered `ArcaneAuthInterface`, if one has /// Provides direct access to the registered `ArcaneAuthInterface`, if one has
/// been registered. /// 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`. /// A shortcut to `status != AuthenticationStatus.unauthenticated`.
bool get isAuthenticated => status != AuthenticationStatus.unauthenticated; bool get isAuthenticated => status != AuthenticationStatus.unauthenticated;
@@ -53,8 +58,20 @@ class ArcaneAuthenticationService extends ArcaneService {
/// provides one. /// provides one.
Future<String?> get refreshToken => authInterface.refreshToken; Future<String?> get refreshToken => authInterface.refreshToken;
/// Removes any registered `ArcaneAuthInterface` and resets all values to
/// default.
Future<void> reset() async {
_authInterface = null;
_status = AuthenticationStatus.unauthenticated;
notifyListeners();
}
/// Registers an `ArcaneAuthInterface` within the `ArcaneAuthenticationService`. /// Registers an `ArcaneAuthInterface` within the `ArcaneAuthenticationService`.
Future<void> registerInterface(ArcaneAuthInterface authInterface) async { Future<void> registerInterface(ArcaneAuthInterface authInterface) async {
if (_authInterface != null) {
throw Exception("ArcaneAuthInterface has already been registered");
}
_authInterface = authInterface; _authInterface = authInterface;
await authInterface.init(); await authInterface.init();
} }
@@ -66,8 +83,6 @@ class ArcaneAuthenticationService extends ArcaneService {
BuildContext context, { BuildContext context, {
Future<void> Function()? onDebugModeSet, Future<void> Function()? onDebugModeSet,
}) async { }) async {
if (_mocked) return;
ArcaneEnvironment? environment; ArcaneEnvironment? environment;
try { try {
@@ -98,8 +113,6 @@ class ArcaneAuthenticationService extends ArcaneService {
BuildContext context, { BuildContext context, {
Future<void> Function()? onDebugModeUnset, Future<void> Function()? onDebugModeUnset,
}) async { }) async {
if (_mocked) return;
ArcaneEnvironment? environment; ArcaneEnvironment? environment;
try { try {
@@ -125,21 +138,15 @@ class ArcaneAuthenticationService extends ArcaneService {
/// Sets `status` to `AuthenticationStatus.authenticated`. /// Sets `status` to `AuthenticationStatus.authenticated`.
void setAuthenticated() { void setAuthenticated() {
if (_mocked) return;
_setStatus(AuthenticationStatus.authenticated); _setStatus(AuthenticationStatus.authenticated);
} }
/// Sets `status` to `AuthenticationStatus.unauthenticated`. /// Sets `status` to `AuthenticationStatus.unauthenticated`.
void setUnauthenticated() { void setUnauthenticated() {
if (_mocked) return;
_setStatus(AuthenticationStatus.unauthenticated); _setStatus(AuthenticationStatus.unauthenticated);
} }
void _setStatus(AuthenticationStatus newStatus) { void _setStatus(AuthenticationStatus newStatus) {
if (_mocked) return;
_status = newStatus; _status = newStatus;
notifyListeners(); notifyListeners();
} }
@@ -147,9 +154,13 @@ class ArcaneAuthenticationService extends ArcaneService {
/// Logs the current user out. Upon successful logout, `status` will be set to /// Logs the current user out. Upon successful logout, `status` will be set to
/// `AuthenticationStatus.unauthenticated`. /// `AuthenticationStatus.unauthenticated`.
Future<void> logOut({Future<void> Function()? onLoggedOut}) async { Future<void> logOut({Future<void> Function()? onLoggedOut}) async {
if (_mocked) return;
if (!isAuthenticated) return; if (!isAuthenticated) return;
assert(
_authInterface != null,
"No ArcaneAuthInterface has been registered",
);
final Result<void, String> loggedOut = await authInterface.logout(); final Result<void, String> loggedOut = await authInterface.logout();
await loggedOut.fold( await loggedOut.fold(
@@ -174,8 +185,6 @@ class ArcaneAuthenticationService extends ArcaneService {
required String password, required String password,
Future<void> Function()? onLoggedIn, Future<void> Function()? onLoggedIn,
}) async { }) async {
if (_mocked) return Result.ok(null);
if (status != AuthenticationStatus.unauthenticated) { if (status != AuthenticationStatus.unauthenticated) {
return Result.error("Cannot sign in. Status is already ${status.name}."); return Result.error("Cannot sign in. Status is already ${status.name}.");
} }
@@ -194,6 +203,27 @@ class ArcaneAuthenticationService extends ArcaneService {
return result; return result;
} }
/// Logs the user in using an optional, generic `T` type of input.
Future<Result<void, String>> login<T>({
T? input,
Future<void> Function()? onLoggedIn,
}) async {
if (_authInterface == null) {
return Result.error("No ArcaneAuthInterface has been registered");
}
final Result<void, String> 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 /// Attempts to register a new account using the provided `email` and
/// `password`. Upon success, returns a `SignUpStep` indicating the next step /// `password`. Upon success, returns a `SignUpStep` indicating the next step
/// in the signup process as a `SignUpStep`. /// in the signup process as a `SignUpStep`.
@@ -201,7 +231,10 @@ class ArcaneAuthenticationService extends ArcaneService {
required String email, required String email,
required String password, required String password,
}) async { }) async {
if (_mocked) return Result.ok(SignUpStep.done); if (_authInterface == null) {
return Result.error("No ArcaneAuthInterface has been registered");
}
final Result<SignUpStep, String> result = await authInterface.signup( final Result<SignUpStep, String> result = await authInterface.signup(
email: email, email: email,
password: password, password: password,
@@ -216,7 +249,10 @@ class ArcaneAuthenticationService extends ArcaneService {
required String email, required String email,
required String confirmationCode, required String confirmationCode,
}) async { }) async {
if (_mocked) return Result.ok(false); if (_authInterface == null) {
return Result.error("No ArcaneAuthInterface has been registered");
}
final Result<bool, String> result = await authInterface.confirmSignup( final Result<bool, String> result = await authInterface.confirmSignup(
username: email, username: email,
confirmationCode: confirmationCode, confirmationCode: confirmationCode,
@@ -228,7 +264,10 @@ class ArcaneAuthenticationService extends ArcaneService {
/// Re-sends a verification code to be used when confirming the user's /// Re-sends a verification code to be used when confirming the user's
/// registration. /// registration.
Future<Result<String, String>> resendVerificationCode(String email) async { Future<Result<String, String>> resendVerificationCode(String email) async {
if (_mocked) return Result.ok(""); if (_authInterface == null) {
return Result.error("No ArcaneAuthInterface has been registered");
}
return authInterface.resendVerificationCode(email); return authInterface.resendVerificationCode(email);
} }
@@ -243,7 +282,10 @@ class ArcaneAuthenticationService extends ArcaneService {
String? newPassword, String? newPassword,
String? confirmationCode, String? confirmationCode,
}) async { }) async {
if (_mocked) return Result.ok(false); if (_authInterface == null) {
return Result.error("No ArcaneAuthInterface has been registered");
}
final Result<bool, String> result = await authInterface.resetPassword( final Result<bool, String> result = await authInterface.resetPassword(
email: email, email: email,
newPassword: newPassword, newPassword: newPassword,
@@ -252,12 +294,4 @@ class ArcaneAuthenticationService extends ArcaneService {
return result; 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;
}
} }
@@ -1,5 +1,4 @@
import "package:arcane_framework/arcane_framework.dart"; import "package:arcane_framework/arcane_framework.dart";
import "package:flutter/foundation.dart";
part "feature_flags_extensions.dart"; part "feature_flags_extensions.dart";
@@ -41,15 +40,6 @@ class ArcaneFeatureFlags extends ArcaneService {
/// to access the instance. /// to access the instance.
static bool get initialized => I._initialized; 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. /// Checks if a specific [feature] is enabled.
/// ///
/// Returns `true` if the [feature] is in the list of enabled features, otherwise returns `false`. /// 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 /// It is called automatically when enabling or disabling features if they haven't
/// already been initialized. /// already been initialized.
void _init() { void _init() {
if (_mocked) return;
_enabledFeatures.clear(); _enabledFeatures.clear();
I._initialized = true; I._initialized = true;
@@ -35,22 +35,12 @@ class ArcaneLogger {
/// Whether the logger has been initialized. /// Whether the logger has been initialized.
bool get initialized => I._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. /// Initializes the logger.
/// ///
/// Sets up error handling for both Flutter and platform-specific errors. /// Sets up error handling for both Flutter and platform-specific errors.
/// Also, retrieves the tracking authorization status if running on iOS or /// Also, retrieves the tracking authorization status if running on iOS or
/// macOS. /// macOS.
Future<void> _init() async { Future<void> _init() async {
if (_mocked) return;
additionalMetadata.clear(); additionalMetadata.clear();
// Handles unhandled Flutter errors by logging them. // Handles unhandled Flutter errors by logging them.
@@ -146,8 +136,6 @@ class ArcaneLogger {
StackTrace? stackTrace, StackTrace? stackTrace,
Map<String, String>? metadata, Map<String, String>? metadata,
}) { }) {
if (I._mocked) return;
if (!I._initialized) { if (!I._initialized) {
throw Exception("ArcaneLogger has not yet been initialized."); throw Exception("ArcaneLogger has not yet been initialized.");
} }
+1 -1
View File
@@ -1,6 +1,6 @@
name: arcane_framework name: arcane_framework
description: "Agnostic Reusable Component Architecture for New Ecosystems: a modern framework for bootstrapping new applications" 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 repository: https://github.com/hanskokx/arcane_framework
issue_tracker: https://github.com/hanskokx/arcane_framework/issues issue_tracker: https://github.com/hanskokx/arcane_framework/issues