Initial project import
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:miaoji_zhang/shared/services/session_store.dart';
|
||||
|
||||
class ApiClient {
|
||||
ApiClient._();
|
||||
static final ApiClient instance = ApiClient._();
|
||||
|
||||
static const _storage = FlutterSecureStorage();
|
||||
static const _tokenKey = 'auth_token';
|
||||
static final sessionExpired = ValueNotifier<int>(0);
|
||||
static bool _handlingUnauthorized = false;
|
||||
static const _internalBuild = bool.fromEnvironment('INTERNAL_BUILD');
|
||||
static const _internalTestBaseUrl = 'https://lt.frp-say.com:38012';
|
||||
static const _configuredBaseUrl = String.fromEnvironment('API_BASE_URL');
|
||||
static const _temporaryFrpHost = 'lt.frp-say.com';
|
||||
static const _temporaryFrpPort = 38012;
|
||||
static const _temporaryFrpCertificateSha1 =
|
||||
'509C3210161E72FB9DA5183D3D2152F63871C31F';
|
||||
|
||||
static const String baseUrl = _configuredBaseUrl != ''
|
||||
? _configuredBaseUrl
|
||||
: _internalBuild
|
||||
? _internalTestBaseUrl
|
||||
: 'https://api.invalid';
|
||||
|
||||
static bool get isInternalBuild => _internalBuild;
|
||||
|
||||
late final Dio dio = _createDio();
|
||||
|
||||
Dio _createDio() {
|
||||
final client =
|
||||
Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 120),
|
||||
),
|
||||
)
|
||||
..interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
final token = await _storage.read(key: _tokenKey);
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
handler.next(options);
|
||||
},
|
||||
onError: (error, handler) async {
|
||||
final unauthorized = error.response?.statusCode == 401;
|
||||
final data = error.response?.data;
|
||||
final aiDenied =
|
||||
error.response?.statusCode == 403 &&
|
||||
data is Map &&
|
||||
data['code'] == 'AI_PERMISSION_DENIED';
|
||||
if (aiDenied) {
|
||||
await SessionStore.instance.setAiEnabled(false);
|
||||
}
|
||||
final isAuthRequest = error.requestOptions.path.startsWith(
|
||||
'/api/auth/',
|
||||
);
|
||||
if (unauthorized && !isAuthRequest && !_handlingUnauthorized) {
|
||||
_handlingUnauthorized = true;
|
||||
await SessionStore.instance.markNeedsReauth();
|
||||
sessionExpired.value++;
|
||||
Future<void>.delayed(
|
||||
const Duration(seconds: 1),
|
||||
() => _handlingUnauthorized = false,
|
||||
);
|
||||
}
|
||||
handler.next(error);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final apiUri = Uri.tryParse(baseUrl);
|
||||
if (_internalBuild &&
|
||||
apiUri?.scheme == 'https' &&
|
||||
apiUri?.host == _temporaryFrpHost &&
|
||||
apiUri?.port == _temporaryFrpPort) {
|
||||
client.httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final httpClient = HttpClient();
|
||||
httpClient.badCertificateCallback = (certificate, host, port) {
|
||||
final certificateSha1 = certificate.sha1
|
||||
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
||||
.join()
|
||||
.toUpperCase();
|
||||
return host == _temporaryFrpHost &&
|
||||
port == _temporaryFrpPort &&
|
||||
certificateSha1 == _temporaryFrpCertificateSha1;
|
||||
};
|
||||
return httpClient;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Future<void> saveToken(String token) =>
|
||||
_storage.write(key: _tokenKey, value: token);
|
||||
Future<String?> readToken() => _storage.read(key: _tokenKey);
|
||||
Future<void> clearToken() => _storage.delete(key: _tokenKey);
|
||||
}
|
||||
|
||||
bool isConnectivityError(Object error) =>
|
||||
error is DioException &&
|
||||
(error.type == DioExceptionType.connectionTimeout ||
|
||||
error.type == DioExceptionType.connectionError ||
|
||||
error.type == DioExceptionType.receiveTimeout ||
|
||||
error.type == DioExceptionType.sendTimeout);
|
||||
String apiErrorMessage(Object e) {
|
||||
if (e is OnlineFeatureRequiredException) return e.message;
|
||||
if (e is StateError) return e.message;
|
||||
if (e is DioException) {
|
||||
final data = e.response?.data;
|
||||
if (data is Map && data['message'] != null)
|
||||
return data['message'] as String;
|
||||
if (e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.connectionError ||
|
||||
e.type == DioExceptionType.receiveTimeout) {
|
||||
return '无法连接服务器,请检查网络';
|
||||
}
|
||||
}
|
||||
return '出错了,请稍后再试';
|
||||
}
|
||||
Reference in New Issue
Block a user