/// Created by Haoyi on 6/3/21 part of "account_manager.dart"; extension AccountServiceExtension on AccountManager { Future _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 _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 _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 _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 _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 _loginGuruWithCredential(Credential credential) async { return await GuruApi.instance.loginGuruWithCredential(credential: credential); } Future _associateCredential(Credential credential) async { return await GuruApi.instance.associateCredential(credential: credential); } Future _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 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); } }