Files
mobile/lib/core/api/api_client.dart

121 lines
3.5 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../config/app_config.dart';
import '../auth/auth_provider.dart';
/// Client HTTP Dio configuré pour l'API Laverie.
class ApiClient {
ApiClient({
required this.baseUrl,
this.getAccessToken,
this.onRefreshToken,
this.onSessionExpired,
}) : _dio = Dio(
BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
),
) {
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
final token = await getAccessToken?.call();
if (token != null && token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
},
onError: (error, handler) async {
final response = error.response;
final alreadyRetried = error.requestOptions.extra['auth_retried'] == true;
if (response?.statusCode == 401 && !alreadyRetried && onRefreshToken != null) {
final newToken = await onRefreshToken!();
if (newToken != null && newToken.isNotEmpty) {
final request = error.requestOptions;
request.extra['auth_retried'] = true;
request.headers['Authorization'] = 'Bearer $newToken';
try {
final retryResponse = await _dio.fetch(request);
return handler.resolve(retryResponse);
} on DioException catch (retryError) {
return handler.next(retryError);
}
}
await onSessionExpired?.call();
}
handler.next(error);
},
),
);
}
final String baseUrl;
final Future<String?> Function()? getAccessToken;
final Future<String?> Function()? onRefreshToken;
final Future<void> Function()? onSessionExpired;
final Dio _dio;
Dio get dio => _dio;
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
}) {
return _dio.get<T>(path, queryParameters: queryParameters);
}
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
}) {
return _dio.post<T>(path, data: data, queryParameters: queryParameters);
}
Future<Response<T>> patch<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
}) {
return _dio.patch<T>(path, data: data, queryParameters: queryParameters);
}
Future<Response<T>> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
}) {
return _dio.put<T>(path, data: data, queryParameters: queryParameters);
}
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
}) {
return _dio.delete<T>(path, data: data, queryParameters: queryParameters);
}
}
/// Fournisseur Riverpod du client API (injecte le token depuis l'état auth).
final apiClientProvider = Provider<ApiClient>((ref) {
final authNotifier = ref.read(authProvider.notifier);
return ApiClient(
baseUrl: kApiBaseUrl,
getAccessToken: () async => ref.read(authProvider).accessToken,
onRefreshToken: () => authNotifier.refreshAccessToken(),
onSessionExpired: () => authNotifier.sessionExpired(),
);
});