275 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Dart
		
	
	
| /// 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);
 | ||
|   }
 | ||
| }
 |