refactor: Throw client init exception on client init fail

This changes the behavior
when client init fails. It
no longer calls logout and does
only clear local data while
returning all available
information in a new
exception type so that the
SDK consumer can decide
to logout or try again to init
with these information. This
should make it much more rare
that users loose their sessions.
This commit is contained in:
krille-chan 2023-12-30 10:59:17 +01:00 committed by Krille
parent 872e565de1
commit a9508d8941
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
3 changed files with 102 additions and 12 deletions

View File

@ -33,6 +33,7 @@ import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/src/models/timeline_chunk.dart'; import 'package:matrix/src/models/timeline_chunk.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import 'package:matrix/src/utils/client_init_exception.dart';
import 'package:matrix/src/utils/compute_callback.dart'; import 'package:matrix/src/utils/compute_callback.dart';
import 'package:matrix/src/utils/multilock.dart'; import 'package:matrix/src/utils/multilock.dart';
import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/utils/run_benchmarked.dart';
@ -1464,16 +1465,26 @@ class Client extends MatrixApi {
newUserID == null || newUserID == null ||
newDeviceID == null || newDeviceID == null ||
newDeviceName == null)) { newDeviceName == null)) {
throw Exception( throw ClientInitPreconditionError(
'If one of [newToken, newUserID, newDeviceID, newDeviceName] is set then all of them must be set!'); 'If one of [newToken, newUserID, newDeviceID, newDeviceName] is set then all of them must be set!',
);
} }
if (_initLock) throw Exception('[init()] has been called multiple times!'); if (_initLock) {
throw ClientInitPreconditionError(
'[init()] has been called multiple times!',
);
}
_initLock = true; _initLock = true;
String? olmAccount;
String? accessToken;
String? userID;
try { try {
Logs().i('Initialize client $clientName'); Logs().i('Initialize client $clientName');
if (isLogged()) { if (isLogged()) {
throw Exception('User is already logged in! Call [logout()] first!'); throw ClientInitPreconditionError(
'User is already logged in! Call [logout()] first!',
);
} }
final databaseBuilder = this.databaseBuilder; final databaseBuilder = this.databaseBuilder;
@ -1487,9 +1498,6 @@ class Client extends MatrixApi {
_groupCallSessionId = randomAlpha(12); _groupCallSessionId = randomAlpha(12);
_serverConfigCache.invalidate(); _serverConfigCache.invalidate();
String? olmAccount;
String? accessToken;
String? userID;
final account = await this.database?.getClient(clientName); final account = await this.database?.getClient(clientName);
if (account != null) { if (account != null) {
_id = account['client_id']; _id = account['client_id'];
@ -1600,12 +1608,24 @@ class Client extends MatrixApi {
await firstSyncReceived; await firstSyncReceived;
} }
return; return;
} catch (e, s) { } on ClientInitPreconditionError {
Logs().e('Initialization failed', e, s);
await logout().catchError((_) => null);
onLoginStateChanged.addError(e, s);
_initLock = false;
rethrow; rethrow;
} catch (e, s) {
Logs().wtf('Client initialization failed', e, s);
onLoginStateChanged.addError(e, s);
final clientInitException = ClientInitException(
e,
homeserver: homeserver,
accessToken: accessToken,
userId: userID,
deviceId: deviceID,
deviceName: deviceName,
olmAccount: olmAccount,
);
await clear();
throw clientInitException;
} finally {
_initLock = false;
} }
} }

View File

@ -0,0 +1,34 @@
/// Exception thrown on Client initialization. This object might contain
/// enough information to restore the session or to decide if you need to
/// logout the session or clear the database.
class ClientInitException implements Exception {
final Object originalException;
final Uri? homeserver;
final String? accessToken;
final String? userId;
final String? deviceId;
final String? deviceName;
final String? olmAccount;
ClientInitException(
this.originalException, {
this.homeserver,
this.accessToken,
this.userId,
this.deviceId,
this.deviceName,
this.olmAccount,
});
@override
String toString() => originalException.toString();
}
class ClientInitPreconditionError implements Exception {
final String cause;
ClientInitPreconditionError(this.cause);
@override
String toString() => 'Client Init Precondition Error: $cause';
}

View File

@ -26,6 +26,7 @@ import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/client_init_exception.dart';
import 'fake_client.dart'; import 'fake_client.dart';
import 'fake_database.dart'; import 'fake_database.dart';
import 'fake_matrix_api.dart'; import 'fake_matrix_api.dart';
@ -1139,6 +1140,41 @@ void main() {
reason: '!5345234235:example.com not found as archived room'); reason: '!5345234235:example.com not found as archived room');
}); });
test('Client Init Exception', () async {
try {
await olm.init();
olm.get_library_version();
} catch (e) {
olmEnabled = false;
Logs().w('[LibOlm] Failed to load LibOlm', e);
}
Logs().w('[LibOlm] Enabled: $olmEnabled');
if (!olmEnabled) return;
final customClient = Client(
'failclient',
databaseBuilder: getMatrixSdkDatabase,
);
try {
await customClient.init(
newToken: 'testtoken',
newDeviceID: 'testdeviceid',
newDeviceName: 'testdevicename',
newHomeserver: Uri.parse('https://test.server'),
newOlmAccount: 'abcd',
newUserID: '@user:server',
);
throw Exception('No exception?');
} on ClientInitException catch (error) {
expect(error.accessToken, 'testtoken');
expect(error.deviceId, 'testdeviceid');
expect(error.deviceName, 'testdevicename');
expect(error.homeserver, Uri.parse('https://test.server'));
expect(error.olmAccount, 'abcd');
expect(error.userId, '@user:server');
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY');
}
});
tearDown(() async { tearDown(() async {
await matrix.dispose(closeDatabase: true); await matrix.dispose(closeDatabase: true);
}); });