2023-12-21 09:14:40 +00:00
|
|
|
|
/// Created by Haoyi on 6/3/21
|
|
|
|
|
|
part of "account_manager.dart";
|
|
|
|
|
|
|
|
|
|
|
|
extension AccountServiceExtension on AccountManager {
|
|
|
|
|
|
Future<bool> _restoreAccount(Account account) async {
|
2024-03-07 03:46:50 +00:00
|
|
|
|
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");
|
|
|
|
|
|
}
|
2023-12-21 09:14:40 +00:00
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
Log.v("_restoreAccount saasUser:$guruUser", tag: "Account");
|
2023-12-21 09:14:40 +00:00
|
|
|
|
final device = account.device;
|
|
|
|
|
|
if (device != null) {
|
|
|
|
|
|
_updateDevice(device);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final accountProfile = account.accountProfile;
|
|
|
|
|
|
if (accountProfile != null) {
|
|
|
|
|
|
_updateAccountProfile(accountProfile);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
final credentials = account.credentials;
|
|
|
|
|
|
if (credentials.isNotEmpty) {
|
|
|
|
|
|
_restoreCredentials(credentials);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (guruUser != null) {
|
|
|
|
|
|
await _updateGuruUser(guruUser);
|
|
|
|
|
|
await _verifyOrReportAuthDevice(guruUser);
|
|
|
|
|
|
await authenticateFirebase();
|
2023-12-21 09:14:40 +00:00
|
|
|
|
if (accountProfile != null) {
|
|
|
|
|
|
await _checkOrUploadAccountProfile(accountProfile);
|
|
|
|
|
|
}
|
2024-03-07 03:46:50 +00:00
|
|
|
|
if (anonymousAuth != null) {
|
|
|
|
|
|
final anonymousCredential = anonymousAuth.credential;
|
|
|
|
|
|
if (anonymousCredential != null) {
|
|
|
|
|
|
_bindCredential(anonymousCredential);
|
|
|
|
|
|
return await _invokeAnonymousLogin(anonymousAuth.user, anonymousCredential);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-12-21 09:14:40 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
Future switchUser(GuruUser newUser) async {
|
|
|
|
|
|
/// 更新 login 的用户信息
|
|
|
|
|
|
_updateGuruUser(newUser);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await _verifyOrReportAuthDevice(newUser);
|
|
|
|
|
|
// 登陆 firebase 不需要同步等待
|
|
|
|
|
|
authenticateFirebase();
|
|
|
|
|
|
} catch (error, stacktrace) {
|
|
|
|
|
|
Log.w("loginWithCredential error:$error, $stacktrace");
|
2023-12-21 09:14:40 +00:00
|
|
|
|
}
|
2024-03-07 03:46:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<bool> _switchAccount(Credential credential) async {
|
|
|
|
|
|
GuruUser? loginUser;
|
|
|
|
|
|
GuruUser? logoutUser;
|
|
|
|
|
|
|
|
|
|
|
|
/// 这里只调用接口获取对应的新用户信息,还没有做对应的绑定操作
|
2023-12-21 09:14:40 +00:00
|
|
|
|
try {
|
2024-03-07 03:46:50 +00:00
|
|
|
|
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);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
}
|
2024-03-07 03:46:50 +00:00
|
|
|
|
|
|
|
|
|
|
/// 因为这里是匿名登陆,因此在冲突的时候通过静默的方法切换账户
|
|
|
|
|
|
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);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-03-07 03:46:50 +00:00
|
|
|
|
return false;
|
2023-12-21 09:14:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
Future<DeviceTrack> _buildDevice(GuruUser saasUser) async {
|
2023-12-21 09:14:40 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
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);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
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);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2024-03-07 03:46:50 +00:00
|
|
|
|
if (isChanged && reportDevice?.isValid == true && guruUser.isValid == true) {
|
2023-12-21 09:14:40 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-07 03:46:50 +00:00
|
|
|
|
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);
|
2023-12-21 09:14:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _updateFirebaseUser(User user) {
|
|
|
|
|
|
accountDataStore.updateFirebaseUser(user);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _updateAccountProfile(AccountProfile accountProfile) {
|
|
|
|
|
|
accountDataStore.updateAccountProfile(accountProfile);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|