Files
social-app/apps/lib/data/network/api_client.dart
T

150 lines
3.6 KiB
Dart
Raw Normal View History

import 'dart:convert';
2026-02-25 14:36:03 +08:00
import 'package:dio/dio.dart';
import 'api_exception.dart';
import 'api_interceptor.dart';
import 'i_api_client.dart';
2026-02-25 14:36:03 +08:00
import '../storage/token_storage.dart';
class ApiClient implements IApiClient {
2026-02-25 14:36:03 +08:00
final Dio _dio;
final TokenStorage _tokenStorage;
final ApiInterceptor _interceptor;
2026-02-25 14:36:03 +08:00
factory ApiClient({
2026-02-25 14:36:03 +08:00
required String baseUrl,
required TokenStorage tokenStorage,
Dio? dio,
}) {
final effectiveDio =
dio ??
Dio(
BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 20),
sendTimeout: const Duration(seconds: 20),
),
);
final interceptor = ApiInterceptor(
tokenStorage: tokenStorage,
dio: effectiveDio,
);
effectiveDio.interceptors.add(interceptor);
return ApiClient._(
dio: effectiveDio,
tokenStorage: tokenStorage,
interceptor: interceptor,
2026-02-25 14:36:03 +08:00
);
}
ApiClient._({
required Dio dio,
required TokenStorage tokenStorage,
required ApiInterceptor interceptor,
}) : _dio = dio,
_tokenStorage = tokenStorage,
_interceptor = interceptor;
2026-02-25 14:36:03 +08:00
Dio get dio => _dio;
void resetInterceptor() {
_interceptor.reset();
}
void setRefreshCallback(Future<bool> Function(String) refresh) {
_interceptor.onTokenRefresh = () async {
final token = await _tokenStorage.getRefreshToken();
if (token == null) return false;
return refresh(token);
};
2026-02-25 14:36:03 +08:00
}
void setAuthFailureCallback(Future<void> Function() onAuthFailure) {
_interceptor.onAuthFailure = onAuthFailure;
}
@override
2026-02-25 14:36:03 +08:00
Future<Response<T>> get<T>(String path, {Options? options}) async {
try {
return await _dio.get<T>(path, options: options);
} on DioException catch (e) {
2026-02-25 14:36:03 +08:00
throw ApiException.fromDioError(e);
}
}
@override
2026-02-25 14:36:03 +08:00
Future<Response<T>> post<T>(
String path, {
dynamic data,
Options? options,
}) async {
try {
return await _dio.post<T>(path, data: data, options: options);
} on DioException catch (e) {
2026-02-25 14:36:03 +08:00
throw ApiException.fromDioError(e);
}
}
@override
Future<Response<T>> patch<T>(
String path, {
dynamic data,
Options? options,
}) async {
try {
return await _dio.patch<T>(path, data: data, options: options);
} on DioException catch (e) {
throw ApiException.fromDioError(e);
}
}
@override
Future<Response<T>> put<T>(
String path, {
dynamic data,
Options? options,
}) async {
try {
return await _dio.put<T>(path, data: data, options: options);
} on DioException catch (e) {
throw ApiException.fromDioError(e);
}
}
@override
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Options? options,
}) async {
try {
return await _dio.delete<T>(path, data: data, options: options);
} on DioException catch (e) {
throw ApiException.fromDioError(e);
}
}
@override
Future<Stream<String>> getSseLines(
String path, {
Map<String, String>? headers,
}) async {
try {
final response = await _dio.get<ResponseBody>(
path,
options: Options(responseType: ResponseType.stream, headers: headers),
);
final responseBody = response.data;
if (responseBody == null) {
return const Stream<String>.empty();
}
return responseBody.stream
.cast<List<int>>()
.transform(utf8.decoder)
.transform(const LineSplitter());
} on DioException catch (e) {
throw ApiException.fromDioError(e);
}
}
2026-02-25 14:36:03 +08:00
}