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:
2024-09-12 10:53:03 +02:00
parent 56ebaeb346
commit a2b3cbb077
9 changed files with 9 additions and 408 deletions
@@ -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();
}
-14
View File
@@ -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,
}
-115
View File
@@ -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;
}
+6 -64
View File
@@ -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;
}
}
}