Refactored error handling, configuration, and telemetry logic for improved readability, efficiency, and maintainability.
This commit is contained in:
parent
d8f91f42e5
commit
73597b9bca
|
|
@ -11,6 +11,40 @@ class AppConfig {
|
||||||
required this.keycloakRedirectUrl,
|
required this.keycloakRedirectUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory AppConfig.fromEnvironment() {
|
||||||
|
const apiBaseUrl = String.fromEnvironment('API_BASE_URL', defaultValue: '');
|
||||||
|
const useLocalAuth =
|
||||||
|
bool.fromEnvironment('USE_LOCAL_AUTH', defaultValue: false);
|
||||||
|
const localTenantId = String.fromEnvironment(
|
||||||
|
'LOCAL_TENANT_ID',
|
||||||
|
defaultValue: '11111111-1111-1111-1111-111111111111',
|
||||||
|
);
|
||||||
|
const localRoles =
|
||||||
|
String.fromEnvironment('LOCAL_ROLES', defaultValue: 'CAREGIVER');
|
||||||
|
const keycloakIssuer =
|
||||||
|
String.fromEnvironment('KEYCLOAK_ISSUER', defaultValue: '');
|
||||||
|
const keycloakIssuerUri =
|
||||||
|
String.fromEnvironment('KEYCLOAK_ISSUER_URI', defaultValue: '');
|
||||||
|
final resolvedIssuer =
|
||||||
|
keycloakIssuer.isNotEmpty ? keycloakIssuer : keycloakIssuerUri;
|
||||||
|
const keycloakClientId =
|
||||||
|
String.fromEnvironment('KEYCLOAK_CLIENT_ID', defaultValue: '');
|
||||||
|
const keycloakRedirectUrl = String.fromEnvironment(
|
||||||
|
'KEYCLOAK_REDIRECT_URL',
|
||||||
|
defaultValue: '',
|
||||||
|
);
|
||||||
|
|
||||||
|
return AppConfig(
|
||||||
|
apiBaseUrl: apiBaseUrl,
|
||||||
|
useLocalAuth: useLocalAuth,
|
||||||
|
localTenantId: localTenantId,
|
||||||
|
localRoles: localRoles,
|
||||||
|
keycloakIssuer: resolvedIssuer,
|
||||||
|
keycloakClientId: keycloakClientId,
|
||||||
|
keycloakRedirectUrl: keycloakRedirectUrl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final String apiBaseUrl;
|
final String apiBaseUrl;
|
||||||
final bool useLocalAuth;
|
final bool useLocalAuth;
|
||||||
final String localTenantId;
|
final String localTenantId;
|
||||||
|
|
|
||||||
|
|
@ -22,37 +22,7 @@ import '../features/telemetry/domain/token_provider.dart';
|
||||||
import '../features/telemetry/presentation/telemetry_controller.dart';
|
import '../features/telemetry/presentation/telemetry_controller.dart';
|
||||||
import '../features/telemetry/presentation/telemetry_state.dart';
|
import '../features/telemetry/presentation/telemetry_state.dart';
|
||||||
|
|
||||||
final appConfigProvider = Provider<AppConfig>((ref) {
|
final appConfigProvider = Provider<AppConfig>((ref) => AppConfig.fromEnvironment());
|
||||||
const apiBaseUrl = String.fromEnvironment('API_BASE_URL', defaultValue: '');
|
|
||||||
const useLocalAuth =
|
|
||||||
bool.fromEnvironment('USE_LOCAL_AUTH', defaultValue: false);
|
|
||||||
const localTenantId = String.fromEnvironment(
|
|
||||||
'LOCAL_TENANT_ID',
|
|
||||||
defaultValue: '11111111-1111-1111-1111-111111111111',
|
|
||||||
);
|
|
||||||
const localRoles =
|
|
||||||
String.fromEnvironment('LOCAL_ROLES', defaultValue: 'CAREGIVER');
|
|
||||||
const keycloakIssuer = String.fromEnvironment('KEYCLOAK_ISSUER', defaultValue: '');
|
|
||||||
const keycloakIssuerUri =
|
|
||||||
String.fromEnvironment('KEYCLOAK_ISSUER_URI', defaultValue: '');
|
|
||||||
final resolvedIssuer =
|
|
||||||
keycloakIssuer.isNotEmpty ? keycloakIssuer : keycloakIssuerUri;
|
|
||||||
const keycloakClientId =
|
|
||||||
String.fromEnvironment('KEYCLOAK_CLIENT_ID', defaultValue: '');
|
|
||||||
const keycloakRedirectUrl = String.fromEnvironment(
|
|
||||||
'KEYCLOAK_REDIRECT_URL',
|
|
||||||
defaultValue: '',
|
|
||||||
);
|
|
||||||
return AppConfig(
|
|
||||||
apiBaseUrl: apiBaseUrl,
|
|
||||||
useLocalAuth: useLocalAuth,
|
|
||||||
localTenantId: localTenantId,
|
|
||||||
localRoles: localRoles,
|
|
||||||
keycloakIssuer: resolvedIssuer,
|
|
||||||
keycloakClientId: keycloakClientId,
|
|
||||||
keycloakRedirectUrl: keycloakRedirectUrl,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
final secureStorageProvider = Provider<FlutterSecureStorage>((ref) {
|
final secureStorageProvider = Provider<FlutterSecureStorage>((ref) {
|
||||||
return const FlutterSecureStorage();
|
return const FlutterSecureStorage();
|
||||||
|
|
@ -85,9 +55,11 @@ final dioProvider = Provider<Dio>((ref) {
|
||||||
if (config.useLocalAuth) {
|
if (config.useLocalAuth) {
|
||||||
final session = await localDataSource.readLocalSession();
|
final session = await localDataSource.readLocalSession();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
options.headers['X-Local-Email'] = session.email;
|
options.headers.addAll({
|
||||||
options.headers['X-Local-Roles'] = session.roles;
|
'X-Local-Email': session.email,
|
||||||
options.headers['X-Tenant-Id'] = session.tenantId;
|
'X-Local-Roles': session.roles,
|
||||||
|
'X-Tenant-Id': session.tenantId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final token = await localDataSource.readToken();
|
final token = await localDataSource.readToken();
|
||||||
|
|
@ -98,21 +70,18 @@ final dioProvider = Provider<Dio>((ref) {
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
},
|
},
|
||||||
onError: (error, handler) async {
|
onError: (error, handler) async {
|
||||||
if (config.useLocalAuth) {
|
|
||||||
handler.next(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final response = error.response;
|
final response = error.response;
|
||||||
final requestOptions = error.requestOptions;
|
final requestOptions = error.requestOptions;
|
||||||
if (response?.statusCode != 401 || requestOptions.extra['retried'] == true) {
|
|
||||||
handler.next(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final refreshToken = (await localDataSource.readToken())?.refreshToken;
|
final refreshToken = (await localDataSource.readToken())?.refreshToken;
|
||||||
if (refreshToken == null || refreshToken.isEmpty) {
|
|
||||||
handler.next(error);
|
if (config.useLocalAuth ||
|
||||||
return;
|
response?.statusCode != 401 ||
|
||||||
|
requestOptions.extra['retried'] == true ||
|
||||||
|
refreshToken == null ||
|
||||||
|
refreshToken.isEmpty) {
|
||||||
|
return handler.next(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final newToken =
|
final newToken =
|
||||||
await authRemoteDataSource.refreshToken(refreshToken: refreshToken);
|
await authRemoteDataSource.refreshToken(refreshToken: refreshToken);
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,15 @@ class AuthLocalDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<LocalAuthSession?> readLocalSession() async {
|
Future<LocalAuthSession?> readLocalSession() async {
|
||||||
final email = await _storage.read(key: _localEmailKey);
|
final results = await Future.wait([
|
||||||
final roles = await _storage.read(key: _localRolesKey);
|
_storage.read(key: _localEmailKey),
|
||||||
final tenantId = await _storage.read(key: _localTenantKey);
|
_storage.read(key: _localRolesKey),
|
||||||
|
_storage.read(key: _localTenantKey),
|
||||||
|
]);
|
||||||
|
final email = results[0];
|
||||||
|
final roles = results[1];
|
||||||
|
final tenantId = results[2];
|
||||||
|
|
||||||
if (email == null || tenantId == null) {
|
if (email == null || tenantId == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -54,11 +60,13 @@ class AuthLocalDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
await _storage.delete(key: _accessTokenKey);
|
await Future.wait([
|
||||||
await _storage.delete(key: _refreshTokenKey);
|
_storage.delete(key: _accessTokenKey),
|
||||||
await _storage.delete(key: _localEmailKey);
|
_storage.delete(key: _refreshTokenKey),
|
||||||
await _storage.delete(key: _localRolesKey);
|
_storage.delete(key: _localEmailKey),
|
||||||
await _storage.delete(key: _localTenantKey);
|
_storage.delete(key: _localRolesKey),
|
||||||
|
_storage.delete(key: _localTenantKey),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,8 @@ class AuthController extends Notifier<AuthState> {
|
||||||
|
|
||||||
String _friendlyError(Object error) {
|
String _friendlyError(Object error) {
|
||||||
final message = error.toString();
|
final message = error.toString();
|
||||||
if (message.startsWith('Exception: ')) {
|
return message.startsWith('Exception: ')
|
||||||
return message.substring('Exception: '.length);
|
? message.substring('Exception: '.length)
|
||||||
}
|
: message;
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,9 @@ class HomePage extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends ConsumerState<HomePage> {
|
class _HomePageState extends ConsumerState<HomePage> {
|
||||||
late final ProviderSubscription<TelemetryState> _telemetrySubscription;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
Widget build(BuildContext context) {
|
||||||
super.initState();
|
ref.listen<TelemetryState>(telemetryControllerProvider, (previous, next) {
|
||||||
_telemetrySubscription =
|
|
||||||
ref.listenManual<TelemetryState>(telemetryControllerProvider, (previous, next) {
|
|
||||||
if (previous?.lastOutcome == next.lastOutcome ||
|
if (previous?.lastOutcome == next.lastOutcome ||
|
||||||
next.lastOutcome == null ||
|
next.lastOutcome == null ||
|
||||||
!mounted) {
|
!mounted) {
|
||||||
|
|
@ -37,16 +33,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_telemetrySubscription.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final telemetryState = ref.watch(telemetryControllerProvider);
|
final telemetryState = ref.watch(telemetryControllerProvider);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,14 @@ class TelemetryServiceImpl implements TelemetryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final payload = _payloadBuilder.build();
|
|
||||||
final response = await _remoteDataSource.sendTestTelemetry(
|
final response = await _remoteDataSource.sendTestTelemetry(
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
payload: payload,
|
payload: _payloadBuilder.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw _failureFromStatus(response.statusCode);
|
throw _failureFromStatus(response.statusCode);
|
||||||
|
}
|
||||||
} on HttpClientException catch (error) {
|
} on HttpClientException catch (error) {
|
||||||
throw _failureFromHttp(error);
|
throw _failureFromHttp(error);
|
||||||
} on TelemetryFailure {
|
} on TelemetryFailure {
|
||||||
|
|
@ -53,19 +50,16 @@ class TelemetryServiceImpl implements TelemetryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
TelemetryFailure _failureFromStatus(int statusCode) {
|
TelemetryFailure _failureFromStatus(int statusCode) {
|
||||||
if (statusCode == 401 || statusCode == 403) {
|
final isUnauthorized = statusCode == 401 || statusCode == 403;
|
||||||
return TelemetryFailure(
|
return TelemetryFailure(
|
||||||
'Not authorized',
|
isUnauthorized ? 'Not authorized' : 'Telemetry request failed',
|
||||||
type: TelemetryFailureType.unauthorized,
|
type: isUnauthorized
|
||||||
|
? TelemetryFailureType.unauthorized
|
||||||
|
: TelemetryFailureType.server,
|
||||||
statusCode: statusCode,
|
statusCode: statusCode,
|
||||||
debugMessage: 'Telemetry request unauthorized ($statusCode)',
|
debugMessage: isUnauthorized
|
||||||
);
|
? 'Telemetry request unauthorized ($statusCode)'
|
||||||
}
|
: 'Telemetry request failed with status $statusCode',
|
||||||
return TelemetryFailure(
|
|
||||||
'Telemetry request failed',
|
|
||||||
type: TelemetryFailureType.server,
|
|
||||||
statusCode: statusCode,
|
|
||||||
debugMessage: 'Telemetry request failed with status $statusCode',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue