From a9508d89419b0196c04628b716e1701323f0b953 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 30 Dec 2023 10:59:17 +0100 Subject: [PATCH] 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. --- lib/src/client.dart | 44 +++++++++++++++++------- lib/src/utils/client_init_exception.dart | 34 ++++++++++++++++++ test/client_test.dart | 36 +++++++++++++++++++ 3 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 lib/src/utils/client_init_exception.dart diff --git a/lib/src/client.dart b/lib/src/client.dart index a752e7ec..d44edc1b 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -33,6 +33,7 @@ import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; import 'package:matrix/src/models/timeline_chunk.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/multilock.dart'; import 'package:matrix/src/utils/run_benchmarked.dart'; @@ -1464,16 +1465,26 @@ class Client extends MatrixApi { newUserID == null || newDeviceID == null || newDeviceName == null)) { - throw Exception( - 'If one of [newToken, newUserID, newDeviceID, newDeviceName] is set then all of them must be set!'); + throw ClientInitPreconditionError( + '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; + String? olmAccount; + String? accessToken; + String? userID; try { Logs().i('Initialize client $clientName'); 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; @@ -1487,9 +1498,6 @@ class Client extends MatrixApi { _groupCallSessionId = randomAlpha(12); _serverConfigCache.invalidate(); - String? olmAccount; - String? accessToken; - String? userID; final account = await this.database?.getClient(clientName); if (account != null) { _id = account['client_id']; @@ -1600,12 +1608,24 @@ class Client extends MatrixApi { await firstSyncReceived; } return; - } catch (e, s) { - Logs().e('Initialization failed', e, s); - await logout().catchError((_) => null); - onLoginStateChanged.addError(e, s); - _initLock = false; + } on ClientInitPreconditionError { 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; } } diff --git a/lib/src/utils/client_init_exception.dart b/lib/src/utils/client_init_exception.dart new file mode 100644 index 00000000..da2e90e6 --- /dev/null +++ b/lib/src/utils/client_init_exception.dart @@ -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'; +} diff --git a/test/client_test.dart b/test/client_test.dart index ef025f07..db86214e 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -26,6 +26,7 @@ import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:matrix/matrix.dart'; +import 'package:matrix/src/utils/client_init_exception.dart'; import 'fake_client.dart'; import 'fake_database.dart'; import 'fake_matrix_api.dart'; @@ -1139,6 +1140,41 @@ void main() { 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 { await matrix.dispose(closeDatabase: true); });