182 lines
5.0 KiB
Dart
182 lines
5.0 KiB
Dart
import 'package:awesome_dio_interceptor/awesome_dio_interceptor.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
|
import 'package:enaklo_pos/presentation/auth/login_page.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class DioClient {
|
|
static final Dio _dio = Dio(BaseOptions(
|
|
connectTimeout: const Duration(seconds: 10),
|
|
receiveTimeout: const Duration(seconds: 10),
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
},
|
|
))
|
|
..interceptors.add(AuthInterceptor())
|
|
..interceptors.add(
|
|
AwesomeDioInterceptor(
|
|
logRequestTimeout: true,
|
|
logRequestHeaders: true,
|
|
logResponseHeaders: true,
|
|
),
|
|
);
|
|
|
|
static Dio get instance => _dio;
|
|
}
|
|
|
|
class AuthInterceptor extends Interceptor {
|
|
static final GlobalKey<NavigatorState> navigatorKey =
|
|
GlobalKey<NavigatorState>();
|
|
|
|
@override
|
|
void onRequest(
|
|
RequestOptions options, RequestInterceptorHandler handler) async {
|
|
// Add token to request headers
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final token = prefs.getString('auth_token');
|
|
|
|
if (token != null) {
|
|
options.headers['Authorization'] = 'Bearer $token';
|
|
}
|
|
|
|
handler.next(options);
|
|
}
|
|
|
|
@override
|
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
|
handler.next(response);
|
|
}
|
|
|
|
@override
|
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
|
// Check if error is 401 (Unauthorized) - token expired
|
|
if (err.response?.statusCode == 401) {
|
|
await _handleTokenExpired();
|
|
}
|
|
|
|
handler.next(err);
|
|
}
|
|
|
|
Future<void> _handleTokenExpired() async {
|
|
// Clear stored token
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove('auth_token');
|
|
await prefs.remove('refresh_token');
|
|
await prefs.clear(); // Optional: clear all user data
|
|
|
|
// Navigate to login page
|
|
final context = navigatorKey.currentContext;
|
|
if (context != null) {
|
|
// Option 1: Navigate and remove all previous routes
|
|
context.pushReplacement(LoginPage());
|
|
|
|
// Option 2: If using GoRouter, uncomment below:
|
|
// GoRouter.of(context).go('/login');
|
|
|
|
// Option 3: If using custom routing, uncomment below:
|
|
// Navigator.of(context).pushAndRemoveUntil(
|
|
// MaterialPageRoute(builder: (context) => LoginPage()),
|
|
// (route) => false,
|
|
// );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Alternative: Dengan Refresh Token Logic
|
|
class AuthInterceptorWithRefresh extends Interceptor {
|
|
static final GlobalKey<NavigatorState> navigatorKey =
|
|
GlobalKey<NavigatorState>();
|
|
|
|
@override
|
|
void onRequest(
|
|
RequestOptions options, RequestInterceptorHandler handler) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final token = prefs.getString('auth_token');
|
|
|
|
if (token != null) {
|
|
options.headers['Authorization'] = 'Bearer $token';
|
|
}
|
|
|
|
handler.next(options);
|
|
}
|
|
|
|
@override
|
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
|
if (err.response?.statusCode == 401) {
|
|
// Try refresh token first
|
|
final success = await _tryRefreshToken();
|
|
|
|
if (success) {
|
|
// Retry the original request
|
|
final response = await _retryRequest(err.requestOptions);
|
|
handler.resolve(response);
|
|
return;
|
|
} else {
|
|
// Refresh failed, redirect to login
|
|
await _handleTokenExpired();
|
|
}
|
|
}
|
|
|
|
handler.next(err);
|
|
}
|
|
|
|
Future<bool> _tryRefreshToken() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final refreshToken = prefs.getString('refresh_token');
|
|
|
|
if (refreshToken == null) return false;
|
|
|
|
final response = await Dio().post(
|
|
'YOUR_REFRESH_TOKEN_ENDPOINT',
|
|
data: {'refresh_token': refreshToken},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final newToken = response.data['access_token'];
|
|
final newRefreshToken = response.data['refresh_token'];
|
|
|
|
await prefs.setString('auth_token', newToken);
|
|
await prefs.setString('refresh_token', newRefreshToken);
|
|
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
print('Refresh token failed: $e');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Future<Response> _retryRequest(RequestOptions options) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final token = prefs.getString('auth_token');
|
|
|
|
options.headers['Authorization'] = 'Bearer $token';
|
|
|
|
return await DioClient.instance.request(
|
|
options.path,
|
|
options: Options(
|
|
method: options.method,
|
|
headers: options.headers,
|
|
),
|
|
data: options.data,
|
|
queryParameters: options.queryParameters,
|
|
);
|
|
}
|
|
|
|
Future<void> _handleTokenExpired() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.clear();
|
|
|
|
final context = navigatorKey.currentContext;
|
|
if (context != null) {
|
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
'/login',
|
|
(route) => false,
|
|
);
|
|
}
|
|
}
|
|
}
|