mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Break the 372-line frontend API client into focused files under lib/utils/api/: error mapping (api_error.dart), Dio setup behind a buildDio factory (dio_config.dart), and device/scanner/notification endpoints as extensions in part files. Collapse the five near-identical scanner-status methods via a generic _getModel helper and de-duplicate the pagination logic via a shared _paginate helper. The BackendAPI singleton and dioErrorToUserMessage remain importable from oott_api.dart unchanged, so no call sites are affected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
2.5 KiB
Dart
90 lines
2.5 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import '../backend_reachability.dart';
|
|
|
|
const _connectTimeout = Duration(seconds: 5);
|
|
const _receiveTimeout = Duration(seconds: 15);
|
|
const _sendTimeout = Duration(seconds: 15);
|
|
|
|
const _retryableStatuses = {408, 429, 500, 502, 503, 504};
|
|
|
|
/// Builds a [Dio] client configured for the OOTT backend: base options,
|
|
/// authentication header, retry/reachability interceptors and, in debug
|
|
/// builds, request logging.
|
|
Dio buildDio({
|
|
required String baseUrl,
|
|
required String apiKey,
|
|
bool withRetry = true,
|
|
bool withReachability = true,
|
|
}) {
|
|
final headers = <String, String>{
|
|
HttpHeaders.contentTypeHeader: 'application/json',
|
|
};
|
|
if (apiKey.isNotEmpty) {
|
|
headers[HttpHeaders.authorizationHeader] = 'Bearer $apiKey';
|
|
}
|
|
|
|
final dio = Dio(
|
|
BaseOptions(
|
|
baseUrl: baseUrl,
|
|
connectTimeout: _connectTimeout,
|
|
receiveTimeout: _receiveTimeout,
|
|
sendTimeout: _sendTimeout,
|
|
headers: headers,
|
|
),
|
|
);
|
|
|
|
if (withRetry) {
|
|
dio.interceptors.add(_buildRetryInterceptor(dio));
|
|
}
|
|
if (withReachability) {
|
|
dio.interceptors.add(_buildReachabilityInterceptor());
|
|
}
|
|
if (kDebugMode) {
|
|
dio.interceptors.add(_buildLogInterceptor());
|
|
}
|
|
return dio;
|
|
}
|
|
|
|
RetryInterceptor _buildRetryInterceptor(Dio dio) => RetryInterceptor(
|
|
dio: dio,
|
|
retries: 2,
|
|
retryDelays: const [Duration(seconds: 1), Duration(seconds: 3)],
|
|
logPrint: kDebugMode ? (msg) => debugPrint(msg.toString()) : null,
|
|
retryEvaluator: (error, attempt) {
|
|
if (error.requestOptions.method != 'GET') return false;
|
|
if (error.type == DioExceptionType.cancel) return false;
|
|
if (error.error is FormatException) return false;
|
|
if (error.type == DioExceptionType.badResponse) {
|
|
final status = error.response?.statusCode;
|
|
return status != null && _retryableStatuses.contains(status);
|
|
}
|
|
return true;
|
|
},
|
|
);
|
|
|
|
InterceptorsWrapper _buildReachabilityInterceptor() => InterceptorsWrapper(
|
|
onResponse: (response, handler) {
|
|
BackendReachability.instance.recordSuccess();
|
|
handler.next(response);
|
|
},
|
|
onError: (error, handler) {
|
|
BackendReachability.instance.recordFailure(error);
|
|
handler.next(error);
|
|
},
|
|
);
|
|
|
|
LogInterceptor _buildLogInterceptor() => LogInterceptor(
|
|
request: true,
|
|
requestHeader: false,
|
|
requestBody: false,
|
|
responseHeader: false,
|
|
responseBody: false,
|
|
error: true,
|
|
logPrint: (obj) => debugPrint(obj.toString()),
|
|
);
|