relatica/lib/services/auth_service.dart
Hank Grabowski d5f4196435 Fix multiple accounts from same server not working
Fixes Issue #72
  - Refactor the initial profile loading
  - Add a clear all profiles button (and service support) to the Profile Manager screen since once profiles are corrupted need to start from scratch
  - Fix the short term cache response to key on headers and body as well as URL/QP
  - Fix ID handling in Basic Credentials copy method
2024-06-27 19:47:52 -04:00

198 lines
6 KiB
Dart

import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../di_initialization.dart';
import '../friendica_client/friendica_client.dart';
import '../globals.dart';
import '../models/auth/credentials_intf.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
import '../update_timer_initialization.dart';
import 'secrets_service.dart';
import 'status_service.dart';
class AccountsService extends ChangeNotifier {
static final _logger = Logger('$AccountsService');
Profile? _currentProfile;
final _loggedInProfiles = <Profile>{};
final _loggedOutProfiles = <Profile>{};
var _initializing = false;
final SecretsService secretsService;
AccountsService(this.secretsService);
bool get loggedIn => _currentProfile != null;
bool get initializing => _initializing;
List<Profile> get loggedInProfiles => UnmodifiableListView(_loggedInProfiles);
List<Profile> get loggedOutProfiles =>
UnmodifiableListView(_loggedOutProfiles);
Profile get currentProfile => _currentProfile!;
FutureResult<bool, ExecError> initialize() async {
final lastActiveProfile = await _getStoredLoginState();
_initializing = true;
final result = await runCatchingAsync(() async {
final initialProfiles = secretsService.profiles;
for (final p in initialProfiles) {
if (!p.loggedIn) {
_loggedOutProfiles.add(p);
continue;
}
final pr = await signIn(p.credentials, withNotification: false);
if (pr.isSuccess) {
final profile = pr.value;
if (profile.id.isNotEmpty && profile.id == lastActiveProfile) {
_currentProfile = profile;
Future.delayed(
const Duration(seconds: 10),
() async => await executeUpdatesForProfile(profile),
);
}
} else {
await signOut(p, withNotification: false);
}
}
if (_currentProfile == null && _loggedInProfiles.isNotEmpty) {
await setActiveProfile(_loggedInProfiles.first);
}
notifyListeners();
return Result.ok(loggedIn);
});
_initializing = false;
return result.execErrorCast();
}
FutureResult<Profile, ExecError> signIn(ICredentials credentials,
{bool withNotification = true}) async {
final result =
await credentials.signIn().andThenAsync((signedInCredentials) async {
final client =
ProfileClient(Profile.credentialsOnly(signedInCredentials));
getIt<StatusService>().setStatus(
'Getting user profile from ${signedInCredentials.serverName}');
return await client.getMyProfile();
}).andThenAsync((profileResult) async {
final profileData = profileResult.$1;
final profile = profileResult.$2;
final loginProfile = Profile(
credentials: profile.credentials,
username: profileData.name,
serverName: profile.credentials.serverName,
avatar: profileData.avatarUrl,
userId: profileData.id,
loggedIn: true,
);
getIt<StatusService>()
.setStatus('Loaded user profile ${profileData.handle}');
if (_loggedInProfiles.isEmpty) {
await setActiveProfile(loginProfile,
withNotification: withNotification);
}
_loggedInProfiles.add(loginProfile);
_loggedOutProfiles.remove(loginProfile);
await secretsService.addOrUpdateProfile(loginProfile);
await updateProfileDependencyInjectors(loginProfile);
if (withNotification) {
notifyListeners();
}
return Result.ok(loginProfile);
});
if (result.isFailure) {
getIt<StatusService>().setStatus('Error signing in: ${result.error}');
_logger.severe('Error signing in: ${result.error}');
}
return result.execErrorCast();
}
Future signOut(Profile profile, {bool withNotification = true}) async {
if (_currentProfile == profile) {
await clearActiveProfile(withNotification: withNotification);
}
_loggedInProfiles.remove(profile);
_loggedOutProfiles.add(profile.copyWithLoginUpdate(false));
await secretsService.addOrUpdateProfile(profile.copyWithLoginUpdate(false));
if (_loggedInProfiles.isNotEmpty) {
setActiveProfile(
_loggedInProfiles.first,
withNotification: withNotification,
);
}
if (withNotification) {
notifyListeners();
}
}
Future removeProfile(Profile profile, {bool withNotification = true}) async {
if (_currentProfile == profile) {
await clearActiveProfile(withNotification: withNotification);
}
_loggedInProfiles.remove(profile);
_loggedOutProfiles.remove(profile);
await secretsService.removeProfile(profile);
if (_loggedInProfiles.isNotEmpty) {
setActiveProfile(
_loggedInProfiles.first,
withNotification: withNotification,
);
}
if (withNotification) {
notifyListeners();
}
}
Future clearActiveProfile({bool withNotification = true}) async {
_currentProfile = null;
if (withNotification) {
notifyListeners();
}
await _saveStoredLoginState();
}
Future<void> setActiveProfile(Profile profile,
{bool withNotification = true}) async {
_currentProfile = profile;
if (withNotification) {
notifyListeners();
}
await _saveStoredLoginState();
}
Future<void> clearAllProfiles() async {
_loggedInProfiles.clear();
_loggedOutProfiles.clear();
_currentProfile = null;
await secretsService.clearCredentials();
notifyListeners();
}
Future<void> _saveStoredLoginState() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('active_profile_id', _currentProfile?.id ?? '');
}
Future<String> _getStoredLoginState() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('active_profile_id') ?? '';
}
}