diff --git a/lib/fa_flutter_api_client.dart b/lib/fa_flutter_api_client.dart index 3ed0cd1..98c4a48 100644 --- a/lib/fa_flutter_api_client.dart +++ b/lib/fa_flutter_api_client.dart @@ -10,3 +10,4 @@ export 'src/interceptors/network_interceptor.dart'; export 'src/api_options/api_options.dart'; export 'src/interceptors/cache_interceptor.dart'; export 'src/interceptors/cancel_token_interceptor.dart'; +export 'src/interceptors/refresh_token_interceptor.dart'; diff --git a/lib/src/api_service_impl.dart b/lib/src/api_service_impl.dart index 6da9dc2..a7909d9 100644 --- a/lib/src/api_service_impl.dart +++ b/lib/src/api_service_impl.dart @@ -1,14 +1,13 @@ import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:fa_flutter_api_client/src/api_options/api_options.dart'; +import 'package:fa_flutter_api_client/fa_flutter_api_client.dart'; +import 'package:fa_flutter_api_client/src/implementations/refresh_token_logging_interceptor_impl.dart'; import 'package:fa_flutter_api_client/src/utils/constants.dart'; import 'package:fa_flutter_core/fa_flutter_core.dart' hide ProgressCallback; import 'package:http_parser/http_parser.dart'; import 'package:path/path.dart'; -import 'base/api_service.dart'; - class ApiServiceImpl implements ApiService { ApiServiceImpl({ required this.baseUrl, @@ -30,11 +29,20 @@ class ApiServiceImpl implements ApiService { if (interceptors != null && interceptors!.isNotEmpty) { _dioFile!.interceptors.addAll(interceptors!); } + + _refreshTokenDio = Dio(); + if (interceptors != null && interceptors!.isNotEmpty && isDebug) { + _refreshTokenDio!.interceptors.add( + RefreshTokenLoggingInterceptorImpl(), + ); + } } String baseUrl; Dio? _dio; + Dio? _refreshTokenDio; + Dio? _dioFile; final List? interceptors; @@ -59,7 +67,20 @@ class ApiServiceImpl implements ApiService { String? url, String? body, ApiOptions? options, + bool isRefreshTokenRequest = false, }) async { + if (isRefreshTokenRequest) { + return _refreshTokenDio!.post( + url ?? '$baseUrl$endpoint', + data: body, + options: Options( + headers: _formatHeaders(options), + receiveTimeout: options?.receiveTimeout, + sendTimeout: options?.sendTimeout, + ), + ); + } + return _dio!.post(url ?? '$baseUrl$endpoint', data: body, options: Options( @@ -153,6 +174,15 @@ class ApiServiceImpl implements ApiService { return _dio; } + @override + Dio getRefreshTokenApiClient() { + if (_refreshTokenDio == null) { + _refreshTokenDio = Dio(); + } + + return _refreshTokenDio!; + } + @override String getIsAuthRequiredKey() => Constants.isAuthRequiredAPIKey; diff --git a/lib/src/base/api_service.dart b/lib/src/base/api_service.dart index 2eae867..8356540 100644 --- a/lib/src/base/api_service.dart +++ b/lib/src/base/api_service.dart @@ -17,6 +17,7 @@ abstract class ApiService { String? endpoint, String? body, ApiOptions? options, + bool isRefreshTokenRequest = false, }); Future> put({ @@ -47,6 +48,8 @@ abstract class ApiService { Dio? getApiClient(); + Dio? getRefreshTokenApiClient(); + /// get key for disabling auth for some requests. String getIsAuthRequiredKey(); } diff --git a/lib/src/implementations/refresh_token_logging_interceptor_impl.dart b/lib/src/implementations/refresh_token_logging_interceptor_impl.dart new file mode 100644 index 0000000..3617a10 --- /dev/null +++ b/lib/src/implementations/refresh_token_logging_interceptor_impl.dart @@ -0,0 +1,12 @@ +import 'dart:developer'; + +import 'package:fa_flutter_api_client/fa_flutter_api_client.dart'; + +/// This is only for refresh token logging +/// Do not use it for other purposes +class RefreshTokenLoggingInterceptorImpl extends LoggingInterceptor { + @override + void logPrint(String msg) { + log(msg); + } +} diff --git a/lib/src/interceptors/error_interceptor.dart b/lib/src/interceptors/error_interceptor.dart index bb6ac6b..b5a0fd2 100644 --- a/lib/src/interceptors/error_interceptor.dart +++ b/lib/src/interceptors/error_interceptor.dart @@ -40,17 +40,17 @@ abstract class ErrorInterceptor extends Interceptor { // () { // }, // ); - if (!isLoginApi) { - handleUnauthenticatedUser(unauthenticatedError); - } - return handler.reject( - UnauthenticatedError( - requestOptions: error.requestOptions, - response: error.response, - type: error.type, - error: error.error, - ), - ); + if (!isLoginApi) { + await handleUnauthenticatedUser(unauthenticatedError); + } + return handler.reject( + UnauthenticatedError( + requestOptions: error.requestOptions, + response: error.response, + type: error.error, + error: error.error, + ), + ); // return null; } else if (code == 403) { return handler.reject( diff --git a/lib/src/interceptors/refresh_token_interceptor.dart b/lib/src/interceptors/refresh_token_interceptor.dart new file mode 100644 index 0000000..382261a --- /dev/null +++ b/lib/src/interceptors/refresh_token_interceptor.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; + +abstract class RefreshTokenInterceptor extends QueuedInterceptorsWrapper { + bool isRefreshingSession = false; + + @override + FutureOr onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + try { + var updatedOptions = options; + if (isExpired(options)) { + updatedOptions = await refreshToken(options); + } + return handler.next(updatedOptions); + } catch (e) { + handler.next(options); + } + } + + /// It will be called when [isExpired] is true + /// Return the updated [RequestOptions] with new token + FutureOr refreshToken(RequestOptions options); + + bool isExpired(RequestOptions options); +}