Files
unionflow-client-quarkus-pr…/unionflow-mobile-apps/lib/core/failures/failures.dart
DahoudG f89f6167cc feat(mobile): Implement Keycloak WebView authentication with HTTP callback
- Replace flutter_appauth with custom WebView implementation to resolve deep link issues
- Add KeycloakWebViewAuthService with integrated WebView for seamless authentication
- Configure Android manifest for HTTP cleartext traffic support
- Add network security config for development environment (192.168.1.11)
- Update Keycloak client to use HTTP callback endpoint (http://192.168.1.11:8080/auth/callback)
- Remove obsolete keycloak_auth_service.dart and temporary scripts
- Clean up dependencies and regenerate injection configuration
- Tested successfully on multiple Android devices (Xiaomi 2201116TG, SM A725F)

BREAKING CHANGE: Authentication flow now uses WebView instead of external browser
- Users will see Keycloak login page within the app instead of browser redirect
- Resolves ERR_CLEARTEXT_NOT_PERMITTED and deep link state management issues
- Maintains full OIDC compliance with PKCE flow and secure token storage

Technical improvements:
- WebView with custom navigation delegate for callback handling
- Automatic token extraction and user info parsing from JWT
- Proper error handling and user feedback
- Consistent authentication state management across app lifecycle
2025-09-15 01:44:16 +00:00

272 lines
6.2 KiB
Dart

/// Classes d'échec pour la gestion d'erreurs structurée
abstract class Failure {
final String message;
final String? code;
final Map<String, dynamic>? details;
const Failure({
required this.message,
this.code,
this.details,
});
@override
String toString() => 'Failure(message: $message, code: $code)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Failure &&
other.message == message &&
other.code == code;
}
@override
int get hashCode => message.hashCode ^ code.hashCode;
}
/// Échec réseau (problèmes de connectivité, timeout, etc.)
class NetworkFailure extends Failure {
const NetworkFailure({
required super.message,
super.code,
super.details,
});
factory NetworkFailure.noConnection() {
return const NetworkFailure(
message: 'Aucune connexion internet disponible',
code: 'NO_CONNECTION',
);
}
factory NetworkFailure.timeout() {
return const NetworkFailure(
message: 'Délai d\'attente dépassé',
code: 'TIMEOUT',
);
}
factory NetworkFailure.serverUnreachable() {
return const NetworkFailure(
message: 'Serveur inaccessible',
code: 'SERVER_UNREACHABLE',
);
}
}
/// Échec serveur (erreurs HTTP 5xx, erreurs API, etc.)
class ServerFailure extends Failure {
final int? statusCode;
const ServerFailure({
required super.message,
super.code,
super.details,
this.statusCode,
});
factory ServerFailure.internalError() {
return const ServerFailure(
message: 'Erreur interne du serveur',
code: 'INTERNAL_ERROR',
statusCode: 500,
);
}
factory ServerFailure.serviceUnavailable() {
return const ServerFailure(
message: 'Service temporairement indisponible',
code: 'SERVICE_UNAVAILABLE',
statusCode: 503,
);
}
factory ServerFailure.badGateway() {
return const ServerFailure(
message: 'Passerelle défaillante',
code: 'BAD_GATEWAY',
statusCode: 502,
);
}
}
/// Échec de validation (données invalides, contraintes non respectées)
class ValidationFailure extends Failure {
final Map<String, List<String>>? fieldErrors;
const ValidationFailure({
required super.message,
super.code,
super.details,
this.fieldErrors,
});
factory ValidationFailure.invalidData(String field, String error) {
return ValidationFailure(
message: 'Données invalides',
code: 'INVALID_DATA',
fieldErrors: {field: [error]},
);
}
factory ValidationFailure.requiredField(String field) {
return ValidationFailure(
message: 'Champ requis manquant',
code: 'REQUIRED_FIELD',
fieldErrors: {field: ['Ce champ est requis']},
);
}
factory ValidationFailure.multipleErrors(Map<String, List<String>> errors) {
return ValidationFailure(
message: 'Plusieurs erreurs de validation',
code: 'MULTIPLE_ERRORS',
fieldErrors: errors,
);
}
}
/// Échec d'authentification (login, permissions, tokens expirés)
class AuthFailure extends Failure {
const AuthFailure({
required super.message,
super.code,
super.details,
});
factory AuthFailure.invalidCredentials() {
return const AuthFailure(
message: 'Identifiants invalides',
code: 'INVALID_CREDENTIALS',
);
}
factory AuthFailure.tokenExpired() {
return const AuthFailure(
message: 'Session expirée, veuillez vous reconnecter',
code: 'TOKEN_EXPIRED',
);
}
factory AuthFailure.insufficientPermissions() {
return const AuthFailure(
message: 'Permissions insuffisantes',
code: 'INSUFFICIENT_PERMISSIONS',
);
}
factory AuthFailure.accountLocked() {
return const AuthFailure(
message: 'Compte verrouillé',
code: 'ACCOUNT_LOCKED',
);
}
}
/// Échec de données (ressource non trouvée, conflit, etc.)
class DataFailure extends Failure {
const DataFailure({
required super.message,
super.code,
super.details,
});
factory DataFailure.notFound(String resource) {
return DataFailure(
message: '$resource non trouvé(e)',
code: 'NOT_FOUND',
details: {'resource': resource},
);
}
factory DataFailure.alreadyExists(String resource) {
return DataFailure(
message: '$resource existe déjà',
code: 'ALREADY_EXISTS',
details: {'resource': resource},
);
}
factory DataFailure.conflict(String reason) {
return DataFailure(
message: 'Conflit de données : $reason',
code: 'CONFLICT',
details: {'reason': reason},
);
}
}
/// Échec de cache (données expirées, cache corrompu)
class CacheFailure extends Failure {
const CacheFailure({
required super.message,
super.code,
super.details,
});
factory CacheFailure.expired() {
return const CacheFailure(
message: 'Données en cache expirées',
code: 'CACHE_EXPIRED',
);
}
factory CacheFailure.corrupted() {
return const CacheFailure(
message: 'Cache corrompu',
code: 'CACHE_CORRUPTED',
);
}
}
/// Échec de fichier (lecture, écriture, format)
class FileFailure extends Failure {
const FileFailure({
required super.message,
super.code,
super.details,
});
factory FileFailure.notFound(String filePath) {
return FileFailure(
message: 'Fichier non trouvé',
code: 'FILE_NOT_FOUND',
details: {'filePath': filePath},
);
}
factory FileFailure.accessDenied(String filePath) {
return FileFailure(
message: 'Accès au fichier refusé',
code: 'ACCESS_DENIED',
details: {'filePath': filePath},
);
}
factory FileFailure.invalidFormat(String expectedFormat) {
return FileFailure(
message: 'Format de fichier invalide',
code: 'INVALID_FORMAT',
details: {'expectedFormat': expectedFormat},
);
}
}
/// Échec générique pour les cas non spécifiés
class UnknownFailure extends Failure {
const UnknownFailure({
required super.message,
super.code,
super.details,
});
factory UnknownFailure.fromException(Exception exception) {
return UnknownFailure(
message: 'Erreur inattendue : ${exception.toString()}',
code: 'UNKNOWN_ERROR',
details: {'exception': exception.toString()},
);
}
}