Files
jizhi/frontend/lib/shared/api/api_client.dart
T

104 lines
3.7 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:miaoji_zhang/shared/api/backend_identity.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
class ApiClient {
ApiClient._();
static final ApiClient instance = ApiClient._();
static const _storage = FlutterSecureStorage();
static const _legacyTokenKey = 'auth_token';
static final _tokenKey = 'auth_token_${BackendIdentity.scope}';
static final sessionExpired = ValueNotifier<int>(0);
static bool _handlingUnauthorized = false;
static const String baseUrl = BackendIdentity.baseUrl;
static bool get isInternalBuild => BackendIdentity.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);
},
),
);
return client;
}
Future<void> saveToken(String token) async {
await _storage.write(key: _tokenKey, value: token);
await _storage.delete(key: _legacyTokenKey);
}
Future<String?> readToken() => _storage.read(key: _tokenKey);
Future<void> clearToken() async {
await _storage.delete(key: _tokenKey);
await _storage.delete(key: _legacyTokenKey);
}
}
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 '出错了,请稍后再试';
}