feat : new injection logic

test : some file :)
chore : upgrade android gradle
This commit is contained in:
2025-08-19 11:22:34 +03:30
parent 9b04c0374b
commit 7c3c1280b2
47 changed files with 1139 additions and 377 deletions

View File

@@ -0,0 +1,168 @@
import 'dart:async';
import '../../core.dart';
/// Callback to refresh the authentication token.
/// Typically used to request a new token from the server.
typedef RefreshTokenCallback = Future<String?> Function();
/// Callback to save a new authentication token.
typedef SaveTokenCallback = Future<void> Function(String token);
/// Callback to clear the authentication token, e.g., on logout or failure.
typedef ClearTokenCallback = Future<void> Function();
/// Callback invoked when token refresh fails.
/// Typically used to redirect the user to login or show a logout message.
typedef OnRefreshFailedCallback = Future<void> Function();
/// Represents a queued request waiting for token refresh.
class QueuedRequest {
/// The original request options.
final RequestOptions options;
/// Completer used to complete the response once the request is retried.
final Completer<Response> completer;
/// Constructs a queued request.
QueuedRequest(this.options, this.completer);
}
/// An interceptor for automatic token management and refresh handling.
///
/// Features:
/// - Queues requests while a token refresh is in progress.
/// - Saves and clears tokens via provided callbacks.
/// - Calls [OnRefreshFailedCallback] if token refresh fails.
class AppInterceptorN extends Interceptor {
/// Callback to refresh the authentication token.
final RefreshTokenCallback? refreshTokenCallback;
/// Callback to save the new token.
final SaveTokenCallback saveTokenCallback;
/// Callback to clear the token.
final ClearTokenCallback clearTokenCallback;
/// Callback executed when token refresh fails.
final OnRefreshFailedCallback onRefreshFailed;
/// Optional additional arguments for authentication.
final dynamic authArguments;
/// The Dio instance used to send requests.
final Dio dio;
/// Maximum number of retry attempts for failed requests.
final int maxRetries;
/// Whether a token refresh is currently in progress.
bool _isRefreshing = false;
/// Queue of requests waiting for a new token.
final List<QueuedRequest> _queue = [];
/// Current token in use.
String? _currentToken;
/// Constructs the interceptor.
AppInterceptorN({
required this.dio,
required this.saveTokenCallback,
required this.clearTokenCallback,
required this.onRefreshFailed,
this.refreshTokenCallback,
this.authArguments,
this.maxRetries = 3,
});
/// Called before sending a request.
/// If a token refresh is in progress, the request is added to the queue.
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
if (_isRefreshing) {
final completer = Completer<Response>();
_queue.add(QueuedRequest(options, completer));
return handler.resolve(await completer.future);
}
handler.next(options);
}
/// Called when an error occurs during a request.
///
/// - If the error is a 401 (unauthorized) and retry count is below `maxRetries`,
/// the token is refreshed and queued requests are retried.
/// - If the token refresh fails, all queued requests are cancelled and
/// [onRefreshFailed] is executed.
@override
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
int currentRetry = err.requestOptions.extra['retryCount'] ?? 0;
if (err.response?.statusCode == 401 &&
err.type != DioExceptionType.cancel &&
currentRetry < maxRetries) {
final completer = Completer<Response>();
final updatedOptions = err.requestOptions.copyWith(
extra: {...err.requestOptions.extra, 'retryCount': currentRetry + 1},
);
_queue.add(QueuedRequest(updatedOptions, completer));
if (!_isRefreshing) {
_isRefreshing = true;
try {
final newToken = await refreshTokenCallback?.call();
if (newToken != null && newToken.isNotEmpty) {
_currentToken = newToken;
await saveTokenCallback(newToken);
for (var req in _queue) {
final newOptions = req.options.copyWith(
headers: {...req.options.headers, 'Authorization': 'Bearer $newToken'},
);
dio
.fetch(newOptions)
.then(req.completer.complete)
.catchError(req.completer.completeError);
}
} else {
await clearTokenCallback();
await _handleRefreshFailure();
for (var req in _queue) {
req.completer.completeError(
DioException(requestOptions: req.options, type: DioExceptionType.cancel),
);
}
}
} catch (e) {
await clearTokenCallback();
await _handleRefreshFailure();
for (var req in _queue) {
req.completer.completeError(e);
}
} finally {
_queue.clear();
_isRefreshing = false;
}
}
return handler.resolve(await completer.future);
}
handler.next(err);
}
/// Handles token refresh failure:
/// - Cancels all ongoing requests via [ApiHandler].
/// - Executes external [onRefreshFailed] callback.
Future<void> _handleRefreshFailure() async {
ApiHandler.cancelAllRequests("Token refresh failed");
await onRefreshFailed.call();
}
@visibleForTesting
set isRefreshingForTest(bool value) => _isRefreshing = value;
@visibleForTesting
List<QueuedRequest> get queue => _queue;
}