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? _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(); 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 _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; } } }