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;
|
return res['success'] == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------- REFRESH TOKEN --------------------------
|
Future<void> forceLogout(BuildContext context) async {
|
||||||
Future<bool> tryRefreshToken(BuildContext context) async {
|
debugPrint('🚪🚪🚪 [AUTH] Force logout triggered');
|
||||||
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
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);
|
|
||||||
|
|
||||||
if (res['success'] == true && res['token'] != null) {
|
|
||||||
await prefs.setString('token', res['token']);
|
|
||||||
_token = res['token'];
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return true;
|
|
||||||
}
|
// Redirect to login & clear navigation stack
|
||||||
return false;
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
|
'/login',
|
||||||
|
(route) => false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
final auth = Provider.of<AuthProvider>(context, listen: false);
|
final auth = Provider.of<AuthProvider>(context, listen: false);
|
||||||
await auth.tryRefreshToken(context);
|
|
||||||
|
|
||||||
final dash = Provider.of<DashboardProvider>(context, listen: false);
|
final dash = Provider.of<DashboardProvider>(context, listen: false);
|
||||||
dash.init(context);
|
dash.init(context);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class AuthService {
|
|||||||
Future<Map<String, dynamic>> refreshToken(String oldToken) async {
|
Future<Map<String, dynamic>> refreshToken(String oldToken) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
'/user/refresh',
|
'/auth/refresh',
|
||||||
options: Options(headers: {
|
options: Options(headers: {
|
||||||
'Authorization': 'Bearer $oldToken',
|
'Authorization': 'Bearer $oldToken',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,37 +1,130 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import '../config/api_config.dart';
|
||||||
import '../providers/auth_provider.dart';
|
import '../providers/auth_provider.dart';
|
||||||
|
|
||||||
class TokenInterceptor extends Interceptor {
|
class TokenInterceptor extends Interceptor {
|
||||||
final AuthProvider auth;
|
final AuthProvider authProvider;
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final Dio dio;
|
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
|
@override
|
||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
void onRequest(
|
||||||
if (auth.token != null) {
|
RequestOptions options, RequestInterceptorHandler handler) async {
|
||||||
options.headers['Authorization'] = 'Bearer ${auth.token}';
|
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);
|
handler.next(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔄 Handle 401 & refresh token
|
||||||
@override
|
@override
|
||||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||||
if (err.response?.statusCode == 401 &&
|
debugPrint(
|
||||||
err.response?.data['message'] == 'Token has expired') {
|
'❌❌❌❌❌ [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) {
|
if (refreshed) {
|
||||||
err.requestOptions.headers['Authorization'] = 'Bearer ${auth.token}';
|
debugPrint('✅✅✅✅✅✅✅✅✅ [REFRESH] Refresh successful, retrying original request');
|
||||||
final newResponse = await dio.fetch(err.requestOptions);
|
|
||||||
return handler.resolve(newResponse);
|
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);
|
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