guru_sdk/guru_app/lib/account/account_service_extension.dart

275 lines
10 KiB
Dart
Raw Normal View History

/// Created by Haoyi on 6/3/21
part of "account_manager.dart";
extension AccountServiceExtension on AccountManager {
Future<bool> _restoreAccount(Account account) async {
AccountAuth? anonymousAuth;
GuruUser? guruUser = account.guruUser;
Log.d("restoreAccount $guruUser", tag: "Account");
try {
if (guruUser == null) {
anonymousAuth = await _retrieveAnonymous();
guruUser = anonymousAuth?.user;
}
} catch (error, stacktrace) {
Log.w("loginWith Anonymous error:$error, $stacktrace");
}
Log.v("_restoreAccount saasUser:$guruUser", tag: "Account");
final device = account.device;
if (device != null) {
_updateDevice(device);
}
final accountProfile = account.accountProfile;
if (accountProfile != null) {
_updateAccountProfile(accountProfile);
}
final credentials = account.credentials;
if (credentials.isNotEmpty) {
_restoreCredentials(credentials);
}
if (guruUser != null) {
await _updateGuruUser(guruUser);
await _verifyOrReportAuthDevice(guruUser);
await authenticateFirebase();
if (accountProfile != null) {
await _checkOrUploadAccountProfile(accountProfile);
}
if (anonymousAuth != null) {
final anonymousCredential = anonymousAuth.credential;
if (anonymousCredential != null) {
_bindCredential(anonymousCredential);
return await _invokeAnonymousLogin(anonymousAuth.user, anonymousCredential);
}
}
return true;
} else {
return false;
}
}
Future switchUser(GuruUser newUser) async {
/// 更新 login 的用户信息
_updateGuruUser(newUser);
try {
await _verifyOrReportAuthDevice(newUser);
// 登陆 firebase 不需要同步等待
authenticateFirebase();
} catch (error, stacktrace) {
Log.w("loginWithCredential error:$error, $stacktrace");
}
}
Future<bool> _switchAccount(Credential credential) async {
GuruUser? loginUser;
GuruUser? logoutUser;
/// 这里只调用接口获取对应的新用户信息,还没有做对应的绑定操作
try {
loginUser = await _loginGuruWithCredential(credential);
} catch (error, stacktrace) {
Log.w("loginWithCredential[${credential.authType}] error:$error, $stacktrace");
return false;
}
if (loginUser.isSame(accountDataStore.user)) {
Log.w("loginWithCredential same user!", tag: "Account");
_bindCredential(credential);
return false;
}
bool result = false;
/// logout 内部进行了拦截,因此这里总是会返回一个 logoutUser
/// logout传入 switch参数表示是一个切换帐号不需要真正的登出
/// 因为在下面的 SwitchAccount方法中会完成后续的过程
logoutUser = await logout(switching: true);
/// 如果这里没有返回出对应的退出用户,将认为退出失败
/// 因为进到 switchAccount 里肯定是非匿名登陆的帐号做登出操作
if (logoutUser != null) {
result = await GuruApp.instance.switchAccount(loginUser, credential, oldUser: logoutUser);
}
return result;
}
Future<bool> _processConflict(Credential credential) async {
final historicalSocialAuths = await AppProperty.getInstance().getHistoricalSocialAuths();
/// 如果是匿名登录,并且在这个设备上同样的用户没有绑定过其它的三方登陆凭证
/// 这种情况下,认定为新用户,中台会静默解决冲突,并且对应的数据库不会发生迁移
if (accountDataStore.isAnonymous && historicalSocialAuths.isEmpty) {
Log.d("associate conflict: _loginGuruWithCredential!");
final user = accountDataStore.user;
final oldUid = user?.uid ?? "";
if (user != null) {
await _invokeAnonymousLogout(user);
}
/// 因为这里是匿名登陆,因此在冲突的时候通过静默的方法切换账户
final guruUser = await _loginGuruWithCredential(credential);
/// 由于是冲突处理,此时的匿名帐号已经和当前新登陆的用户不能配对
/// 因此在新用户登陆成功后,这里需要将匿名帐户的凭证解绑,并清除匿名的密钥
/// 这样做的目的是为了在该帐号退出时,判断匿名帐号是否存在,
/// 如果不存在会创建一个新的匿名帐号,确保数据不被污染
await _unbindCredential(AuthType.anonymous);
/// 将新的用户进行关联,此时当前设备上只有一个登陆凭证
await processLogin(guruUser, credential);
GuruAnalytics.instance.logGuruEvent("switch_account", {
"auth": getAuthName(credential.authType),
"old_uid": oldUid,
"new_uid": guruUser.uid,
"silent": true
});
return true;
} else {
final canSwitch = await _invokeConflict();
if (canSwitch) {
return await _switchAccount(credential);
}
}
return false;
}
Future<DeviceTrack> _buildDevice(GuruUser saasUser) async {
final DeviceInfo? deviceInfo = await AppProperty.getInstance().getAccountDevice();
final firebasePushToken = await RemoteMessagingManager.instance.getToken();
if (firebasePushToken != null) {
final deviceId = await AppProperty.getInstance().getDeviceId();
final newDeviceInfo = await DeviceUtils.buildDeviceInfo(
deviceId: deviceId, firebasePushToken: firebasePushToken, uid: saasUser.uid);
return DeviceTrack(newDeviceInfo, deviceInfo);
}
return DeviceTrack(null, deviceInfo);
}
Future<AccountAuth?> _retrieveAnonymous() async {
final result = await AuthCredentialManager.instance.loginWith(AuthType.anonymous);
final credential = result.credential;
if (!result.isSuccess || credential == null) {
Log.w("_retrieveAnonymous error!", tag: "Account");
return null;
}
final user = await _requestGuruUser(credential);
return AccountAuth(user, credential: credential);
}
Future<GuruUser> _loginGuruWithCredential(Credential credential) async {
return await GuruApi.instance.loginGuruWithCredential(credential: credential);
}
Future<GuruUser> _associateCredential(Credential credential) async {
return await GuruApi.instance.associateCredential(credential: credential);
}
Future<GuruUser> _requestGuruUser(Credential credential) async {
//MetaData是匿名请求或者当前没有任何 GuruUser Id走signIn接口
if (!accountDataStore.hasUid || credential.isAnonymous) {
Log.d("_loginGuruWithCredential!", tag: "Account");
return await _loginGuruWithCredential(credential);
} else {
Log.d("_associateCredential!");
//当前有 GuruUser id并且MetaData是三方登录Token走associate接口不管已有的SaasUser是不是三方登录
return await _associateCredential(credential);
}
}
Future _verifyOrReportAuthDevice(GuruUser guruUser) async {
final deviceTrack = await _buildDevice(guruUser);
final latestReportDeviceTimestamp =
await AppProperty.getInstance().getLatestReportDeviceTimestamp();
final elapsedInterval = DateTimeUtils.currentTimeInMillis() - latestReportDeviceTimestamp;
final isChanged = (elapsedInterval > DateTimeUtils.sixHourInMillis) || deviceTrack.isChanged;
final reportDevice = deviceTrack.device;
final deviceId = deviceTrack.device?.deviceId ?? "";
if (deviceId.isNotEmpty) {
GuruAnalytics.instance.setDeviceId(deviceId);
}
if (isChanged && reportDevice?.isValid == true && guruUser.isValid == true) {
final result = await GuruApi.instance.reportDevice(reportDevice!).then((_) {
return true;
}).catchError((error) {
Log.i("reportDevice error:$error", tag: "Account");
return false;
});
if (result) {
reportDevice.dumpDevice(msg: "REPORT DEVICE SUCCESS");
_updateDevice(reportDevice);
AppProperty.getInstance()
.setLatestReportDeviceTimestamp(DateTimeUtils.currentTimeInMillis());
AppProperty.getInstance().setAccountDevice(reportDevice);
}
}
}
Future _checkOrUploadAccountProfile(AccountProfile accountProfile) async {
bool upload = accountProfile.dirty;
String? changedCountryCode = DeviceUtils.buildLocaleInfo().countryCode.toLowerCase();
if (DartExt.isBlank(changedCountryCode) || accountProfile.countryCode == changedCountryCode) {
changedCountryCode = null;
} else {
upload = true;
}
Log.d(
"_checkOrUploadAccountProfile dirty:${accountProfile.dirty} upload:$upload $changedCountryCode",
tag: "Account");
if (upload) {
await modifyProfile(countryCode: changedCountryCode);
}
}
void refreshFcmToken() {
final saasUser = accountDataStore.user;
if (saasUser != null) {
_verifyOrReportAuthDevice(saasUser);
}
}
void _updateDevice(DeviceInfo device) {
accountDataStore.updateDeviceInfo(device);
}
Future _bindCredential(Credential credential) async {
accountDataStore.bindCredential(credential);
/// 这里匿名帐号是不会保存凭证的,因为匿名帐号的登陆凭证是自生成的
if (credential.authType != AuthType.anonymous) {
await AppProperty.getInstance().saveCredential(credential);
}
}
Future _unbindCredential(AuthType authType) async {
accountDataStore.unbindCredential(authType);
if (authType != AuthType.anonymous) {
await AppProperty.getInstance().deleteCredential(authType);
} else {
await AppProperty.getInstance().clearAnonymousSecretKey();
}
}
void _restoreCredentials(Map<AuthType, Credential> credentials) {
accountDataStore.updateCredentials(credentials);
}
Future _updateGuruUser(GuruUser guruUser) async {
accountDataStore.updateGuruUser(guruUser);
await AppProperty.getInstance().setAccountGuruUser(guruUser);
await GuruAnalytics.instance.setUserId(guruUser.uid);
}
void _updateFirebaseUser(User user) {
accountDataStore.updateFirebaseUser(user);
}
void _updateAccountProfile(AccountProfile accountProfile) {
accountDataStore.updateAccountProfile(accountProfile);
}
}