mirror of
https://github.com/hanskokx/arcane_framework.git
synced 2026-05-14 10:29:06 +02:00
Removed ID and secure storage services to improve compatibility with different platforms. Removed app tracking handling from the logging service.
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -37,17 +37,6 @@ class Arcane {
|
||||
/// customizing them.
|
||||
static ArcaneReactiveTheme get theme => ArcaneReactiveTheme.I;
|
||||
|
||||
/// Provides access to the singleton instance of the secure storage service.
|
||||
///
|
||||
/// `ArcaneSecureStorage` is responsible for securely storing key-value pairs,
|
||||
/// such as tokens or user data.
|
||||
static ArcaneSecureStorage get storage => ArcaneSecureStorage.I;
|
||||
|
||||
/// Provides access to the singleton instance of the ID service.
|
||||
///
|
||||
/// `ArcaneIdService` manages unique identifiers for installations and sessions.
|
||||
static ArcaneIdService get id => ArcaneIdService.I;
|
||||
|
||||
/// Returns a list of all services available in the Arcane framework.
|
||||
///
|
||||
/// This list includes the feature flags, authentication, theme, and ID services.
|
||||
@@ -55,7 +44,6 @@ class Arcane {
|
||||
features,
|
||||
auth,
|
||||
theme,
|
||||
id,
|
||||
];
|
||||
|
||||
/// Logs a message using the integrated logger.
|
||||
|
||||
@@ -52,8 +52,6 @@ class ArcaneAuthenticationService extends ArcaneService {
|
||||
/// provides one.
|
||||
Future<String?> get refreshToken => authInterface.refreshToken;
|
||||
|
||||
static ArcaneSecureStorage get _storage => Arcane.storage;
|
||||
|
||||
/// Registers an `ArcaneAuthInterface` within the `ArcaneAuthenticationService`.
|
||||
Future<void> registerInterface(ArcaneAuthInterface authInterface) async {
|
||||
_authInterface = authInterface;
|
||||
@@ -113,7 +111,6 @@ class ArcaneAuthenticationService extends ArcaneService {
|
||||
|
||||
await loggedOut.fold(
|
||||
onSuccess: (_) async {
|
||||
await _storage.deleteAll();
|
||||
setUnauthenticated();
|
||||
onLoggedOut();
|
||||
},
|
||||
@@ -147,7 +144,6 @@ class ArcaneAuthenticationService extends ArcaneService {
|
||||
);
|
||||
|
||||
if (result.isSuccess) {
|
||||
await _storage.setValue(ArcaneSecureStorage.emailKey, email);
|
||||
setAuthenticated();
|
||||
if (onLoggedIn != null) await onLoggedIn();
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
part of "id_service.dart";
|
||||
|
||||
/// Enum representing different types of IDs managed by the `ArcaneIdService`.
|
||||
///
|
||||
/// The `ID` enum has two possible values:
|
||||
/// - `session`: Represents the session ID.
|
||||
/// - `install`: Represents the install ID.
|
||||
enum ID {
|
||||
/// Represents the session ID.
|
||||
session,
|
||||
|
||||
/// Represents the install ID.
|
||||
install,
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:uuid/uuid.dart";
|
||||
|
||||
part "id_enums.dart";
|
||||
|
||||
/// A singleton service that manages unique IDs, including install and session IDs.
|
||||
///
|
||||
/// The `ArcaneIdService` provides a way to generate and retrieve unique identifiers
|
||||
/// for application installs and sessions. It interacts with secure storage to persist
|
||||
/// the install ID across app launches and generates new session IDs for each session.
|
||||
class ArcaneIdService extends ArcaneService {
|
||||
/// Whether the service is mocked for testing purposes.
|
||||
static bool _mocked = false;
|
||||
|
||||
/// The singleton instance of `ArcaneIdService`.
|
||||
static final ArcaneIdService _instance = ArcaneIdService._internal();
|
||||
|
||||
/// Provides access to the singleton instance of `ArcaneIdService`.
|
||||
static ArcaneIdService get I => _instance;
|
||||
|
||||
ArcaneIdService._internal();
|
||||
|
||||
/// Whether the service has been initialized.
|
||||
bool _initialized = false;
|
||||
|
||||
/// Returns `true` if the service has been initialized.
|
||||
bool get initialized => I._initialized;
|
||||
|
||||
/// The unique install ID.
|
||||
///
|
||||
/// This ID is persisted across app launches and is used to uniquely identify
|
||||
/// the installation of the app.
|
||||
String? _installId;
|
||||
|
||||
/// Retrieves the install ID.
|
||||
///
|
||||
/// If the install ID is not yet initialized, this method initializes the service
|
||||
/// and generates a new ID if necessary.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// String? id = await ArcaneIdService.I.installId;
|
||||
/// ```
|
||||
Future<String?> get installId async {
|
||||
if (!initialized) await _init();
|
||||
return I._installId;
|
||||
}
|
||||
|
||||
/// The unique session ID.
|
||||
///
|
||||
/// This ID is generated for each app session and is used to uniquely identify
|
||||
/// the current session.
|
||||
String? _sessionId;
|
||||
|
||||
/// Retrieves the session ID.
|
||||
///
|
||||
/// If the session ID is not yet initialized, this method initializes the service
|
||||
/// and generates a new session ID.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// String? sessionId = await ArcaneIdService.I.sessionId;
|
||||
/// ```
|
||||
Future<String?> get sessionId async {
|
||||
if (!initialized) await _init();
|
||||
return I._sessionId;
|
||||
}
|
||||
|
||||
/// Generates a new unique ID.
|
||||
///
|
||||
/// This method uses UUID version 7 to generate a new unique ID.
|
||||
String get newId => uuid.v7();
|
||||
|
||||
/// The `Uuid` instance used for generating unique IDs.
|
||||
static const Uuid uuid = Uuid();
|
||||
|
||||
/// Initializes the `ArcaneIdService`.
|
||||
///
|
||||
/// This method retrieves the install ID from secure storage, generating and storing a new
|
||||
/// one if it does not exist. It also generates a new session ID.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// await ArcaneIdService.I._init();
|
||||
/// ```
|
||||
Future<ArcaneIdService> _init() async {
|
||||
if (_mocked) return I;
|
||||
if (!Arcane.storage.initialized) Arcane.storage.init();
|
||||
|
||||
I._installId = await Arcane.storage.getValue(
|
||||
ArcaneSecureStorage.installIdKey,
|
||||
);
|
||||
|
||||
if (I._installId == null) {
|
||||
// Generate a new ID and store it
|
||||
I._installId = uuid.v7();
|
||||
await Arcane.storage.setValue(
|
||||
ArcaneSecureStorage.installIdKey,
|
||||
I._installId,
|
||||
);
|
||||
}
|
||||
|
||||
I._sessionId = uuid.v7();
|
||||
I._initialized = true;
|
||||
return I;
|
||||
}
|
||||
|
||||
/// Sets the service as mocked for testing purposes.
|
||||
///
|
||||
/// When the service is mocked, it bypasses certain initializations and uses
|
||||
/// mocked data for testing.
|
||||
@visibleForTesting
|
||||
static void setMocked() => _mocked = true;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import "dart:async";
|
||||
import "dart:io" show Platform;
|
||||
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
||||
@@ -14,9 +13,7 @@ part "logging_interface.dart";
|
||||
///
|
||||
/// The `ArcaneLogger` provides a centralized way to log messages across
|
||||
/// different parts of an application. It supports multiple logging interfaces,
|
||||
/// metadata, and platform-specific error handling. It integrates with
|
||||
/// [AppTrackingTransparency] for tracking authorization status on fruit-shaped
|
||||
/// operating systems.
|
||||
/// metadata, and platform-specific error handling.
|
||||
class ArcaneLogger {
|
||||
ArcaneLogger._internal();
|
||||
|
||||
@@ -35,11 +32,6 @@ class ArcaneLogger {
|
||||
/// Additional metadata that is included in all logs.
|
||||
Map<String, String> get additionalMetadata => I._additionalMetadata;
|
||||
|
||||
TrackingStatus _trackingStatus = TrackingStatus.notDetermined;
|
||||
|
||||
/// The tracking authorization status for the current platform.
|
||||
TrackingStatus get trackingStatus => I._trackingStatus;
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
/// Whether the logger has been initialized.
|
||||
@@ -66,36 +58,23 @@ class ArcaneLogger {
|
||||
// Handles unhandled Flutter errors by logging them.
|
||||
FlutterError.onError = (errorDetails) {
|
||||
log(
|
||||
"UNHANDLED FLUTTER ERROR",
|
||||
errorDetails.exceptionAsString(),
|
||||
level: Level.error,
|
||||
module: errorDetails.library,
|
||||
stackTrace: errorDetails.stack,
|
||||
metadata: {
|
||||
"details": errorDetails.exceptionAsString(),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Handles unhandled platform-specific errors by logging them.
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
log(
|
||||
"UNHANDLED PLATFORM ERROR",
|
||||
"$error",
|
||||
level: Level.error,
|
||||
stackTrace: stack,
|
||||
metadata: {
|
||||
"details": error.toString(),
|
||||
},
|
||||
);
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
I._trackingStatus =
|
||||
await AppTrackingTransparency.trackingAuthorizationStatus;
|
||||
|
||||
if (!(Platform.isIOS || Platform.isMacOS)) {
|
||||
I._trackingStatus = TrackingStatus.authorized;
|
||||
}
|
||||
|
||||
I._initialized = true;
|
||||
}
|
||||
|
||||
@@ -213,9 +192,8 @@ class ArcaneLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a [LoggingInterface] with the [ArcaneLogger]. If the current
|
||||
/// operating system is not a fruit-shaped OS, it will automatically be
|
||||
/// initalized. Otherwise, app tracking permissions must first be checked for
|
||||
/// Registers a [LoggingInterface] with the [ArcaneLogger]. Due to iOS app
|
||||
/// tracking permissions, permission to track must first be checked for
|
||||
/// and (optionally) granted before the interface is automatically initialized.
|
||||
///
|
||||
/// Once your [LoggingInterface] has been registered and initialized, logs
|
||||
@@ -248,42 +226,6 @@ class ArcaneLogger {
|
||||
return I;
|
||||
}
|
||||
|
||||
/// This will ask the user to approve app tracking permissions on
|
||||
/// fruit-shaped operating systems. An optional `trackingDialog` method can be
|
||||
/// passed in, which could be used to display a message to users that they're
|
||||
/// about to be asked for tracking permissions. The `trackingDialog` method
|
||||
/// will only be run if the tracking status is `notDetermined`.
|
||||
///
|
||||
/// If app tracking has been allowed, all registered [LoggingInterface]s will
|
||||
/// be initialized.
|
||||
Future<void> initalizeAppTracking({
|
||||
Future<void>? trackingDialog,
|
||||
}) async {
|
||||
if (I._mocked) return;
|
||||
if (!I._initialized) await _init();
|
||||
if (I._trackingStatus == TrackingStatus.authorized) {
|
||||
await initializeInterfaces();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the system can show an authorization request dialog
|
||||
if (I._trackingStatus == TrackingStatus.notDetermined) {
|
||||
// Show a custom explainer dialog before the system dialog
|
||||
if (trackingDialog != null) await trackingDialog;
|
||||
// Wait for dialog popping animation
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
// Request system's tracking authorization dialog
|
||||
await AppTrackingTransparency.requestTrackingAuthorization();
|
||||
}
|
||||
|
||||
I._trackingStatus =
|
||||
await AppTrackingTransparency.trackingAuthorizationStatus;
|
||||
|
||||
if (I._trackingStatus == TrackingStatus.authorized) {
|
||||
await initializeInterfaces();
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a specific key from the persistent metadata.
|
||||
ArcaneLogger removePersistentMetadata(String key) {
|
||||
final bool keyPresent = additionalMetadata.containsKey(key);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
||||
import "package:flutter_secure_storage/flutter_secure_storage.dart";
|
||||
import "package:get_it/get_it.dart";
|
||||
|
||||
/// A singleton class that provides secure storage functionality using
|
||||
/// `FlutterSecureStorage`.
|
||||
///
|
||||
/// The `ArcaneSecureStorage` class is responsible for securely storing and
|
||||
/// retrieving key-value pairs, such as user email and install IDs. It supports
|
||||
/// caching for certain keys and provides initialization, deletion, and
|
||||
/// read/write methods for interacting with the secure storage.
|
||||
class ArcaneSecureStorage {
|
||||
/// The singleton instance of `ArcaneSecureStorage`.
|
||||
static final ArcaneSecureStorage _instance = ArcaneSecureStorage._internal();
|
||||
|
||||
/// Provides access to the singleton instance of `ArcaneSecureStorage`.
|
||||
static ArcaneSecureStorage get I => _instance;
|
||||
|
||||
ArcaneSecureStorage._internal();
|
||||
|
||||
/// The underlying secure storage instance.
|
||||
///
|
||||
/// This is initialized with `FlutterSecureStorage`, using encrypted shared
|
||||
/// preferences for Android.
|
||||
late final FlutterSecureStorage _storage;
|
||||
|
||||
/// Caches the user's email in memory.
|
||||
///
|
||||
/// This is used to reduce the number of reads to secure storage for the email
|
||||
/// key.
|
||||
String? _emailCache;
|
||||
|
||||
/// Provides access to the cached email if it exists.
|
||||
String? get cachedEmail => _emailCache;
|
||||
|
||||
/// The key used to store and retrieve the email from secure storage.
|
||||
static const String emailKey = "email";
|
||||
|
||||
/// The key used to store and retrieve the install ID from secure storage.
|
||||
static const String installIdKey = "installId";
|
||||
|
||||
/// Indicates whether the secure storage has been initialized.
|
||||
bool _initialized = false;
|
||||
|
||||
/// Returns `true` if the secure storage has been initialized.
|
||||
bool get initialized => I._initialized;
|
||||
|
||||
/// Initializes the secure storage and registers it with `GetIt` for
|
||||
/// dependency injection.
|
||||
///
|
||||
/// This method sets up the `FlutterSecureStorage` with encrypted shared
|
||||
/// preferences for Android, and registers it under the instance name
|
||||
/// `ArcaneSecureStorage`. It also sets the initialized flag to `true`.
|
||||
ArcaneSecureStorage init() {
|
||||
GetIt.I.registerSingleton<FlutterSecureStorage>(
|
||||
instanceName: "ArcaneSecureStorage",
|
||||
const FlutterSecureStorage(
|
||||
aOptions: AndroidOptions(
|
||||
encryptedSharedPreferences: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
I._storage = GetIt.I<FlutterSecureStorage>(
|
||||
instanceName: "ArcaneSecureStorage",
|
||||
);
|
||||
I._initialized = true;
|
||||
return I;
|
||||
}
|
||||
|
||||
/// Deletes all key-value pairs stored in secure storage.
|
||||
///
|
||||
/// This method clears the cache and deletes all data stored in the secure\
|
||||
/// storage. It returns `true` on success and `false` on failure.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// bool success = await ArcaneSecureStorage.I.deleteAll();
|
||||
/// ```
|
||||
Future<bool> deleteAll() async {
|
||||
if (!initialized) init();
|
||||
try {
|
||||
_emailCache = null;
|
||||
await _storage.deleteAll();
|
||||
return true;
|
||||
} catch (exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves a value associated with the given [key] from secure storage.
|
||||
///
|
||||
/// If the [key] is `emailKey`, the value will be cached in memory for future
|
||||
/// use.
|
||||
/// This method returns `null` if the key is not found or if an error occurs.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// String? email = await ArcaneSecureStorage.I.getValue(ArcaneSecureStorage.emailKey);
|
||||
/// ```
|
||||
Future<String?> getValue(String key) async {
|
||||
if (!initialized) init();
|
||||
String? value;
|
||||
|
||||
try {
|
||||
value = await _storage.read(key: key);
|
||||
if (value.isNullOrEmpty) return null;
|
||||
|
||||
// Cache the email for future use
|
||||
if (key == emailKey) _emailCache = value;
|
||||
} catch (e) {
|
||||
throw Exception(e);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Writes the given [value] associated with the [key] to secure storage.
|
||||
///
|
||||
/// This method returns `true` on success and `false` on failure.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// bool success = await ArcaneSecureStorage.I.setValue(ArcaneSecureStorage.emailKey, "user@example.com");
|
||||
/// ```
|
||||
Future<bool> setValue(String key, String? value) async {
|
||||
if (!initialized) init();
|
||||
|
||||
try {
|
||||
await _storage.write(key: key, value: value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user