86 lines
2.6 KiB
Dart
86 lines
2.6 KiB
Dart
|
|
import 'package:dio/dio.dart';
|
||
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||
|
|
|
||
|
|
class ApiClient {
|
||
|
|
late final Dio dio;
|
||
|
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||
|
|
VoidCallback? onForceLogout;
|
||
|
|
|
||
|
|
ApiClient() {
|
||
|
|
dio = Dio(BaseOptions(
|
||
|
|
connectTimeout: const Duration(seconds: 15),
|
||
|
|
receiveTimeout: const Duration(seconds: 30),
|
||
|
|
));
|
||
|
|
|
||
|
|
dio.interceptors.add(InterceptorsWrapper(
|
||
|
|
onRequest: (options, handler) async {
|
||
|
|
final token = await _storage.read(key: 'access_token');
|
||
|
|
if (token != null) {
|
||
|
|
options.headers['Authorization'] = 'Bearer $token';
|
||
|
|
}
|
||
|
|
handler.next(options);
|
||
|
|
},
|
||
|
|
onError: (error, handler) async {
|
||
|
|
if (error.response?.statusCode == 401) {
|
||
|
|
final refreshed = await _tryRefreshToken();
|
||
|
|
if (refreshed) {
|
||
|
|
final opts = error.requestOptions;
|
||
|
|
final token = await _storage.read(key: 'access_token');
|
||
|
|
opts.headers['Authorization'] = 'Bearer $token';
|
||
|
|
try {
|
||
|
|
final response = await dio.fetch(opts);
|
||
|
|
return handler.resolve(response);
|
||
|
|
} catch (e) {
|
||
|
|
return handler.next(error);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
onForceLogout?.call();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
handler.next(error);
|
||
|
|
},
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> setBaseUrl(String serverUrl) async {
|
||
|
|
final url = serverUrl.endsWith('/')
|
||
|
|
? serverUrl.substring(0, serverUrl.length - 1)
|
||
|
|
: serverUrl;
|
||
|
|
dio.options.baseUrl = '$url/wp-json/twilio-mobile/v1';
|
||
|
|
await _storage.write(key: 'server_url', value: url);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> restoreBaseUrl() async {
|
||
|
|
final url = await _storage.read(key: 'server_url');
|
||
|
|
if (url != null) {
|
||
|
|
dio.options.baseUrl = '$url/wp-json/twilio-mobile/v1';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<bool> _tryRefreshToken() async {
|
||
|
|
try {
|
||
|
|
final refreshToken = await _storage.read(key: 'refresh_token');
|
||
|
|
if (refreshToken == null) return false;
|
||
|
|
|
||
|
|
final response = await dio.post(
|
||
|
|
'/auth/refresh',
|
||
|
|
data: {'refresh_token': refreshToken},
|
||
|
|
options: Options(headers: {'Authorization': ''}),
|
||
|
|
);
|
||
|
|
|
||
|
|
if (response.statusCode == 200 && response.data['success'] == true) {
|
||
|
|
await _storage.write(
|
||
|
|
key: 'access_token', value: response.data['access_token']);
|
||
|
|
if (response.data['refresh_token'] != null) {
|
||
|
|
await _storage.write(
|
||
|
|
key: 'refresh_token', value: response.data['refresh_token']);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
} catch (_) {}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
typedef VoidCallback = void Function();
|