feat : change app inspector and exception handling

This commit is contained in:
2025-07-12 17:06:29 +03:30
parent e52674de37
commit 0fc16569a6
24 changed files with 472 additions and 322 deletions

View File

@@ -1,62 +1,101 @@
import 'dart:async';
import '../../core.dart';
typedef RefreshTokenCallback = Future<String?> Function();
typedef SaveTokenCallback = Future<void> Function(String token);
typedef ClearTokenCallback = Future<void> Function();
class AppInterceptor extends Interceptor {
final RefreshTokenCallback refreshTokenCallback;
Completer<String?>? _refreshCompleter;
final RefreshTokenCallback? refreshTokenCallback;
final SaveTokenCallback saveTokenCallback;
final ClearTokenCallback clearTokenCallback;
late final Dio dio;
static Completer<String?>? _refreshCompleter;
static bool _isRefreshing = false;
AppInterceptor({required this.refreshTokenCallback});
AppInterceptor({
required this.saveTokenCallback,
required this.clearTokenCallback,
this.refreshTokenCallback,
});
@override
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
if (_isRefreshing && _refreshCompleter != null) {
try {
final newToken = await _refreshCompleter!.future;
if (newToken != null && newToken.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $newToken';
}
} catch (_) {
handler.reject(DioException(requestOptions: options, type: DioExceptionType.cancel));
return;
}
}
handler.next(options);
}
@override
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401 && !ApiHandler.isRefreshing) {
ApiHandler.cancelAllRequests("Token expired - refreshing");
if (_refreshCompleter == null) {
_refreshCompleter = Completer<String?>();
ApiHandler.isRefreshing = true;
try {
final newToken = await refreshTokenCallback();
if (newToken == null) throw Exception("Refresh failed");
ApiHandler.reset();
_refreshCompleter?.complete(newToken);
} catch (e) {
_refreshCompleter?.completeError(e);
if (!ApiHandler.isRedirecting) {
ApiHandler.isRedirecting = true;
ApiHandler.cancelAllRequests("Cancel All Requests - Unauthorized");
if (Get.currentRoute != '/Auth') {
Get.offAllNamed('/Auth');
}
}
} finally {
ApiHandler.isRefreshing = false;
_refreshCompleter = null;
}
if (err.response?.statusCode == 401) {
final retryResult = await _handleUnauthorizedError(err);
if (retryResult != null) {
handler.resolve(retryResult);
return;
}
}
handler.next(err);
}
Future<Response?> _handleUnauthorizedError(DioException err) async {
if (_isRefreshing && _refreshCompleter != null) {
try {
final newToken = await _refreshCompleter!.future;
if (newToken != null) {
final opts = err.requestOptions;
opts.headers['Authorization'] = 'Bearer $newToken';
final dio = Dio();
final cloneReq = await dio.fetch(opts);
handler.resolve(cloneReq);
return;
}
return newToken != null ? await _retryRequest(err.requestOptions, newToken) : null;
} catch (_) {
handler.reject(err);
return null;
}
} else if (err.type == DioExceptionType.cancel) {
handler.next(err);
} else {
handler.next(err);
}
_isRefreshing = true;
_refreshCompleter = Completer<String?>();
try {
final newToken = await refreshTokenCallback!();
if (!_refreshCompleter!.isCompleted) _refreshCompleter!.complete(newToken);
if (newToken != null) {
await saveTokenCallback(newToken); // ✅ ذخیره توکن جدید
return await _retryRequest(err.requestOptions, newToken);
} else {
await clearTokenCallback(); // ✅ پاک‌کردن توکن‌های قبلی
return null;
}
} catch (e) {
if (!_refreshCompleter!.isCompleted) _refreshCompleter!.completeError(e);
await clearTokenCallback(); // ✅ پاک‌کردن توکن در صورت خطا
_handleRefreshFailure();
return null;
} finally {
_isRefreshing = false;
_refreshCompleter = null;
}
}
Future<Response> _retryRequest(RequestOptions options, String token) async {
final newOptions = options.copyWith();
newOptions.headers['Authorization'] = 'Bearer $token';
return dio.fetch(newOptions);
}
void _handleRefreshFailure() {
ApiHandler.cancelAllRequests("Token refresh failed");
Future.delayed(const Duration(milliseconds: 100), () {
if (Get.currentRoute != '/Auth') {
Get.offAllNamed('/Auth');
}
});
}
}

View File

@@ -1,45 +1,45 @@
import 'package:flutter/foundation.dart';
import 'package:rasadyar_core/core.dart';
class DioRemote implements IHttpClient {
String? baseUrl;
late final Dio _dio;
final List<Interceptor> interceptors;
late Dio dio;
final AppInterceptor interceptors;
DioRemote({this.baseUrl, this.interceptors = const []});
DioRemote({this.baseUrl, required this.interceptors});
@override
Future<void> init() async {
final dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
dio.interceptors.addAll(interceptors);
dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
dio.interceptors.add(interceptors);
if (kDebugMode) {
dio.interceptors.add(
PrettyDioLogger(
requestHeader: true,
responseHeader: true,
requestBody: true,
responseBody: true,
),
);
}
_dio = dio;
}
@override
Future<DioResponse<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
ProgressCallback? onReceiveProgress,
T Function(Map<String, dynamic> json)? fromJson,
T Function(List<dynamic> json)? fromJsonList,
}) async {
final response = await _dio.get(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
ProgressCallback? onReceiveProgress,
T Function(Map<String, dynamic> json)? fromJson,
T Function(List<dynamic> json)? fromJsonList,
}) async {
final response = await dio.get(
path,
queryParameters: queryParameters,
options: Options(headers: headers),
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
if (fromJsonList != null && response.data is List) {
response.data = fromJsonList(response.data);
@@ -62,21 +62,19 @@ class DioRemote implements IHttpClient {
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.post(
final response = await dio.post(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
if (fromJson != null) {
final rawData = response.data;
final parsedData = rawData is Map<String, dynamic>
? fromJson(rawData)
: null;
final parsedData = rawData is Map<String, dynamic> ? fromJson(rawData) : null;
response.data = parsedData;
return DioResponse<T>(response);
}
@@ -93,14 +91,14 @@ class DioRemote implements IHttpClient {
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.put(
final response = await dio.put(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse<T>(response);
}
@@ -112,12 +110,12 @@ class DioRemote implements IHttpClient {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.delete<T>(
final response = await dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse<T>(response);
}
@@ -127,11 +125,11 @@ class DioRemote implements IHttpClient {
String url, {
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.get<Uint8List>(
final response = await dio.get<Uint8List>(
url,
options: Options(responseType: ResponseType.bytes),
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse(response);
}
@@ -143,12 +141,12 @@ class DioRemote implements IHttpClient {
Map<String, String>? headers,
ProgressCallback? onSendProgress,
}) async {
final response = await _dio.post(
final response = await dio.post(
path,
data: (formData as DioFormData).raw,
options: Options(headers: headers, contentType: 'multipart/form-data'),
onSendProgress: onSendProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse<T>(response);
}