download option in invoide
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -125,23 +125,27 @@ class AuthProvider extends ChangeNotifier {
|
||||
return res['success'] == true;
|
||||
}
|
||||
|
||||
// --------------------- REFRESH TOKEN --------------------------
|
||||
Future<bool> tryRefreshToken(BuildContext context) async {
|
||||
Future<void> forceLogout(BuildContext context) async {
|
||||
debugPrint('🚪🚪🚪 [AUTH] Force logout triggered');
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final oldToken = prefs.getString('token');
|
||||
|
||||
if (oldToken == null) return false;
|
||||
await prefs.remove('token');
|
||||
await prefs.remove('user');
|
||||
await prefs.remove('saved_login_id');
|
||||
await prefs.remove('saved_password');
|
||||
|
||||
_service ??= AuthService(context);
|
||||
_token = null;
|
||||
_user = null;
|
||||
|
||||
final res = await _service!.refreshToken(oldToken);
|
||||
notifyListeners();
|
||||
|
||||
if (res['success'] == true && res['token'] != null) {
|
||||
await prefs.setString('token', res['token']);
|
||||
_token = res['token'];
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// Redirect to login & clear navigation stack
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
'/login',
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final auth = Provider.of<AuthProvider>(context, listen: false);
|
||||
await auth.tryRefreshToken(context);
|
||||
|
||||
final dash = Provider.of<DashboardProvider>(context, listen: false);
|
||||
dash.init(context);
|
||||
|
||||
@@ -46,7 +46,7 @@ class AuthService {
|
||||
Future<Map<String, dynamic>> refreshToken(String oldToken) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/user/refresh',
|
||||
'/auth/refresh',
|
||||
options: Options(headers: {
|
||||
'Authorization': 'Bearer $oldToken',
|
||||
}),
|
||||
|
||||
@@ -1,37 +1,130 @@
|
||||
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 auth;
|
||||
final AuthProvider authProvider;
|
||||
final BuildContext context;
|
||||
final Dio dio;
|
||||
|
||||
TokenInterceptor(this.auth, this.context, this.dio);
|
||||
Completer<bool>? _refreshCompleter;
|
||||
|
||||
TokenInterceptor(this.authProvider, this.context, this.dio);
|
||||
|
||||
// 🔐 Attach token to every request
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (auth.token != null) {
|
||||
options.headers['Authorization'] = 'Bearer ${auth.token}';
|
||||
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 {
|
||||
if (err.response?.statusCode == 401 &&
|
||||
err.response?.data['message'] == 'Token has expired') {
|
||||
debugPrint(
|
||||
'❌❌❌❌❌ [ERROR] ${err.response?.statusCode} on ${err.requestOptions.uri}');
|
||||
|
||||
final refreshed = await auth.tryRefreshToken(context);
|
||||
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) {
|
||||
err.requestOptions.headers['Authorization'] = 'Bearer ${auth.token}';
|
||||
final newResponse = await dio.fetch(err.requestOptions);
|
||||
return handler.resolve(newResponse);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user