131 lines
4.2 KiB
Dart
131 lines
4.2 KiB
Dart
import 'dart:async';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../config/api_config.dart';
|
|
import '../providers/auth_provider.dart';
|
|
|
|
class TokenInterceptor extends Interceptor {
|
|
final AuthProvider authProvider;
|
|
final BuildContext context;
|
|
final Dio dio;
|
|
|
|
Completer<bool>? _refreshCompleter;
|
|
|
|
TokenInterceptor(this.authProvider, this.context, this.dio);
|
|
|
|
// 🔐 Attach token to every request
|
|
@override
|
|
void onRequest(
|
|
RequestOptions options, RequestInterceptorHandler handler) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final token = prefs.getString('token');
|
|
|
|
if (token != null && token.isNotEmpty) {
|
|
options.headers['Authorization'] = 'Bearer $token';
|
|
debugPrint('🔐🔐🔐🔐🔐🔐 [REQUEST] Token attached → ${options.uri}');
|
|
} else {
|
|
debugPrint('⚠️⚠️⚠️⚠️⚠️⚠️ [REQUEST] No token found → ${options.uri}');
|
|
}
|
|
|
|
handler.next(options);
|
|
}
|
|
|
|
// 🔄 Handle 401 & refresh token
|
|
@override
|
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
|
debugPrint(
|
|
'❌❌❌❌❌ [ERROR] ${err.response?.statusCode} on ${err.requestOptions.uri}');
|
|
|
|
if (err.response?.statusCode == 401) {
|
|
debugPrint('🔄🔄🔄🔄🔄🔄🔄 [AUTH] 401 detected, attempting refresh…');
|
|
|
|
// If refresh already running, wait
|
|
if (_refreshCompleter != null) {
|
|
debugPrint('⏳⏳⏳⏳⏳⏳⏳⏳⏳ [REFRESH] Waiting for ongoing refresh to complete…');
|
|
|
|
final success = await _refreshCompleter!.future;
|
|
|
|
if (success) {
|
|
debugPrint('✅✅✅✅✅✅✅✅ [REFRESH] Token refreshed, retrying request');
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
err.requestOptions.headers['Authorization'] =
|
|
'Bearer ${prefs.getString('token')}';
|
|
|
|
final response = await dio.fetch(err.requestOptions);
|
|
return handler.resolve(response);
|
|
} else {
|
|
debugPrint('❌❌❌❌❌❌ [REFRESH] Refresh failed while waiting');
|
|
}
|
|
}
|
|
|
|
_refreshCompleter = Completer<bool>();
|
|
|
|
debugPrint('🚀🚀🚀🚀🚀🚀🚀🚀 [REFRESH] Starting new refresh request');
|
|
|
|
final refreshed = await _refreshToken();
|
|
|
|
_refreshCompleter!.complete(refreshed);
|
|
_refreshCompleter = null;
|
|
|
|
if (refreshed) {
|
|
debugPrint('✅✅✅✅✅✅✅✅✅ [REFRESH] Refresh successful, retrying original request');
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
err.requestOptions.headers['Authorization'] =
|
|
'Bearer ${prefs.getString('token')}';
|
|
|
|
final response = await dio.fetch(err.requestOptions);
|
|
return handler.resolve(response);
|
|
}
|
|
|
|
debugPrint('🚪🚪🚪🚪🚪🚪 [AUTH] Refresh failed → logging out user');
|
|
await authProvider.logout(context);
|
|
//await authProvider.forceLogout(context);
|
|
|
|
}
|
|
|
|
handler.next(err);
|
|
}
|
|
|
|
// 🔁 Call refresh API using SEPARATE Dio
|
|
Future<bool> _refreshToken() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final oldToken = prefs.getString('token');
|
|
|
|
if (oldToken == null) {
|
|
debugPrint('❌❌❌❌❌❌❌ [REFRESH] No old token found');
|
|
return false;
|
|
}
|
|
|
|
debugPrint('📤📤📤📤📤 [REFRESH] Calling /auth/refresh');
|
|
|
|
final refreshDio = Dio(
|
|
BaseOptions(
|
|
baseUrl: ApiConfig.baseUrl,
|
|
headers: {
|
|
'Authorization': 'Bearer $oldToken',
|
|
'Accept': 'application/json',
|
|
},
|
|
),
|
|
);
|
|
|
|
final res = await refreshDio.post('/auth/refresh');
|
|
|
|
if (res.data['success'] == true && res.data['token'] != null) {
|
|
await prefs.setString('token', res.data['token']);
|
|
debugPrint('💾💾💾💾💾💾💾 [REFRESH] New token saved to SharedPreferences');
|
|
return true;
|
|
}
|
|
|
|
debugPrint('❌❌❌❌❌❌ [REFRESH] API responded but refresh not allowed');
|
|
return false;
|
|
} catch (e) {
|
|
debugPrint('🔥🔥🔥🔥🔥🔥 [REFRESH] Exception occurred: $e');
|
|
return false;
|
|
}
|
|
}
|
|
}
|