Files
pangolin/client/lib/services/auth_api.dart
T
wangjia 4dc4127252 feat(client): 三端布局架构 + macOS 桌面端 + app 图标
三端布局(mobile/tablet/desktop):
- core/responsive/form_factor.dart 形态判定 + shell/ 分发器(home_shell→desktop/mobile)
- desktop_shell 对照 ui_kits/desktop/dapp.jsx: 侧栏204·6项 + 套餐卡 + 顶栏(标题/状态/主题切换) + 连接页居中单列
- 新增组件 nav_sidebar / plan_badge_card / content_top_bar / bottom_tab_bar
- 新增一级页 contact_page / settings_page; navigation_provider(NavView)
- 删除旧 widgets/home_shell.dart(逻辑迁入 shell/)

macOS 桌面端:
- 窗口默认 920×600 + 最小 720×560(MainFlutterWindow.swift)
- app 图标替换为穿山甲(AppIcon.appiconset 全套, 由 app-icon.svg 渲染)

其余(本会话):
- Phase2 接线: auth_api/token_store/auth_provider/vpn_bridge_provider + 真实 connection/nodes
- lucide_icons 兼容补丁(packages/lucide_icons_patched) 修复 IconData final 报错
- 测试修复: connect_passthrough(UTF-8) / harness / golden @Skip
- l10n 新增 settingsTitle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:23:20 +08:00

136 lines
4.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// auth_api.dart — 控制面认证 HTTP 客户端
//
// 职责:封装 /v1/auth/* 接口调用。
// 关键约束:所有 HTTP 错误统一包装为 AuthApiException
// UI 层通过 e.statusCode / e.messageZh 显示错误文案。
import 'dart:convert';
import 'package:http/http.dart' as http;
/// 认证接口调用失败时抛出。
/// [statusCode] < 0 表示网络层错误。
class AuthApiException implements Exception {
const AuthApiException({
required this.statusCode,
required this.messageZh,
required this.messageEn,
});
final int statusCode;
final String messageZh;
final String messageEn;
@override
String toString() => 'AuthApiException($statusCode): $messageZh';
}
/// 登录 / 注册成功后返回的 JWT 令牌对。
class AuthTokens {
const AuthTokens({required this.accessToken, required this.refreshToken});
final String accessToken;
final String refreshToken;
factory AuthTokens.fromJson(Map<String, dynamic> m) => AuthTokens(
accessToken: m['access_token'] as String? ?? '',
refreshToken: m['refresh_token'] as String? ?? '',
);
}
/// [AuthApi] 封装 /v1/auth 接口族。
class AuthApi {
AuthApi({required this.baseUrl, http.Client? client})
: _client = client ?? http.Client();
final String baseUrl;
final http.Client _client;
// ── 发送验证码:POST /v1/auth/code ─────────────────────────────
/// 向 [email] 发送 6 位验证码。成功无返回,失败抛 [AuthApiException]。
Future<void> sendCode(String email) async {
final resp = await _post('/v1/auth/code', {'email': email});
if (resp.statusCode != 204 && resp.statusCode != 200) {
_throwFromResponse(resp);
}
}
// ── 注册:POST /v1/auth/register ───────────────────────────────
Future<AuthTokens> register({
required String email,
required String code,
required String password,
}) async {
final resp = await _post('/v1/auth/register', {
'email': email,
'code': code,
'password': password,
});
if (resp.statusCode != 200 && resp.statusCode != 201) {
_throwFromResponse(resp);
}
return AuthTokens.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
}
// ── 登录:POST /v1/auth/login ───────────────────────────────────
Future<AuthTokens> login({
required String email,
required String password,
}) async {
final resp = await _post('/v1/auth/login', {
'email': email,
'password': password,
});
if (resp.statusCode != 200) _throwFromResponse(resp);
return AuthTokens.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
}
// ── 刷新 tokenPOST /v1/auth/refresh ──────────────────────────
Future<AuthTokens> refresh(String refreshToken) async {
final resp = await _post('/v1/auth/refresh', {'refresh_token': refreshToken});
if (resp.statusCode != 200) _throwFromResponse(resp);
return AuthTokens.fromJson(jsonDecode(resp.body) as Map<String, dynamic>);
}
// ── 内部 ────────────────────────────────────────────────────────
Future<http.Response> _post(String path, Map<String, dynamic> body) async {
final uri = Uri.parse('$baseUrl$path');
try {
return await _client
.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
)
.timeout(const Duration(seconds: 15));
} on Exception catch (e) {
throw AuthApiException(
statusCode: -1,
messageZh: '网络请求失败,请检查连接后重试',
messageEn: 'Network error: $e',
);
}
}
void _throwFromResponse(http.Response resp) {
String messageZh = '操作失败 (HTTP ${resp.statusCode})';
String messageEn = 'Request failed (HTTP ${resp.statusCode})';
try {
final body = jsonDecode(resp.body) as Map<String, dynamic>;
messageZh = body['message_zh'] as String? ?? messageZh;
messageEn = body['message_en'] as String? ?? messageEn;
} catch (_) {}
throw AuthApiException(
statusCode: resp.statusCode,
messageZh: messageZh,
messageEn: messageEn,
);
}
void dispose() => _client.close();
}