api intigreted of scan Qr and recent scan histoy ,scan history,scan history details
This commit is contained in:
@@ -8,11 +8,14 @@ class ApiService {
|
||||
static const String _baseUrl = 'https://your-api-base-url.com/api';
|
||||
|
||||
static final ApiService _instance = ApiService._internal();
|
||||
|
||||
late Dio _dio;
|
||||
late Dio _tokenDio; // ✅ Separate Dio for token refresh (no interceptors)
|
||||
|
||||
factory ApiService() => _instance;
|
||||
|
||||
ApiService._internal() {
|
||||
// ================= MAIN DIO =================
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: _baseUrl,
|
||||
@@ -25,7 +28,21 @@ class ApiService {
|
||||
),
|
||||
);
|
||||
|
||||
// ================= RETRY INTERCEPTOR =================
|
||||
// ================= TOKEN DIO (No interceptors — used only for refresh) =================
|
||||
_tokenDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: _baseUrl,
|
||||
connectTimeout: const Duration(seconds: 30),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// ================= 1. RETRY INTERCEPTOR =================
|
||||
// ✅ Added FIRST so it only retries network errors
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onError: (err, handler) async {
|
||||
@@ -42,7 +59,7 @@ class ApiService {
|
||||
if (shouldRetry) {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'🔁 Retrying request (${currentRetry + 1}) => ${options.uri}',
|
||||
'🔁 Retrying request (${currentRetry + 1}/$maxRetries) => ${options.uri}',
|
||||
);
|
||||
}
|
||||
options.extra['retry'] = currentRetry + 1;
|
||||
@@ -54,12 +71,13 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
return handler.reject(err);
|
||||
return handler.next(err);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// ================= MAIN INTERCEPTOR (Queued for concurrency) =================
|
||||
// ================= 2. MAIN INTERCEPTOR (Auth + Token Refresh) =================
|
||||
// ✅ Added SECOND — handles auth and token refresh
|
||||
_dio.interceptors.add(
|
||||
QueuedInterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
@@ -70,6 +88,10 @@ class ApiService {
|
||||
handler.next(options);
|
||||
},
|
||||
|
||||
onResponse: (response, handler) {
|
||||
handler.next(response);
|
||||
},
|
||||
|
||||
onError: (error, handler) async {
|
||||
if (error.response?.statusCode == 401) {
|
||||
final requestOptions = error.requestOptions;
|
||||
@@ -80,13 +102,18 @@ class ApiService {
|
||||
if (refreshed) {
|
||||
final newToken = await LocalPreference.getAccessToken();
|
||||
requestOptions.headers['Authorization'] = 'Bearer $newToken';
|
||||
|
||||
// ✅ Retry original request with new token
|
||||
final response = await _dio.fetch(requestOptions);
|
||||
return handler.resolve(response);
|
||||
} else {
|
||||
await _forceLogout();
|
||||
return handler.reject(error);
|
||||
}
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ Error during token refresh flow: $e');
|
||||
}
|
||||
await _forceLogout();
|
||||
return handler.reject(error);
|
||||
}
|
||||
@@ -97,7 +124,8 @@ class ApiService {
|
||||
),
|
||||
);
|
||||
|
||||
// ================= LOGGING INTERCEPTOR =================
|
||||
// ================= 3. LOGGING INTERCEPTOR =================
|
||||
// ✅ Added LAST so it captures the final state of all requests/responses
|
||||
if (kDebugMode) {
|
||||
_dio.interceptors.add(
|
||||
LogInterceptor(
|
||||
@@ -105,7 +133,9 @@ class ApiService {
|
||||
requestHeader: true,
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
responseHeader: false,
|
||||
error: true,
|
||||
logPrint: (log) => print('📡 $log'),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -198,26 +228,67 @@ class ApiService {
|
||||
}
|
||||
|
||||
// ================= REFRESH TOKEN =================
|
||||
// ✅ Uses _tokenDio (no interceptors) to avoid QueuedInterceptor deadlock
|
||||
Future<bool> _refreshToken() async {
|
||||
try {
|
||||
final refreshToken = await LocalPreference.getRefreshToken();
|
||||
if (refreshToken == null) return false;
|
||||
|
||||
final response = await _dio.post(
|
||||
if (kDebugMode) print('🔍 Refresh token from storage: $refreshToken');
|
||||
|
||||
if (refreshToken == null || refreshToken.isEmpty) {
|
||||
if (kDebugMode) print('❌ No refresh token found');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kDebugMode) print('🔄 Attempting token refresh...');
|
||||
|
||||
final response = await _tokenDio.post(
|
||||
ApiUrls.refreshToken,
|
||||
data: {"refreshToken": refreshToken},
|
||||
options: Options(headers: {'Authorization': null}),
|
||||
data: '', // ✅ Empty body — server reads token from Cookie header
|
||||
options: Options(
|
||||
headers: {
|
||||
// ✅ Manually inject refresh token as cookie header
|
||||
'Cookie': 'partner_refresh_token=$refreshToken',
|
||||
},
|
||||
validateStatus: (status) => status != null && status < 500,
|
||||
),
|
||||
);
|
||||
|
||||
await LocalPreference.setAccessToken(response.data['accessToken']);
|
||||
if (kDebugMode) {
|
||||
print("🔄 Refresh response status: ${response.statusCode}");
|
||||
print("✅ REFRESH RESPONSE => ${response.data}");
|
||||
}
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
if (kDebugMode) {
|
||||
print('❌ Refresh failed with status: ${response.statusCode}');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
final newAccessToken = response.data['accessToken'];
|
||||
|
||||
if (newAccessToken == null || (newAccessToken as String).isEmpty) {
|
||||
if (kDebugMode) print('❌ Access token missing in refresh response');
|
||||
return false;
|
||||
}
|
||||
|
||||
await LocalPreference.setAccessToken(newAccessToken);
|
||||
if (kDebugMode) print('✅ Token refreshed successfully');
|
||||
return true;
|
||||
} catch (_) {
|
||||
|
||||
} on DioException catch (e) {
|
||||
if (kDebugMode) print('❌ Refresh token DioException: ${e.message}');
|
||||
return false;
|
||||
} catch (e) {
|
||||
if (kDebugMode) print('❌ Refresh token unexpected error: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ================= FORCE LOGOUT =================
|
||||
Future<void> _forceLogout() async {
|
||||
if (kDebugMode) print('🚪 Force logout triggered');
|
||||
await LocalPreference.clearAll();
|
||||
await LocalPreference.setLogin(false);
|
||||
}
|
||||
@@ -241,10 +312,8 @@ class ApiService {
|
||||
responseData['error'] ??
|
||||
"Invalid status code: ${error.response?.statusCode}";
|
||||
}
|
||||
if (responseData is String) {
|
||||
return responseData.isNotEmpty
|
||||
? responseData
|
||||
: "Invalid status code: ${error.response?.statusCode}";
|
||||
if (responseData is String && responseData.isNotEmpty) {
|
||||
return responseData;
|
||||
}
|
||||
return "Invalid status code: ${error.response?.statusCode}";
|
||||
} catch (_) {
|
||||
|
||||
Reference in New Issue
Block a user