154 lines
4.1 KiB
Dart
154 lines
4.1 KiB
Dart
import 'package:dio/dio.dart';
|
|
import '../models/api_error.dart';
|
|
import '../blocs/session/session_bloc.dart';
|
|
import '../blocs/session/session_event.dart';
|
|
import '../blocs/session/session_state.dart';
|
|
|
|
class ApiClient {
|
|
late final Dio _dio;
|
|
final SessionBloc _sessionBloc;
|
|
|
|
ApiClient(this._sessionBloc, {String baseUrl = 'https://go.b0esche.cloud'}) {
|
|
_dio = Dio(
|
|
BaseOptions(
|
|
baseUrl: baseUrl,
|
|
connectTimeout: const Duration(seconds: 10),
|
|
receiveTimeout: const Duration(seconds: 10),
|
|
),
|
|
);
|
|
|
|
_dio.interceptors.add(
|
|
InterceptorsWrapper(
|
|
onRequest: (options, handler) {
|
|
// Add JWT if available
|
|
final token = _getCurrentToken();
|
|
if (token != null) {
|
|
options.headers['Authorization'] = 'Bearer $token';
|
|
}
|
|
return handler.next(options);
|
|
},
|
|
onError: (error, handler) async {
|
|
if (error.response?.statusCode == 401) {
|
|
// Try refresh
|
|
final refreshSuccess = await _tryRefreshToken();
|
|
if (refreshSuccess) {
|
|
// Retry the request
|
|
final token = _getCurrentToken();
|
|
if (token != null) {
|
|
error.requestOptions.headers['Authorization'] = 'Bearer $token';
|
|
try {
|
|
final response = await _dio.fetch(error.requestOptions);
|
|
return handler.resolve(response);
|
|
} catch (e) {
|
|
// If retry fails, proceed to error
|
|
}
|
|
}
|
|
}
|
|
// If refresh failed, logout
|
|
_sessionBloc.add(SessionExpired());
|
|
}
|
|
return handler.next(error);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
String? _getCurrentToken() {
|
|
// Get from SessionBloc state
|
|
final state = _sessionBloc.state;
|
|
if (state is SessionActive) {
|
|
return state.token;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<bool> _tryRefreshToken() async {
|
|
try {
|
|
final response = await _dio.post('/auth/refresh');
|
|
if (response.statusCode == 200) {
|
|
final newToken = response.data['token'];
|
|
_sessionBloc.add(SessionRefreshed(newToken));
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
// Refresh failed
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Future<T> get<T>(
|
|
String path, {
|
|
Map<String, dynamic>? queryParameters,
|
|
required T Function(dynamic data) fromJson,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.get(path, queryParameters: queryParameters);
|
|
return fromJson(response.data);
|
|
} on DioException catch (e) {
|
|
throw _handleError(e);
|
|
}
|
|
}
|
|
|
|
Future<T> post<T>(
|
|
String path, {
|
|
dynamic data,
|
|
required T Function(dynamic data) fromJson,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post(path, data: data);
|
|
return fromJson(response.data);
|
|
} on DioException catch (e) {
|
|
throw _handleError(e);
|
|
}
|
|
}
|
|
|
|
Future<List<T>> getList<T>(
|
|
String path, {
|
|
Map<String, dynamic>? queryParameters,
|
|
required T Function(dynamic data) fromJson,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.get(path, queryParameters: queryParameters);
|
|
return (response.data as List).map(fromJson).toList();
|
|
} on DioException catch (e) {
|
|
throw _handleError(e);
|
|
}
|
|
}
|
|
|
|
ApiError _handleError(DioException e) {
|
|
final status = e.response?.statusCode;
|
|
final data = e.response?.data;
|
|
if (status == 403) {
|
|
return ApiError(
|
|
code: 'permission_denied',
|
|
message: 'Access denied',
|
|
status: status,
|
|
);
|
|
} else if (status == 404) {
|
|
return ApiError(
|
|
code: 'not_found',
|
|
message: 'Resource not found',
|
|
status: status,
|
|
);
|
|
} else if (status == 409) {
|
|
return ApiError(
|
|
code: 'conflict',
|
|
message: 'Version conflict',
|
|
status: status,
|
|
);
|
|
} else if (status == 401) {
|
|
return ApiError(
|
|
code: 'unauthorized',
|
|
message: 'Unauthorized',
|
|
status: status,
|
|
);
|
|
} else {
|
|
return ApiError(
|
|
code: 'server_error',
|
|
message: data?['message'] ?? 'Server error',
|
|
status: status,
|
|
);
|
|
}
|
|
}
|
|
}
|