Files
pfm-ocr/lib/core/network/api_client.dart
T
fhanyuh caf8e98378 chore: normalize line endings (CRLF -> LF)
No content changes: git diff --ignore-all-space over these files is empty.
The churn came from editing on Windows against a repo checked out with LF.
2026-08-27 10:40:49 +07:00

76 lines
2.6 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/app_config.dart';
import 'api_exception.dart';
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(
BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
// Upload triggers a synchronous OCR pass server-side that can take up to
// 210s worst case (see v1/documents/upload/route.ts's AbortSignal.timeout).
// Keep a safety margin above that so a legitimately slow-but-successful
// parse isn't killed client-side as a false "connection timeout".
receiveTimeout: const Duration(seconds: 240),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
_setupInterceptors();
}
void _setupInterceptors() {
// Request interceptor to append JWT token
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
));
// Logging interceptor for debugging
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
// Standardized Error Interceptor: wraps every failure into an ApiException
// (attached as DioException.error) so call sites read one consistent shape
// instead of reverse-engineering DioException.toString().
_dio.interceptors.add(InterceptorsWrapper(
onError: (DioException e, handler) {
final apiException = ApiException.fromDioException(e);
final customError = DioException(
requestOptions: e.requestOptions,
response: e.response,
type: e.type,
error: apiException,
);
return handler.next(customError);
},
));
// Dummy Response Interceptor for Development
// TODO: Remove this interceptor before production release
// _dio.interceptors.add(DummyResponseInterceptor());
}
Dio get client => _dio;
}
// Single shared instance so every screen/notifier reuses the same Dio HTTP client
// (connection keep-alive) instead of paying a fresh TCP/TLS handshake per call site.
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());