client_framework/lib/src/services/auth_service.dart
2026-04-22 11:13:54 +08:00

325 lines
10 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.

import 'dart:async';
import 'package:flutter/foundation.dart';
import '../api/api_client.dart';
import '../api/proxy_client.dart';
import '../config/attribution_config.dart';
import '../config/ext_config_models.dart';
import '../config/ext_config_runtime.dart';
import '../config/video_home_runtime.dart';
import '../entities/user_entities.dart';
import 'adjust_service.dart';
import 'analytics_attribution_callbacks.dart';
import 'user_api.dart';
/// 认证服务回调
/// 用于在认证流程各阶段通知调用方
abstract class AuthServiceCallbacks {
/// 获取设备 ID由调用方实现
Future<String> getDeviceId();
/// 计算签名(由调用方实现)
String computeSign(String deviceId);
/// 登录成功后回调
void onLoginSuccess(FastLoginResponse data) {}
/// 通用信息获取后回调
void onCommonInfoLoaded(CommonInfoResponse data) {}
/// 登录失败回调
void onLoginFailed(String msg) {}
}
/// APP 启动认证服务
/// 封装完整的启动登录流程,包括:
/// 1. 快速登录
/// 2. 归因上报
/// 3. 获取通用信息
abstract class FrameworkAuthService {
static AuthServiceCallbacks? _callbacks;
static Future<void>? _loginFuture;
static bool _isInitialized = false;
/// 登录是否已完成
static final ValueNotifier<bool> isLoginComplete = ValueNotifier(false);
/// 最近一次快速登录成功时的 [FastLoginResponse.userId](失败或未登录为空)。
///
/// 宿主在调用需 `userId` 的接口(如生图 `create-task`)前可读取;若为空应提示用户稍后重试。
static String? lastLoggedInUserId;
/// 登录完成后的 Future需鉴权接口应 await 此 Future 再请求
static Future<void> get loginComplete => _loginFuture ?? Future<void>.value();
/// 初始化认证服务。
///
/// 默认注册 [AnalyticsAttributionCallbacks](与 Adjust 缓存一致)。可传入
/// [attributionCallbacks] 覆盖(例如自定义 referer 格式)。
static void init(
AuthServiceCallbacks callbacks, {
AttributionCallbacks? attributionCallbacks,
}) {
AttributionService.init(
attributionCallbacks ?? AnalyticsAttributionCallbacks(),
);
_callbacks = callbacks;
_isInitialized = true;
}
/// 启动登录流程
/// [delaySeconds] 启动延迟秒数,默认 2 秒
/// [maxRetries] 最大重试次数,默认 3 次
/// [retryDelaySeconds] 重试延迟秒数,默认 2 秒
static Future<void> start({
int delaySeconds = 2,
int maxRetries = 3,
int retryDelaySeconds = 2,
}) async {
if (!_isInitialized || _callbacks == null) {
throw StateError(
'FrameworkAuthService not initialized. '
'Call FrameworkAuthService.init(callbacks) first.',
);
}
if (_loginFuture != null) return _loginFuture!;
final completer = Completer<void>();
_loginFuture = completer.future;
if (kDebugMode) {
debugPrint('[AuthService] start: 开始登录流程');
}
try {
await Future<void>.delayed(Duration(seconds: delaySeconds));
final deviceId = await _callbacks!.getDeviceId();
if (kDebugMode) {
debugPrint('[AuthService] start: deviceId=$deviceId');
}
final sign = _callbacks!.computeSign(deviceId);
if (kDebugMode) {
debugPrint('[AuthService] start: sign=$sign');
}
final referer = await AttributionService.getReferrer();
if (kDebugMode && referer != null) {
debugPrint('[AuthService] start: referer=$referer');
}
// 确定归因类型
String? referrerType;
final adjustReferrer = await AttributionService.getAdjustReferrer();
final fbReferrer = await AttributionService.getFacebookReferrer();
if (adjustReferrer != null && adjustReferrer.isNotEmpty) {
referrerType = defaultTargetPlatform == TargetPlatform.iOS
? 'ios_adjust'
: 'android_adjust';
} else if (fbReferrer != null && fbReferrer.isNotEmpty) {
referrerType = 'fb';
}
if (kDebugMode) {
debugPrint('[AuthService] start: referrerType=$referrerType');
}
// 尝试快速登录
EntityResponse<FastLoginResponse>? res;
for (var i = 0; i < maxRetries; i++) {
if (i > 0) {
if (kDebugMode) {
debugPrint('[AuthService] start: 第 ${i + 1} 次重试...');
}
await Future<void>.delayed(Duration(seconds: retryDelaySeconds));
}
try {
final cfg = ApiClient.instance.config;
final appType = defaultTargetPlatform == TargetPlatform.iOS
? cfg.backendAppTypeIOS
: cfg.backendAppTypeAndroid;
res = await UserApi.fastLogin(
deviceId: deviceId,
sign: sign,
referer: referer ?? '',
app: appType,
type: referrerType,
);
break;
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] start: 第 ${i + 1} 次请求失败: $e');
}
if (i == maxRetries - 1) rethrow;
}
}
if (res == null) {
lastLoggedInUserId = null;
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
completer.complete();
return;
}
if (kDebugMode) {
debugPrint('[AuthService] start: 登录结果 code=${res.code} msg=${res.msg}');
}
if (res.isSuccess && res.data != null) {
final loginData = res.data!;
final uid = loginData.userId?.trim();
lastLoggedInUserId =
uid != null && uid.isNotEmpty ? uid : null;
// 设置 Token
if (loginData.userToken != null && loginData.userToken!.isNotEmpty) {
ApiClient.instance.setUserToken(loginData.userToken!);
if (kDebugMode) {
debugPrint('[AuthService] start: 已设置 userToken');
}
}
// 回调登录成功
_callbacks!.onLoginSuccess(loginData);
// 上报归因并获取通用信息
await _reportReferrersAndLoadCommonInfo(
uid: loginData.userId ?? '',
deviceId: deviceId,
);
} else {
lastLoggedInUserId = null;
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
_callbacks!.onLoginFailed(res.msg);
}
} catch (e, st) {
lastLoggedInUserId = null;
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
if (kDebugMode) {
debugPrint('[AuthService] start: 异常 $e\n$st');
}
_callbacks!.onLoginFailed(e.toString());
} finally {
if (!completer.isCompleted) {
completer.complete();
isLoginComplete.value = true;
}
}
}
/// 上报归因并获取通用信息
static Future<void> _reportReferrersAndLoadCommonInfo({
required String uid,
required String deviceId,
}) async {
if (uid.isEmpty) {
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
if (kDebugMode) {
debugPrint(
'[AuthService] common_info: 跳过userId 为空),已标记 common_info 失败',
);
}
return;
}
final config = ApiClient.instance.config;
final backendApp = defaultTargetPlatform == TargetPlatform.iOS
? config.backendAppTypeIOS
: config.backendAppTypeAndroid;
// 上报 Adjust 归因
final adjustReferer = await AttributionService.getAdjustReferrer();
if (adjustReferer != null && adjustReferer.isNotEmpty) {
final adjustType = defaultTargetPlatform == TargetPlatform.iOS
? 'ios_adjust'
: 'android_adjust';
try {
final rAdjust = await UserApi.referrer(
app: backendApp,
userId: uid,
referer: adjustReferer,
deviceId: deviceId,
type: adjustType,
);
if (kDebugMode) {
debugPrint(
'[AuthService] referrer($adjustType): ${rAdjust.isSuccess ? "成功" : "失败"}');
}
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] referrer($adjustType): 异常 $e');
}
}
}
// 上报 Google Play 归因(从 AdjustService 获取缓存的 referrer
final playReferrer = AdjustService.cachedPlayReferrer;
if (playReferrer != null && playReferrer.isNotEmpty) {
try {
final rGg = await UserApi.referrer(
app: backendApp,
userId: uid,
referer: playReferrer,
deviceId: deviceId,
type: 'gg',
);
if (kDebugMode) {
debugPrint(
'[AuthService] referrer(gg): ${rGg.isSuccess ? "成功" : "失败"}');
}
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] referrer(gg): 异常 $e');
}
}
}
// 获取通用信息
try {
final commonRes = await UserApi.getCommonInfo(
app: backendApp,
pkg: config.packageName,
userId: uid,
deviceId: deviceId,
);
if (commonRes.isSuccess && commonRes.data != null) {
ExtConfigRuntime.applyCommonInfoSuccess(commonRes.data!);
_callbacks?.onCommonInfoLoaded(commonRes.data!);
unawaited(
VideoHomeRuntime.hydrateAfterCommonInfo(
userId: uid,
app: backendApp,
),
);
if (kDebugMode) {
debugPrint('[AuthService] common_info: 获取成功');
}
} else {
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
if (kDebugMode) {
debugPrint(
'[AuthService] common_info: 失败 code=${commonRes.code} msg=${commonRes.msg}');
}
}
} catch (e) {
VideoHomeRuntime.reset();
ExtConfigRuntime.applyCommonInfoFailure();
if (kDebugMode) {
debugPrint('[AuthService] common_info: 异常 $e');
}
}
}
/// 解析 extConfig JSON 字符串为 Map兼容旧代码结构化解析请用 [ExtConfigData.parse])。
static Map<String, dynamic>? parseExtConfig(String? extConfigStr) {
return ExtConfigData.parseRawMap(extConfigStr);
}
}