guru_sdk/guru_app/lib/account/account_data_store.dart

188 lines
6.1 KiB
Dart

import 'dart:convert';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:guru_app/account/model/account.dart';
import 'package:guru_app/account/model/account_profile.dart';
import 'package:guru_app/account/model/user.dart';
import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/api/guru_api.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_utils/auth/auth_credential_manager.dart';
import 'package:guru_utils/device/device_info.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:http/http.dart' as http;
/// Created by Haoyi on 6/3/21
///
enum AccountDataStatus { idle, initializing, initialized, waiting, error }
class AccountDataStore {
static final AccountDataStore instance = AccountDataStore._();
final BehaviorSubject<DeviceInfo?> _deviceInfoSubject = BehaviorSubject.seeded(null);
final BehaviorSubject<GuruUser?> _guruUserSubject = BehaviorSubject.seeded(null);
final BehaviorSubject<User?> _firebaseUser = BehaviorSubject.seeded(null);
final BehaviorSubject<AccountProfile?> _accountProfile = BehaviorSubject.seeded(null);
final BehaviorSubject<AccountDataStatus> _accountDataStatus =
BehaviorSubject<AccountDataStatus>.seeded(AccountDataStatus.idle);
final BehaviorSubject<Map<AuthType, Credential>> _credentials =
BehaviorSubject.seeded(<AuthType, Credential>{});
int initRetryCount = 0;
Stream<AccountProfile?> get observableAccountProfile => _accountProfile.stream;
// final EnvConfig envConfig;
AccountDataStore._();
@Deprecated("use guruToken instead")
String? get saasToken => _guruUserSubject.value?.token;
String? get guruToken => _guruUserSubject.value?.token;
String? get uid => _guruUserSubject.value?.uid;
AccountProfile? get accountProfile => _accountProfile.value;
String? get nickname => _accountProfile.value?.nickname;
String? get countryCode => _accountProfile.value?.countryCode;
GuruUser? get user => _guruUserSubject.value;
String? get avatar => _accountProfile.value?.avatar;
DeviceInfo? get currentDevice => _deviceInfoSubject.value;
AccountDataStatus get accountDataStatus => _accountDataStatus.value;
bool get initialized => _accountDataStatus.value == AccountDataStatus.initialized;
Stream<bool> get observableInitialized =>
_accountDataStatus.stream.map((status) => status == AccountDataStatus.initialized);
bool get hasUid => uid?.isNotEmpty == true;
bool get isAnonymous =>
(uid?.isNotEmpty != true) ||
(_credentials.value.containsKey(AuthType.anonymous) && _credentials.value.length == 1);
Stream<GuruUser?> get observableSaasUser => _guruUserSubject.stream;
Map<AuthType, Credential> get credentials => _credentials.value;
Account get account => Account.restore(
guruUser: user,
device: currentDevice,
accountProfile: accountProfile,
firebaseUser: _firebaseUser.value,
credentials: credentials);
Stream<Account> get observableAccount => Rx.combineLatestList([
_guruUserSubject.stream,
_deviceInfoSubject.stream,
_accountProfile.stream,
_firebaseUser.stream,
_credentials.stream
]).debounceTime(const Duration(milliseconds: 100)).map((_) => account);
bool get isSocialLogged => (uid?.isNotEmpty == true) && credentials.isNotEmpty;
void dispose() {
_deviceInfoSubject.close();
_guruUserSubject.close();
_firebaseUser.close();
_accountProfile.close();
_credentials.close();
}
Future<GuruUser?> signInAnonymousInLocked() async {
// 这里需要使用原始的http post请求。否则这里将会死锁DIO所有请求
final secret = await AppProperty.getInstance().getAnonymousSecretKey();
final headers = {
"X-APP-ID": GuruApp.instance.details.saasAppId,
"content-type": "application/json"
};
try {
final uri = Uri.parse("${GuruApi.saasApiHost}/auth/api/v1/tokens/provider/secret");
final response = await http
.post(uri,
headers: headers,
body: jsonEncode(AnonymousLoginReqBody(secret: secret)),
encoding: utf8)
.timeout(const Duration(seconds: 30));
final data = const Utf8Decoder().convert(response.bodyBytes);
if (data.isNotEmpty) {
final result = json.decode(data);
return GuruUser.fromJson(result["data"]);
}
} catch (error, stacktrace) {
Log.v("signInAnonymousInLocked error:$error", tag: "Account");
}
return null;
}
Future refreshAuth() async {
final guruUser = await signInAnonymousInLocked();
if (guruUser != null) {
updateGuruUser(guruUser);
}
}
void updateDeviceInfo(DeviceInfo deviceInfo) {
_deviceInfoSubject.addEx(deviceInfo);
}
@Deprecated("use updateGuruUser instead")
void updateSaasUser(GuruUser saasUser) {
updateGuruUser(saasUser);
}
void updateGuruUser(GuruUser guruUser) {
_guruUserSubject.addEx(guruUser);
if (guruUser.createAtTimestamp > 0) {
GuruAnalytics.instance
.setUserProperty("user_created_timestamp", guruUser.createAtTimestamp.toString());
}
}
void updateFirebaseUser(User user) {
_firebaseUser.addEx(user);
}
void updateAccountProfile(AccountProfile profile) {
_accountProfile.addEx(profile);
}
void bindCredential(Credential credential) {
final newCredentials = Map.of(_credentials.value);
newCredentials[credential.authType] = credential;
_credentials.addEx(newCredentials);
}
void unbindCredential(AuthType authType) {
final newCredentials = Map.of(_credentials.value);
newCredentials.remove(authType);
_credentials.addEx(newCredentials);
}
void updateCredentials(Map<AuthType, Credential> credentials) {
_credentials.addEx(Map.of(credentials));
}
bool transitionTo(AccountDataStatus status, {AccountDataStatus? expectStatus}) {
return _accountDataStatus.addIfChanged(status);
}
logout() {
_guruUserSubject.addEx(null);
_firebaseUser.addEx(null);
_deviceInfoSubject.addEx(null);
_accountProfile.addEx(null);
_accountDataStatus.addIfChanged(AccountDataStatus.idle);
}
}