Files
swift_companion/swiftyCompanion/swifty_companion/lib/services/intra_http.dart
2026-10-21 08:54:03 +02:00

76 lines
2.0 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'intra_api_exception.dart';
import 'intra_auth_service.dart';
/// GET API 42 avec gestion 401 (refresh token), 429 (retry + backoff).
Future<http.Response> intraApiGet(
Uri uri, {
required IntraAuthService auth,
int maxAttempts = 4,
}) async {
var retriedAfterRefresh = false;
for (var attempt = 0; attempt < maxAttempts; attempt++) {
final response = await http.get(uri, headers: auth.authHeaders());
if (response.statusCode == 401) {
if (!retriedAfterRefresh && await auth.refreshAccessToken()) {
retriedAfterRefresh = true;
continue;
}
throw const IntraUnauthorizedException();
}
if (response.statusCode == 429) {
if (attempt + 1 >= maxAttempts) {
throw const IntraRateLimitException();
}
await Future<void>.delayed(_retryDelay(response, attempt));
continue;
}
return response;
}
throw const IntraRateLimitException();
}
Duration _retryDelay(http.Response response, int attempt) {
final header = response.headers['retry-after'];
if (header != null) {
final seconds = int.tryParse(header.trim());
if (seconds != null && seconds > 0) {
return Duration(seconds: seconds.clamp(1, 30));
}
}
final ms = 800 * (1 << attempt);
return Duration(milliseconds: ms.clamp(800, 8000));
}
/// Lance une [IntraApiException] si le code HTTP n'est pas 200.
void ensureIntraOk(http.Response response) {
if (response.statusCode == 401) {
throw const IntraUnauthorizedException();
}
if (response.statusCode == 403) {
throw const IntraForbiddenException();
}
if (response.statusCode == 429) {
throw const IntraRateLimitException();
}
if (response.statusCode != 200) {
throw IntraApiErrorException(
'Erreur API (${response.statusCode})',
);
}
}
/// Décode le corps JSON après contrôle du statut HTTP.
dynamic decodeIntraBody(http.Response response) {
ensureIntraOk(response);
return jsonDecode(response.body);
}