/// API Service - Main HTTP client using Dio library; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import '../../../core/constants/api_constants.dart'; import '../../../presentation/providers/auth_provider.dart'; /// API Service provider final apiServiceProvider = Provider((ref) { final authState = ref.watch(authProvider); final token = authState?.accessToken; final options = BaseOptions( baseUrl: ApiConstants.baseUrl, connectTimeout: const Duration(milliseconds: ApiConstants.connectionTimeoutMs), receiveTimeout: const Duration(milliseconds: ApiConstants.receiveTimeoutMs), sendTimeout: const Duration(milliseconds: ApiConstants.sendTimeoutMs), headers: { 'Content-Type': 'application/json', if (token != null) 'Authorization': 'Bearer $token', }, ); final dio = Dio(options); // Add logger ONLY in debug mode to prevent exposing sensitive data in production if (kDebugMode) { dio.interceptors.add( PrettyDioLogger( requestHeader: true, requestBody: true, responseBody: true, responseHeader: false, error: true, compact: true, ), ); } // Add token refresh interceptor dio.interceptors.add( InterceptorsWrapper( onError: (error, handler) async { if (error.response?.statusCode == 401) { // Try to refresh token try { final newToken = await ref.read(authProvider.notifier).refreshToken(); if (newToken != null) { // Retry original request with new token final opts = RequestOptions( path: error.requestOptions.path, data: error.requestOptions.data, onReceiveProgress: error.requestOptions.onReceiveProgress, onSendProgress: error.requestOptions.onSendProgress, queryParameters: error.requestOptions.queryParameters, cancelToken: error.requestOptions.cancelToken, headers: { ...error.requestOptions.headers, 'Authorization': 'Bearer $newToken', }, extra: error.requestOptions.extra, method: error.requestOptions.method, responseType: error.requestOptions.responseType, validateStatus: error.requestOptions.validateStatus, ); final clonedReq = await dio.fetch(opts); return handler.resolve(clonedReq); } } on DioException catch (e) { // Log the specific error for debugging debugPrint('Token refresh failed: ${e.type} - ${e.message}'); // Notify user before logout // Note: In a real app, you'd want to show a snackbar or dialog here // For now, we just log the user out with a clear message debugPrint('Your session has expired. Please log in again.'); // Refresh failed, logout user await ref.read(authProvider.notifier).logout(); } catch (e) { // Log unexpected errors debugPrint('Unexpected error during token refresh: $e'); // Logout on any error await ref.read(authProvider.notifier).logout(); } } return handler.next(error); }, ), ); return dio; }); /// Get API client Dio getDio(Ref ref) { return ref.read(apiServiceProvider); }