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
This commit is contained in:
Hank Grabowski 2024-06-27 19:47:52 -04:00
parent 8ee7b3eec1
commit d5f4196435
6 changed files with 52 additions and 17 deletions

View file

@ -779,18 +779,21 @@ class ProfileClient extends FriendicaClient {
ProfileClient(super.credentials) : super(); ProfileClient(super.credentials) : super();
FutureResult<Connection, ExecError> getMyProfile() async { FutureResult<(Connection, Profile), ExecError> getMyProfile() async {
_logger.finest(() => 'Getting logged in user profile'); _logger.finest(() => 'Getting logged in user profile');
final request = final request =
Uri.parse('https://$serverName/api/v1/accounts/verify_credentials'); Uri.parse('https://$serverName/api/v1/accounts/verify_credentials');
return (await _getApiRequest(request, timeout: oauthTimeout)) return (await _getApiRequest(request, timeout: oauthTimeout))
.mapValue((json) => ConnectionMastodonExtensions.fromJson( .mapValue((json) {
final connection = ConnectionMastodonExtensions.fromJson(
json, json,
defaultServerName: serverName, defaultServerName: serverName,
).copy( ).copy(
status: ConnectionStatus.you, status: ConnectionStatus.you,
network: 'friendica', network: 'friendica',
)); );
return (connection, profile);
});
} }
} }

View file

@ -52,6 +52,7 @@ class BasicCredentials implements ICredentials {
String? serverName, String? serverName,
}) { }) {
return BasicCredentials( return BasicCredentials(
id: id,
username: username ?? this.username, username: username ?? this.username,
password: password ?? this.password, password: password ?? this.password,
serverName: serverName ?? this.serverName, serverName: serverName ?? this.serverName,

View file

@ -373,6 +373,20 @@ class _SignInScreenState extends State<SignInScreen> {
}).toList(), }).toList(),
), ),
), ),
const VerticalPadding(),
ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(context,
'Are you sure you want to logout and delete *all* accounts? This cannot be undone.') ??
false;
print(confirm);
if (!confirm) {
return;
}
await getIt<AccountsService>().clearAllProfiles();
},
child: Text('Clear All')),
], ],
), ),
), ),
@ -395,8 +409,9 @@ class _SignInScreenState extends State<SignInScreen> {
} else { } else {
switch (authType) { switch (authType) {
case usernamePasswordType: case usernamePasswordType:
final username = usernameController.text.split('@').first;
creds = BasicCredentials( creds = BasicCredentials(
username: usernameController.text, username: username,
password: passwordController.text, password: passwordController.text,
serverName: serverNameController.text); serverName: serverNameController.text);
break; break;

View file

@ -78,20 +78,20 @@ class AccountsService extends ChangeNotifier {
FutureResult<Profile, ExecError> signIn(ICredentials credentials, FutureResult<Profile, ExecError> signIn(ICredentials credentials,
{bool withNotification = true}) async { {bool withNotification = true}) async {
ICredentials? credentialsCache;
final result = final result =
await credentials.signIn().andThenAsync((signedInCredentials) async { await credentials.signIn().andThenAsync((signedInCredentials) async {
final client = final client =
ProfileClient(Profile.credentialsOnly(signedInCredentials)); ProfileClient(Profile.credentialsOnly(signedInCredentials));
credentialsCache = signedInCredentials;
getIt<StatusService>().setStatus( getIt<StatusService>().setStatus(
'Getting user profile from ${signedInCredentials.serverName}'); 'Getting user profile from ${signedInCredentials.serverName}');
return await client.getMyProfile(); return await client.getMyProfile();
}).andThenAsync((profileData) async { }).andThenAsync((profileResult) async {
final profileData = profileResult.$1;
final profile = profileResult.$2;
final loginProfile = Profile( final loginProfile = Profile(
credentials: credentialsCache!, credentials: profile.credentials,
username: profileData.name, username: profileData.name,
serverName: credentialsCache!.serverName, serverName: profile.credentials.serverName,
avatar: profileData.avatarUrl, avatar: profileData.avatarUrl,
userId: profileData.id, userId: profileData.id,
loggedIn: true, loggedIn: true,
@ -178,6 +178,14 @@ class AccountsService extends ChangeNotifier {
await _saveStoredLoginState(); await _saveStoredLoginState();
} }
Future<void> clearAllProfiles() async {
_loggedInProfiles.clear();
_loggedOutProfiles.clear();
_currentProfile = null;
await secretsService.clearCredentials();
notifyListeners();
}
Future<void> _saveStoredLoginState() async { Future<void> _saveStoredLoginState() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('active_profile_id', _currentProfile?.id ?? ''); await prefs.setString('active_profile_id', _currentProfile?.id ?? '');

View file

@ -41,6 +41,7 @@ class SecretsService {
try { try {
await _secureStorage.delete(key: _basicProfilesKey); await _secureStorage.delete(key: _basicProfilesKey);
await _secureStorage.delete(key: _oauthProfilesKey); await _secureStorage.delete(key: _oauthProfilesKey);
profiles.clear();
return Result.ok(profiles); return Result.ok(profiles);
} catch (e) { } catch (e) {
return Result.error(ExecError( return Result.error(ExecError(

View file

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart'; import 'package:result_monad/result_monad.dart';
@ -80,10 +81,16 @@ class _CachedResponse {
other is _CachedResponse && other is _CachedResponse &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
requestType == other.requestType && requestType == other.requestType &&
requestUri == other.requestUri; requestUri == other.requestUri &&
const MapEquality().equals(requestBody, other.requestBody) &&
const MapEquality().equals(headers, other.headers);
@override @override
int get hashCode => requestType.hashCode ^ requestUri.hashCode; int get hashCode =>
requestType.hashCode ^
requestUri.hashCode ^
const MapEquality().hash(requestBody) ^
const MapEquality().hash(headers);
} }
class _ExpiringRequestCache { class _ExpiringRequestCache {
@ -121,7 +128,7 @@ class _ExpiringRequestCache {
late final http.Response response; late final http.Response response;
if (_responses.containsKey(requestStub)) { if (_responses.containsKey(requestStub)) {
print('Returning cached response for $type => $url'); _logger.fine('Returning cached response for $type => $url');
response = _responses[requestStub]?.response ?? http.Response('', 555); response = _responses[requestStub]?.response ?? http.Response('', 555);
} else { } else {
final request = RelaticaUserAgentHttpClient().get( final request = RelaticaUserAgentHttpClient().get(