download option in invoide

This commit is contained in:
Abhishek Mali
2025-12-19 10:48:19 +05:30
parent d606156a6d
commit e85ac4bf8c
5 changed files with 124 additions and 28 deletions

File diff suppressed because one or more lines are too long

View File

@@ -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);
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,
);
}
}

View File

@@ -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);

View File

@@ -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',
}),

View File

@@ -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;
}
}
}