diff --git a/analysis_options.yaml b/analysis_options.yaml index 2d42f778..62953a6f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,10 +11,6 @@ linter: analyzer: errors: todo: ignore - import_of_legacy_library_into_null_safe: ignore - # ignore those until we are completely nullsafe - invalid_null_aware_operator: ignore - unnecessary_null_comparison: ignore exclude: - example/main.dart # needed until crypto packages upgrade diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index 73637f5a..6d243f21 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -90,9 +90,11 @@ class CrossSigning { final masterPrivateKey = base64.decode(await handle.getStored(EventTypes.CrossSigningMasterKey)); final keyObj = olm.PkSigning(); - String masterPubkey; + String? masterPubkey; try { masterPubkey = keyObj.init_with_seed(masterPrivateKey); + } catch (e) { + masterPubkey = null; } finally { keyObj.free(); } @@ -131,9 +133,6 @@ class CrossSigning { final addSignature = (SignableKey key, SignableKey signedWith, String signature) { - if (key == null || signedWith == null || signature == null) { - return; - } final signedKey = key.cloneForSigning(); ((signedKey.signatures ??= >{})[signedWith.userId] ??= diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index b2a2c761..1714c786 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -62,7 +62,8 @@ class Encryption { crossSigning = CrossSigning(this); } - Future init(String olmAccount) async { + // initial login passes null to init a new olm account + Future init(String? olmAccount) async { await olmManager.init(olmAccount); _backgroundTasksRunning = true; _backgroundTasks(); // start the background tasks @@ -86,7 +87,7 @@ class Encryption { ); void handleDeviceOneTimeKeysCount( - Map countJson, List unusedFallbackKeyTypes) { + Map? countJson, List? unusedFallbackKeyTypes) { runInRoot(() => olmManager.handleDeviceOneTimeKeysCount( countJson, unusedFallbackKeyTypes)); } @@ -308,9 +309,10 @@ class Encryption { await client.database?.storeEventUpdate( EventUpdate( content: event.toJson(), - roomID: event.roomId, + roomID: roomId, type: updateType, ), + client, ); } return event; @@ -374,12 +376,13 @@ class Encryption { Future autovalidateMasterOwnKey() async { // check if we can set our own master key as verified, if it isn't yet - final masterKey = client.userDeviceKeys[client.userID]?.masterKey; + final userId = client.userID; + final masterKey = client.userDeviceKeys[userId]?.masterKey; if (client.database != null && masterKey != null && + userId != null && !masterKey.directVerified && - masterKey - .hasValidSignatureChain(onlyValidateUserIds: {client.userID})) { + masterKey.hasValidSignatureChain(onlyValidateUserIds: {userId})) { await masterKey.setVerified(true); } } diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index ac56873a..e2229f45 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -63,7 +63,8 @@ class KeyManager { _requestedSessionIds.clear(); for (final room in client.rooms) { final lastEvent = room.lastEvent; - if (lastEvent.type == EventTypes.Encrypted && + if (lastEvent != null && + lastEvent.type == EventTypes.Encrypted && lastEvent.content['can_request_session'] == true) { try { maybeAutoRequest(room.id, lastEvent.content['session_id'], @@ -95,6 +96,8 @@ class KeyManager { }) { final senderClaimedKeys_ = senderClaimedKeys ?? {}; final allowedAtIndex_ = allowedAtIndex ?? >{}; + final userId = client.userID; + if (userId == null) return; if (!senderClaimedKeys_.containsKey('ed25519')) { final device = client.getUserDeviceKeysByCurve25519Key(senderKey); @@ -126,7 +129,7 @@ class KeyManager { indexes: {}, roomId: roomId, sessionId: sessionId, - key: client.userID, + key: userId, senderKey: senderKey, senderClaimedKeys: senderClaimedKeys_, allowedAtIndex: allowedAtIndex_, @@ -157,7 +160,7 @@ class KeyManager { ?.storeInboundGroupSession( roomId, sessionId, - inboundGroupSession.pickle(client.userID), + inboundGroupSession.pickle(userId), json.encode(content), json.encode({}), json.encode(allowedAtIndex_), @@ -169,7 +172,7 @@ class KeyManager { return; } if (uploaded) { - client.database.markInboundGroupSessionAsUploaded(roomId, sessionId); + client.database?.markInboundGroupSessionAsUploaded(roomId, sessionId); } else { _haveKeysToUpload = true; } @@ -239,10 +242,10 @@ class KeyManager { } final session = await client.database?.getInboundGroupSession(roomId, sessionId); - if (session == null) { - return null; - } - final dbSess = SessionKey.fromDb(session, client.userID); + if (session == null) return null; + final userID = client.userID; + if (userID == null) return null; + final dbSess = SessionKey.fromDb(session, userID); final roomInboundGroupSessions = _inboundGroupSessions[roomId] ??= {}; if (!dbSess.isValid || @@ -394,12 +397,10 @@ class KeyManager { sess.outboundGroupSession!.message_index(); } } - if (client.database != null) { - await client.database.updateInboundGroupSessionAllowedAtIndex( - json.encode(inboundSess!.allowedAtIndex), - room.id, - sess.outboundGroupSession!.session_id()); - } + await client.database?.updateInboundGroupSessionAllowedAtIndex( + json.encode(inboundSess!.allowedAtIndex), + room.id, + sess.outboundGroupSession!.session_id()); // send out the key await client.sendToDeviceEncryptedChunked( devicesToReceive, EventTypes.RoomKey, rawSession); @@ -422,9 +423,11 @@ class KeyManager { /// Store an outbound group session in the database Future storeOutboundGroupSession( String roomId, OutboundGroupSession sess) async { + final userID = client.userID; + if (userID == null) return; await client.database?.storeOutboundGroupSession( roomId, - sess.outboundGroupSession!.pickle(client.userID), + sess.outboundGroupSession!.pickle(userID), json.encode(sess.devices), sess.creationTime.millisecondsSinceEpoch); } @@ -464,6 +467,12 @@ class KeyManager { throw Exception( 'Tried to create a megolm session in a non-existing room ($roomId)!'); } + final userID = client.userID; + if (userID == null) { + throw Exception( + 'Tried to create a megolm session without being logged in!'); + } + final deviceKeys = await room.getUserDeviceKeys(); final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys); deviceKeys.removeWhere((k) => !k.encryptToDevice); @@ -498,7 +507,7 @@ class KeyManager { devices: deviceKeyIds, creationTime: DateTime.now(), outboundGroupSession: outboundGroupSession, - key: client.userID, + key: userID, ); try { await client.sendToDeviceEncryptedChunked( @@ -523,15 +532,18 @@ class KeyManager { /// Load an outbound group session from database Future loadOutboundGroupSession(String roomId) async { + final database = client.database; + final userID = client.userID; if (_loadedOutboundGroupSessions.contains(roomId) || _outboundGroupSessions.containsKey(roomId) || - client.database == null) { + database == null || + userID == null) { return; // nothing to do } _loadedOutboundGroupSessions.add(roomId); - final sess = await client.database.getOutboundGroupSession( + final sess = await database.getOutboundGroupSession( roomId, - client.userID, + userID, ); if (sess == null || !sess.isValid) { return; @@ -699,7 +711,9 @@ class KeyManager { bool _isUploadingKeys = false; bool _haveKeysToUpload = true; Future backgroundTasks() async { - if (_isUploadingKeys || client.database == null) { + final database = client.database; + final userID = client.userID; + if (_isUploadingKeys || database == null || userID == null) { return; } _isUploadingKeys = true; @@ -707,8 +721,7 @@ class KeyManager { if (!_haveKeysToUpload || !(await isCached())) { return; // we can't backup anyways } - final dbSessions = - await client.database.getInboundGroupSessionsToUpload(); + final dbSessions = await database.getInboundGroupSessionsToUpload(); if (dbSessions.isEmpty) { _haveKeysToUpload = false; return; // nothing to do @@ -730,7 +743,7 @@ class KeyManager { final args = _GenerateUploadKeysArgs( pubkey: backupPubKey, dbSessions: <_DbInboundGroupSessionBundle>[], - userId: client.userID, + userId: userID, ); // we need to calculate verified beforehand, as else we pass a closure to an isolate // with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here @@ -741,7 +754,7 @@ class KeyManager { client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey); args.dbSessions.add(_DbInboundGroupSessionBundle( dbSession: dbSession, - verified: device.verified, + verified: device?.verified ?? false, )); i++; if (i > 10) { @@ -758,7 +771,7 @@ class KeyManager { // and now finally mark all the keys as uploaded // no need to optimze this, as we only run it so seldomly and almost never with many keys at once for (final dbSession in dbSessions) { - await client.database.markInboundGroupSessionAsUploaded( + await database.markInboundGroupSessionAsUploaded( dbSession.roomId, dbSession.sessionId); } } finally { diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index 6573be08..52eca0f9 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -36,7 +36,7 @@ class OlmManager { /// Returns the base64 encoded keys to store them in a store. /// This String should **never** leave the device! String? get pickledOlmAccount => - enabled ? _olmAccount!.pickle(client.userID) : null; + enabled ? _olmAccount!.pickle(client.userID!) : null; String? get fingerprintKey => enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null; String? get identityKey => @@ -57,8 +57,7 @@ class OlmManager { await olm.init(); _olmAccount = olm.Account(); _olmAccount!.create(); - if (await uploadKeys(uploadDeviceKeys: true, updateDatabase: false) == - false) { + if (!await uploadKeys(uploadDeviceKeys: true, updateDatabase: false)) { throw ('Upload key failed'); } } catch (_) { @@ -70,7 +69,7 @@ class OlmManager { try { await olm.init(); _olmAccount = olm.Account(); - _olmAccount!.unpickle(client.userID, olmAccount); + _olmAccount!.unpickle(client.userID!, olmAccount); } catch (_) { _olmAccount?.free(); _olmAccount = null; @@ -279,7 +278,7 @@ class OlmManager { // fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway. if (keyCount > _olmAccount!.max_number_of_one_time_keys()) { final requestingKeysFrom = { - client.userID: {client.deviceID: 'signed_curve25519'} + client.userID!: {client.deviceID!: 'signed_curve25519'} }; client.claimKeys(requestingKeysFrom, timeout: 10000); } @@ -311,10 +310,7 @@ class OlmManager { // update an existing session _olmSessions[session.identityKey]![ix] = session; } - if (client.database == null) { - return; - } - await client.database.storeOlmSession( + await client.database?.storeOlmSession( session.identityKey, session.sessionId!, session.pickledSession!, @@ -362,7 +358,7 @@ class OlmManager { if (session.session == null) { continue; } - if (type == 0 && session.session!.matches_inbound(body) == true) { + if (type == 0 && session.session!.matches_inbound(body)) { try { plaintext = session.session!.decrypt(type, body); } catch (e) { @@ -395,7 +391,7 @@ class OlmManager { client.database?.updateClientKeys(pickledOlmAccount!); plaintext = newSession.decrypt(type, body); runInRoot(() => storeOlmSession(OlmSession( - key: client.userID, + key: client.userID!, identityKey: senderKey, sessionId: newSession.session_id(), session: newSession, @@ -428,25 +424,19 @@ class OlmManager { } Future> getOlmSessionsFromDatabase(String senderKey) async { - if (client.database == null) { - return []; - } final olmSessions = - await client.database.getOlmSessions(senderKey, client.userID); - return olmSessions.where((sess) => sess.isValid).toList(); + await client.database?.getOlmSessions(senderKey, client.userID!); + return olmSessions?.where((sess) => sess.isValid).toList() ?? []; } Future getOlmSessionsForDevicesFromDatabase( List senderKeys) async { - if (client.database == null) { - return; - } - final rows = await client.database.getOlmSessionsForDevices( + final rows = await client.database?.getOlmSessionsForDevices( senderKeys, - client.userID, + client.userID!, ); final res = >{}; - for (final sess in rows) { + for (final sess in rows ?? []) { res[sess.identityKey] ??= []; if (sess.isValid) { res[sess.identityKey]!.add(sess); @@ -565,7 +555,7 @@ class OlmManager { session.create_outbound( _olmAccount!, identityKey, deviceKey['key']); await storeOlmSession(OlmSession( - key: client.userID, + key: client.userID!, identityKey: identityKey, sessionId: session.session_id(), session: session, @@ -602,7 +592,7 @@ class OlmManager { await storeOlmSession(sess.first); if (client.database != null) { // ignore: unawaited_futures - runInRoot(() => client.database.setLastSentMessageUserDeviceKey( + runInRoot(() => client.database?.setLastSentMessageUserDeviceKey( json.encode({ 'type': type, 'content': payload, @@ -668,8 +658,10 @@ class OlmManager { Logs().v( '[OlmManager] Device ${device.userId}:${device.deviceId} generated a new olm session, replaying last sent message...'); final lastSentMessageRes = await client.database - .getLastSentMessageUserDeviceKey(device.userId, device.deviceId!); - if (lastSentMessageRes.isEmpty || (lastSentMessageRes.first.isEmpty)) { + ?.getLastSentMessageUserDeviceKey(device.userId, device.deviceId!); + if (lastSentMessageRes == null || + lastSentMessageRes.isEmpty || + lastSentMessageRes.first.isEmpty) { return; } final lastSentMessage = json.decode(lastSentMessageRes.first); diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index e741dbb2..3a8a380f 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -187,7 +187,7 @@ class SSSS { Future setDefaultKeyId(String keyId) async { await client.setAccountData( - client.userID, + client.userID!, EventTypes.SecretStorageDefaultKey, SecretStorageDefaultKeyContent(key: keyId).toJson(), ); @@ -250,7 +250,7 @@ class SSSS { syncUpdate.accountData! .any((accountData) => accountData.type == accountDataType)); await client.setAccountData( - client.userID, accountDataType, content.toJson()); + client.userID!, accountDataType, content.toJson()); await waitForAccountData; final key = open(keyId); @@ -295,7 +295,7 @@ class SSSS { if (_cache.containsKey(type) && isValid(_cache[type])) { return _cache[type]?.content; } - final ret = await client.database.getSSSSCache(type); + final ret = await client.database?.getSSSSCache(type); if (ret == null) { return null; } @@ -321,10 +321,10 @@ class SSSS { final encryptInfo = _Encrypted( iv: enc['iv'], ciphertext: enc['ciphertext'], mac: enc['mac']); final decrypted = await decryptAes(encryptInfo, key, type); - if (cacheTypes.contains(type) && client.database != null) { + final db = client.database; + if (cacheTypes.contains(type) && db != null) { // cache the thing - await client.database - .storeSSSSCache(type, keyId, enc['ciphertext'], decrypted); + await db.storeSSSSCache(type, keyId, enc['ciphertext'], decrypted); if (_cacheCallbacks.containsKey(type) && await getCached(type) == null) { _cacheCallbacks[type]!(decrypted); } @@ -351,11 +351,11 @@ class SSSS { 'mac': encrypted.mac, }; // store the thing in your account data - await client.setAccountData(client.userID, type, content); - if (cacheTypes.contains(type) && client.database != null) { + await client.setAccountData(client.userID!, type, content); + final db = client.database; + if (cacheTypes.contains(type) && db != null) { // cache the thing - await client.database - .storeSSSSCache(type, keyId, encrypted.ciphertext, secret); + await db.storeSSSSCache(type, keyId, encrypted.ciphertext, secret); if (_cacheCallbacks.containsKey(type) && await getCached(type) == null) { _cacheCallbacks[type]!(secret); } @@ -381,10 +381,10 @@ class SSSS { throw Exception('Secrets do not match up!'); } // store the thing in your account data - await client.setAccountData(client.userID, type, content); - if (cacheTypes.contains(type) && client.database != null) { + await client.setAccountData(client.userID!, type, content); + if (cacheTypes.contains(type)) { // cache the thing - await client.database.storeSSSSCache( + await client.database?.storeSSSSCache( type, keyId, content['encrypted'][keyId]['ciphertext'], secret); } } @@ -542,13 +542,13 @@ class SSSS { return; // our request is more than 15min in the past...better not trust it anymore } Logs().i('[SSSS] Secret for type ${request.type} is ok, storing it'); - if (client.database != null) { + final db = client.database; + if (db != null) { final keyId = keyIdFromType(request.type); if (keyId != null) { final ciphertext = client.accountData[request.type]! .content['encrypted'][keyId]['ciphertext']; - await client.database - .storeSSSSCache(request.type, keyId, ciphertext, secret); + await db.storeSSSSCache(request.type, keyId, ciphertext, secret); if (_cacheCallbacks.containsKey(request.type)) { _cacheCallbacks[request.type]!(secret); } diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart index e784d900..aef6518c 100644 --- a/lib/encryption/utils/bootstrap.dart +++ b/lib/encryption/utils/bootstrap.dart @@ -357,6 +357,7 @@ class Bootstrap { checkOnlineKeyBackup(); return; } + final userID = client.userID!; try { Uint8List masterSigningKey; final secretsToStore = {}; @@ -370,7 +371,7 @@ class Bootstrap { masterSigningKey = master.generate_seed(); masterPub = master.init_with_seed(masterSigningKey); final json = { - 'user_id': client.userID, + 'user_id': userID, 'usage': ['master'], 'keys': { 'ed25519:$masterPub': masterPub, @@ -414,7 +415,7 @@ class Bootstrap { final selfSigningPriv = selfSigning.generate_seed(); final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv); final json = { - 'user_id': client.userID, + 'user_id': userID, 'usage': ['self_signing'], 'keys': { 'ed25519:$selfSigningPub': selfSigningPub, @@ -422,7 +423,7 @@ class Bootstrap { }; final signature = _sign(json); json['signatures'] = { - client.userID: { + userID: { 'ed25519:$masterPub': signature, }, }; @@ -439,7 +440,7 @@ class Bootstrap { final userSigningPriv = userSigning.generate_seed(); final userSigningPub = userSigning.init_with_seed(userSigningPriv); final json = { - 'user_id': client.userID, + 'user_id': userID, 'usage': ['user_signing'], 'keys': { 'ed25519:$userSigningPub': userSigningPub, @@ -447,7 +448,7 @@ class Bootstrap { }; final signature = _sign(json); json['signatures'] = { - client.userID: { + userID: { 'ed25519:$masterPub': signature, }, }; @@ -462,7 +463,7 @@ class Bootstrap { state = BootstrapState.loading; Logs().v('Upload device signing keys.'); await client.uiaRequestBackground( - (AuthenticationData auth) => client.uploadCrossSigningKeys( + (AuthenticationData? auth) => client.uploadCrossSigningKeys( masterKey: masterKey, selfSigningKey: selfSigningKey, userSigningKey: userSigningKey, diff --git a/lib/encryption/utils/json_signature_check_extension.dart b/lib/encryption/utils/json_signature_check_extension.dart index 7c89bca8..63afe7b2 100644 --- a/lib/encryption/utils/json_signature_check_extension.dart +++ b/lib/encryption/utils/json_signature_check_extension.dart @@ -24,8 +24,10 @@ import '../../matrix.dart'; extension JsonSignatureCheckExtension on Map { /// Checks the signature of a signed json object. bool checkJsonSignature(String key, String userId, String deviceId) { - final Map signatures = this['signatures']; - if (signatures == null || !signatures.containsKey(userId)) return false; + final signatures = this['signatures']; + if (signatures == null || + !(signatures is Map) || + !signatures.containsKey(userId)) return false; remove('unsigned'); remove('signatures'); if (!signatures[userId].containsKey('ed25519:$deviceId')) return false; diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 449568ac..d21e9736 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -356,10 +356,8 @@ class KeyVerification { if (_nextAction == 'request') { sendStart(); } else if (_nextAction == 'done') { - if (_verifiedDevices != null) { - // and now let's sign them all in the background - encryption.crossSigning.sign(_verifiedDevices); - } + // and now let's sign them all in the background + encryption.crossSigning.sign(_verifiedDevices); setState(KeyVerificationState.done); } }; @@ -530,8 +528,7 @@ class KeyVerification { } Future verifyActivity() async { - if (lastActivity != null && - lastActivity.add(Duration(minutes: 10)).isAfter(DateTime.now())) { + if (lastActivity.add(Duration(minutes: 10)).isAfter(DateTime.now())) { lastActivity = DateTime.now(); return true; } @@ -876,7 +873,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { : theirInfo + ourInfo) + request.transactionId!; } else if (keyAgreementProtocol == 'curve25519') { - final ourInfo = client.userID + client.deviceID; + final ourInfo = client.userID! + client.deviceID!; final theirInfo = request.userId + request.deviceId!; sasInfo = 'MATRIX_KEY_VERIFICATION_SAS' + (request.startedVerification @@ -891,8 +888,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { Future _sendMac() async { final baseInfo = 'MATRIX_KEY_VERIFICATION_MAC' + - client.userID + - client.deviceID + + client.userID! + + client.deviceID! + request.userId + request.deviceId! + request.transactionId!; @@ -929,8 +926,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { final baseInfo = 'MATRIX_KEY_VERIFICATION_MAC' + request.userId + request.deviceId! + - client.userID + - client.deviceID + + client.userID! + + client.deviceID! + request.transactionId!; final keyList = payload['mac'].keys.toList(); diff --git a/lib/matrix.dart b/lib/matrix.dart index ee403b14..bbd34d5a 100644 --- a/lib/matrix.dart +++ b/lib/matrix.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH diff --git a/lib/src/client.dart b/lib/src/client.dart index 4dbbb3dd..1b680df3 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH @@ -26,6 +25,7 @@ import 'package:http/http.dart' as http; import 'package:matrix/src/utils/run_in_root.dart'; import 'package:mime/mime.dart'; import 'package:olm/olm.dart' as olm; +import 'package:collection/collection.dart' show IterableExtension; import '../encryption.dart'; import '../matrix.dart'; @@ -56,28 +56,28 @@ extension TrailingSlash on Uri { /// [Matrix](https://matrix.org) homeserver and is the entry point for this /// SDK. class Client extends MatrixApi { - int _id; + int? _id; // Keeps track of the currently ongoing syncRequest // in case we want to cancel it. - int _currentSyncId; + int _currentSyncId = -1; - int get id => _id; + int? get id => _id; - final FutureOr Function(Client) databaseBuilder; - final FutureOr Function(Client) legacyDatabaseBuilder; - final FutureOr Function(Client) databaseDestroyer; - final FutureOr Function(Client) legacyDatabaseDestroyer; - DatabaseApi _database; + final FutureOr Function(Client)? databaseBuilder; + final FutureOr Function(Client)? legacyDatabaseBuilder; + final FutureOr Function(Client)? databaseDestroyer; + final FutureOr Function(Client)? legacyDatabaseDestroyer; + DatabaseApi? _database; - DatabaseApi get database => _database; + DatabaseApi? get database => _database; bool enableE2eeRecovery; @deprecated MatrixApi get api => this; - Encryption encryption; + Encryption? encryption; Set verificationMethods; @@ -96,16 +96,17 @@ class Client extends MatrixApi { bool mxidLocalPartFallback = true; // For CommandsClientExtension - final Map Function(CommandArgs)> commands = {}; + final Map Function(CommandArgs)> commands = {}; final Filter syncFilter; - String syncFilterId; + String? syncFilterId; final Future Function(FutureOr Function(Q), Q, - {String debugLabel}) compute; + {String debugLabel})? compute; Future runInBackground( FutureOr Function(U arg) function, U arg) async { + final compute = this.compute; if (compute != null) { return await compute(function, arg); } @@ -155,30 +156,33 @@ class Client extends MatrixApi { this.legacyDatabaseBuilder, this.legacyDatabaseDestroyer, this.enableE2eeRecovery = false, - this.verificationMethods, - http.Client httpClient, - this.importantStateEvents, - this.roomPreviewLastEvents, + Set? verificationMethods, + http.Client? httpClient, + Set? importantStateEvents, + Set? roomPreviewLastEvents, this.pinUnreadRooms = false, this.pinInvitedRooms = true, this.sendMessageTimeoutSeconds = 60, this.requestHistoryOnLimitedTimeline = false, - this.supportedLoginTypes, + Set? supportedLoginTypes, this.compute, - Filter syncFilter, - @deprecated bool debug, + Filter? syncFilter, + @deprecated bool? debug, }) : syncFilter = syncFilter ?? Filter( room: RoomFilter( state: StateFilter(lazyLoadMembers: true), ), ), + importantStateEvents = importantStateEvents ??= {}, + roomPreviewLastEvents = roomPreviewLastEvents ??= {}, + supportedLoginTypes = + supportedLoginTypes ?? {AuthenticationTypes.password}, + __loginState = LoginState.loggedOut, + verificationMethods = verificationMethods ?? {}, super( httpClient: VariableTimeoutHttpClient(httpClient ?? http.Client())) { - supportedLoginTypes ??= {AuthenticationTypes.password}; - verificationMethods ??= {}; - importantStateEvents ??= {}; importantStateEvents.addAll([ EventTypes.RoomName, EventTypes.RoomAvatar, @@ -191,7 +195,6 @@ class Client extends MatrixApi { EventTypes.spaceParent, EventTypes.RoomCreate, ]); - roomPreviewLastEvents ??= {}; roomPreviewLastEvents.addAll([ EventTypes.Message, EventTypes.Encrypted, @@ -206,19 +209,19 @@ class Client extends MatrixApi { final String clientName; /// The Matrix ID of the current logged user. - String get userID => _userID; - String _userID; + String? get userID => _userID; + String? _userID; /// This points to the position in the synchronization history. - String prevBatch; + String? prevBatch; /// The device ID is an unique identifier for this device. - String get deviceID => _deviceID; - String _deviceID; + String? get deviceID => _deviceID; + String? _deviceID; /// The device name is a human readable identifier for this device. - String get deviceName => _deviceName; - String _deviceName; + String? get deviceName => _deviceName; + String? _deviceName; /// Returns the current login state. LoginState get loginState => __loginState; @@ -235,7 +238,7 @@ class Client extends MatrixApi { List _rooms = []; /// Whether this client supports end-to-end encryption using olm. - bool get encryptionEnabled => encryption != null && encryption.enabled; + bool get encryptionEnabled => encryption?.enabled == true; /// Whether this client is able to encrypt and decrypt files. bool get fileEncryptionEnabled => encryptionEnabled && true; @@ -246,9 +249,7 @@ class Client extends MatrixApi { /// Wheather this session is unknown to others bool get isUnknownSession => - !userDeviceKeys.containsKey(userID) || - !userDeviceKeys[userID].deviceKeys.containsKey(deviceID) || - !userDeviceKeys[userID].deviceKeys[deviceID].signed; + userDeviceKeys[userID]?.deviceKeys[deviceID]?.signed != true; /// Warning! This endpoint is for testing only! set rooms(List newList) { @@ -269,7 +270,7 @@ class Client extends MatrixApi { return '$clientName-$_transactionCounter-${DateTime.now().millisecondsSinceEpoch}'; } - Room getRoomByAlias(String alias) { + Room? getRoomByAlias(String alias) { for (final room in rooms) { if (room.canonicalAlias == alias) return room; } @@ -279,7 +280,7 @@ class Client extends MatrixApi { /// Searches in the local cache for the given room and returns null if not /// found. If you have loaded the [loadArchive()] before, it can also return /// archived rooms. - Room getRoomById(String id) { + Room? getRoomById(String id) { for (final room in [...rooms, ..._archivedRooms]) { if (room.id == id) return room; } @@ -288,36 +289,35 @@ class Client extends MatrixApi { } Map get directChats => - accountData['m.direct'] != null ? accountData['m.direct'].content : {}; + accountData['m.direct']?.content ?? {}; /// Returns the (first) room ID from the store which is a private chat with the user [userId]. /// Returns null if there is none. - String getDirectChatFromUserId(String userId) { - if (accountData['m.direct'] != null && - accountData['m.direct'].content[userId] is List && - accountData['m.direct'].content[userId].length > 0) { - final potentialRooms = accountData['m.direct'] - .content[userId] + String? getDirectChatFromUserId(String userId) { + final directChats = accountData['m.direct']?.content[userId]; + if (directChats is List && directChats.isNotEmpty) { + final potentialRooms = directChats .cast() .map(getRoomById) .where((room) => room != null && room.membership == Membership.join); if (potentialRooms.isNotEmpty) { - return potentialRooms - .fold( - null, - (prev, r) => prev == null - ? r - : (prev.lastEvent.originServerTs.millisecondsSinceEpoch < - r.lastEvent.originServerTs.millisecondsSinceEpoch - ? r - : prev)) - .id; + return potentialRooms.fold(potentialRooms.first!, + (Room prev, Room? r) { + if (r == null) { + return prev; + } + final prevLast = + prev.lastEvent?.originServerTs.millisecondsSinceEpoch ?? 0; + final rLast = r.lastEvent?.originServerTs.millisecondsSinceEpoch ?? 0; + + return rLast > prevLast ? r : prev; + }).id; } } for (final room in rooms) { if (room.membership == Membership.invite && - room.getState(EventTypes.RoomMember, userID)?.senderId == userId && - room.getState(EventTypes.RoomMember, userID).content['is_direct'] == + room.getState(EventTypes.RoomMember, userID!)?.senderId == userId && + room.getState(EventTypes.RoomMember, userID!)?.content['is_direct'] == true) { return room.id; } @@ -330,8 +330,8 @@ class Client extends MatrixApi { String MatrixIdOrDomain, ) async { try { - final response = await http.get( - Uri.https(MatrixIdOrDomain.domain, '/.well-known/matrix/client')); + final response = await http.get(Uri.https( + MatrixIdOrDomain.domain ?? '', '/.well-known/matrix/client')); var respBody = response.body; try { respBody = utf8.decode(response.bodyBytes); @@ -345,7 +345,7 @@ class Client extends MatrixApi { // provide a reasonable fallback. return DiscoveryInformation( mHomeserver: HomeserverInformation( - baseUrl: Uri.https(MatrixIdOrDomain.domain, '')), + baseUrl: Uri.https(MatrixIdOrDomain.domain ?? '', '')), ); } } @@ -364,19 +364,20 @@ class Client extends MatrixApi { /// login types. Throws an exception if the server is not compatible with the /// client and sets [homeserver] to [homeserverUrl] if it is. Supports the /// types `Uri` and `String`. - Future checkHomeserver(dynamic homeserverUrl, + Future checkHomeserver(dynamic homeserverUrl, {bool checkWellKnown = true}) async { try { - homeserver = + var homeserver = this.homeserver = (homeserverUrl is Uri) ? homeserverUrl : Uri.parse(homeserverUrl); - homeserver = homeserver.stripTrailingSlash(); + homeserver = this.homeserver = homeserver.stripTrailingSlash(); // Look up well known - DiscoveryInformation wellKnown; + DiscoveryInformation? wellKnown; if (checkWellKnown) { try { wellKnown = await getWellknown(); - homeserver = wellKnown.mHomeserver.baseUrl.stripTrailingSlash(); + homeserver = this.homeserver = + wellKnown.mHomeserver.baseUrl.stripTrailingSlash(); } catch (e) { Logs().v('Found no well known information', e); } @@ -390,10 +391,10 @@ class Client extends MatrixApi { versions.versions.toSet(), supportedVersions); } - final loginTypes = await getLoginFlows(); + final loginTypes = await getLoginFlows() ?? []; if (!loginTypes.any((f) => supportedLoginTypes.contains(f.type))) { throw BadServerLoginTypesException( - loginTypes.map((f) => f.type).toSet(), supportedLoginTypes); + loginTypes.map((f) => f.type ?? '').toSet(), supportedLoginTypes); } return wellKnown; @@ -408,13 +409,13 @@ class Client extends MatrixApi { /// You have to call [checkHomeserver] first to set a homeserver. @override Future register({ - String username, - String password, - String deviceId, - String initialDeviceDisplayName, - bool inhibitLogin, - AuthenticationData auth, - AccountKind kind, + String? username, + String? password, + String? deviceId, + String? initialDeviceDisplayName, + bool? inhibitLogin, + AuthenticationData? auth, + AccountKind? kind, }) async { final response = await super.register( username: username, @@ -426,17 +427,20 @@ class Client extends MatrixApi { ); // Connect if there is an access token in the response. - if (response.accessToken == null || - response.deviceId == null || - response.userId == null) { - throw Exception('Registered but token, device ID or user ID is null.'); + final accessToken = response.accessToken; + final deviceId_ = response.deviceId; + final userId = response.userId; + final homeserver = this.homeserver; + if (accessToken == null || deviceId_ == null || homeserver == null) { + throw Exception( + 'Registered but token, device ID, user ID or homeserver is null.'); } await init( - newToken: response.accessToken, - newUserID: response.userId, + newToken: accessToken, + newUserID: userId, newHomeserver: homeserver, newDeviceName: initialDeviceDisplayName ?? '', - newDeviceID: response.deviceId); + newDeviceID: deviceId_); return response; } @@ -450,20 +454,20 @@ class Client extends MatrixApi { @override Future login( LoginType type, { - AuthenticationIdentifier identifier, - String password, - String token, - String deviceId, - String initialDeviceDisplayName, - AuthenticationData auth, - @Deprecated('Deprecated in favour of identifier.') String user, - @Deprecated('Deprecated in favour of identifier.') String medium, - @Deprecated('Deprecated in favour of identifier.') String address, + AuthenticationIdentifier? identifier, + String? password, + String? token, + String? deviceId, + String? initialDeviceDisplayName, + AuthenticationData? auth, + @Deprecated('Deprecated in favour of identifier.') String? user, + @Deprecated('Deprecated in favour of identifier.') String? medium, + @Deprecated('Deprecated in favour of identifier.') String? address, }) async { - if (homeserver == null && user.isValidMatrixId) { + if (homeserver == null && user != null && user.isValidMatrixId) { await checkHomeserver(user.domain); } - final loginResp = await super.login( + final response = await super.login( type, identifier: identifier, password: password, @@ -479,19 +483,24 @@ class Client extends MatrixApi { ); // Connect if there is an access token in the response. - if (loginResp.accessToken == null || - loginResp.deviceId == null || - loginResp.userId == null) { + final accessToken = response.accessToken; + final deviceId_ = response.deviceId; + final userId = response.userId; + final homeserver_ = homeserver; + if (accessToken == null || + deviceId_ == null || + userId == null || + homeserver_ == null) { throw Exception('Registered but token, device ID or user ID is null.'); } await init( - newToken: loginResp.accessToken, - newUserID: loginResp.userId, - newHomeserver: homeserver, + newToken: accessToken, + newUserID: userId, + newHomeserver: homeserver_, newDeviceName: initialDeviceDisplayName ?? '', - newDeviceID: loginResp.deviceId, + newDeviceID: deviceId_, ); - return loginResp; + return response; } /// Sends a logout command to the homeserver and clears all local data, @@ -524,18 +533,20 @@ class Client extends MatrixApi { /// Run any request and react on user interactive authentication flows here. Future uiaRequestBackground( - Future Function(AuthenticationData auth) request) { + Future Function(AuthenticationData? auth) request) { final completer = Completer(); - UiaRequest uia; + UiaRequest? uia; uia = UiaRequest( request: request, onUpdate: (state) { - if (state == UiaRequestState.done) { - completer.complete(uia.result); - } else if (state == UiaRequestState.fail) { - completer.completeError(uia.error); - } else { - onUiaRequest.add(uia); + if (uia != null) { + if (state == UiaRequestState.done) { + completer.complete(uia.result); + } else if (state == UiaRequestState.fail) { + completer.completeError(uia.error!); + } else { + onUiaRequest.add(uia); + } } }, ); @@ -556,8 +567,6 @@ class Client extends MatrixApi { preset: CreateRoomPreset.trustedPrivateChat, ); - if (roomId == null) return roomId; - await Room(id: roomId, client: this).addToDirectChat(mxid); return roomId; @@ -571,13 +580,13 @@ class Client extends MatrixApi { /// /// https://github.com/matrix-org/matrix-doc/blob/matthew/msc1772/proposals/1772-groups-as-rooms.md Future createSpace({ - String name, - String topic, + String? name, + String? topic, Visibility visibility = Visibility.public, - String spaceAliasName, - List invite, - List invite3pid, - String roomVersion, + String? spaceAliasName, + List? invite, + List? invite3pid, + String? roomVersion, }) => createRoom( name: name, @@ -599,12 +608,12 @@ class Client extends MatrixApi { if (rooms.isNotEmpty) { final profileSet = {}; for (final room in rooms) { - final user = room.getUserByMXIDSync(userID); + final user = room.getUserByMXIDSync(userID!); profileSet.add(Profile.fromJson(user.content)); } if (profileSet.length == 1) return profileSet.first; } - return getProfileFromUserId(userID); + return getProfileFromUserId(userID!); } final Map _profileCache = {}; @@ -619,13 +628,9 @@ class Client extends MatrixApi { Future getProfileFromUserId(String userId, {bool cache = true, bool getFromRooms = true}) async { if (getFromRooms) { - final room = rooms.firstWhere( - (Room room) => - room - .getParticipants() - .indexWhere((User user) => user.id == userId) != - -1, - orElse: () => null); + final room = rooms.firstWhereOrNull((Room room) => + room.getParticipants().indexWhere((User user) => user.id == userId) != + -1); if (room != null) { final user = room.getParticipants().firstWhere((User user) => user.id == userId); @@ -635,14 +640,15 @@ class Client extends MatrixApi { avatarUrl: user.avatarUrl); } } - if (cache && _profileCache.containsKey(userId)) { - final profile = _profileCache[userId]; + + var profile = _profileCache[userId]; + if (cache && profile != null) { return Profile( userId: userId, displayName: profile.displayname, avatarUrl: profile.avatarUrl); } - final profile = await getUserProfile(userId); + profile = await getUserProfile(userId); _profileCache[userId] = profile; return Profile( userId: userId, @@ -661,8 +667,10 @@ class Client extends MatrixApi { filter: '{"room":{"include_leave":true,"timeline":{"limit":10}}}', timeout: 0, ); - if (syncResp.rooms?.leave != null) { - for (final entry in syncResp.rooms.leave.entries) { + + final leave = syncResp.rooms?.leave; + if (leave != null) { + for (final entry in leave.entries) { final id = entry.key; final room = entry.value; final leftRoom = Room( @@ -670,25 +678,22 @@ class Client extends MatrixApi { membership: Membership.leave, client: this, roomAccountData: - room.accountData?.asMap()?.map((k, v) => MapEntry(v.type, v)) ?? + room.accountData?.asMap().map((k, v) => MapEntry(v.type, v)) ?? {}, ); - if (room.timeline?.events != null) { - for (final event in room.timeline.events) { - leftRoom.setState(Event.fromMatrixEvent( - event, - leftRoom, - )); - } - } - if (room.state != null) { - for (final event in room.state) { - leftRoom.setState(Event.fromMatrixEvent( - event, - leftRoom, - )); - } - } + + room.timeline?.events?.forEach((event) { + leftRoom.setState(Event.fromMatrixEvent( + event, + leftRoom, + )); + }); + room.state?.forEach((event) { + leftRoom.setState(Event.fromMatrixEvent( + event, + leftRoom, + )); + }); _archivedRooms.add(leftRoom); } } @@ -699,12 +704,13 @@ class Client extends MatrixApi { /// and returns the mxc url. @override Future uploadContent(Uint8List file, - {String filename, String contentType}) async { + {String? filename, String? contentType}) async { contentType ??= lookupMimeType(filename ?? '', headerBytes: file); final mxc = await super .uploadContent(file, filename: filename, contentType: contentType); - final storeable = database != null && file.length <= database.maxFileSize; - if (storeable) { + + final database = this.database; + if (database != null && file.length <= database.maxFileSize) { await database.storeFile( mxc, file, DateTime.now().millisecondsSinceEpoch); } @@ -717,13 +723,13 @@ class Client extends MatrixApi { String userId, String roomId, bool typing, { - int timeout, + int? timeout, }) async { await super.setTyping(userId, roomId, typing, timeout: timeout); final room = getRoomById(roomId); if (typing && room != null && encryptionEnabled && room.encrypted) { // ignore: unawaited_futures - encryption.keyManager.prepareOutboundGroupSession(roomId); + encryption?.keyManager.prepareOutboundGroupSession(roomId); } } @@ -734,19 +740,21 @@ class Client extends MatrixApi { filename: file.name, contentType: file.mimeType, ); - await setAvatarUrl(userID, uploadResp); + await setAvatarUrl(userID!, uploadResp); return; } /// Returns the global push rules for the logged in user. - PushRuleSet get globalPushRules => accountData.containsKey('m.push_rules') - ? PushRuleSet.fromJson(accountData['m.push_rules'].content['global']) - : null; + PushRuleSet? get globalPushRules { + final pushrules = accountData['m.push_rules']?.content['global']; + return pushrules != null ? PushRuleSet.fromJson(pushrules) : null; + } /// Returns the device push rules for the logged in user. - PushRuleSet get devicePushRules => accountData.containsKey('m.push_rules') - ? PushRuleSet.fromJson(accountData['m.push_rules'].content['device']) - : null; + PushRuleSet? get devicePushRules { + final pushrules = accountData['m.push_rules']?.content['device']; + return pushrules != null ? PushRuleSet.fromJson(pushrules) : null; + } static const Set supportedVersions = {'r0.5.0', 'r0.6.0'}; static const List supportedDirectEncryptionAlgorithms = [ @@ -848,12 +856,12 @@ class Client extends MatrixApi { @Deprecated('Use init() instead') void connect({ - String newToken, - Uri newHomeserver, - String newUserID, - String newDeviceName, - String newDeviceID, - String newOlmAccount, + String? newToken, + Uri? newHomeserver, + String? newUserID, + String? newDeviceName, + String? newDeviceID, + String? newOlmAccount, }) => init( newToken: newToken, @@ -880,12 +888,12 @@ class Client extends MatrixApi { /// all of them must be set! If you don't set them, this method will try to /// get them from the database. Future init({ - String newToken, - Uri newHomeserver, - String newUserID, - String newDeviceName, - String newDeviceID, - String newOlmAccount, + String? newToken, + Uri? newHomeserver, + String? newUserID, + String? newDeviceName, + String? newDeviceID, + String? newOlmAccount, bool waitForFirstSync = true, }) async { if ((newToken != null || @@ -909,35 +917,35 @@ class Client extends MatrixApi { } if (databaseBuilder != null) { - _database ??= await databaseBuilder(this); + _database ??= await databaseBuilder?.call(this); } - String olmAccount; - if (database != null) { - final account = await database.getClient(clientName); - if (account != null) { - _id = account['client_id']; - homeserver = Uri.parse(account['homeserver_url']); - accessToken = account['token']; - _userID = account['user_id']; - _deviceID = account['device_id']; - _deviceName = account['device_name']; - syncFilterId = account['sync_filter_id']; - prevBatch = account['prev_batch']; - olmAccount = account['olm_account']; - } + String? olmAccount; + String? accessToken; + String? _userID; + final account = await this.database?.getClient(clientName); + if (account != null) { + _id = account['client_id']; + homeserver = Uri.parse(account['homeserver_url']); + accessToken = this.accessToken = account['token']; + _userID = this._userID = account['user_id']; + _deviceID = account['device_id']; + _deviceName = account['device_name']; + syncFilterId = account['sync_filter_id']; + prevBatch = account['prev_batch']; + olmAccount = account['olm_account']; } if (newToken != null) { - accessToken = newToken; + accessToken = this.accessToken = newToken; homeserver = newHomeserver; - _userID = newUserID; + _userID = this._userID = newUserID; _deviceID = newDeviceID; _deviceName = newDeviceName; olmAccount = newOlmAccount; } else { - accessToken = newToken ?? accessToken; + accessToken = this.accessToken = newToken ?? accessToken; homeserver = newHomeserver ?? homeserver; - _userID = newUserID ?? _userID; + _userID = this._userID = newUserID ?? _userID; _deviceID = newDeviceID ?? _deviceID; _deviceName = newDeviceName ?? _deviceName; olmAccount = newOlmAccount ?? olmAccount; @@ -970,6 +978,7 @@ class Client extends MatrixApi { } await encryption?.init(olmAccount); + final database = this.database; if (database != null) { if (id != null) { await database.updateClient( @@ -1002,7 +1011,7 @@ class Client extends MatrixApi { _initLock = false; _loginState = LoginState.loggedIn; Logs().i( - 'Successfully connected as ${userID.localpart} with ${homeserver.toString()}', + 'Successfully connected as ${userID?.localpart} with ${homeserver.toString()}', ); final syncFuture = _sync(); @@ -1042,6 +1051,7 @@ class Client extends MatrixApi { _rooms = []; encryption?.dispose(); encryption = null; + final databaseDestroyer = this.databaseDestroyer; if (databaseDestroyer != null) { try { await database?.close(); @@ -1055,7 +1065,8 @@ class Client extends MatrixApi { } bool _backgroundSync = true; - Future _currentSync, _retryDelay = Future.value(); + Future? _currentSync; + Future _retryDelay = Future.value(); bool get syncPending => _currentSync != null; @@ -1073,25 +1084,28 @@ class Client extends MatrixApi { return _sync(); } - Future _sync() { + Future _sync() async { if (_currentSync == null) { - _currentSync = _innerSync(); + final _currentSync = this._currentSync = _innerSync(); + // ignore: unawaited_futures _currentSync.whenComplete(() { - _currentSync = null; + this._currentSync = null; if (_backgroundSync && isLogged() && !_disposed) { _sync(); } }); } - return _currentSync; + await _currentSync; } /// Presence that is set on sync. - PresenceType syncPresence; + PresenceType? syncPresence; Future _checkSyncFilter() async { - if (syncFilterId == null) { - syncFilterId = await defineFilter(userID, syncFilter); + final userID = this.userID; + if (syncFilterId == null && userID != null) { + final syncFilterId = + this.syncFilterId = await defineFilter(userID, syncFilter); await database?.storeSyncFilterId(syncFilterId); } return; @@ -1113,7 +1127,7 @@ class Client extends MatrixApi { since: prevBatch, timeout: prevBatch != null ? 30000 : null, setPresence: syncPresence, - ).catchError((e) { + ).then((v) => Future.value(v)).catchError((e) { syncError = e; return null; }); @@ -1127,6 +1141,8 @@ class Client extends MatrixApi { .w('Current sync request ID has changed. Dropping this sync loop!'); return; } + + final database = this.database; if (database != null) { _currentTransaction = database.transaction(() async { await _handleSync(syncResp); @@ -1151,7 +1167,7 @@ class Client extends MatrixApi { DateTime.now().subtract(Duration(days: 30)).millisecondsSinceEpoch); await updateUserDeviceKeys(); if (encryptionEnabled) { - encryption.onSync(); + encryption?.onSync(); } // try to process the to_device queue @@ -1189,47 +1205,45 @@ class Client extends MatrixApi { } Future _handleSync(SyncUpdate sync, {bool sortAtTheEnd = false}) async { - if (sync.toDevice != null) { - await _handleToDeviceEvents(sync.toDevice); + final syncToDevice = sync.toDevice; + if (syncToDevice != null) { + await _handleToDeviceEvents(syncToDevice); } + if (sync.rooms != null) { - if (sync.rooms.join != null) { - await _handleRooms(sync.rooms.join, Membership.join, - sortAtTheEnd: sortAtTheEnd); + final join = sync.rooms?.join; + if (join != null) { + await _handleRooms(join, sortAtTheEnd: sortAtTheEnd); } - if (sync.rooms.invite != null) { - await _handleRooms(sync.rooms.invite, Membership.invite, - sortAtTheEnd: sortAtTheEnd); + final invite = sync.rooms?.invite; + if (invite != null) { + await _handleRooms(invite, sortAtTheEnd: sortAtTheEnd); } - if (sync.rooms.leave != null) { - await _handleRooms(sync.rooms.leave, Membership.leave, - sortAtTheEnd: sortAtTheEnd); + final leave = sync.rooms?.leave; + if (leave != null) { + await _handleRooms(leave, sortAtTheEnd: sortAtTheEnd); } _sortRooms(); } - if (sync.presence != null) { - for (final newPresence in sync.presence) { - presences[newPresence.senderId] = newPresence; - onPresence.add(newPresence); - } + for (final newPresence in sync.presence ?? []) { + presences[newPresence.senderId] = newPresence; + onPresence.add(newPresence); } - if (sync.accountData != null) { - for (final newAccountData in sync.accountData) { - if (database != null) { - await database.storeAccountData( - newAccountData.type, - jsonEncode(newAccountData.content), - ); - } - accountData[newAccountData.type] = newAccountData; - if (onAccountData != null) onAccountData.add(newAccountData); - } + for (final newAccountData in sync.accountData ?? []) { + await database?.storeAccountData( + newAccountData.type, + jsonEncode(newAccountData.content), + ); + accountData[newAccountData.type] = newAccountData; + onAccountData.add(newAccountData); } - if (sync.deviceLists != null) { - await _handleDeviceListsEvents(sync.deviceLists); + + final syncDeviceLists = sync.deviceLists; + if (syncDeviceLists != null) { + await _handleDeviceListsEvents(syncDeviceLists); } if (encryptionEnabled) { - encryption.handleDeviceOneTimeKeysCount( + encryption?.handleDeviceOneTimeKeysCount( sync.deviceOneTimeKeysCount, sync.deviceUnusedFallbackKeyTypes); } onSync.add(sync); @@ -1237,15 +1251,14 @@ class Client extends MatrixApi { Future _handleDeviceListsEvents(DeviceListsUpdate deviceLists) async { if (deviceLists.changed is List) { - for (final userId in deviceLists.changed) { - if (_userDeviceKeys.containsKey(userId)) { - _userDeviceKeys[userId].outdated = true; - if (database != null) { - await database.storeUserDeviceKeysInfo(userId, true); - } + for (final userId in deviceLists.changed ?? []) { + final userKeys = _userDeviceKeys[userId]; + if (userKeys != null) { + userKeys.outdated = true; + await database?.storeUserDeviceKeysInfo(userId, true); } } - for (final userId in deviceLists.left) { + for (final userId in deviceLists.left ?? []) { if (_userDeviceKeys.containsKey(userId)) { _userDeviceKeys.remove(userId); } @@ -1258,18 +1271,17 @@ class Client extends MatrixApi { var toDeviceEvent = ToDeviceEvent.fromJson(event.toJson()); Logs().v('Got to_device event of type ${toDeviceEvent.type}'); if (toDeviceEvent.type == EventTypes.Encrypted && encryptionEnabled) { - toDeviceEvent = await encryption.decryptToDeviceEvent(toDeviceEvent); + toDeviceEvent = await encryption!.decryptToDeviceEvent(toDeviceEvent); Logs().v('Decrypted type is: ${toDeviceEvent.type}'); } if (encryptionEnabled) { - await encryption.handleToDeviceEvent(toDeviceEvent); + await encryption?.handleToDeviceEvent(toDeviceEvent); } onToDeviceEvent.add(toDeviceEvent); } } - Future _handleRooms( - Map rooms, Membership membership, + Future _handleRooms(Map rooms, {bool sortAtTheEnd = false}) async { var handledRooms = 0; for (final entry in rooms.entries) { @@ -1280,67 +1292,73 @@ class Client extends MatrixApi { final id = entry.key; final room = entry.value; - if (database != null) { - // TODO: This method seems to be rather slow for some updates - // Perhaps don't dynamically build that one query? - await database.storeRoomUpdate(id, room, getRoomById(id)); - } + await database?.storeRoomUpdate(id, room, this); _updateRoomsByRoomUpdate(id, room); /// Handle now all room events and save them in the database if (room is JoinedRoomUpdate) { - if (room.state?.isNotEmpty ?? false) { + final state = room.state; + if (state != null && state.isNotEmpty) { // TODO: This method seems to be comperatively slow for some updates - await _handleRoomEvents(id, - room.state.map((i) => i.toJson()).toList(), EventUpdateType.state, + await _handleRoomEvents( + id, state.map((i) => i.toJson()).toList(), EventUpdateType.state, sortAtTheEnd: sortAtTheEnd); } - if (room.timeline?.events?.isNotEmpty ?? false) { + + final timelineEvents = room.timeline?.events; + if (timelineEvents != null && timelineEvents.isNotEmpty) { await _handleRoomEvents( id, - room.timeline.events.map((i) => i.toJson()).toList(), + timelineEvents.map((i) => i.toJson()).toList(), sortAtTheEnd ? EventUpdateType.history : EventUpdateType.timeline, sortAtTheEnd: sortAtTheEnd); } - if (room.ephemeral?.isNotEmpty ?? false) { + + final ephemeral = room.ephemeral; + if (ephemeral != null && ephemeral.isNotEmpty) { // TODO: This method seems to be comperatively slow for some updates await _handleEphemerals( - id, room.ephemeral.map((i) => i.toJson()).toList()); + id, ephemeral.map((i) => i.toJson()).toList()); } - if (room.accountData?.isNotEmpty ?? false) { + + final accountData = room.accountData; + if (accountData != null && accountData.isNotEmpty) { await _handleRoomEvents( id, - room.accountData.map((i) => i.toJson()).toList(), + accountData.map((i) => i.toJson()).toList(), EventUpdateType.accountData); } } + if (room is LeftRoomUpdate) { - if (room.timeline?.events?.isNotEmpty ?? false) { + final timelineEvents = room.timeline?.events; + if (timelineEvents != null && timelineEvents.isNotEmpty) { await _handleRoomEvents( id, - room.timeline.events.map((i) => i.toJson()).toList(), + timelineEvents.map((i) => i.toJson()).toList(), EventUpdateType.timeline, sortAtTheEnd: sortAtTheEnd); } - if (room.accountData?.isNotEmpty ?? false) { + final accountData = room.accountData; + if (accountData != null && accountData.isNotEmpty) { await _handleRoomEvents( id, - room.accountData.map((i) => i.toJson()).toList(), + accountData.map((i) => i.toJson()).toList(), EventUpdateType.accountData); } - if (room.state?.isNotEmpty ?? false) { + final state = room.state; + if (state != null && state.isNotEmpty) { await _handleRoomEvents( - id, - room.state.map((i) => i.toJson()).toList(), - EventUpdateType.state); + id, state.map((i) => i.toJson()).toList(), EventUpdateType.state); } } - if (room is InvitedRoomUpdate && - (room.inviteState?.isNotEmpty ?? false)) { - await _handleRoomEvents( - id, - room.inviteState.map((i) => i.toJson()).toList(), - EventUpdateType.inviteState); + + if (room is InvitedRoomUpdate) { + final state = room.inviteState; + if (state != null && state.isNotEmpty) { + await _handleRoomEvents(id, state.map((i) => i.toJson()).toList(), + EventUpdateType.inviteState); + } } } } @@ -1354,7 +1372,7 @@ class Client extends MatrixApi { // there. if (event['type'] == 'm.receipt') { var room = getRoomById(id); - room ??= Room(id: id); + room ??= Room(id: id, client: this); final receiptStateContent = room.roomAccountData['m.receipt']?.content ?? {}; @@ -1425,17 +1443,17 @@ class Client extends MatrixApi { database != null && room.getState(EventTypes.RoomMember, event['sender']) == null) { // In order to correctly render room list previews we need to fetch the member from the database - final user = await database.getUser(event['sender'], room); + final user = await database?.getUser(event['sender'], room); if (user != null) { room.setState(user); } } _updateRoomsByEventUpdate(update); - if (type != EventUpdateType.ephemeral && database != null) { - await database.storeEventUpdate(update); + if (type != EventUpdateType.ephemeral) { + await database?.storeEventUpdate(update, this); } if (encryptionEnabled) { - await encryption.handleEventUpdate(update); + await encryption?.handleEventUpdate(update); } onEvent.add(update); @@ -1478,11 +1496,8 @@ class Client extends MatrixApi { void _updateRoomsByRoomUpdate(String roomId, SyncRoomUpdate chatUpdate) { // Update the chat list item. // Search the room in the rooms - num j = 0; - for (j = 0; j < rooms.length; j++) { - if (rooms[j].id == roomId) break; - } - final found = (j < rooms.length && rooms[j].id == roomId); + final roomIndex = rooms.indexWhere((r) => r.id == roomId); + final found = roomIndex != -1; final membership = chatUpdate is LeftRoomUpdate ? Membership.leave : chatUpdate is InvitedRoomUpdate @@ -1491,16 +1506,17 @@ class Client extends MatrixApi { // Does the chat already exist in the list rooms? if (!found && membership != Membership.leave) { - final position = membership == Membership.invite ? 0 : j; + final position = membership == Membership.invite ? 0 : rooms.length; // Add the new chat to the list final newRoom = chatUpdate is JoinedRoomUpdate ? Room( id: roomId, membership: membership, prev_batch: chatUpdate.timeline?.prevBatch, - highlightCount: chatUpdate.unreadNotifications?.highlightCount, + highlightCount: + chatUpdate.unreadNotifications?.highlightCount ?? 0, notificationCount: - chatUpdate.unreadNotifications?.notificationCount, + chatUpdate.unreadNotifications?.notificationCount ?? 0, summary: chatUpdate.summary, client: this, ) @@ -1509,36 +1525,39 @@ class Client extends MatrixApi { } // If the membership is "leave" then remove the item and stop here else if (found && membership == Membership.leave) { - rooms.removeAt(j); + rooms.removeAt(roomIndex); } // Update notification, highlight count and/or additional informations else if (found && chatUpdate is JoinedRoomUpdate && - (rooms[j].membership != membership || - rooms[j].notificationCount != + (rooms[roomIndex].membership != membership || + rooms[roomIndex].notificationCount != (chatUpdate.unreadNotifications?.notificationCount ?? 0) || - rooms[j].highlightCount != + rooms[roomIndex].highlightCount != (chatUpdate.unreadNotifications?.highlightCount ?? 0) || chatUpdate.summary != null || chatUpdate.timeline?.prevBatch != null)) { - rooms[j].membership = membership; - rooms[j].notificationCount = + rooms[roomIndex].membership = membership; + rooms[roomIndex].notificationCount = chatUpdate.unreadNotifications?.notificationCount ?? 0; - rooms[j].highlightCount = + rooms[roomIndex].highlightCount = chatUpdate.unreadNotifications?.highlightCount ?? 0; if (chatUpdate.timeline?.prevBatch != null) { - rooms[j].prev_batch = chatUpdate.timeline?.prevBatch; + rooms[roomIndex].prev_batch = chatUpdate.timeline?.prevBatch; } - if (chatUpdate.summary != null) { - final roomSummaryJson = rooms[j].summary.toJson() - ..addAll(chatUpdate.summary.toJson()); - rooms[j].summary = RoomSummary.fromJson(roomSummaryJson); + + final summary = chatUpdate.summary; + if (summary != null) { + final roomSummaryJson = rooms[roomIndex].summary.toJson() + ..addAll(summary.toJson()); + rooms[roomIndex].summary = RoomSummary.fromJson(roomSummaryJson); } - if (rooms[j].onUpdate != null) rooms[j].onUpdate.add(rooms[j].id); - if ((chatUpdate?.timeline?.limited ?? false) && + rooms[roomIndex].onUpdate.add(rooms[roomIndex].id); + if ((chatUpdate.timeline?.limited ?? false) && requestHistoryOnLimitedTimeline) { - Logs().v('Limited timeline for ${rooms[j].id} request history now'); - runInRoot(rooms[j].requestHistory); + Logs().v( + 'Limited timeline for ${rooms[roomIndex].id} request history now'); + runInRoot(rooms[roomIndex].requestHistory); } } } @@ -1568,10 +1587,10 @@ class Client extends MatrixApi { } else { if (stateEvent.type != EventTypes.Message || stateEvent.relationshipType != RelationshipTypes.edit || - stateEvent.relationshipEventId == room.lastEvent.eventId || - ((room.lastEvent.relationshipType == RelationshipTypes.edit && + stateEvent.relationshipEventId == room.lastEvent?.eventId || + ((room.lastEvent?.relationshipType == RelationshipTypes.edit && stateEvent.relationshipEventId == - room.lastEvent.relationshipEventId))) { + room.lastEvent?.relationshipEventId))) { room.setState(stateEvent); } } @@ -1611,7 +1630,7 @@ class Client extends MatrixApi { void _sortRooms() { if (prevBatch == null || _sortLock || rooms.length < 2) return; _sortLock = true; - rooms?.sort(sortRoomsBy); + rooms.sort(sortRoomsBy); _sortLock = false; } @@ -1620,10 +1639,10 @@ class Client extends MatrixApi { Map _userDeviceKeys = {}; /// Gets user device keys by its curve25519 key. Returns null if it isn't found - DeviceKeys getUserDeviceKeysByCurve25519Key(String senderKey) { + DeviceKeys? getUserDeviceKeysByCurve25519Key(String senderKey) { for (final user in userDeviceKeys.values) { final device = user.deviceKeys.values - .firstWhere((e) => e.curve25519Key == senderKey, orElse: () => null); + .firstWhereOrNull((e) => e.curve25519Key == senderKey); if (device != null) { return device; } @@ -1655,11 +1674,12 @@ class Client extends MatrixApi { Future updateUserDeviceKeys() async { try { + final database = this.database; if (!isLogged() || database == null) return; final dbActions = Function()>[]; final trackedUserIds = await _getUserIdsInEncryptedRooms(); if (!isLogged()) return; - trackedUserIds.add(userID); + trackedUserIds.add(userID!); // Remove all userIds we no longer need to track the devices of. _userDeviceKeys @@ -1668,15 +1688,14 @@ class Client extends MatrixApi { // Check if there are outdated device key lists. Add it to the set. final outdatedLists = >{}; for (final userId in trackedUserIds) { - if (!userDeviceKeys.containsKey(userId)) { - _userDeviceKeys[userId] = DeviceKeysList(userId, this); - } - final deviceKeysList = userDeviceKeys[userId]; + final deviceKeysList = + _userDeviceKeys[userId] ??= DeviceKeysList(userId, this); + final failure = _keyQueryFailures[userId.domain]; if (deviceKeysList.outdated && - (!_keyQueryFailures.containsKey(userId.domain) || + (failure == null || DateTime.now() .subtract(Duration(minutes: 5)) - .isAfter(_keyQueryFailures[userId.domain]))) { + .isAfter(failure))) { outdatedLists[userId] = []; } } @@ -1686,15 +1705,14 @@ class Client extends MatrixApi { final response = await queryKeys(outdatedLists, timeout: 10000); if (!isLogged()) return; - if (response.deviceKeys != null) { - for (final rawDeviceKeyListEntry in response.deviceKeys.entries) { + final deviceKeys = response.deviceKeys; + if (deviceKeys != null) { + for (final rawDeviceKeyListEntry in deviceKeys.entries) { final userId = rawDeviceKeyListEntry.key; - if (!userDeviceKeys.containsKey(userId)) { - _userDeviceKeys[userId] = DeviceKeysList(userId, this); - } - final oldKeys = Map.from( - _userDeviceKeys[userId].deviceKeys); - _userDeviceKeys[userId].deviceKeys = {}; + final userKeys = + _userDeviceKeys[userId] ??= DeviceKeysList(userId, this); + final oldKeys = Map.from(userKeys.deviceKeys); + userKeys.deviceKeys = {}; for (final rawDeviceKeyEntry in rawDeviceKeyListEntry.value.entries) { final deviceId = rawDeviceKeyEntry.key; @@ -1702,51 +1720,54 @@ class Client extends MatrixApi { // Set the new device key for this device final entry = DeviceKeys.fromMatrixDeviceKeys( rawDeviceKeyEntry.value, this, oldKeys[deviceId]?.lastActive); - if (entry.isValid && deviceId == entry.deviceId) { + final ed25519Key = entry.ed25519Key; + final curve25519Key = entry.curve25519Key; + if (entry.isValid && + deviceId == entry.deviceId && + ed25519Key != null && + curve25519Key != null) { // Check if deviceId or deviceKeys are known if (!oldKeys.containsKey(deviceId)) { final oldPublicKeys = await database.deviceIdSeen(userId, deviceId); if (oldPublicKeys != null && - oldPublicKeys != entry.curve25519Key + entry.ed25519Key) { + oldPublicKeys != curve25519Key + ed25519Key) { Logs().w( 'Already seen Device ID has been added again. This might be an attack!'); continue; } - final oldDeviceId = - await database.publicKeySeen(entry.ed25519Key); + final oldDeviceId = await database.publicKeySeen(ed25519Key); if (oldDeviceId != null && oldDeviceId != deviceId) { Logs().w( 'Already seen ED25519 has been added again. This might be an attack!'); continue; } final oldDeviceId2 = - await database.publicKeySeen(entry.curve25519Key); + await database.publicKeySeen(curve25519Key); if (oldDeviceId2 != null && oldDeviceId2 != deviceId) { Logs().w( 'Already seen Curve25519 has been added again. This might be an attack!'); continue; } await database.addSeenDeviceId( - userId, deviceId, entry.curve25519Key + entry.ed25519Key); - await database.addSeenPublicKey(entry.ed25519Key, deviceId); - await database.addSeenPublicKey( - entry.curve25519Key, deviceId); + userId, deviceId, curve25519Key + ed25519Key); + await database.addSeenPublicKey(ed25519Key, deviceId); + await database.addSeenPublicKey(curve25519Key, deviceId); } // is this a new key or the same one as an old one? // better store an update - the signatures might have changed! - if (!oldKeys.containsKey(deviceId) || - (oldKeys[deviceId].ed25519Key == entry.ed25519Key && - oldKeys[deviceId].curve25519Key == - entry.curve25519Key)) { - if (oldKeys.containsKey(deviceId)) { + final oldKey = oldKeys[deviceId]; + if (oldKey == null || + (oldKey.ed25519Key == entry.ed25519Key && + oldKey.curve25519Key == entry.curve25519Key)) { + if (oldKey != null) { // be sure to save the verified status - entry.setDirectVerified(oldKeys[deviceId].directVerified); - entry.blocked = oldKeys[deviceId].blocked; - entry.validSignatures = oldKeys[deviceId].validSignatures; + entry.setDirectVerified(oldKey.directVerified); + entry.blocked = oldKey.blocked; + entry.validSignatures = oldKey.validSignatures; } - _userDeviceKeys[userId].deviceKeys[deviceId] = entry; + userKeys.deviceKeys[deviceId] = entry; if (deviceId == deviceID && entry.ed25519Key == fingerprintKey) { // Always trust the own device @@ -1764,8 +1785,7 @@ class Client extends MatrixApi { // This shouldn't ever happen. The same device ID has gotten // a new public key. So we ignore the update. TODO: ask krille // if we should instead use the new key with unknown verified / blocked status - _userDeviceKeys[userId].deviceKeys[deviceId] = - oldKeys[deviceId]; + userKeys.deviceKeys[deviceId] = oldKeys[deviceId]!; } } else { Logs().w('Invalid device ${entry.userId}:${entry.deviceId}'); @@ -1774,17 +1794,15 @@ class Client extends MatrixApi { // delete old/unused entries for (final oldDeviceKeyEntry in oldKeys.entries) { final deviceId = oldDeviceKeyEntry.key; - if (!_userDeviceKeys[userId].deviceKeys.containsKey(deviceId)) { + if (!userKeys.deviceKeys.containsKey(deviceId)) { // we need to remove an old key dbActions .add(() => database.removeUserDeviceKey(userId, deviceId)); } } - _userDeviceKeys[userId].outdated = false; - if (database != null) { - dbActions - .add(() => database.storeUserDeviceKeysInfo(userId, false)); - } + userKeys.outdated = false; + dbActions + .add(() => database.storeUserDeviceKeysInfo(userId, false)); } } // next we parse and persist the cross signing keys @@ -1801,18 +1819,16 @@ class Client extends MatrixApi { } for (final crossSigningKeyListEntry in keys.entries) { final userId = crossSigningKeyListEntry.key; - if (!userDeviceKeys.containsKey(userId)) { - _userDeviceKeys[userId] = DeviceKeysList(userId, this); - } - final oldKeys = Map.from( - _userDeviceKeys[userId].crossSigningKeys); - _userDeviceKeys[userId].crossSigningKeys = {}; + final userKeys = + _userDeviceKeys[userId] ??= DeviceKeysList(userId, this); + final oldKeys = + Map.from(userKeys.crossSigningKeys); + userKeys.crossSigningKeys = {}; // add the types we aren't handling atm back for (final oldEntry in oldKeys.entries) { if (!oldEntry.value.usage.contains(keyType)) { - _userDeviceKeys[userId].crossSigningKeys[oldEntry.key] = - oldEntry.value; - } else if (database != null) { + userKeys.crossSigningKeys[oldEntry.key] = oldEntry.value; + } else { // There is a previous cross-signing key with this usage, that we no // longer need/use. Clear it from the database. dbActions.add(() => @@ -1821,45 +1837,40 @@ class Client extends MatrixApi { } final entry = CrossSigningKey.fromMatrixCrossSigningKey( crossSigningKeyListEntry.value, this); - if (entry.isValid) { - final publicKey = entry.publicKey; - if (!oldKeys.containsKey(publicKey) || - oldKeys[publicKey].ed25519Key == entry.ed25519Key) { - if (oldKeys.containsKey(publicKey)) { + final publicKey = entry.publicKey; + if (entry.isValid && publicKey != null) { + final oldKey = oldKeys[publicKey]; + if (oldKey == null || oldKey.ed25519Key == entry.ed25519Key) { + if (oldKey != null) { // be sure to save the verification status - entry.setDirectVerified(oldKeys[publicKey].directVerified); - entry.blocked = oldKeys[publicKey].blocked; - entry.validSignatures = oldKeys[publicKey].validSignatures; + entry.setDirectVerified(oldKey.directVerified); + entry.blocked = oldKey.blocked; + entry.validSignatures = oldKey.validSignatures; } - _userDeviceKeys[userId].crossSigningKeys[publicKey] = entry; + userKeys.crossSigningKeys[publicKey] = entry; } else { // This shouldn't ever happen. The same device ID has gotten // a new public key. So we ignore the update. TODO: ask krille // if we should instead use the new key with unknown verified / blocked status - _userDeviceKeys[userId].crossSigningKeys[publicKey] = - oldKeys[publicKey]; - } - if (database != null) { - dbActions.add(() => database.storeUserCrossSigningKey( - userId, - publicKey, - json.encode(entry.toJson()), - entry.directVerified, - entry.blocked, - )); + userKeys.crossSigningKeys[publicKey] = oldKey; } + dbActions.add(() => database.storeUserCrossSigningKey( + userId, + publicKey, + json.encode(entry.toJson()), + entry.directVerified, + entry.blocked, + )); } - _userDeviceKeys[userId].outdated = false; - if (database != null) { - dbActions - .add(() => database.storeUserDeviceKeysInfo(userId, false)); - } + _userDeviceKeys[userId]?.outdated = false; + dbActions + .add(() => database.storeUserDeviceKeysInfo(userId, false)); } } // now process all the failures if (response.failures != null) { - for (final failureDomain in response.failures.keys) { + for (final failureDomain in response.failures?.keys ?? []) { _keyQueryFailures[failureDomain] = DateTime.now(); } } @@ -1867,7 +1878,7 @@ class Client extends MatrixApi { if (dbActions.isNotEmpty) { if (!isLogged()) return; - await database?.transaction(() async { + await database.transaction(() async { for (final f in dbActions) { await f(); } @@ -1884,6 +1895,7 @@ class Client extends MatrixApi { /// This function MAY throw an error, which just means the to_device queue wasn't /// proccessed all the way. Future processToDeviceQueue() async { + final database = this.database; if (database == null || !_toDeviceQueueNeedsProcessing) { return; } @@ -1923,6 +1935,7 @@ class Client extends MatrixApi { '[Client] Problem while sending to_device event, retrying later...', e, s); + final database = this.database; if (database != null) { _toDeviceQueueNeedsProcessing = true; await database.insertIntoToDeviceQueue( @@ -1938,13 +1951,12 @@ class Client extends MatrixApi { Set users, String eventType, Map message, { - String messageId, + String? messageId, }) async { // Send with send-to-device messaging final data = >>{}; for (final user in users) { - data[user] = {}; - data[user]['*'] = message; + data[user] = {'*': message}; } await sendToDevice( eventType, messageId ?? generateUniqueTransactionId(), data); @@ -1958,15 +1970,15 @@ class Client extends MatrixApi { List deviceKeys, String eventType, Map message, { - String messageId, + String? messageId, bool onlyVerified = false, }) async { - if (!encryptionEnabled) return; + final encryption = this.encryption; + if (!encryptionEnabled || encryption == null) return; // Don't send this message to blocked devices, and if specified onlyVerified // then only send it to verified devices if (deviceKeys.isNotEmpty) { deviceKeys.removeWhere((DeviceKeys deviceKeys) => - deviceKeys == null || deviceKeys.blocked || (deviceKeys.userId == userID && deviceKeys.deviceId == deviceID) || (onlyVerified && !deviceKeys.verified)); @@ -1985,9 +1997,9 @@ class Client extends MatrixApi { await _sendToDeviceEncryptedLock.lock(deviceKeys); // Send with send-to-device messaging - var data = >>{}; - data = await encryption.encryptToDeviceMessage( - deviceKeys, eventType, message); + final data = await encryption.encryptToDeviceMessage( + deviceKeys, eventType, message) + as Map>>; eventType = EventTypes.Encrypted; await sendToDevice( eventType, messageId ?? generateUniqueTransactionId(), data); @@ -2057,12 +2069,8 @@ class Client extends MatrixApi { /// Whether all push notifications are muted using the [.m.rule.master] /// rule of the push rules: https://matrix.org/docs/spec/client_server/r0.6.0#m-rule-master bool get allPushNotificationsMuted { - if (!accountData.containsKey('m.push_rules') || - !(accountData['m.push_rules'].content['global'] is Map)) { - return false; - } - final Map globalPushRules = - accountData['m.push_rules'].content['global']; + final Map? globalPushRules = + accountData['m.push_rules']?.content['global']; if (globalPushRules == null) return false; if (globalPushRules['override'] is List) { @@ -2088,7 +2096,11 @@ class Client extends MatrixApi { /// Changes the password. You should either set oldPasswort or another authentication flow. @override Future changePassword(String newPassword, - {String oldPassword, AuthenticationData auth, bool logoutDevices}) async { + {String? oldPassword, + AuthenticationData? auth, + bool? logoutDevices}) async { + final userID = this.userID; + if (userID == null) return; try { if (oldPassword != null) { auth = AuthenticationPassword( @@ -2102,9 +2114,10 @@ class Client extends MatrixApi { if (!matrixException.requireAdditionalAuthentication) { rethrow; } - if (matrixException.authenticationFlows.length != 1 || - !matrixException.authenticationFlows.first.stages - .contains(AuthenticationTypes.password)) { + if (matrixException.authenticationFlows?.length != 1 || + !(matrixException.authenticationFlows?.first.stages + .contains(AuthenticationTypes.password) ?? + false)) { rethrow; } if (oldPassword == null) { @@ -2136,7 +2149,7 @@ class Client extends MatrixApi { prevBatch = null; rooms.clear(); await database?.clearCache(); - encryption?.keyManager?.clearOutboundGroupSessions(); + encryption?.keyManager.clearOutboundGroupSessions(); onCacheCleared.add(true); // Restart the syncloop backgroundSync = true; @@ -2145,9 +2158,9 @@ class Client extends MatrixApi { /// A list of mxids of users who are ignored. List get ignoredUsers => (accountData .containsKey('m.ignored_user_list') && - accountData['m.ignored_user_list'].content['ignored_users'] is Map) + accountData['m.ignored_user_list']?.content['ignored_users'] is Map) ? List.from( - accountData['m.ignored_user_list'].content['ignored_users'].keys) + accountData['m.ignored_user_list']?.content['ignored_users'].keys) : []; /// Ignore another user. This will clear the local cached messages to @@ -2156,7 +2169,7 @@ class Client extends MatrixApi { if (!userId.isValidMatrixId) { throw Exception('$userId is not a valid mxid!'); } - await setAccountData(userID, 'm.ignored_user_list', { + await setAccountData(userID!, 'm.ignored_user_list', { 'ignored_users': Map.fromEntries( (ignoredUsers..add(userId)).map((key) => MapEntry(key, {}))), }); @@ -2173,7 +2186,7 @@ class Client extends MatrixApi { if (!ignoredUsers.contains(userId)) { throw Exception('$userId is not in the ignore list!'); } - await setAccountData(userID, 'm.ignored_user_list', { + await setAccountData(userID!, 'm.ignored_user_list', { 'ignored_users': Map.fromEntries( (ignoredUsers..remove(userId)).map((key) => MapEntry(key, {}))), }); @@ -2209,9 +2222,9 @@ class Client extends MatrixApi { encryption?.dispose(); encryption = null; try { - if (closeDatabase && database != null) { + if (closeDatabase) { await database - .close() + ?.close() .catchError((e, s) => Logs().w('Failed to close database: ', e, s)); _database = null; } @@ -2223,10 +2236,11 @@ class Client extends MatrixApi { Future _migrateFromLegacyDatabase() async { Logs().i('Check legacy database for migration data...'); - final legacyDatabase = await legacyDatabaseBuilder(this); - final migrateClient = await legacyDatabase.getClient(clientName); + final legacyDatabase = await legacyDatabaseBuilder?.call(this); + final migrateClient = await legacyDatabase?.getClient(clientName); + final database = this.database; - if (migrateClient != null) { + if (migrateClient != null && legacyDatabase != null && database != null) { Logs().i('Found data in the legacy database!'); _id = migrateClient['client_id']; await database.insertClient( @@ -2246,9 +2260,9 @@ class Client extends MatrixApi { Logs().d('Migrate $type...'); await database.storeSSSSCache( type, - ssssCache.keyId, - ssssCache.ciphertext, - ssssCache.content, + ssssCache.keyId ?? '', + ssssCache.ciphertext ?? '', + ssssCache.content ?? '', ); } } @@ -2257,30 +2271,41 @@ class Client extends MatrixApi { for (final userId in userDeviceKeys.keys) { Logs().d('Migrate Device Keys of user $userId...'); final deviceKeysList = userDeviceKeys[userId]; - for (final crossSigningKey in deviceKeysList.crossSigningKeys.values) { - Logs().d( - 'Migrate cross signing key with usage ${crossSigningKey.usage} and verified ${crossSigningKey.directVerified}...'); - await database.storeUserCrossSigningKey( - userId, - crossSigningKey.publicKey, - jsonEncode(crossSigningKey.toJson()), - crossSigningKey.directVerified, - crossSigningKey.blocked, - ); + for (final crossSigningKey + in deviceKeysList?.crossSigningKeys.values ?? []) { + final pubKey = crossSigningKey.publicKey; + if (pubKey != null) { + Logs().d( + 'Migrate cross signing key with usage ${crossSigningKey.usage} and verified ${crossSigningKey.directVerified}...'); + await database.storeUserCrossSigningKey( + userId, + pubKey, + jsonEncode(crossSigningKey.toJson()), + crossSigningKey.directVerified, + crossSigningKey.blocked, + ); + } } - for (final deviceKeys in deviceKeysList.deviceKeys.values) { - Logs().d('Migrate device keys for ${deviceKeys.deviceId}...'); - await database.storeUserDeviceKey( - userId, - deviceKeys.deviceId, - jsonEncode(deviceKeys.toJson()), - deviceKeys.directVerified, - deviceKeys.blocked, - deviceKeys.lastActive.millisecondsSinceEpoch, - ); + + if (deviceKeysList != null) { + for (final deviceKeys in deviceKeysList.deviceKeys.values) { + final deviceId = deviceKeys.deviceId; + if (deviceId != null) { + Logs().d('Migrate device keys for ${deviceKeys.deviceId}...'); + await database.storeUserDeviceKey( + userId, + deviceId, + jsonEncode(deviceKeys.toJson()), + deviceKeys.directVerified, + deviceKeys.blocked, + deviceKeys.lastActive.millisecondsSinceEpoch, + ); + } + } + Logs().d('Migrate user device keys info...'); + await database.storeUserDeviceKeysInfo( + userId, deviceKeysList.outdated); } - Logs().d('Migrate user device keys info...'); - await database.storeUserDeviceKeysInfo(userId, deviceKeysList.outdated); } Logs().d('Migrate inbound group sessions...'); try { @@ -2306,7 +2331,7 @@ class Client extends MatrixApi { await legacyDatabase.clear(); await legacyDatabaseDestroyer?.call(this); } - await legacyDatabase.close(); + await legacyDatabase?.close(); _initLock = false; if (migrateClient != null) { return init(); @@ -2316,15 +2341,15 @@ class Client extends MatrixApi { class SdkError { dynamic exception; - StackTrace stackTrace; + StackTrace? stackTrace; SdkError({this.exception, this.stackTrace}); } class SyncStatusUpdate { final SyncStatus status; - final SdkError error; - final double progress; + final SdkError? error; + final double? progress; const SyncStatusUpdate(this.status, {this.error, this.progress}); } diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index 9345d8f2..368f1ce8 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -35,10 +35,10 @@ abstract class DatabaseApi { String homeserverUrl, String token, String userId, - String deviceId, - String deviceName, - String prevBatch, - String olmAccount, + String? deviceId, + String? deviceName, + String? prevBatch, + String? olmAccount, ); Future insertClient( @@ -46,10 +46,10 @@ abstract class DatabaseApi { String homeserverUrl, String token, String userId, - String deviceId, - String deviceName, - String prevBatch, - String olmAccount, + String? deviceId, + String? deviceName, + String? prevBatch, + String? olmAccount, ); Future> getRoomList(Client client); @@ -58,12 +58,12 @@ abstract class DatabaseApi { /// Stores a RoomUpdate object in the database. Must be called inside of /// [transaction]. - Future storeRoomUpdate(String roomId, SyncRoomUpdate roomUpdate, - [Room oldRoom]); + Future storeRoomUpdate( + String roomId, SyncRoomUpdate roomUpdate, Client client); /// Stores an EventUpdate object in the database. Must be called inside of /// [transaction]. - Future storeEventUpdate(EventUpdate eventUpdate); + Future storeEventUpdate(EventUpdate eventUpdate, Client client); Future getEventById(String eventId, Room room); @@ -233,6 +233,7 @@ abstract class DatabaseApi { Future setRoomPrevBatch( String prevBatch, String roomId, + Client client, ); Future resetNotificationCount(String roomId); diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index f9a4a1da..a5b81b57 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -507,6 +507,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi { Future> getRoomList(Client client) => runBenchmarked>('Get room list from hive', () async { final rooms = {}; + final userID = client.userID; final importantRoomStates = client.importantStateEvents; for (final key in _roomsBox.keys) { // Get the room @@ -515,12 +516,15 @@ class FamedlySdkHiveDatabase extends DatabaseApi { // let's see if we need any m.room.member events // We always need the member event for ourself - final membersToPostload = {client.userID}; + final membersToPostload = {if (userID != null) userID}; // If the room is a direct chat, those IDs should be there too - if (room.isDirectChat) membersToPostload.add(room.directChatMatrixID); + if (room.isDirectChat) { + membersToPostload.add(room.directChatMatrixID!); + } // the lastEvent message preview might have an author we need to fetch, if it is a group chat - if (room.getState(EventTypes.Message) != null && !room.isDirectChat) { - membersToPostload.add(room.getState(EventTypes.Message).senderId); + final lastEvent = room.getState(EventTypes.Message); + if (lastEvent != null && !room.isDirectChat) { + membersToPostload.add(lastEvent.senderId); } // if the room has no name and no canonical alias, its name is calculated // based on the heroes of the room @@ -528,7 +532,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi { room.getState(EventTypes.RoomCanonicalAlias) == null) { // we don't have a name and no canonical alias, so we'll need to // post-load the heroes - membersToPostload.addAll(room.summary?.mHeroes ?? []); + membersToPostload.addAll(room.summary.mHeroes ?? []); } // Load members for (final userId in membersToPostload) { @@ -671,10 +675,10 @@ class FamedlySdkHiveDatabase extends DatabaseApi { String homeserverUrl, String token, String userId, - String deviceId, - String deviceName, - String prevBatch, - String olmAccount) async { + String? deviceId, + String? deviceName, + String? prevBatch, + String? olmAccount) async { await _clientBox.put('homeserver_url', homeserverUrl); await _clientBox.put('token', token); await _clientBox.put('user_id', userId); @@ -816,10 +820,11 @@ class FamedlySdkHiveDatabase extends DatabaseApi { } @override - Future setRoomPrevBatch(String prevBatch, String roomId) async { + Future setRoomPrevBatch( + String prevBatch, String roomId, Client client) async { final raw = await _roomsBox.get(roomId.toHiveKey); if (raw == null) return; - final room = Room.fromJson(convertToJson(raw)); + final room = Room.fromJson(convertToJson(raw), client); room.prev_batch = prevBatch; await _roomsBox.put(roomId.toHiveKey, room.toJson()); return; @@ -860,13 +865,13 @@ class FamedlySdkHiveDatabase extends DatabaseApi { } @override - Future storeEventUpdate(EventUpdate eventUpdate) async { + Future storeEventUpdate(EventUpdate eventUpdate, Client client) async { // Ephemerals should not be stored if (eventUpdate.type == EventUpdateType.ephemeral) return; // In case of this is a redaction event if (eventUpdate.content['type'] == EventTypes.Redaction) { - final tmpRoom = Room(id: eventUpdate.roomID); + final tmpRoom = Room(id: eventUpdate.roomID, client: client); final event = await getEventById(eventUpdate.content['redacts'], tmpRoom); if (event != null) { event.setRedactionEvent(Event.fromJson(eventUpdate.content, tmpRoom)); @@ -906,13 +911,12 @@ class FamedlySdkHiveDatabase extends DatabaseApi { return; } - final status = - newStatus.isError || prevEvent == null || prevEvent.status != null - ? newStatus - : latestEventStatus( - prevEvent.status, - newStatus, - ); + final status = newStatus.isError || prevEvent == null + ? newStatus + : latestEventStatus( + prevEvent.status, + newStatus, + ); // Add the status and the sort order to the content so it get stored eventUpdate.content['unsigned'] ??= {}; @@ -1076,8 +1080,8 @@ class FamedlySdkHiveDatabase extends DatabaseApi { } @override - Future storeRoomUpdate(String roomId, SyncRoomUpdate roomUpdate, - [dynamic _]) async { + Future storeRoomUpdate( + String roomId, SyncRoomUpdate roomUpdate, Client client) async { // Leave room if membership is leave if (roomUpdate is LeftRoomUpdate) { await forgetRoom(roomId); @@ -1094,26 +1098,31 @@ class FamedlySdkHiveDatabase extends DatabaseApi { roomId.toHiveKey, roomUpdate is JoinedRoomUpdate ? Room( + client: client, id: roomId, membership: membership, highlightCount: - roomUpdate.unreadNotifications?.highlightCount?.toInt(), + roomUpdate.unreadNotifications?.highlightCount?.toInt() ?? + 0, notificationCount: roomUpdate - .unreadNotifications?.notificationCount - ?.toInt(), + .unreadNotifications?.notificationCount + ?.toInt() ?? + 0, prev_batch: roomUpdate.timeline?.prevBatch, summary: roomUpdate.summary, ).toJson() : Room( + client: client, id: roomId, membership: membership, ).toJson()); } else if (roomUpdate is JoinedRoomUpdate) { final currentRawRoom = await _roomsBox.get(roomId.toHiveKey); - final currentRoom = Room.fromJson(convertToJson(currentRawRoom)); + final currentRoom = Room.fromJson(convertToJson(currentRawRoom), client); await _roomsBox.put( roomId.toHiveKey, Room( + client: client, id: roomId, membership: membership, highlightCount: @@ -1250,10 +1259,10 @@ class FamedlySdkHiveDatabase extends DatabaseApi { String homeserverUrl, String token, String userId, - String deviceId, - String deviceName, - String prevBatch, - String olmAccount, + String? deviceId, + String? deviceName, + String? prevBatch, + String? olmAccount, ) async { await _clientBox.put('homeserver_url', homeserverUrl); await _clientBox.put('token', token); diff --git a/lib/src/event.dart b/lib/src/event.dart index 853bd683..17fb4d18 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH @@ -39,7 +38,13 @@ abstract class RelationshipTypes { /// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event. class Event extends MatrixEvent { - User get sender => room.getUserByMXIDSync(senderId ?? '@unknown:unknown'); + User get sender => + room?.getUserByMXIDSync(senderId) ?? + User.fromState( + stateKey: senderId, + typeKey: EventTypes.RoomMember, + originServerTs: DateTime.now(), + ); @Deprecated('Use [originServerTs] instead') DateTime get time => originServerTs; @@ -48,10 +53,10 @@ class Event extends MatrixEvent { String get typeKey => type; @Deprecated('Use [sender.calcDisplayname()] instead') - String get senderName => sender.calcDisplayname(); + String? get senderName => sender.calcDisplayname(); /// The room this event belongs to. May be null. - final Room room; + final Room? room; /// The status of this event. EventStatus status; @@ -59,33 +64,39 @@ class Event extends MatrixEvent { static const EventStatus defaultStatus = EventStatus.synced; /// Optional. The event that redacted this event, if any. Otherwise null. - Event get redactedBecause => - unsigned != null && unsigned['redacted_because'] is Map - ? Event.fromJson(unsigned['redacted_because'], room) - : null; + Event? get redactedBecause { + final redacted_because = unsigned?['redacted_because']; + final room = this.room; + return (redacted_because is Map) + ? Event.fromJson(redacted_because, room) + : null; + } bool get redacted => redactedBecause != null; - User get stateKeyUser => room.getUserByMXIDSync(stateKey); + User? get stateKeyUser => room?.getUserByMXIDSync(stateKey!); Event({ this.status = defaultStatus, - Map content, - String type, - String eventId, - String roomId, - String senderId, - DateTime originServerTs, - Map unsigned, - Map prevContent, - String stateKey, + required Map content, + required String type, + required String eventId, + String? roomId, + required String senderId, + required DateTime originServerTs, + Map? unsigned, + Map? prevContent, + String? stateKey, this.room, - }) { - this.content = content; - this.type = type; + }) : super( + content: content, + type: type, + eventId: eventId, + senderId: senderId, + originServerTs: originServerTs, + roomId: roomId ?? room?.id, + ) { this.eventId = eventId; - this.roomId = roomId ?? room?.id; - this.senderId = senderId; this.unsigned = unsigned; // synapse unfortunately isn't following the spec and tosses the prev_content // into the unsigned block. @@ -103,17 +114,17 @@ class Event extends MatrixEvent { // A strange bug in dart web makes this crash } this.stateKey = stateKey; - this.originServerTs = originServerTs; // Mark event as failed to send if status is `sending` and event is older // than the timeout. This should not happen with the deprecated Moor // database! - if (status.isSending && room?.client?.database != null) { + if (status.isSending && room?.client.database != null) { // Age of this event in milliseconds final age = DateTime.now().millisecondsSinceEpoch - originServerTs.millisecondsSinceEpoch; - if (age > room.client.sendMessageTimeoutSeconds * 1000) { + final room = this.room; + if (room != null && age > room.client.sendMessageTimeoutSeconds * 1000) { // Update this event in database and open timelines final json = toJson(); json['unsigned'] ??= {}; @@ -143,7 +154,7 @@ class Event extends MatrixEvent { factory Event.fromMatrixEvent( MatrixEvent matrixEvent, Room room, { - EventStatus status, + EventStatus status = defaultStatus, }) => Event( status: status, @@ -162,7 +173,7 @@ class Event extends MatrixEvent { /// Get a State event from a table row or from the event stream. factory Event.fromJson( Map jsonPayload, - Room room, + Room? room, ) { final content = Event.getMapFromPayload(jsonPayload['content']); final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']); @@ -175,7 +186,7 @@ class Event extends MatrixEvent { prevContent: prevContent, content: content, type: jsonPayload['type'], - eventId: jsonPayload['event_id'], + eventId: jsonPayload['event_id'] ?? '', roomId: jsonPayload['room_id'], senderId: jsonPayload['sender'], originServerTs: jsonPayload.containsKey('origin_server_ts') @@ -190,7 +201,7 @@ class Event extends MatrixEvent { Map toJson() { final data = {}; if (stateKey != null) data['state_key'] = stateKey; - if (prevContent != null && prevContent.isNotEmpty) { + if (prevContent?.isNotEmpty == true) { data['prev_content'] = prevContent; } data['content'] = content; @@ -199,14 +210,15 @@ class Event extends MatrixEvent { data['room_id'] = roomId; data['sender'] = senderId; data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch; - if (unsigned != null && unsigned.isNotEmpty) { + if (unsigned?.isNotEmpty == true) { data['unsigned'] = unsigned; } return data; } User get asUser => User.fromState( - stateKey: stateKey, + // state key should always be set for member events + stateKey: stateKey!, prevContent: prevContent, content: content, typeKey: type, @@ -282,8 +294,10 @@ class Event extends MatrixEvent { /// Returns a list of [Receipt] instances for this event. List get receipts { - if (!(room.roomAccountData.containsKey('m.receipt'))) return []; - return room.roomAccountData['m.receipt'].content.entries + final room = this.room; + final receipt = room?.roomAccountData['m.receipt']; + if (receipt == null || room == null) return []; + return receipt.content.entries .where((entry) => entry.value['event_id'] == eventId) .map((entry) => Receipt(room.getUserByMXIDSync(entry.key), DateTime.fromMillisecondsSinceEpoch(entry.value['ts']))) @@ -294,6 +308,11 @@ class Event extends MatrixEvent { /// This event will just be removed from the database and the timelines. /// Returns [false] if not removed. Future remove() async { + final room = this.room; + if (room == null) { + return false; + } + if (!status.isSent) { await room.client.database?.removeEvent(eventId, room.id); @@ -311,29 +330,33 @@ class Event extends MatrixEvent { return false; } - /// Try to send this event again. Only works with events of `EventStatus.isError`. - Future sendAgain({String txid}) async { + /// Try to send this event again. Only works with events of status -1. + Future sendAgain({String? txid}) async { if (!status.isError) return null; // we do not remove the event here. It will automatically be updated // in the `sendEvent` method to transition -1 -> 0 -> 1 -> 2 - final newEventId = await room.sendEvent( + final newEventId = await room?.sendEvent( content, - txid: txid ?? unsigned['transaction_id'] ?? eventId, + txid: txid ?? unsigned?['transaction_id'] ?? eventId, ); return newEventId; } /// Whether the client is allowed to redact this event. - bool get canRedact => senderId == room.client.userID || room.canRedact; + bool get canRedact => + senderId == room?.client.userID || (room?.canRedact ?? false); /// Redacts this event. Throws `ErrorResponse` on error. - Future redactEvent({String reason, String txid}) => - room.redactEvent(eventId, reason: reason, txid: txid); + Future redactEvent({String? reason, String? txid}) async => + await room?.redactEvent(eventId, reason: reason, txid: txid); /// Searches for the reply event in the given timeline. - Future getReplyEvent(Timeline timeline) async { + Future getReplyEvent(Timeline timeline) async { if (relationshipType != RelationshipTypes.reply) return null; - return await timeline.getEventById(relationshipEventId); + final relationshipEventId = this.relationshipEventId; + return relationshipEventId == null + ? null + : await timeline.getEventById(relationshipEventId); } /// If this event is encrypted and the decryption was not successful because @@ -346,7 +369,7 @@ class Event extends MatrixEvent { content['can_request_session'] != true) { throw ('Session key not requestable'); } - await room.requestSessionKey(content['session_id'], content['sender_key']); + await room?.requestSessionKey(content['session_id'], content['sender_key']); return; } @@ -388,16 +411,21 @@ class Event extends MatrixEvent { : ''); /// Gets the underlying mxc url of an attachment of a file event, or null if not present - Uri get attachmentMxcUrl => Uri.parse( - isAttachmentEncrypted ? content['file']['url'] : content['url']); + Uri? get attachmentMxcUrl { + final url = isAttachmentEncrypted ? content['file']['url'] : content['url']; + return url is String ? Uri.tryParse(url) : null; + } /// Gets the underlying mxc url of a thumbnail of a file event, or null if not present - Uri get thumbnailMxcUrl => Uri.parse(isThumbnailEncrypted - ? infoMap['thumbnail_file']['url'] - : infoMap['thumbnail_url']); + Uri? get thumbnailMxcUrl { + final url = isThumbnailEncrypted + ? infoMap['thumbnail_file']['url'] + : infoMap['thumbnail_url']; + return url is String ? Uri.tryParse(url) : null; + } /// Gets the mxc url of an attachment/thumbnail of a file event, taking sizes into account, or null if not present - Uri attachmentOrThumbnailMxcUrl({bool getThumbnail = false}) { + Uri? attachmentOrThumbnailMxcUrl({bool getThumbnail = false}) { if (getThumbnail && infoMap['size'] is int && thumbnailInfoMap['size'] is int && @@ -420,7 +448,7 @@ class Event extends MatrixEvent { /// [minNoThumbSize] is the minimum size that an original image may be to not fetch its thumbnail, defaults to 80k /// [useThumbnailMxcUrl] says weather to use the mxc url of the thumbnail, rather than the original attachment. /// [animated] says weather the thumbnail is animated - Uri getAttachmentUrl( + Uri? getAttachmentUrl( {bool getThumbnail = false, bool useThumbnailMxcUrl = false, double width = 800.0, @@ -428,6 +456,10 @@ class Event extends MatrixEvent { ThumbnailMethod method = ThumbnailMethod.scale, int minNoThumbSize = _minNoThumbSize, bool animated = false}) { + final client = room?.client; + if (client == null) { + return null; + } if (![EventTypes.Message, EventTypes.Sticker].contains(type) || !hasAttachment || isAttachmentEncrypted) { @@ -449,14 +481,14 @@ class Event extends MatrixEvent { // now generate the actual URLs if (getThumbnail) { return Uri.parse(thisMxcUrl).getThumbnail( - room.client, + client, width: width, height: height, method: method, animated: animated, ); } else { - return Uri.parse(thisMxcUrl).getDownloadLink(room.client); + return Uri.parse(thisMxcUrl).getDownloadLink(client); } } @@ -472,13 +504,17 @@ class Event extends MatrixEvent { getThumbnail = mxcUrl != attachmentMxcUrl; // Is this file storeable? final thisInfoMap = getThumbnail ? thumbnailInfoMap : infoMap; - final storeable = room.client.database != null && - thisInfoMap['size'] is int && - thisInfoMap['size'] <= room.client.database.maxFileSize; + final database = room?.client.database; + if (database == null) { + return false; + } - Uint8List uint8list; + final storeable = thisInfoMap['size'] is int && + thisInfoMap['size'] <= database.maxFileSize; + + Uint8List? uint8list; if (storeable) { - uint8list = await room.client.database.getFile(mxcUrl); + uint8list = await database.getFile(mxcUrl); } return uint8list != null; } @@ -487,12 +523,17 @@ class Event extends MatrixEvent { /// event and returns it as a [MatrixFile]. If this event doesn't /// contain an attachment, this throws an error. Set [getThumbnail] to /// true to download the thumbnail instead. - Future downloadAndDecryptAttachment( + Future downloadAndDecryptAttachment( {bool getThumbnail = false, - Future Function(Uri) downloadCallback}) async { + Future Function(Uri)? downloadCallback}) async { if (![EventTypes.Message, EventTypes.Sticker].contains(type)) { throw ("This event has the type '$type' and so it can't contain an attachment."); } + final client = room?.client; + final database = room?.client.database; + if (client == null) { + throw 'This event has no valid client.'; + } final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail); if (mxcUrl == null) { throw "This event hasn't any attachment or thumbnail."; @@ -500,33 +541,30 @@ class Event extends MatrixEvent { getThumbnail = mxcUrl != attachmentMxcUrl; final isEncrypted = getThumbnail ? isThumbnailEncrypted : isAttachmentEncrypted; - - if (isEncrypted && !room.client.encryptionEnabled) { + if (isEncrypted && !client.encryptionEnabled) { throw ('Encryption is not enabled in your Client.'); } - Uint8List uint8list; - // Is this file storeable? final thisInfoMap = getThumbnail ? thumbnailInfoMap : infoMap; - var storeable = room.client.database != null && + var storeable = database != null && thisInfoMap['size'] is int && - thisInfoMap['size'] <= room.client.database.maxFileSize; + thisInfoMap['size'] <= database.maxFileSize; + Uint8List? uint8list; if (storeable) { - uint8list = await room.client.database.getFile(mxcUrl); + uint8list = await client.database?.getFile(mxcUrl); } // Download the file if (uint8list == null) { - downloadCallback ??= (Uri url) async { - return (await http.get(url)).bodyBytes; - }; - uint8list = await downloadCallback(mxcUrl.getDownloadLink(room.client)); - storeable = storeable && - uint8list.lengthInBytes < room.client.database.maxFileSize; + downloadCallback ??= (Uri url) async => (await http.get(url)).bodyBytes; + uint8list = await downloadCallback(mxcUrl.getDownloadLink(client)); + storeable = database != null && + storeable && + uint8list.lengthInBytes < database.maxFileSize; if (storeable) { - await room.client.database.storeFile( + await database.storeFile( mxcUrl, uint8list, DateTime.now().millisecondsSinceEpoch); } } @@ -544,9 +582,10 @@ class Event extends MatrixEvent { k: fileMap['key']['k'], sha256: fileMap['hashes']['sha256'], ); - uint8list = await room.client.runInBackground(decryptFile, encryptedFile); + uint8list = await client.runInBackground( + decryptFile, encryptedFile); } - return MatrixFile(bytes: uint8list, name: body); + return uint8list != null ? MatrixFile(bytes: uint8list, name: body) : null; } /// Returns if this is a known event type. @@ -565,7 +604,7 @@ class Event extends MatrixEvent { bool plaintextBody = false, }) { if (redacted) { - return i18n.removedBy(redactedBecause.sender.calcDisplayname()); + return i18n.removedBy(redactedBecause?.sender.calcDisplayname() ?? ''); } var body = plaintextBody ? this.plaintextBody : this.body; @@ -607,8 +646,9 @@ class Event extends MatrixEvent { if (withSenderNamePrefix && type == EventTypes.Message && textOnlyMessageTypes.contains(messageType)) { - final senderNameOrYou = - senderId == room.client.userID ? i18n.you : sender.calcDisplayname(); + final senderNameOrYou = senderId == room?.client.userID + ? i18n.you + : (sender.calcDisplayname()); localizedBody = '$senderNameOrYou: $localizedBody'; } @@ -623,19 +663,19 @@ class Event extends MatrixEvent { }; /// returns if this event matches the passed event or transaction id - bool matchesEventOrTransactionId(String search) { + bool matchesEventOrTransactionId(String? search) { if (search == null) { return false; } if (eventId == search) { return true; } - return unsigned != null && unsigned['transaction_id'] == search; + return unsigned?['transaction_id'] == search; } /// Get the relationship type of an event. `null` if there is none - String get relationshipType { - if (content?.tryGet>('m.relates_to') == null) { + String? get relationshipType { + if (content.tryGet>('m.relates_to') == null) { return null; } if (content['m.relates_to'].containsKey('m.in_reply_to')) { @@ -643,12 +683,12 @@ class Event extends MatrixEvent { } return content .tryGet>('m.relates_to') - .tryGet('rel_type'); + ?.tryGet('rel_type'); } /// Get the event ID that this relationship will reference. `null` if there is none - String get relationshipEventId { - if (content == null || !(content['m.relates_to'] is Map)) { + String? get relationshipEventId { + if (!(content['m.relates_to'] is Map)) { return null; } if (content['m.relates_to'].containsKey('event_id')) { @@ -664,15 +704,12 @@ class Event extends MatrixEvent { /// Get whether this event has aggregated events from a certain [type] /// To be able to do that you need to pass a [timeline] bool hasAggregatedEvents(Timeline timeline, String type) => - timeline.aggregatedEvents.containsKey(eventId) && - timeline.aggregatedEvents[eventId].containsKey(type); + timeline.aggregatedEvents[eventId]?.containsKey(type) == true; /// Get all the aggregated event objects for a given [type]. To be able to do this /// you have to pass a [timeline] Set aggregatedEvents(Timeline timeline, String type) => - hasAggregatedEvents(timeline, type) - ? timeline.aggregatedEvents[eventId][type] - : {}; + timeline.aggregatedEvents[eventId]?[type] ?? {}; /// Fetches the event to be rendered, taking into account all the edits and the like. /// It needs a [timeline] for that. diff --git a/lib/src/room.dart b/lib/src/room.dart index f2cb7a9a..8721f685 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH @@ -22,6 +21,7 @@ import 'dart:convert'; import 'package:html_unescape/html_unescape.dart'; import 'package:matrix/src/utils/space_child.dart'; +import 'package:collection/collection.dart'; import '../matrix.dart'; import 'client.dart'; @@ -78,18 +78,18 @@ class Room { int highlightCount; /// A token that can be supplied to the from parameter of the rooms/{roomId}/messages endpoint. - String prev_batch; + String? prev_batch; RoomSummary summary; @deprecated - List get mHeroes => summary.mHeroes; + List? get mHeroes => summary.mHeroes; @deprecated - int get mJoinedMemberCount => summary.mJoinedMemberCount; + int? get mJoinedMemberCount => summary.mJoinedMemberCount; @deprecated - int get mInvitedMemberCount => summary.mInvitedMemberCount; + int? get mInvitedMemberCount => summary.mInvitedMemberCount; /// The room states are a key value store of the key (`type`,`state_key`) => State(event). /// In a lot of cases the `state_key` might be an empty string. You **should** use the @@ -113,7 +113,7 @@ class Room { 'oldest_sort_order': 0, }; - factory Room.fromJson(Map json, [Client client]) => Room( + factory Room.fromJson(Map json, Client client) => Room( client: client, id: json['id'], membership: Membership.values.singleWhere( @@ -136,23 +136,25 @@ class Room { /// This load all the missing state events for the room from the database /// If the room has already been loaded, this does nothing. Future postLoad() async { - if (!partial || client.database == null) { + if (!partial) { return; } final allStates = await client.database - .getUnimportantRoomEventStatesForRoom( + ?.getUnimportantRoomEventStatesForRoom( client.importantStateEvents.toList(), this); - for (final state in allStates) { - setState(state); + if (allStates != null) { + for (final state in allStates) { + setState(state); + } } partial = false; } /// Returns the [Event] for the given [typeKey] and optional [stateKey]. /// If no [stateKey] is provided, it defaults to an empty string. - Event getState(String typeKey, [String stateKey = '']) => - states[typeKey] != null ? states[typeKey][stateKey] : null; + Event? getState(String typeKey, [String stateKey = '']) => + states[typeKey]?[stateKey]; /// Adds the [state] to this room and overwrites a state with the same /// typeKey/stateKey key pair if there is one. @@ -160,7 +162,7 @@ class Room { // Decrypt if necessary if (state.type == EventTypes.Encrypted && client.encryptionEnabled) { try { - state = client.encryption.decryptRoomEventSync(id, state); + state = client.encryption?.decryptRoomEventSync(id, state) ?? state; } catch (e, s) { Logs().e('[LibOlm] Could not decrypt room state', e, s); } @@ -188,27 +190,26 @@ class Room { } // Ignore other non-state events - if (!isMessageEvent && state.stateKey == null) { + final stateKey = isMessageEvent ? '' : state.stateKey; + final roomId = state.roomId; + if (stateKey == null || roomId == null) { return; } // Do not set old events as state events - final prevEvent = getState(state.type, state.stateKey); + final prevEvent = getState(state.type, stateKey); if (prevEvent != null && prevEvent.eventId != state.eventId && - client.database != null && - client.database.eventIsKnown(state.eventId, state.roomId)) { + client.database?.eventIsKnown(state.eventId, roomId) == true) { return; } - states[state.type] ??= {}; - states[state.type][state.stateKey ?? ''] = state; + (states[state.type] ??= {})[stateKey] = state; } /// ID of the fully read marker event. - String get fullyRead => roomAccountData['m.fully_read'] != null - ? roomAccountData['m.fully_read'].content['event_id'] - : ''; + String get fullyRead => + roomAccountData['m.fully_read']?.content['event_id'] ?? ''; /// If something changes, this callback will be triggered. Will return the /// room id. @@ -220,19 +221,17 @@ class Room { StreamController.broadcast(); /// The name of the room if set by a participant. - String get name => getState(EventTypes.RoomName) != null && - getState(EventTypes.RoomName).content['name'] is String - ? getState(EventTypes.RoomName).content['name'] - : ''; + String get name { + final n = getState(EventTypes.RoomName)?.content['name']; + return (n is String) ? n : ''; + } /// The pinned events for this room. If there are none this returns an empty /// list. - List get pinnedEventIds => getState(EventTypes.RoomPinnedEvents) != - null - ? (getState(EventTypes.RoomPinnedEvents).content['pinned'] is List - ? getState(EventTypes.RoomPinnedEvents).content['pinned'] - : []) - : []; + List get pinnedEventIds { + final pinned = getState(EventTypes.RoomPinnedEvents)?.content['pinned']; + return pinned is List ? pinned : []; + } /// Returns a localized displayname for this server. If the room is a groupchat /// without a name, then it will return the localized version of 'Group with Alice' instead @@ -240,36 +239,37 @@ class Room { /// Empty chats will become the localized version of 'Empty Chat'. /// This method requires a localization class which implements [MatrixLocalizations] String getLocalizedDisplayname(MatrixLocalizations i18n) { - if ((name?.isEmpty ?? true) && - (canonicalAlias?.isEmpty ?? true) && + if (name.isEmpty && + canonicalAlias.isEmpty && !isDirectChat && - (summary.mHeroes != null && summary.mHeroes.isNotEmpty)) { + (summary.mHeroes != null && summary.mHeroes?.isNotEmpty == true)) { return i18n.groupWith(displayname); } - if (displayname?.isNotEmpty ?? false) { + if (displayname.isNotEmpty) { return displayname; } return i18n.emptyChat; } /// The topic of the room if set by a participant. - String get topic => getState(EventTypes.RoomTopic) != null && - getState(EventTypes.RoomTopic).content['topic'] is String - ? getState(EventTypes.RoomTopic).content['topic'] - : ''; + String get topic { + final t = getState(EventTypes.RoomTopic)?.content['topic']; + return t is String ? t : ''; + } /// The avatar of the room if set by a participant. - Uri get avatar { - if (getState(EventTypes.RoomAvatar) != null && - getState(EventTypes.RoomAvatar).content['url'] is String) { - return Uri.tryParse(getState(EventTypes.RoomAvatar).content['url']); + Uri? get avatar { + final avatarUrl = getState(EventTypes.RoomAvatar)?.content['url']; + if (avatarUrl is String) { + return Uri.tryParse(avatarUrl); } - if (summary.mHeroes != null && - summary.mHeroes.length == 1 && - getState(EventTypes.RoomMember, summary.mHeroes.first) != null) { - return getState(EventTypes.RoomMember, summary.mHeroes.first) - .asUser - .avatarUrl; + + final heroes = summary.mHeroes; + if (heroes != null && heroes.length == 1) { + final hero = getState(EventTypes.RoomMember, heroes.first); + if (hero != null) { + return hero.asUser.avatarUrl; + } } if (isDirectChat) { final user = directChatMatrixID; @@ -277,19 +277,17 @@ class Room { return getUserByMXIDSync(user).avatarUrl; } } - if (membership == Membership.invite && - getState(EventTypes.RoomMember, client.userID) != null) { - return getState(EventTypes.RoomMember, client.userID).sender.avatarUrl; + if (membership == Membership.invite) { + return getState(EventTypes.RoomMember, client.userID!)?.sender.avatarUrl; } return null; } /// The address in the format: #roomname:homeserver.org. - String get canonicalAlias => - getState(EventTypes.RoomCanonicalAlias) != null && - getState(EventTypes.RoomCanonicalAlias).content['alias'] is String - ? getState(EventTypes.RoomCanonicalAlias).content['alias'] - : ''; + String get canonicalAlias { + final alias = getState(EventTypes.RoomCanonicalAlias)?.content['alias']; + return (alias is String) ? alias : ''; + } /// Sets the canonical alias. If the [canonicalAlias] is not yet an alias of /// this room, it will create one. @@ -305,31 +303,31 @@ class Room { /// If this room is a direct chat, this is the matrix ID of the user. /// Returns null otherwise. - String get directChatMatrixID { - String returnUserId; + String? get directChatMatrixID { if (membership == Membership.invite) { - final invitation = getState(EventTypes.RoomMember, client.userID); + final invitation = getState(EventTypes.RoomMember, client.userID!); if (invitation != null && invitation.content['is_direct'] == true) { return invitation.senderId; } } + if (client.directChats is Map) { - client.directChats.forEach((String userId, dynamic roomIds) { - if (roomIds is List && roomIds.contains(id)) { - returnUserId = userId; - } - }); + return client.directChats.entries + .firstWhereOrNull((MapEntry e) { + final roomIds = e.value; + return roomIds is List && roomIds.contains(id); + })?.key; } - return returnUserId; + return null; } /// Wheither this is a direct chat or not bool get isDirectChat => directChatMatrixID != null; /// Must be one of [all, mention] - String notificationSettings; + String? notificationSettings; - Event get lastEvent { + Event? get lastEvent { // as lastEvent calculation is based on the state events we unfortunately cannot // use sortOrder here: With many state events we just know which ones are the // newest ones, without knowing in which order they actually happened. As such, @@ -338,7 +336,7 @@ class Room { // said room list, so it should be good enough. var lastTime = DateTime.fromMillisecondsSinceEpoch(0); final lastEvents = - client.roomPreviewLastEvents.map(getState).where((e) => e != null); + client.roomPreviewLastEvents.map(getState).whereType(); var lastEvent = lastEvents.isEmpty ? null @@ -356,11 +354,10 @@ class Room { }); if (lastEvent == null) { states.forEach((final String key, final entry) { - if (!entry.containsKey('')) return; final state = entry['']; - if (state.originServerTs != null && - state.originServerTs.millisecondsSinceEpoch > - lastTime.millisecondsSinceEpoch) { + if (state == null) return; + if (state.originServerTs.millisecondsSinceEpoch > + lastTime.millisecondsSinceEpoch) { lastTime = state.originServerTs; lastEvent = state; } @@ -371,29 +368,28 @@ class Room { /// Returns a list of all current typing users. List get typingUsers { - if (!ephemerals.containsKey('m.typing')) return []; - final List typingMxid = ephemerals['m.typing'].content['user_ids']; - return typingMxid.cast().map(getUserByMXIDSync).toList(); + final typingMxid = ephemerals['m.typing']?.content['user_ids']; + return (typingMxid is List) + ? typingMxid.cast().map(getUserByMXIDSync).toList() + : []; } /// Your current client instance. final Client client; Room({ - this.id, + required this.id, this.membership = Membership.join, - int notificationCount, - int highlightCount, + this.notificationCount = 0, + this.highlightCount = 0, this.prev_batch, - this.client, + required this.client, this.notificationSettings, - Map roomAccountData, + Map? roomAccountData, double newestSortOrder = 0.0, double oldestSortOrder = 0.0, - RoomSummary summary, - }) : notificationCount = notificationCount ?? 0, - highlightCount = highlightCount ?? 0, - roomAccountData = roomAccountData ?? {}, + RoomSummary? summary, + }) : roomAccountData = roomAccountData ?? {}, summary = summary ?? RoomSummary.fromJson({ 'm.joined_member_count': 0, @@ -408,14 +404,16 @@ class Room { /// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and /// then generates a name from the heroes. String get displayname { - if (name != null && name.isNotEmpty) return name; - if (canonicalAlias != null && - canonicalAlias.isNotEmpty && - canonicalAlias.length > 3) { - return canonicalAlias.localpart; + if (name.isNotEmpty) return name; + + final canonicalAlias = this.canonicalAlias.localpart; + if (canonicalAlias != null && canonicalAlias.isNotEmpty) { + return canonicalAlias; } - if (summary.mHeroes != null && summary.mHeroes.isNotEmpty) { - return summary.mHeroes + + final heroes = summary.mHeroes; + if (heroes != null && heroes.isNotEmpty) { + return heroes .where((hero) => hero.isNotEmpty) .map((hero) => getUserByMXIDSync(hero).calcDisplayname()) .join(', '); @@ -423,34 +421,23 @@ class Room { if (isDirectChat) { final user = directChatMatrixID; if (user != null) { - return getUserByMXIDSync(user).displayName; + return getUserByMXIDSync(user).calcDisplayname(); } } - if (membership == Membership.invite && - getState(EventTypes.RoomMember, client.userID) != null) { - return getState(EventTypes.RoomMember, client.userID) - .sender + if (membership == Membership.invite) { + final sender = getState(EventTypes.RoomMember, client.userID!) + ?.sender .calcDisplayname(); + if (sender != null) return sender; } return 'Empty chat'; } @Deprecated('Use [lastEvent.body] instead') - String get lastMessage { - if (lastEvent != null) { - return lastEvent.body; - } else { - return ''; - } - } + String get lastMessage => lastEvent?.body ?? ''; /// When the last message received. - DateTime get timeCreated { - if (lastEvent != null) { - return lastEvent.originServerTs; - } - return DateTime.now(); - } + DateTime get timeCreated => lastEvent?.originServerTs ?? DateTime.now(); /// Call the Matrix API to change the name of this room. Returns the event ID of the /// new m.room.name event. @@ -470,8 +457,8 @@ class Room { ); /// Add a tag to the room. - Future addTag(String tag, {double order}) => client.setRoomTag( - client.userID, + Future addTag(String tag, {double? order}) => client.setRoomTag( + client.userID!, id, tag, order: order, @@ -479,7 +466,7 @@ class Room { /// Removes a tag from the room. Future removeTag(String tag) => client.deleteRoomTag( - client.userID, + client.userID!, id, tag, ); @@ -498,14 +485,16 @@ class Room { /// Returns all tags for this room. Map get tags { - if (roomAccountData['m.tag'] == null || - !(roomAccountData['m.tag'].content['tags'] is Map)) { - return {}; + final tags = roomAccountData['m.tag']?.content['tags']; + + if (tags is Map) { + final parsedTags = + tags.map((k, v) => MapEntry(k, _tryTagFromJson(v))); + parsedTags.removeWhere((k, v) => !TagType.isValid(k)); + return parsedTags; } - final tags = (roomAccountData['m.tag'].content['tags'] as Map) - .map((k, v) => MapEntry(k, _tryTagFromJson(v))); - tags.removeWhere((k, v) => !TagType.isValid(k)); - return tags; + + return {}; } bool get markedUnread { @@ -533,12 +522,13 @@ class Room { ) ]))))); await client.setAccountDataPerRoom( - client.userID, + client.userID!, id, EventType.markedUnread, content, ); - if (unread == false && lastEvent != null) { + final lastEvent = this.lastEvent; + if (!unread && lastEvent != null) { await setReadMarker( lastEvent.eventId, mRead: lastEvent.eventId, @@ -570,19 +560,18 @@ class Room { getImagePacksFlat(ImagePackUsage.emoticon); /// returns the resolved mxid for a mention string, or null if none found - String getMention(String mention) => getParticipants() - .firstWhere((u) => u.mentionFragments.contains(mention), - orElse: () => null) + String? getMention(String mention) => getParticipants() + .firstWhereOrNull((u) => u.mentionFragments.contains(mention)) ?.id; /// Sends a normal text message to this room. Returns the event ID generated /// by the server for this message. - Future sendTextEvent(String message, - {String txid, - Event inReplyTo, - String editEventId, + Future sendTextEvent(String message, + {String? txid, + Event? inReplyTo, + String? editEventId, bool parseMarkdown = true, - @deprecated Map> emotePacks, + @deprecated Map>? emotePacks, bool parseCommands = true, String msgtype = MessageTypes.Text}) { if (parseCommands) { @@ -610,7 +599,7 @@ class Room { /// Sends a reaction to an event with an [eventId] and the content [key] into a room. /// Returns the event ID generated by the server for this reaction. - Future sendReaction(String eventId, String key, {String txid}) { + Future sendReaction(String eventId, String key, {String? txid}) { return sendEvent({ 'm.relates_to': { 'rel_type': RelationshipTypes.reaction, @@ -622,7 +611,7 @@ class Room { /// Sends the location with description [body] and geo URI [geoUri] into a room. /// Returns the event ID generated by the server for this message. - Future sendLocation(String body, String geoUri, {String txid}) { + Future sendLocation(String body, String geoUri, {String? txid}) { final event = { 'msgtype': 'm.location', 'body': body, @@ -638,17 +627,18 @@ class Room { /// Optionally specify [extraContent] to tack on to the event. Future sendFileEvent( MatrixFile file, { - String txid, - Event inReplyTo, - String editEventId, + String? txid, + Event? inReplyTo, + String? editEventId, bool waitUntilSent = false, - MatrixImageFile thumbnail, - Map extraContent, + MatrixImageFile? thumbnail, + Map? extraContent, }) async { MatrixFile uploadFile = file; // ignore: omit_local_variable_types - MatrixFile uploadThumbnail = thumbnail; // ignore: omit_local_variable_types - EncryptedFile encryptedFile; - EncryptedFile encryptedThumbnail; + MatrixFile? uploadThumbnail = + thumbnail; // ignore: omit_local_variable_types + EncryptedFile? encryptedFile; + EncryptedFile? encryptedThumbnail; if (encrypted && client.fileEncryptionEnabled) { encryptedFile = await file.encrypt(); uploadFile = encryptedFile.toMatrixFile(); @@ -727,15 +717,15 @@ class Room { return uploadResp; } - Future _sendContent( + Future _sendContent( String type, Map content, { - String txid, + String? txid, }) async { txid ??= client.generateUniqueTransactionId(); final mustEncrypt = encrypted && client.encryptionEnabled; final sendMessageContent = mustEncrypt - ? await client.encryption + ? await client.encryption! .encryptGroupMessagePayload(id, content, type: type) : content; return await client.sendMessage( @@ -748,15 +738,13 @@ class Room { /// Sends an event to this room with this json as a content. Returns the /// event ID generated from the server. - Future sendEvent( + Future sendEvent( Map content, { - String type, - String txid, - Event inReplyTo, - String editEventId, + String type = EventTypes.Message, + String? txid, + Event? inReplyTo, + String? editEventId, }) async { - type = type ?? EventTypes.Message; - // Create new transaction id String messageID; if (txid == null) { @@ -782,7 +770,7 @@ class Room { .convert(content.tryGet('body') ?? '') .replaceAll('\n', '
'); content['formatted_body'] = - '
In reply to ${inReplyTo.senderId}
$replyHtml
$repliedHtml'; + '
In reply to ${inReplyTo.senderId}
$replyHtml
$repliedHtml'; // We escape all @room-mentions here to prevent accidental room pings when an admin // replies to a message containing that! content['body'] = @@ -817,7 +805,7 @@ class Room { content: content, type: type, eventId: messageID, - senderId: client.userID, + senderId: client.userID!, originServerTs: sentDate, unsigned: { messageSendingStatusKey: EventStatus.sending.intValue, @@ -828,7 +816,7 @@ class Room { await _handleFakeSync(syncUpdate); // Send the text and on success, store and display a *sent* event. - String res; + String? res; while (res == null) { try { res = await _sendContent( @@ -846,16 +834,16 @@ class Room { await Future.delayed(Duration(seconds: 1)); } else { Logs().w('[Client] Problem while sending message', e, s); - syncUpdate.rooms.join.values.first.timeline.events.first - .unsigned[messageSendingStatusKey] = EventStatus.error.intValue; + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![messageSendingStatusKey] = EventStatus.error.intValue; await _handleFakeSync(syncUpdate); return null; } } } - syncUpdate.rooms.join.values.first.timeline.events.first - .unsigned[messageSendingStatusKey] = EventStatus.sent.intValue; - syncUpdate.rooms.join.values.first.timeline.events.first.eventId = res; + syncUpdate.rooms!.join!.values.first.timeline!.events!.first + .unsigned![messageSendingStatusKey] = EventStatus.sent.intValue; + syncUpdate.rooms!.join!.values.first.timeline!.events!.first.eventId = res; await _handleFakeSync(syncUpdate); return res; @@ -867,7 +855,7 @@ class Room { Future join({bool leaveIfNotFound = true}) async { try { await client.joinRoomById(id); - final invitation = getState(EventTypes.RoomMember, client.userID); + final invitation = getState(EventTypes.RoomMember, client.userID!); if (invitation != null && invitation.content['is_direct'] is bool && invitation.content['is_direct']) { @@ -926,11 +914,11 @@ class Room { /// Returns the event ID of the new state event. If there is no known /// power level event, there might something broken and this returns null. Future setPower(String userID, int power) async { - if (getState(EventTypes.RoomPowerLevels) == null) return null; - final powerMap = {} - ..addAll(getState(EventTypes.RoomPowerLevels).content); - if (powerMap['users'] == null) powerMap['users'] = {}; - powerMap['users'][userID] = power; + var powerMap = getState(EventTypes.RoomPowerLevels)?.content; + if (!(powerMap is Map)) { + powerMap = {}; + } + (powerMap['users'] ??= {})[userID] = power; return await client.setRoomStateWithKey( id, @@ -948,7 +936,8 @@ class Room { /// the historical events will be published in the onEvent stream. Future requestHistory( {int historyCount = defaultHistoryCount, - void Function() onHistoryReceived}) async { + void Function()? onHistoryReceived}) async { + final prev_batch = this.prev_batch; if (prev_batch == null) { throw 'Tried to request history without a prev_batch token'; } @@ -961,7 +950,7 @@ class Room { ); if (onHistoryReceived != null) onHistoryReceived(); - prev_batch = resp.end; + this.prev_batch = resp.end; final loadFn = () async { if (!((resp.chunk?.isNotEmpty ?? false) && resp.end != null)) return; @@ -989,8 +978,8 @@ class Room { }; if (client.database != null) { - await client.database.transaction(() async { - await client.database.setRoomPrevBatch(resp.end, id); + await client.database?.transaction(() async { + await client.database?.setRoomPrevBatch(resp.end!, id, client); await loadFn(); }); } else { @@ -1012,7 +1001,7 @@ class Room { } await client.setAccountData( - client.userID, + client.userID!, 'm.direct', directChats, ); @@ -1030,7 +1019,7 @@ class Room { } // Nothing to do here await client.setAccountDataPerRoom( - client.userID, + client.userID!, id, 'm.direct', directChats, @@ -1040,7 +1029,7 @@ class Room { /// Sets the position of the read marker for a given room, and optionally the /// read receipt's location. - Future setReadMarker(String eventId, {String mRead}) async { + Future setReadMarker(String eventId, {String? mRead}) async { if (mRead != null) { notificationCount = 0; await client.database?.resetNotificationCount(id); @@ -1082,26 +1071,24 @@ class Room { /// Creates a timeline from the store. Returns a [Timeline] object. Future getTimeline( - {void Function() onUpdate, void Function(int insertID) onInsert}) async { + {void Function()? onUpdate, + void Function(int insertID)? onInsert}) async { await postLoad(); var events; - if (client.database != null) { - events = await client.database.getEventList( - this, - limit: defaultHistoryCount, - ); - } else { - events = []; - } + events = await client.database?.getEventList( + this, + limit: defaultHistoryCount, + ) ?? + []; // Try again to decrypt encrypted events and update the database. if (encrypted && client.database != null && client.encryptionEnabled) { - await client.database.transaction(() async { + await client.database?.transaction(() async { for (var i = 0; i < events.length; i++) { if (events[i].type == EventTypes.Encrypted && events[i].content['can_request_session'] == true) { events[i] = await client.encryption - .decryptRoomEvent(id, events[i], store: true); + ?.decryptRoomEvent(id, events[i], store: true); } } }); @@ -1124,8 +1111,9 @@ class Room { /// case. List getParticipants() { final userList = []; - if (states[EventTypes.RoomMember] is Map) { - for (final entry in states[EventTypes.RoomMember].entries) { + final members = states[EventTypes.RoomMember]; + if (members != null && members is Map) { + for (final entry in members.entries) { final state = entry.value; if (state.type == EventTypes.RoomMember) userList.add(state.asUser); } @@ -1138,9 +1126,9 @@ class Room { /// Request the full list of participants from the server. The local list /// from the store is not complete if the client uses lazy loading. Future> requestParticipants() async { - if (!participantListComplete && partial && client.database != null) { + if (!participantListComplete && partial) { // we aren't fully loaded, maybe the users are in the database - final users = await client.database.getUsers(this); + final users = await client.database?.getUsers(this) ?? []; for (final user in users) { setState(user); } @@ -1149,8 +1137,10 @@ class Room { return getParticipants(); } final matrixEvents = await client.getMembersByRoom(id); - final users = - matrixEvents.map((e) => Event.fromMatrixEvent(e, this).asUser).toList(); + final users = matrixEvents + ?.map((e) => Event.fromMatrixEvent(e, this).asUser) + .toList() ?? + []; for (final user in users) { setState(user); // at *least* cache this in-memory } @@ -1172,18 +1162,15 @@ class Room { /// Returns the [User] object for the given [mxID] or requests it from /// the homeserver and waits for a response. @Deprecated('Use [requestUser] instead') - Future getUserByMXID(String mxID) async { - if (getState(EventTypes.RoomMember, mxID) != null) { - return getState(EventTypes.RoomMember, mxID).asUser; - } - return requestUser(mxID); - } + Future getUserByMXID(String mxID) async => + getState(EventTypes.RoomMember, mxID)?.asUser ?? await requestUser(mxID); /// Returns the [User] object for the given [mxID] or requests it from /// the homeserver and returns a default [User] object while waiting. User getUserByMXIDSync(String mxID) { - if (getState(EventTypes.RoomMember, mxID) != null) { - return getState(EventTypes.RoomMember, mxID).asUser; + final user = getState(EventTypes.RoomMember, mxID); + if (user != null) { + return user.asUser; } else { requestUser(mxID, ignoreErrors: true); return User(mxID, room: this); @@ -1195,28 +1182,19 @@ class Room { /// Requests a missing [User] for this room. Important for clients using /// lazy loading. If the user can't be found this method tries to fetch /// the displayname and avatar from the profile if [requestProfile] is true. - Future requestUser( + Future requestUser( String mxID, { bool ignoreErrors = false, bool requestProfile = true, }) async { - // TODO: Why is this bug happening at all? - if (mxID == null) { - // Show a warning but first generate a stacktrace. - try { - throw Exception(); - } catch (e, s) { - Logs().w('requestUser has been called with a null mxID', e, s); - } - return null; + final stateUser = getState(EventTypes.RoomMember, mxID); + if (stateUser != null) { + return stateUser.asUser; } - if (getState(EventTypes.RoomMember, mxID) != null) { - return getState(EventTypes.RoomMember, mxID).asUser; - } - if (client.database != null) { + { // it may be in the database - final user = await client.database.getUser(mxID, this); + final user = await client.database?.getUser(mxID, this); if (user != null) { setState(user); onUpdate.add(id); @@ -1224,7 +1202,7 @@ class Room { } } if (!_requestingMatrixIds.add(mxID)) return null; - Map resp; + Map? resp; try { Logs().v( 'Request missing user $mxID in room $displayname from the server...'); @@ -1272,12 +1250,13 @@ class Room { 'content': resp, 'state_key': mxID, }; - await client.database.storeEventUpdate( + await client.database?.storeEventUpdate( EventUpdate( content: content, roomID: id, type: EventUpdateType.state, ), + client, ); }); onUpdate.add(id); @@ -1286,14 +1265,14 @@ class Room { } /// Searches for the event on the server. Returns null if not found. - Future getEventById(String eventID) async { + Future getEventById(String eventID) async { try { final matrixEvent = await client.getOneRoomEvent(id, eventID); final event = Event.fromMatrixEvent(matrixEvent, this); if (event.type == EventTypes.Encrypted && client.encryptionEnabled) { // attempt decryption return await client.encryption - .decryptRoomEvent(id, event, store: false); + ?.decryptRoomEvent(id, event, store: false); } return event; } on MatrixException catch (err) { @@ -1322,15 +1301,13 @@ class Room { } /// Returns the user's own power level. - int get ownPowerLevel => getPowerLevelByUserId(client.userID); + int get ownPowerLevel => getPowerLevelByUserId(client.userID!); /// Returns the power levels from all users for this room or null if not given. - Map get powerLevels { - final powerLevelState = getState(EventTypes.RoomPowerLevels); - if (powerLevelState.content['users'] is Map) { - return powerLevelState.content['users']; - } - return null; + Map? get powerLevels { + final powerLevelState = + getState(EventTypes.RoomPowerLevels)?.content['users']; + return (powerLevelState is Map) ? powerLevelState : null; } /// Uploads a new user avatar for this room. Returns the event ID of the new @@ -1347,12 +1324,11 @@ class Room { } bool _hasPermissionFor(String action) { - if (getState(EventTypes.RoomPowerLevels) == null || - getState(EventTypes.RoomPowerLevels).content[action] == null) { + final pl = getState(EventTypes.RoomPowerLevels)?.content[action]; + if (pl == null) { return true; } - return ownPowerLevel >= - getState(EventTypes.RoomPowerLevels).content[action]; + return ownPowerLevel >= pl; } /// The level required to ban a user. @@ -1376,28 +1352,24 @@ class Room { bool get canChangePowerLevel => canSendEvent(EventTypes.RoomPowerLevels); bool canSendEvent(String eventType) { - if (getState(EventTypes.RoomPowerLevels) == null) return true; - if (getState(EventTypes.RoomPowerLevels).content['events'] == null || - getState(EventTypes.RoomPowerLevels).content['events'][eventType] == - null) { + final pl = + getState(EventTypes.RoomPowerLevels)?.content['events']?[eventType]; + if (pl == null) { return eventType == EventTypes.Message ? canSendDefaultMessages : canSendDefaultStates; } - return ownPowerLevel >= - getState(EventTypes.RoomPowerLevels).content['events'][eventType]; + return ownPowerLevel >= pl; } /// Returns the [PushRuleState] for this room, based on the m.push_rules stored in /// the account_data. PushRuleState get pushRuleState { - if (!client.accountData.containsKey('m.push_rules') || - !(client.accountData['m.push_rules'].content['global'] is Map)) { + final globalPushRules = + client.accountData['m.push_rules']?.content['global']; + if (!(globalPushRules is Map)) { return PushRuleState.notify; } - final Map globalPushRules = - client.accountData['m.push_rules'].content['global']; - if (globalPushRules == null) return PushRuleState.notify; if (globalPushRules['override'] is List) { for (final pushRule in globalPushRules['override']) { @@ -1476,8 +1448,8 @@ class Room { } /// Redacts this event. Throws `ErrorResponse` on error. - Future redactEvent(String eventId, - {String reason, String txid}) async { + Future redactEvent(String eventId, + {String? reason, String? txid}) async { // Create new transaction id String messageID; final now = DateTime.now().millisecondsSinceEpoch; @@ -1499,11 +1471,11 @@ class Room { /// This tells the server that the user is typing for the next N milliseconds /// where N is the value specified in the timeout key. Alternatively, if typing is false, /// it tells the server that the user has stopped typing. - Future setTyping(bool isTyping, {int timeout}) => - client.setTyping(client.userID, id, isTyping, timeout: timeout); + Future setTyping(bool isTyping, {int? timeout}) => + client.setTyping(client.userID!, id, isTyping, timeout: timeout); @Deprecated('Use sendTypingNotification instead') - Future sendTypingInfo(bool isTyping, {int timeout}) => + Future sendTypingInfo(bool isTyping, {int? timeout}) => setTyping(isTyping, timeout: timeout); /// This is sent by the caller when they wish to establish a call. @@ -1516,13 +1488,13 @@ class Room { /// [invitee] The user ID of the person who is being invited. Invites without an invitee field are defined to be /// intended for any member of the room other than the sender of the event. /// [party_id] The party ID for call, Can be set to client.deviceId. - Future inviteToCall( - String callId, int lifetime, String party_id, String invitee, String sdp, + Future inviteToCall( + String callId, int lifetime, String party_id, String? invitee, String sdp, {String type = 'offer', String version = voipProtoVersion, - String txid, - CallCapabilities capabilities, - SDPStreamMetadata metadata}) async { + String? txid, + CallCapabilities? capabilities, + SDPStreamMetadata? metadata}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { @@ -1551,9 +1523,9 @@ class Room { /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. /// [selected_party_id] The party ID for the selected answer. - Future selectCallAnswer( + Future selectCallAnswer( String callId, int lifetime, String party_id, String selected_party_id, - {String version = voipProtoVersion, String txid}) async { + {String version = voipProtoVersion, String? txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { @@ -1575,8 +1547,8 @@ class Room { /// [callId] is a unique identifier for the call. /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. - Future sendCallReject(String callId, int lifetime, String party_id, - {String version = voipProtoVersion, String txid}) async { + Future sendCallReject(String callId, int lifetime, String party_id, + {String version = voipProtoVersion, String? txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { @@ -1598,13 +1570,13 @@ class Room { /// [callId] is a unique identifier for the call. /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. - Future sendCallNegotiate( + Future sendCallNegotiate( String callId, int lifetime, String party_id, String sdp, {String type = 'offer', String version = voipProtoVersion, - String txid, - CallCapabilities capabilities, - SDPStreamMetadata metadata}) async { + String? txid, + CallCapabilities? capabilities, + SDPStreamMetadata? metadata}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { 'call_id': callId, @@ -1642,12 +1614,12 @@ class Room { /// } /// ], /// ``` - Future sendCallCandidates( + Future sendCallCandidates( String callId, String party_id, List> candidates, { String version = voipProtoVersion, - String txid, + String? txid, }) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { @@ -1669,12 +1641,12 @@ class Room { /// [type] The type of session description. Must be 'answer'. /// [sdp] The SDP text of the session description. /// [party_id] The party ID for call, Can be set to client.deviceId. - Future answerCall(String callId, String sdp, String party_id, + Future answerCall(String callId, String sdp, String party_id, {String type = 'answer', String version = voipProtoVersion, - String txid, - CallCapabilities capabilities, - SDPStreamMetadata metadata}) async { + String? txid, + CallCapabilities? capabilities, + SDPStreamMetadata? metadata}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { 'call_id': callId, @@ -1695,8 +1667,9 @@ class Room { /// [callId] The ID of the call this event relates to. /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. - Future hangupCall(String callId, String party_id, String hangupCause, - {String version = voipProtoVersion, String txid}) async { + Future hangupCall( + String callId, String party_id, String? hangupCause, + {String version = voipProtoVersion, String? txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { @@ -1726,9 +1699,9 @@ class Room { /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. /// [metadata] The sdp_stream_metadata object. - Future sendSDPStreamMetadataChanged( + Future sendSDPStreamMetadataChanged( String callId, String party_id, SDPStreamMetadata metadata, - {String version = voipProtoVersion, String txid}) async { + {String version = voipProtoVersion, String? txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { 'call_id': callId, @@ -1749,9 +1722,9 @@ class Room { /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. /// [callReplaces] transfer info - Future sendCallReplaces( + Future sendCallReplaces( String callId, String party_id, CallReplaces callReplaces, - {String version = voipProtoVersion, String txid}) async { + {String version = voipProtoVersion, String? txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { 'call_id': callId, @@ -1772,9 +1745,9 @@ class Room { /// [version] is the version of the VoIP specification this message adheres to. This specification is version 1. /// [party_id] The party ID for call, Can be set to client.deviceId. /// [assertedIdentity] the asserted identity - Future sendAssertedIdentity( + Future sendAssertedIdentity( String callId, String party_id, AssertedIdentity assertedIdentity, - {String version = voipProtoVersion, String txid}) async { + {String version = voipProtoVersion, String? txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; final content = { 'call_id': callId, @@ -1793,13 +1766,13 @@ class Room { /// it can be invite meaning that a user who wishes to join the room must first receive an invite /// to the room from someone already inside of the room. Currently, knock and private are reserved /// keywords which are not implemented. - JoinRules get joinRules => getState(EventTypes.RoomJoinRules) != null - ? JoinRules.values.firstWhere( - (r) => - r.toString().replaceAll('JoinRules.', '') == - getState(EventTypes.RoomJoinRules).content['join_rule'], - orElse: () => null) - : null; + JoinRules? get joinRules { + final joinRule = getState(EventTypes.RoomJoinRules)?.content['join_rule']; + return joinRule != null + ? JoinRules.values.firstWhereOrNull( + (r) => r.toString().replaceAll('JoinRules.', '') == joinRule) + : null; + } /// Changes the join rules. You should check first if the user is able to change it. Future setJoinRules(JoinRules joinRules) async { @@ -1819,10 +1792,13 @@ class Room { /// This event controls whether guest users are allowed to join rooms. If this event /// is absent, servers should act as if it is present and has the guest_access value "forbidden". - GuestAccess get guestAccess => getState(EventTypes.GuestAccess) != null - ? _guestAccessMap.map((k, v) => MapEntry(v, k))[ - getState(EventTypes.GuestAccess).content['guest_access']] - : GuestAccess.forbidden; + GuestAccess get guestAccess { + final ga = getState(EventTypes.GuestAccess)?.content['guest_access']; + return ga != null + ? (_guestAccessMap.map((k, v) => MapEntry(v, k))[ga] ?? + GuestAccess.forbidden) + : GuestAccess.forbidden; + } /// Changes the guest access. You should check first if the user is able to change it. Future setGuestAccess(GuestAccess guestAccess) async { @@ -1841,12 +1817,13 @@ class Room { bool get canChangeGuestAccess => canSendEvent(EventTypes.GuestAccess); /// This event controls whether a user can see the events that happened in a room from before they joined. - HistoryVisibility get historyVisibility => - getState(EventTypes.HistoryVisibility) != null - ? _historyVisibilityMap.map((k, v) => MapEntry(v, k))[ - getState(EventTypes.HistoryVisibility) - .content['history_visibility']] - : null; + HistoryVisibility? get historyVisibility { + final hv = + getState(EventTypes.HistoryVisibility)?.content['history_visibility']; + return hv != null + ? _historyVisibilityMap.map((k, v) => MapEntry(v, k))[hv] + : null; + } /// Changes the history visibility. You should check first if the user is able to change it. Future setHistoryVisibility(HistoryVisibility historyVisibility) async { @@ -1867,8 +1844,8 @@ class Room { /// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported. /// Returns null if there is no encryption algorithm. - String get encryptionAlgorithm => - getState(EventTypes.Encryption)?.parsedRoomEncryptionContent?.algorithm; + String? get encryptionAlgorithm => + getState(EventTypes.Encryption)?.parsedRoomEncryptionContent.algorithm; /// Checks if this room is encrypted. bool get encrypted => encryptionAlgorithm != null; @@ -1892,10 +1869,10 @@ class Room { final deviceKeys = []; final users = await requestParticipants(); for (final user in users) { + final userDeviceKeys = client.userDeviceKeys[user.id]?.deviceKeys.values; if ([Membership.invite, Membership.join].contains(user.membership) && - client.userDeviceKeys.containsKey(user.id)) { - for (final deviceKeyEntry - in client.userDeviceKeys[user.id].deviceKeys.values) { + userDeviceKeys != null) { + for (final deviceKeyEntry in userDeviceKeys) { deviceKeys.add(deviceKeyEntry); } } @@ -1907,13 +1884,13 @@ class Room { if (!client.encryptionEnabled) { return; } - await client.encryption.keyManager.request(this, sessionId, senderKey); + await client.encryption?.keyManager.request(this, sessionId, senderKey); } Future _handleFakeSync(SyncUpdate syncUpdate, {bool sortAtTheEnd = false}) async { if (client.database != null) { - await client.database.transaction(() async { + await client.database?.transaction(() async { await client.handleSync(syncUpdate, sortAtTheEnd: sortAtTheEnd); }); } else { @@ -1927,14 +1904,13 @@ class Room { bool get isExtinct => getState(EventTypes.RoomTombstone) != null; /// Returns informations about how this room is - TombstoneContent get extinctInformations => isExtinct - ? getState(EventTypes.RoomTombstone).parsedTombstoneContent - : null; + TombstoneContent? get extinctInformations => + getState(EventTypes.RoomTombstone)?.parsedTombstoneContent; /// Checks if the `m.room.create` state has a `type` key with the value /// `m.space`. bool get isSpace => - getState(EventTypes.RoomCreate)?.content?.tryGet('type') == + getState(EventTypes.RoomCreate)?.content.tryGet('type') == RoomCreationTypes.mSpace; // TODO: Magic string! /// The parents of this room. Currently this SDK doesn't yet set the canonical @@ -1944,9 +1920,9 @@ class Room { List get spaceParents => states[EventTypes.spaceParent] ?.values - ?.map((state) => SpaceParent.fromState(state)) - ?.where((child) => child.via?.isNotEmpty ?? false) - ?.toList() ?? + .map((state) => SpaceParent.fromState(state)) + .where((child) => child.via?.isNotEmpty ?? false) + .toList() ?? []; /// List all children of this space. Children without a `via` domain will be @@ -1957,9 +1933,9 @@ class Room { ? throw Exception('Room is not a space!') : (states[EventTypes.spaceChild] ?.values - ?.map((state) => SpaceChild.fromState(state)) - ?.where((child) => child.via?.isNotEmpty ?? false) - ?.toList() ?? + .map((state) => SpaceChild.fromState(state)) + .where((child) => child.via?.isNotEmpty ?? false) + .toList() ?? []) ..sort((a, b) => a.order.isEmpty || b.order.isEmpty ? b.order.compareTo(a.order) @@ -1968,12 +1944,12 @@ class Room { /// Adds or edits a child of this space. Future setSpaceChild( String roomId, { - List via, - String order, - bool suggested, + List? via, + String? order, + bool? suggested, }) async { if (!isSpace) throw Exception('Room is not a space!'); - via ??= [roomId.domain]; + via ??= [client.userID!.domain!]; await client.setRoomStateWithKey(id, EventTypes.spaceChild, roomId, { 'via': via, if (order != null) 'order': order, diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index 6ace2a09..b4cc82b7 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -132,15 +132,16 @@ class Timeline { void _sessionKeyReceived(String sessionId) async { var decryptAtLeastOneEvent = false; final decryptFn = () async { - if (!room.client.encryptionEnabled) { + final encryption = room.client.encryption; + if (!room.client.encryptionEnabled || encryption == null) { return; } for (var i = 0; i < events.length; i++) { if (events[i].type == EventTypes.Encrypted && events[i].messageType == MessageTypes.BadEncrypted && events[i].content['session_id'] == sessionId) { - events[i] = await room.client.encryption - .decryptRoomEvent(room.id, events[i], store: true); + events[i] = await encryption.decryptRoomEvent(room.id, events[i], + store: true); if (events[i].type != EventTypes.Encrypted) { decryptAtLeastOneEvent = true; } @@ -148,7 +149,7 @@ class Timeline { } }; if (room.client.database != null) { - await room.client.database.transaction(decryptFn); + await room.client.database?.transaction(decryptFn); } else { await decryptFn(); } @@ -162,7 +163,7 @@ class Timeline { event.messageType == MessageTypes.BadEncrypted && event.content['can_request_session'] == true) { try { - room.client.encryption.keyManager.maybeAutoRequest(room.id, + room.client.encryption?.keyManager.maybeAutoRequest(room.id, event.content['session_id'], event.content['sender_key']); } catch (_) { // dispose @@ -186,13 +187,11 @@ class Timeline { } int i; for (i = 0; i < events.length; i++) { - final searchHaystack = {}; - if (events[i].eventId != null) { - searchHaystack.add(events[i].eventId); - } - if (events[i].unsigned != null && - events[i].unsigned['transaction_id'] != null) { - searchHaystack.add(events[i].unsigned['transaction_id']); + final searchHaystack = {events[i].eventId}; + + final txnid = events[i].unsigned?['transaction_id']; + if (txnid != null) { + searchHaystack.add(txnid); } if (searchNeedle.intersection(searchHaystack).isNotEmpty) { break; @@ -205,19 +204,18 @@ class Timeline { eventSet.removeWhere((e) => e.matchesEventOrTransactionId(event.eventId) || (event.unsigned != null && - e.matchesEventOrTransactionId(event.unsigned['transaction_id']))); + e.matchesEventOrTransactionId(event.unsigned?['transaction_id']))); } void addAggregatedEvent(Event event) { // we want to add an event to the aggregation tree - if (event.relationshipType == null || event.relationshipEventId == null) { + final relationshipType = event.relationshipType; + final relationshipEventId = event.relationshipEventId; + if (relationshipType == null || relationshipEventId == null) { return; // nothing to do } - if (!aggregatedEvents.containsKey(event.relationshipEventId)) { - aggregatedEvents[event.relationshipEventId] = >{}; - } - final events = (aggregatedEvents[event.relationshipEventId] ??= - >{})[event.relationshipType] ??= {}; + final events = (aggregatedEvents[relationshipEventId] ??= + >{})[relationshipType] ??= {}; // remove a potential old event _removeEventFromSet(events, event); // add the new one @@ -227,7 +225,7 @@ class Timeline { void removeAggregatedEvent(Event event) { aggregatedEvents.remove(event.eventId); if (event.unsigned != null) { - aggregatedEvents.remove(event.unsigned['transaction_id']); + aggregatedEvents.remove(event.unsigned?['transaction_id']); } for (final types in aggregatedEvents.values) { for (final events in types.values) { diff --git a/lib/src/user.dart b/lib/src/user.dart index fe0ac2d8..73306433 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -49,9 +49,9 @@ class User extends Event { required String stateKey, dynamic content, required String typeKey, - String? eventId, + String eventId = 'fakevent', String? roomId, - String? senderId, + String senderId = 'fakesender', required DateTime originServerTs, dynamic unsigned, Room? room}) @@ -68,11 +68,11 @@ class User extends Event { room: room); /// The full qualified Matrix ID in the format @username:server.abc. - String get id => stateKey; + String get id => stateKey ?? '@unknown:unknown'; /// The displayname of the user if the user has set one. String? get displayName => - content?.tryGet('displayname') ?? + content.tryGet('displayname') ?? prevContent?.tryGet('displayname'); /// Returns the power level of this user. @@ -91,13 +91,16 @@ class User extends Event { }, orElse: () => Membership.join); /// The avatar if the user has one. - Uri? get avatarUrl => content != null && content.containsKey('avatar_url') - ? (content['avatar_url'] is String - ? Uri.tryParse(content['avatar_url']) - : null) - : (prevContent != null && prevContent['avatar_url'] is String - ? Uri.tryParse(prevContent['avatar_url']) - : null); + Uri? get avatarUrl { + final prevContent = this.prevContent; + return content.containsKey('avatar_url') + ? (content['avatar_url'] is String + ? Uri.tryParse(content['avatar_url']) + : null) + : (prevContent != null && prevContent['avatar_url'] is String + ? Uri.tryParse(prevContent['avatar_url']) + : null); + } /// Returns the displayname or the local part of the Matrix ID if the user /// has no displayname. If [formatLocalpart] is true, then the localpart will @@ -109,8 +112,8 @@ class User extends Event { bool? formatLocalpart, bool? mxidLocalPartFallback, }) { - formatLocalpart ??= room?.client?.formatLocalpart ?? true; - mxidLocalPartFallback ??= room?.client?.mxidLocalPartFallback ?? true; + formatLocalpart ??= room?.client.formatLocalpart ?? true; + mxidLocalPartFallback ??= room?.client.mxidLocalPartFallback ?? true; final displayName = this.displayName; if (displayName != null && displayName.isNotEmpty) { return displayName; @@ -132,37 +135,40 @@ class User extends Event { } /// Call the Matrix API to kick this user from this room. - Future kick() => room.kick(id); + Future kick() async => await room?.kick(id); /// Call the Matrix API to ban this user from this room. - Future ban() => room.ban(id); + Future ban() async => await room?.ban(id); /// Call the Matrix API to unban this banned user from this room. - Future unban() => room.unban(id); + Future unban() async => await room?.unban(id); /// Call the Matrix API to change the power level of this user. - Future setPower(int power) => room.setPower(id, power); + Future setPower(int power) async => await room?.setPower(id, power); /// Returns an existing direct chat ID with this user or creates a new one. /// Returns null on error. - Future startDirectChat() => room.client.startDirectChat(id); + Future startDirectChat() async => room?.client.startDirectChat(id); /// The newest presence of this user if there is any and null if not. - Presence? get presence => room.client.presences[id]; + Presence? get presence => room?.client.presences[id]; /// Whether the client is able to ban/unban this user. - bool get canBan => room.canBan && powerLevel < room.ownPowerLevel; + bool get canBan => + (room?.canBan ?? false) && + powerLevel < (room?.ownPowerLevel ?? powerLevel); /// Whether the client is able to kick this user. bool get canKick => [Membership.join, Membership.invite].contains(membership) && - room.canKick && - powerLevel < room.ownPowerLevel; + (room?.canKick ?? false) && + powerLevel < (room?.ownPowerLevel ?? powerLevel); /// Whether the client is allowed to change the power level of this user. /// Please be aware that you can only set the power level to at least your own! bool get canChangePowerLevel => - room.canChangePowerLevel && powerLevel < room.ownPowerLevel; + (room?.canChangePowerLevel ?? false) && + powerLevel < (room?.ownPowerLevel ?? powerLevel); @override bool operator ==(dynamic other) => (other is User && @@ -190,7 +196,7 @@ class User extends Event { : '[$displayName]'); // get all the users with the same display name - final allUsersWithSameDisplayname = room.getParticipants(); + final allUsersWithSameDisplayname = room?.getParticipants() ?? []; allUsersWithSameDisplayname.removeWhere((user) => user.id == id || (user.displayName?.isEmpty ?? true) || diff --git a/lib/src/utils/commands_extension.dart b/lib/src/utils/commands_extension.dart index 666c897e..4167ae9f 100644 --- a/lib/src/utils/commands_extension.dart +++ b/lib/src/utils/commands_extension.dart @@ -171,33 +171,35 @@ extension CommandsClientExtension on Client { }); addCommand('myroomnick', (CommandArgs args) async { final currentEventJson = args.room - .getState(EventTypes.RoomMember, args.room.client.userID) - .content - .copy(); + .getState(EventTypes.RoomMember, args.room.client.userID!) + ?.content + .copy() ?? + {}; currentEventJson['displayname'] = args.msg; return await args.room.client.setRoomStateWithKey( args.room.id, EventTypes.RoomMember, - args.room.client.userID, + args.room.client.userID!, currentEventJson, ); }); addCommand('myroomavatar', (CommandArgs args) async { final currentEventJson = args.room - .getState(EventTypes.RoomMember, args.room.client.userID) - .content - .copy(); + .getState(EventTypes.RoomMember, args.room.client.userID!) + ?.content + .copy() ?? + {}; currentEventJson['avatar_url'] = args.msg; return await args.room.client.setRoomStateWithKey( args.room.id, EventTypes.RoomMember, - args.room.client.userID, + args.room.client.userID!, currentEventJson, ); }); addCommand('discardsession', (CommandArgs args) async { await encryption?.keyManager - ?.clearOrUseOutboundGroupSession(args.room.id, wipe: true); + .clearOrUseOutboundGroupSession(args.room.id, wipe: true); return ''; }); } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 7e3e217b..6babeb1e 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -68,17 +68,18 @@ class DeviceKeysList { } Future startVerification() async { + final encryption = client.encryption; + if (encryption == null) { + throw Exception('Encryption not enabled'); + } if (userId != client.userID) { // in-room verification with someone else final roomId = await client.startDirectChat(userId); - if (roomId == - null /* can be null as long as startDirectChat is not migrated */) { - throw Exception('Unable to start new room'); - } + final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client); - final request = KeyVerification( - encryption: client.encryption, room: room, userId: userId); + final request = + KeyVerification(encryption: encryption, room: room, userId: userId); await request.start(); // no need to add to the request client object. As we are doing a room // verification request that'll happen automatically once we know the transaction id @@ -86,9 +87,9 @@ class DeviceKeysList { } else { // broadcast self-verification final request = KeyVerification( - encryption: client.encryption, userId: userId, deviceId: '*'); + encryption: encryption, userId: userId, deviceId: '*'); await request.start(); - client.encryption.keyVerificationManager.addRequest(request); + encryption.keyVerificationManager.addRequest(request); return request; } } @@ -314,13 +315,15 @@ abstract class SignableKey extends MatrixSignableKey { Future setVerified(bool newVerified, [bool sign = true]) async { _verified = newVerified; + final encryption = client.encryption; if (newVerified && sign && + encryption != null && client.encryptionEnabled && - client.encryption.crossSigning.signable([this])) { + encryption.crossSigning.signable([this])) { // sign the key! // ignore: unawaited_futures - client.encryption.crossSigning.sign([this]); + encryption.crossSigning.sign([this]); } } @@ -493,11 +496,16 @@ class DeviceKeys extends SignableKey { if (!isValid) { throw Exception('setVerification called on invalid key'); } + final encryption = client.encryption; + if (encryption == null) { + throw Exception('setVerification called with disabled encryption'); + } + final request = KeyVerification( - encryption: client.encryption, userId: userId, deviceId: deviceId!); + encryption: encryption, userId: userId, deviceId: deviceId!); request.start(); - client.encryption.keyVerificationManager.addRequest(request); + encryption.keyVerificationManager.addRequest(request); return request; } } diff --git a/lib/src/utils/event_localizations.dart b/lib/src/utils/event_localizations.dart index f33c5c05..84685625 100644 --- a/lib/src/utils/event_localizations.dart +++ b/lib/src/utils/event_localizations.dart @@ -104,12 +104,11 @@ abstract class EventLocalizations { }, EventTypes.RoomMember: (event, i18n, body) { var text = 'Failed to parse member event'; - final targetName = event.stateKeyUser.calcDisplayname(); + final targetName = event.stateKeyUser?.calcDisplayname() ?? ''; // Has the membership changed? final newMembership = event.content['membership'] ?? ''; - final oldMembership = event.prevContent != null - ? event.prevContent['membership'] ?? '' - : ''; + final oldMembership = event.prevContent?['membership'] ?? ''; + if (newMembership != oldMembership) { if (oldMembership == 'invite' && newMembership == 'join') { text = i18n.acceptedTheInvitation(targetName); @@ -146,22 +145,19 @@ abstract class EventLocalizations { } } else if (newMembership == 'join') { final newAvatar = event.content['avatar_url'] ?? ''; - final oldAvatar = event.prevContent != null - ? event.prevContent['avatar_url'] ?? '' - : ''; + final oldAvatar = event.prevContent?['avatar_url'] ?? ''; final newDisplayname = event.content['displayname'] ?? ''; - final oldDisplayname = event.prevContent != null - ? event.prevContent['displayname'] ?? '' - : ''; + final oldDisplayname = event.prevContent?['displayname'] ?? ''; + final stateKey = event.stateKey; // Has the user avatar changed? if (newAvatar != oldAvatar) { text = i18n.changedTheProfileAvatar(targetName); } - // Has the user avatar changed? - else if (newDisplayname != oldDisplayname) { - text = i18n.changedTheDisplaynameTo(event.stateKey, newDisplayname); + // Has the user displayname changed? + else if (newDisplayname != oldDisplayname && stateKey != null) { + text = i18n.changedTheDisplaynameTo(stateKey, newDisplayname); } } return text; @@ -201,7 +197,7 @@ abstract class EventLocalizations { EventTypes.Encryption: (event, i18n, body) { var localizedBody = i18n.activatedEndToEndEncryption(event.sender.calcDisplayname()); - if (!event.room.client.encryptionEnabled) { + if (event.room?.client.encryptionEnabled == false) { localizedBody += '. ' + i18n.needPantalaimonWarning; } return localizedBody; diff --git a/lib/src/utils/event_update.dart b/lib/src/utils/event_update.dart index 11feef5c..922e90e2 100644 --- a/lib/src/utils/event_update.dart +++ b/lib/src/utils/event_update.dart @@ -49,12 +49,14 @@ class EventUpdate { }); Future decrypt(Room room, {bool store = false}) async { + final encryption = room.client.encryption; if (content['type'] != EventTypes.Encrypted || - !room.client.encryptionEnabled) { + !room.client.encryptionEnabled || + encryption == null) { return this; } try { - final decrpytedEvent = await room.client.encryption.decryptRoomEvent( + final decrpytedEvent = await encryption.decryptRoomEvent( room.id, Event.fromJson(content, room), store: store, updateType: type); return EventUpdate( diff --git a/lib/src/utils/image_pack_extension.dart b/lib/src/utils/image_pack_extension.dart index 03dc544e..92b3b924 100644 --- a/lib/src/utils/image_pack_extension.dart +++ b/lib/src/utils/image_pack_extension.dart @@ -79,7 +79,9 @@ extension ImagePackRoomExtension on Room { for (final entry in allRoomEmotes.entries) { addImagePack(entry.value, room: this, - slug: entry.value.stateKey.isEmpty ? 'room' : entry.value.stateKey); + slug: (entry.value.stateKey?.isNotEmpty == true) + ? entry.value.stateKey + : 'room'); } } return packs; diff --git a/lib/src/utils/markdown.dart b/lib/src/utils/markdown.dart index 857cd3f4..0924e9dc 100644 --- a/lib/src/utils/markdown.dart +++ b/lib/src/utils/markdown.dart @@ -183,7 +183,7 @@ class PillSyntax extends InlineSyntax { } class MentionSyntax extends InlineSyntax { - final String Function(String)? getMention; + final String? Function(String)? getMention; MentionSyntax(this.getMention) : super(r'(@(?:\[[^\]:]+\]|\w+)(?:#\w+)?)'); @override @@ -206,7 +206,7 @@ class MentionSyntax extends InlineSyntax { String markdown( String text, { Map> Function()? getEmotePacks, - String Function(String)? getMention, + String? Function(String)? getMention, }) { var ret = markdownToHtml( text, diff --git a/lib/src/utils/space_child.dart b/lib/src/utils/space_child.dart index 5d977002..bb409809 100644 --- a/lib/src/utils/space_child.dart +++ b/lib/src/utils/space_child.dart @@ -21,7 +21,7 @@ import 'package:matrix_api_lite/matrix_api_lite.dart'; import '../event.dart'; class SpaceChild { - final String roomId; + final String? roomId; final List? via; final String order; final bool? suggested; @@ -35,7 +35,7 @@ class SpaceChild { } class SpaceParent { - final String roomId; + final String? roomId; final List? via; final bool? canonical; diff --git a/lib/src/utils/uri_extension.dart b/lib/src/utils/uri_extension.dart index e4d875a4..6202ad3b 100644 --- a/lib/src/utils/uri_extension.dart +++ b/lib/src/utils/uri_extension.dart @@ -24,7 +24,9 @@ extension MxcUriExtension on Uri { /// Returns a download Link to this content. Uri getDownloadLink(Client matrix) => isScheme('mxc') ? matrix.homeserver != null - ? matrix.homeserver.resolve('_matrix/media/r0/download/$host$path') + ? matrix.homeserver + ?.resolve('_matrix/media/r0/download/$host$path') ?? + Uri() : Uri() : this; @@ -39,14 +41,15 @@ extension MxcUriExtension on Uri { ThumbnailMethod? method = ThumbnailMethod.crop, bool? animated = false}) { if (!isScheme('mxc')) return this; - if (matrix.homeserver == null) { + final homeserver = matrix.homeserver; + if (homeserver == null) { return Uri(); } return Uri( - scheme: matrix.homeserver.scheme, - host: matrix.homeserver.host, + scheme: homeserver.scheme, + host: homeserver.host, path: '/_matrix/media/r0/thumbnail/$host$path', - port: matrix.homeserver.port, + port: homeserver.port, queryParameters: { if (width != null) 'width': width.round().toString(), if (height != null) 'height': height.round().toString(), diff --git a/lib/src/voip_content.dart b/lib/src/voip_content.dart index 1ac24ebf..c39dce7f 100644 --- a/lib/src/voip_content.dart +++ b/lib/src/voip_content.dart @@ -84,8 +84,8 @@ class CallCapabilities { transferee: json['m.call.transferee'] as bool? ?? false, ); Map toJson() => { - if (transferee != null) 'm.call.transferee': transferee, - if (dtmf != null) 'm.call.dtmf': dtmf, + 'm.call.transferee': transferee, + 'm.call.dtmf': dtmf, }; } @@ -118,8 +118,8 @@ class SDPStreamPurpose { Map toJson() => { 'purpose': purpose, - if (audio_muted != null) 'audio_muted': audio_muted, - if (video_muted != null) 'video_muted': video_muted, + 'audio_muted': audio_muted, + 'video_muted': video_muted, }; } diff --git a/test/canonical_json_test.dart b/test/canonical_json_test.dart index 89bd50d9..8787dce0 100644 --- a/test/canonical_json_test.dart +++ b/test/canonical_json_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH diff --git a/test/client_test.dart b/test/client_test.dart index 2d5572d0..09a720dd 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -35,7 +34,7 @@ import 'fake_database.dart'; import 'fake_matrix_api.dart'; void main() { - Client matrix; + late Client matrix; Future> eventUpdateListFuture; Future> toDeviceUpdateListFuture; @@ -87,7 +86,7 @@ void main() { try { await matrix.checkHomeserver('https://fakeserver.wrongaddress'); } catch (exception) { - expect(exception != null, true); + expect(exception.toString().isNotEmpty, true); } await matrix.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); @@ -128,7 +127,7 @@ void main() { expect(matrix.getDirectChatFromUserId('@bob:example.com'), '!726s6s6q:example.com'); expect(matrix.rooms[1].directChatMatrixID, '@bob:example.com'); - expect(matrix.directChats, matrix.accountData['m.direct'].content); + expect(matrix.directChats, matrix.accountData['m.direct']?.content); expect(matrix.presences.length, 1); expect(matrix.rooms[1].ephemerals.length, 2); expect(matrix.rooms[1].typingUsers.length, 1); @@ -139,29 +138,30 @@ void main() { Client.supportedGroupEncryptionAlgorithms.first); expect( matrix.rooms[1].roomAccountData['m.receipt'] - .content['@alice:example.com']['ts'], + ?.content['@alice:example.com']['ts'], 1436451550453); expect( matrix.rooms[1].roomAccountData['m.receipt'] - .content['@alice:example.com']['event_id'], + ?.content['@alice:example.com']['event_id'], '7365636s6r6432:example.com'); expect(matrix.rooms.length, 2); expect(matrix.rooms[1].canonicalAlias, - "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"); - expect(matrix.presences['@alice:example.com'].presence.presence, + "#famedlyContactDiscovery:${matrix.userID!.split(":")[1]}"); + expect(matrix.presences['@alice:example.com']?.presence.presence, PresenceType.online); expect(presenceCounter, 1); expect(accountDataCounter, 9); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.userDeviceKeys.length, 4); - expect(matrix.userDeviceKeys['@alice:example.com'].outdated, false); - expect(matrix.userDeviceKeys['@alice:example.com'].deviceKeys.length, 2); + expect(matrix.userDeviceKeys['@alice:example.com']?.outdated, false); + expect(matrix.userDeviceKeys['@alice:example.com']?.deviceKeys.length, 2); expect( - matrix.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] - .verified, + matrix.userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS'] + ?.verified, false); await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', 'device_lists': { 'changed': [ '@alice:example.com', @@ -173,9 +173,10 @@ void main() { })); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.userDeviceKeys.length, 3); - expect(matrix.userDeviceKeys['@alice:example.com'].outdated, true); + expect(matrix.userDeviceKeys['@alice:example.com']?.outdated, true); await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', 'rooms': { 'join': { '!726s6s6q:example.com': { @@ -199,7 +200,7 @@ void main() { expect( matrix.getRoomByAlias( - "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"), + "#famedlyContactDiscovery:${matrix.userID!.split(":")[1]}"), null); }); @@ -310,7 +311,7 @@ void main() { identifier: AuthenticationUserIdentifier(user: 'test'), password: '1234'); - expect(loginResp != null, true); + expect(loginResp.userId != null, true); }); test('setAvatar', () async { @@ -340,7 +341,7 @@ void main() { expect(archive[0].id, '!5345234234:example.com'); expect(archive[0].membership, Membership.leave); expect(archive[0].name, 'The room name'); - expect(archive[0].lastEvent.body, 'This is an example text message'); + expect(archive[0].lastEvent?.body, 'This is an example text message'); expect(archive[0].roomAccountData.length, 1); expect(archive[1].id, '!5345234235:example.com'); expect(archive[1].membership, Membership.leave); @@ -364,7 +365,7 @@ void main() { } FakeMatrixApi.calledEndpoints.clear(); await matrix.sendToDeviceEncrypted( - matrix.userDeviceKeys['@alice:example.com'].deviceKeys.values + matrix.userDeviceKeys['@alice:example.com']!.deviceKeys.values .toList(), 'm.message', { @@ -382,7 +383,7 @@ void main() { } FakeMatrixApi.calledEndpoints.clear(); await matrix.sendToDeviceEncryptedChunked( - matrix.userDeviceKeys['@alice:example.com'].deviceKeys.values + matrix.userDeviceKeys['@alice:example.com']!.deviceKeys.values .toList(), 'm.message', { @@ -483,11 +484,11 @@ void main() { expect( json.decode(FakeMatrixApi .calledEndpoints['/client/r0/sendToDevice/foxies/floof_txnid'] - [0])['messages'], + ?[0])['messages'], foxContent); expect( json.decode(FakeMatrixApi.calledEndpoints[ - '/client/r0/sendToDevice/raccoon/raccoon_txnid'][0])['messages'], + '/client/r0/sendToDevice/raccoon/raccoon_txnid']?[0])['messages'], raccoonContent); FakeMatrixApi.calledEndpoints.clear(); await client.sendToDevice('bunny', 'bunny_txnid', bunnyContent); @@ -502,7 +503,7 @@ void main() { expect( json.decode(FakeMatrixApi .calledEndpoints['/client/r0/sendToDevice/bunny/bunny_txnid'] - [0])['messages'], + ?[0])['messages'], bunnyContent); await client.dispose(closeDatabase: true); }); @@ -546,16 +547,16 @@ void main() { expect( json.decode(FakeMatrixApi .calledEndpoints['/client/r0/sendToDevice/foxies/floof_txnid'] - [0])['messages'], + ?[0])['messages'], foxContent); expect( json.decode(FakeMatrixApi.calledEndpoints[ - '/client/r0/sendToDevice/raccoon/raccoon_txnid'][0])['messages'], + '/client/r0/sendToDevice/raccoon/raccoon_txnid']?[0])['messages'], raccoonContent); expect( json.decode(FakeMatrixApi .calledEndpoints['/client/r0/sendToDevice/bunny/bunny_txnid'] - [0])['messages'], + ?[0])['messages'], bunnyContent); await client.dispose(closeDatabase: true); }); @@ -596,10 +597,12 @@ void main() { expect(client2.homeserver, client1.homeserver); expect(client2.deviceID, client1.deviceID); expect(client2.deviceName, client1.deviceName); + expect(client2.rooms.length, 2); if (client2.encryptionEnabled) { - expect(client2.encryption.fingerprintKey, - client1.encryption.fingerprintKey); - expect(client2.encryption.identityKey, client1.encryption.identityKey); + expect(client2.encryption?.fingerprintKey, + client1.encryption?.fingerprintKey); + expect( + client2.encryption?.identityKey, client1.encryption?.identityKey); expect(client2.rooms[1].id, client1.rooms[1].id); } @@ -628,16 +631,18 @@ void main() { final response = await client.uploadContent(Uint8List(0), filename: 'file.jpeg'); expect(response.toString(), 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); - expect(await client.database.getFile(response) != null, - client.database.supportsFileStoring); + expect(await client.database?.getFile(response) != null, + client.database?.supportsFileStoring); await client.dispose(closeDatabase: true); }); test('object equality', () async { final time1 = DateTime.fromMillisecondsSinceEpoch(1); final time2 = DateTime.fromMillisecondsSinceEpoch(0); - final user1 = User('@user1:example.org', room: Room(id: '!room1')); - final user2 = User('@user2:example.org', room: Room(id: '!room1')); + final user1 = + User('@user1:example.org', room: Room(id: '!room1', client: matrix)); + final user2 = + User('@user2:example.org', room: Room(id: '!room1', client: matrix)); // receipts expect(Receipt(user1, time1) == Receipt(user1, time1), true); expect(Receipt(user1, time1) == Receipt(user1, time2), false); @@ -648,19 +653,29 @@ void main() { expect(user1 == user1, true); expect(user1 == user2, false); expect( - user1 == User('@user1:example.org', room: Room(id: '!room2')), false); + user1 == + User('@user1:example.org', + room: Room(id: '!room2', client: matrix)), + false); expect( user1 == User('@user1:example.org', - room: Room(id: '!room1'), membership: 'leave'), + room: Room(id: '!room1', client: matrix), + membership: 'leave'), false); // ignore: unrelated_type_equality_checks expect(user1 == 'beep', false); // rooms - expect(Room(id: '!room1') == Room(id: '!room1'), true); - expect(Room(id: '!room1') == Room(id: '!room2'), false); + expect( + Room(id: '!room1', client: matrix) == + Room(id: '!room1', client: matrix), + true); + expect( + Room(id: '!room1', client: matrix) == + Room(id: '!room2', client: matrix), + false); // ignore: unrelated_type_equality_checks - expect(Room(id: '!room1') == 'beep', false); + expect(Room(id: '!room1', client: matrix) == 'beep', false); }); test('clearCache', () async { diff --git a/test/commands_test.dart b/test/commands_test.dart index f9b3b211..04898d08 100644 --- a/test/commands_test.dart +++ b/test/commands_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH @@ -27,16 +26,16 @@ import 'fake_matrix_api.dart'; void main() { group('Commands', () { - Client client; - Room room; + late Client client; + late Room room; var olmEnabled = true; final getLastMessagePayload = - ([String type = 'm.room.message', String stateKey]) { + ([String type = 'm.room.message', String? stateKey]) { final state = stateKey != null; return json.decode(FakeMatrixApi.calledEndpoints.entries .firstWhere((e) => e.key.startsWith( - '/client/r0/rooms/${Uri.encodeComponent(room.id)}/${state ? 'state' : 'send'}/${Uri.encodeComponent(type)}${state && stateKey.isNotEmpty ? '/' + Uri.encodeComponent(stateKey) : ''}')) + '/client/r0/rooms/${Uri.encodeComponent(room.id)}/${state ? 'state' : 'send'}/${Uri.encodeComponent(type)}${state && stateKey?.isNotEmpty == true ? '/' + Uri.encodeComponent(stateKey!) : ''}')) .value .first); }; @@ -55,12 +54,18 @@ void main() { content: {}, room: room, stateKey: '', + eventId: '\$fakeeventid', + originServerTs: DateTime.now(), + senderId: '\@fakeuser:fakeServer.notExisting', )); room.setState(Event( type: 'm.room.member', content: {'membership': 'join'}, room: room, stateKey: client.userID, + eventId: '\$fakeeventid', + originServerTs: DateTime.now(), + senderId: '\@fakeuser:fakeServer.notExisting', )); }); @@ -135,7 +140,18 @@ void main() { test('react', () async { FakeMatrixApi.calledEndpoints.clear(); await room.sendTextEvent('/react ๐ŸฆŠ', - inReplyTo: Event(eventId: '\$event')); + inReplyTo: Event( + eventId: '\$event', + type: 'm.room.message', + content: { + 'msgtype': 'm.text', + 'body': 'yay', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'yay', + }, + originServerTs: DateTime.now(), + senderId: client.userID!, + )); final sent = getLastMessagePayload('m.reaction'); expect(sent, { 'm.relates_to': { @@ -152,7 +168,7 @@ void main() { expect( FakeMatrixApi .calledEndpoints['/client/r0/join/!newroom%3Aexample.com'] - .first != + ?.first != null, true); }); @@ -164,7 +180,7 @@ void main() { FakeMatrixApi .calledEndpoints[ '/client/r0/rooms/!1234%3AfakeServer.notExisting/leave'] - .first != + ?.first != null, true); }); @@ -192,7 +208,7 @@ void main() { json.decode(FakeMatrixApi .calledEndpoints[ '/client/r0/rooms/!1234%3AfakeServer.notExisting/kick'] - .first), + ?.first), { 'user_id': '@baduser:example.org', }); @@ -205,7 +221,7 @@ void main() { json.decode(FakeMatrixApi .calledEndpoints[ '/client/r0/rooms/!1234%3AfakeServer.notExisting/ban'] - .first), + ?.first), { 'user_id': '@baduser:example.org', }); @@ -218,7 +234,7 @@ void main() { json.decode(FakeMatrixApi .calledEndpoints[ '/client/r0/rooms/!1234%3AfakeServer.notExisting/unban'] - .first), + ?.first), { 'user_id': '@baduser:example.org', }); @@ -231,7 +247,7 @@ void main() { json.decode(FakeMatrixApi .calledEndpoints[ '/client/r0/rooms/!1234%3AfakeServer.notExisting/invite'] - .first), + ?.first), { 'user_id': '@baduser:example.org', }); @@ -259,14 +275,14 @@ void main() { test('discardsession', () async { if (olmEnabled) { - await client.encryption.keyManager.createOutboundGroupSession(room.id); + await client.encryption?.keyManager.createOutboundGroupSession(room.id); expect( - client.encryption.keyManager.getOutboundGroupSession(room.id) != + client.encryption?.keyManager.getOutboundGroupSession(room.id) != null, true); await room.sendTextEvent('/discardsession'); expect( - client.encryption.keyManager.getOutboundGroupSession(room.id) != + client.encryption?.keyManager.getOutboundGroupSession(room.id) != null, false); } diff --git a/test/database_api_test.dart b/test/database_api_test.dart index ca405984..ca02d873 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -49,8 +48,8 @@ Future olmEnabled() async { void testDatabase( Future futureDatabase, ) { - DatabaseApi database; - int toDeviceQueueIndex; + late DatabaseApi database; + late int toDeviceQueueIndex; test('Open', () async { database = await futureDatabase; }); @@ -115,8 +114,9 @@ void testDatabase( 'limited_timeline': false, 'membership': Membership.join, }); - await database.storeRoomUpdate('!testroom', roomUpdate); - final rooms = await database.getRoomList(Client('testclient')); + final client = Client('testclient'); + await database.storeRoomUpdate('!testroom', roomUpdate, client); + final rooms = await database.getRoomList(client); expect(rooms.single.id, '!testroom'); }); test('getRoomList', () async { @@ -124,8 +124,9 @@ void testDatabase( expect(list.single.id, '!testroom'); }); test('setRoomPrevBatch', () async { - await database.setRoomPrevBatch('1234', '!testroom'); - final rooms = await database.getRoomList(Client('testclient')); + final client = Client('testclient'); + await database.setRoomPrevBatch('1234', '!testroom', client); + final rooms = await database.getRoomList(client); expect(rooms.single.prev_batch, '1234'); }); test('forgetRoom', () async { @@ -149,7 +150,7 @@ void testDatabase( ); final client = await database.getClient('name'); - expect(client['token'], 'token'); + expect(client?['token'], 'token'); }); test('updateClient', () async { await database.updateClient( @@ -162,21 +163,21 @@ void testDatabase( 'olmAccount', ); final client = await database.getClient('name'); - expect(client['token'], 'token_different'); + expect(client?['token'], 'token_different'); }); test('updateClientKeys', () async { await database.updateClientKeys( 'olmAccount2', ); final client = await database.getClient('name'); - expect(client['olm_account'], 'olmAccount2'); + expect(client?['olm_account'], 'olmAccount2'); }); test('storeSyncFilterId', () async { await database.storeSyncFilterId( '1234', ); final client = await database.getClient('name'); - expect(client['sync_filter_id'], '1234'); + expect(client?['sync_filter_id'], '1234'); }); test('getAccountData', () async { await database.getAccountData(); @@ -192,52 +193,53 @@ void testDatabase( }); test('storeEventUpdate', () async { await database.storeEventUpdate( - EventUpdate( - roomID: '!testroom:example.com', - type: EventUpdateType.timeline, - content: { - 'type': EventTypes.Message, - 'content': { - 'body': '* edit 3', - 'msgtype': 'm.text', - 'm.new_content': { - 'body': 'edit 3', + EventUpdate( + roomID: '!testroom:example.com', + type: EventUpdateType.timeline, + content: { + 'type': EventTypes.Message, + 'content': { + 'body': '* edit 3', 'msgtype': 'm.text', + 'm.new_content': { + 'body': 'edit 3', + 'msgtype': 'm.text', + }, + 'm.relates_to': { + 'event_id': '\$source', + 'rel_type': RelationshipTypes.edit, + }, }, - 'm.relates_to': { - 'event_id': '\$source', - 'rel_type': RelationshipTypes.edit, - }, + 'event_id': '\$event:example.com', + 'sender': '@bob:example.org', }, - 'event_id': '\$event:example.com', - 'sender': '@bob:example.org', - }, - ), - ); + ), + Client('testclient')); }); test('getEventById', () async { - final event = await database.getEventById( - '\$event:example.com', Room(id: '!testroom:example.com')); - expect(event.type, EventTypes.Message); + final event = await database.getEventById('\$event:example.com', + Room(id: '!testroom:example.com', client: Client('testclient'))); + expect(event?.type, EventTypes.Message); }); test('getEventList', () async { - final events = - await database.getEventList(Room(id: '!testroom:example.com')); + final events = await database.getEventList( + Room(id: '!testroom:example.com', client: Client('testclient'))); expect(events.single.type, EventTypes.Message); }); test('getUser', () async { - final user = await database.getUser( - '@bob:example.org', Room(id: '!testroom:example.com')); + final user = await database.getUser('@bob:example.org', + Room(id: '!testroom:example.com', client: Client('testclient'))); expect(user, null); }); test('getUsers', () async { - final users = await database.getUsers(Room(id: '!testroom:example.com')); + final users = await database.getUsers( + Room(id: '!testroom:example.com', client: Client('testclient'))); expect(users.isEmpty, true); }); test('removeEvent', () async { await database.removeEvent('\$event:example.com', '!testroom:example.com'); - final event = await database.getEventById( - '\$event:example.com', Room(id: '!testroom:example.com')); + final event = await database.getEventById('\$event:example.com', + Room(id: '!testroom:example.com', client: Client('testclient'))); expect(event, null); }); test('getAllInboundGroupSessions', () async { @@ -265,7 +267,7 @@ void testDatabase( '!testroom:example.com', 'sessionId', ); - expect(jsonDecode(session.content)['foo'], 'bar'); + expect(jsonDecode(session!.content)['foo'], 'bar'); }); test('markInboundGroupSessionAsUploaded', () async { await database.markInboundGroupSessionAsUploaded( @@ -294,7 +296,7 @@ void testDatabase( }); test('storeSSSSCache', () async { await database.storeSSSSCache('type', 'keyId', 'ciphertext', '{}'); - final cache = await database.getSSSSCache('type'); + final cache = (await database.getSSSSCache('type'))!; expect(cache.type, 'type'); expect(cache.keyId, 'keyId'); expect(cache.ciphertext, 'ciphertext'); @@ -347,7 +349,7 @@ void testDatabase( '!testroom:example.com', '@alice:example.com', ); - expect(session.devices.isEmpty, true); + expect(session?.devices.isEmpty, true); }); test('getLastSentMessageUserDeviceKey', () async { final list = await database.getLastSentMessageUserDeviceKey( @@ -359,7 +361,7 @@ void testDatabase( test('getUnimportantRoomEventStatesForRoom', () async { final events = await database.getUnimportantRoomEventStatesForRoom( ['events'], - Room(id: '!mep'), + Room(id: '!mep', client: Client('testclient')), ); expect(events.isEmpty, true); }); diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index ec8a176e..98560862 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -34,7 +33,7 @@ void main() { var olmEnabled = true; - Client client; + late Client client; test('setupClient', () async { try { @@ -135,8 +134,8 @@ void main() { test('set blocked / verified', () async { if (!olmEnabled) return; final key = - client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']; - client.userDeviceKeys[client.userID].deviceKeys['UNSIGNEDDEVICE'] = + client.userDeviceKeys[client.userID]!.deviceKeys['OTHERDEVICE']!; + client.userDeviceKeys[client.userID]?.deviceKeys['UNSIGNEDDEVICE'] = DeviceKeys.fromJson({ 'user_id': '@test:fakeServer.notExisting', 'device_id': 'UNSIGNEDDEVICE', @@ -157,10 +156,10 @@ void main() { }, }, }, client); - final masterKey = client.userDeviceKeys[client.userID].masterKey; + final masterKey = client.userDeviceKeys[client.userID]!.masterKey!; masterKey.setDirectVerified(true); // we need to populate the ssss cache to be able to test signing easily - final handle = client.encryption.ssss.open(); + final handle = client.encryption!.ssss.open(); await handle.unlock(recoveryKey: ssssKey); await handle.maybeCacheAll(); @@ -174,16 +173,16 @@ void main() { expect(key.verified, true); // still verified via cross-sgining expect(key.encryptToDevice, true); expect( - client.userDeviceKeys[client.userID].deviceKeys['UNSIGNEDDEVICE'] - .encryptToDevice, + client.userDeviceKeys[client.userID]?.deviceKeys['UNSIGNEDDEVICE'] + ?.encryptToDevice, false); expect(masterKey.verified, true); await masterKey.setBlocked(true); expect(masterKey.verified, false); expect( - client.userDeviceKeys[client.userID].deviceKeys['UNSIGNEDDEVICE'] - .encryptToDevice, + client.userDeviceKeys[client.userID]?.deviceKeys['UNSIGNEDDEVICE'] + ?.encryptToDevice, true); await masterKey.setBlocked(false); expect(masterKey.verified, true); @@ -205,57 +204,57 @@ void main() { .any((k) => k == '/client/r0/keys/signatures/upload'), false); expect(key.directVerified, false); - client.userDeviceKeys[client.userID].deviceKeys.remove('UNSIGNEDDEVICE'); + client.userDeviceKeys[client.userID]?.deviceKeys.remove('UNSIGNEDDEVICE'); }); test('verification based on signatures', () async { if (!olmEnabled) return; - final user = client.userDeviceKeys[client.userID]; - user.masterKey.setDirectVerified(true); - expect(user.deviceKeys['GHTYAJCE'].crossVerified, true); - expect(user.deviceKeys['GHTYAJCE'].signed, true); - expect(user.getKey('GHTYAJCE').crossVerified, true); - expect(user.deviceKeys['OTHERDEVICE'].crossVerified, true); - expect(user.selfSigningKey.crossVerified, true); + final user = client.userDeviceKeys[client.userID]!; + user.masterKey?.setDirectVerified(true); + expect(user.deviceKeys['GHTYAJCE']?.crossVerified, true); + expect(user.deviceKeys['GHTYAJCE']?.signed, true); + expect(user.getKey('GHTYAJCE')?.crossVerified, true); + expect(user.deviceKeys['OTHERDEVICE']?.crossVerified, true); + expect(user.selfSigningKey?.crossVerified, true); expect( user .getKey('F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY') - .crossVerified, + ?.crossVerified, true); - expect(user.userSigningKey.crossVerified, true); + expect(user.userSigningKey?.crossVerified, true); expect(user.verified, UserVerifiedStatus.verified); - user.masterKey.setDirectVerified(false); - expect(user.deviceKeys['GHTYAJCE'].crossVerified, false); - expect(user.deviceKeys['OTHERDEVICE'].crossVerified, false); + user.masterKey?.setDirectVerified(false); + expect(user.deviceKeys['GHTYAJCE']?.crossVerified, false); + expect(user.deviceKeys['OTHERDEVICE']?.crossVerified, false); expect(user.verified, UserVerifiedStatus.unknown); - user.deviceKeys['OTHERDEVICE'].setDirectVerified(true); + user.deviceKeys['OTHERDEVICE']?.setDirectVerified(true); expect(user.verified, UserVerifiedStatus.verified); - user.deviceKeys['OTHERDEVICE'].setDirectVerified(false); + user.deviceKeys['OTHERDEVICE']?.setDirectVerified(false); - user.masterKey.setDirectVerified(true); - user.deviceKeys['GHTYAJCE'].signatures[client.userID] - .removeWhere((k, v) => k != 'ed25519:GHTYAJCE'); - expect(user.deviceKeys['GHTYAJCE'].verified, + user.masterKey?.setDirectVerified(true); + user.deviceKeys['GHTYAJCE']?.signatures?[client.userID] + ?.removeWhere((k, v) => k != 'ed25519:GHTYAJCE'); + expect(user.deviceKeys['GHTYAJCE']?.verified, true); // it's our own device, should be direct verified - expect( - user.deviceKeys['GHTYAJCE'].signed, false); // not verified for others - user.deviceKeys['OTHERDEVICE'].signatures.clear(); + expect(user.deviceKeys['GHTYAJCE']?.signed, + false); // not verified for others + user.deviceKeys['OTHERDEVICE']?.signatures?.clear(); expect(user.verified, UserVerifiedStatus.unknownDevice); }); test('start verification', () async { if (!olmEnabled) return; var req = client - .userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] - .startVerification(); + .userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS'] + ?.startVerification(); expect(req != null, true); - expect(req.room != null, false); + expect(req?.room != null, false); - req = - await client.userDeviceKeys['@alice:example.com'].startVerification(); + req = await client.userDeviceKeys['@alice:example.com'] + ?.startVerification(); expect(req != null, true); - expect(req.room != null, true); + expect(req?.room != null, true); }); test('dispose client', () async { diff --git a/test/encryption/bootstrap_test.dart b/test/encryption/bootstrap_test.dart index 7cd9d1aa..69a643fe 100644 --- a/test/encryption/bootstrap_test.dart +++ b/test/encryption/bootstrap_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -33,9 +32,9 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; - Map oldSecret; - String origKeyId; + late Client client; + late Map oldSecret; + late String origKeyId; test('setupClient', () async { client = await getClient(); @@ -53,8 +52,8 @@ void main() { Logs().i('[LibOlm] Enabled: $olmEnabled'); if (!olmEnabled) return; - Bootstrap bootstrap; - bootstrap = client.encryption.bootstrap( + Bootstrap? bootstrap; + bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); @@ -82,7 +81,7 @@ void main() { while (bootstrap.state != BootstrapState.done) { await Future.delayed(Duration(milliseconds: 50)); } - final defaultKey = client.encryption.ssss.open(); + final defaultKey = client.encryption!.ssss.open(); await defaultKey.unlock(passphrase: 'foxies'); // test all the x-signing keys match up @@ -95,8 +94,8 @@ void main() { expect( pubKey, client.userDeviceKeys[client.userID] - .getCrossSigningKey(keyType) - .publicKey); + ?.getCrossSigningKey(keyType) + ?.publicKey); } finally { keyObj.free(); } @@ -104,14 +103,15 @@ void main() { await defaultKey.store('foxes', 'floof'); await Future.delayed(Duration(milliseconds: 50)); - oldSecret = json.decode(json.encode(client.accountData['foxes'].content)); + oldSecret = + json.decode(json.encode(client.accountData['foxes']!.content)); origKeyId = defaultKey.keyId; }, timeout: Timeout(Duration(minutes: 2))); test('change recovery passphrase', () async { if (!olmEnabled) return; - Bootstrap bootstrap; - bootstrap = client.encryption.bootstrap( + Bootstrap? bootstrap; + bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); @@ -121,7 +121,7 @@ void main() { } else if (bootstrap.state == BootstrapState.askUseExistingSsss) { bootstrap.useExistingSsss(false); } else if (bootstrap.state == BootstrapState.askUnlockSsss) { - await bootstrap.oldSsssKeys[client.encryption.ssss.defaultKeyId] + await bootstrap.oldSsssKeys![client.encryption!.ssss.defaultKeyId]! .unlock(passphrase: 'foxies'); bootstrap.unlockedSsss(); } else if (bootstrap.state == BootstrapState.askNewSsss) { @@ -136,7 +136,7 @@ void main() { while (bootstrap.state != BootstrapState.done) { await Future.delayed(Duration(milliseconds: 50)); } - final defaultKey = client.encryption.ssss.open(); + final defaultKey = client.encryption!.ssss.open(); await defaultKey.unlock(passphrase: 'newfoxies'); // test all the x-signing keys match up @@ -149,8 +149,8 @@ void main() { expect( pubKey, client.userDeviceKeys[client.userID] - .getCrossSigningKey(keyType) - .publicKey); + ?.getCrossSigningKey(keyType) + ?.publicKey); } finally { keyObj.free(); } @@ -161,11 +161,11 @@ void main() { test('change passphrase with multiple keys', () async { if (!olmEnabled) return; - await client.setAccountData(client.userID, 'foxes', oldSecret); + await client.setAccountData(client.userID!, 'foxes', oldSecret); await Future.delayed(Duration(milliseconds: 50)); - Bootstrap bootstrap; - bootstrap = client.encryption.bootstrap( + Bootstrap? bootstrap; + bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); @@ -175,9 +175,10 @@ void main() { } else if (bootstrap.state == BootstrapState.askUseExistingSsss) { bootstrap.useExistingSsss(false); } else if (bootstrap.state == BootstrapState.askUnlockSsss) { - await bootstrap.oldSsssKeys[client.encryption.ssss.defaultKeyId] + await bootstrap.oldSsssKeys![client.encryption!.ssss.defaultKeyId]! .unlock(passphrase: 'newfoxies'); - await bootstrap.oldSsssKeys[origKeyId].unlock(passphrase: 'foxies'); + await bootstrap.oldSsssKeys![origKeyId]! + .unlock(passphrase: 'foxies'); bootstrap.unlockedSsss(); } else if (bootstrap.state == BootstrapState.askNewSsss) { await bootstrap.newSsss('supernewfoxies'); @@ -191,7 +192,7 @@ void main() { while (bootstrap.state != BootstrapState.done) { await Future.delayed(Duration(milliseconds: 50)); } - final defaultKey = client.encryption.ssss.open(); + final defaultKey = client.encryption!.ssss.open(); await defaultKey.unlock(passphrase: 'supernewfoxies'); // test all the x-signing keys match up @@ -204,8 +205,8 @@ void main() { expect( pubKey, client.userDeviceKeys[client.userID] - .getCrossSigningKey(keyType) - .publicKey); + ?.getCrossSigningKey(keyType) + ?.publicKey); } finally { keyObj.free(); } @@ -217,8 +218,8 @@ void main() { test('setup new ssss', () async { if (!olmEnabled) return; client.accountData.clear(); - Bootstrap bootstrap; - bootstrap = client.encryption.bootstrap( + Bootstrap? bootstrap; + bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); @@ -236,18 +237,18 @@ void main() { while (bootstrap.state != BootstrapState.done) { await Future.delayed(Duration(milliseconds: 50)); } - final defaultKey = client.encryption.ssss.open(); + final defaultKey = client.encryption!.ssss.open(); await defaultKey.unlock(passphrase: 'thenewestfoxies'); }, timeout: Timeout(Duration(minutes: 2))); test('bad ssss', () async { if (!olmEnabled) return; client.accountData.clear(); - await client.setAccountData(client.userID, 'foxes', oldSecret); + await client.setAccountData(client.userID!, 'foxes', oldSecret); await Future.delayed(Duration(milliseconds: 50)); var askedBadSsss = false; - Bootstrap bootstrap; - bootstrap = client.encryption.bootstrap( + Bootstrap? bootstrap; + bootstrap = client.encryption!.bootstrap( onUpdate: () async { while (bootstrap == null) { await Future.delayed(Duration(milliseconds: 5)); diff --git a/test/encryption/cross_signing_test.dart b/test/encryption/cross_signing_test.dart index 56628249..260dff98 100644 --- a/test/encryption/cross_signing_test.dart +++ b/test/encryption/cross_signing_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -32,7 +31,7 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; test('setupClient', () async { try { @@ -50,41 +49,43 @@ void main() { test('basic things', () async { if (!olmEnabled) return; - expect(client.encryption.crossSigning.enabled, true); + expect(client.encryption?.crossSigning.enabled, true); }); test('selfSign', () async { if (!olmEnabled) return; - final key = client.userDeviceKeys[client.userID].masterKey; + final key = client.userDeviceKeys[client.userID]!.masterKey!; key.setDirectVerified(false); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.crossSigning.selfSign(recoveryKey: ssssKey); + await client.encryption!.crossSigning.selfSign(recoveryKey: ssssKey); expect(key.directVerified, true); expect( FakeMatrixApi.calledEndpoints .containsKey('/client/r0/keys/signatures/upload'), true); - expect(await client.encryption.crossSigning.isCached(), true); + expect(await client.encryption!.crossSigning.isCached(), true); }); test('signable', () async { if (!olmEnabled) return; expect( - client.encryption.crossSigning - .signable([client.userDeviceKeys[client.userID].masterKey]), + client.encryption!.crossSigning + .signable([client.userDeviceKeys[client.userID!]!.masterKey!]), true); expect( - client.encryption.crossSigning.signable([ - client.userDeviceKeys[client.userID].deviceKeys[client.deviceID] + client.encryption!.crossSigning.signable([ + client.userDeviceKeys[client.userID!]!.deviceKeys[client.deviceID!]! ]), false); expect( - client.encryption.crossSigning.signable( - [client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']]), + client.encryption!.crossSigning.signable([ + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']! + ]), true); expect( - client.encryption.crossSigning.signable([ - client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + client.encryption!.crossSigning.signable([ + client + .userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! ]), false); }); @@ -92,24 +93,24 @@ void main() { test('sign', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.crossSigning.sign([ - client.userDeviceKeys[client.userID].masterKey, - client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE'], - client.userDeviceKeys['@othertest:fakeServer.notExisting'].masterKey + await client.encryption!.crossSigning.sign([ + client.userDeviceKeys[client.userID!]!.masterKey!, + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!, + client.userDeviceKeys['@othertest:fakeServer.notExisting']!.masterKey! ]); final body = json.decode(FakeMatrixApi - .calledEndpoints['/client/r0/keys/signatures/upload'].first); + .calledEndpoints['/client/r0/keys/signatures/upload']!.first); expect(body['@test:fakeServer.notExisting']?.containsKey('OTHERDEVICE'), true); expect( body['@test:fakeServer.notExisting'].containsKey( - client.userDeviceKeys[client.userID].masterKey.publicKey), + client.userDeviceKeys[client.userID]!.masterKey!.publicKey), true); expect( body['@othertest:fakeServer.notExisting'].containsKey(client .userDeviceKeys['@othertest:fakeServer.notExisting'] - .masterKey - .publicKey), + ?.masterKey + ?.publicKey), true); }); diff --git a/test/encryption/encrypt_decrypt_room_message_test.dart b/test/encryption/encrypt_decrypt_room_message_test.dart index 24d1fb2d..6c0c4f89 100644 --- a/test/encryption/encrypt_decrypt_room_message_test.dart +++ b/test/encryption/encrypt_decrypt_room_message_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -29,10 +28,10 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; final roomId = '!726s6s6q:example.com'; - Room room; - Map payload; + late Room room; + late Map payload; final now = DateTime.now(); test('setupClient', () async { @@ -47,12 +46,12 @@ void main() { if (!olmEnabled) return; client = await getClient(); - room = client.getRoomById(roomId); + room = client.getRoomById(roomId)!; }); test('encrypt payload', () async { if (!olmEnabled) return; - payload = await client.encryption.encryptGroupMessagePayload(roomId, { + payload = await client.encryption!.encryptGroupMessagePayload(roomId, { 'msgtype': 'm.text', 'text': 'Hello foxies!', }); @@ -72,9 +71,10 @@ void main() { room: room, originServerTs: now, eventId: '\$event', + senderId: client.userID!, ); final decryptedEvent = - await client.encryption.decryptRoomEvent(roomId, encryptedEvent); + await client.encryption!.decryptRoomEvent(roomId, encryptedEvent); expect(decryptedEvent.type, 'm.room.message'); expect(decryptedEvent.content['msgtype'], 'm.text'); expect(decryptedEvent.content['text'], 'Hello foxies!'); @@ -82,7 +82,7 @@ void main() { test('decrypt payload nocache', () async { if (!olmEnabled) return; - client.encryption.keyManager.clearInboundGroupSessions(); + client.encryption!.keyManager.clearInboundGroupSessions(); final encryptedEvent = Event( type: EventTypes.Encrypted, content: payload, @@ -93,11 +93,11 @@ void main() { senderId: '@alice:example.com', ); final decryptedEvent = - await client.encryption.decryptRoomEvent(roomId, encryptedEvent); + await client.encryption!.decryptRoomEvent(roomId, encryptedEvent); expect(decryptedEvent.type, 'm.room.message'); expect(decryptedEvent.content['msgtype'], 'm.text'); expect(decryptedEvent.content['text'], 'Hello foxies!'); - await client.encryption + await client.encryption! .decryptRoomEvent(roomId, encryptedEvent, store: true); }); diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 47fec8df..69828063 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -35,11 +34,11 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; final otherClient = Client('othertestclient', httpClient: FakeMatrixApi(), databaseBuilder: getDatabase); - DeviceKeys device; - Map payload; + late DeviceKeys device; + late Map payload; test('setupClient', () async { try { @@ -83,7 +82,7 @@ void main() { test('encryptToDeviceMessage', () async { if (!olmEnabled) return; - payload = await otherClient.encryption + payload = await otherClient.encryption! .encryptToDeviceMessage([device], 'm.to_device', {'hello': 'foxies'}); }); @@ -95,15 +94,15 @@ void main() { content: payload[client.userID][client.deviceID], ); final decryptedEvent = - await client.encryption.decryptToDeviceEvent(encryptedEvent); + await client.encryption!.decryptToDeviceEvent(encryptedEvent); expect(decryptedEvent.type, 'm.to_device'); expect(decryptedEvent.content['hello'], 'foxies'); }); test('decryptToDeviceEvent nocache', () async { if (!olmEnabled) return; - client.encryption.olmManager.olmSessions.clear(); - payload = await otherClient.encryption.encryptToDeviceMessage( + client.encryption!.olmManager.olmSessions.clear(); + payload = await otherClient.encryption!.encryptToDeviceMessage( [device], 'm.to_device', {'hello': 'superfoxies'}); final encryptedEvent = ToDeviceEvent( sender: '@othertest:fakeServer.notExisting', @@ -111,7 +110,7 @@ void main() { content: payload[client.userID][client.deviceID], ); final decryptedEvent = - await client.encryption.decryptToDeviceEvent(encryptedEvent); + await client.encryption!.decryptToDeviceEvent(encryptedEvent); expect(decryptedEvent.type, 'm.to_device'); expect(decryptedEvent.content['hello'], 'superfoxies'); }); diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index 0b93386c..c73af662 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -32,7 +31,7 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; test('setupClient', () async { try { @@ -55,7 +54,7 @@ void main() { final sessionKey = 'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw'; - client.encryption.keyManager.clearInboundGroupSessions(); + client.encryption!.keyManager.clearInboundGroupSessions(); var event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key', @@ -68,9 +67,9 @@ void main() { encryptedContent: { 'sender_key': validSenderKey, }); - await client.encryption.keyManager.handleToDeviceEvent(event); + await client.encryption!.keyManager.handleToDeviceEvent(event); expect( - client.encryption.keyManager.getInboundGroupSession( + client.encryption!.keyManager.getInboundGroupSession( '!726s6s6q:example.com', validSessionId, validSenderKey) != null, true); @@ -78,7 +77,7 @@ void main() { // now test a few invalid scenarios // not encrypted - client.encryption.keyManager.clearInboundGroupSessions(); + client.encryption!.keyManager.clearInboundGroupSessions(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.room_key', @@ -88,9 +87,9 @@ void main() { 'session_id': validSessionId, 'session_key': sessionKey, }); - await client.encryption.keyManager.handleToDeviceEvent(event); + await client.encryption!.keyManager.handleToDeviceEvent(event); expect( - client.encryption.keyManager.getInboundGroupSession( + client.encryption!.keyManager.getInboundGroupSession( '!726s6s6q:example.com', validSessionId, validSenderKey) != null, false); @@ -100,99 +99,104 @@ void main() { if (!olmEnabled) return; final roomId = '!726s6s6q:example.com'; expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); - var sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); + var sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, true); - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, true); - var inbound = client.encryption.keyManager.getInboundGroupSession( - roomId, sess.outboundGroupSession.session_id(), client.identityKey); + var inbound = client.encryption!.keyManager.getInboundGroupSession( + roomId, sess.outboundGroupSession!.session_id(), client.identityKey); expect(inbound != null, true); expect( - inbound.allowedAtIndex['@alice:example.com'] - ['L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8'], + inbound!.allowedAtIndex['@alice:example.com'] + ?['L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8'], 0); expect( inbound.allowedAtIndex['@alice:example.com'] - ['wMIDhiQl5jEXQrTB03ePOSQfR8sA/KMrW0CIfFfXKEE'], + ?['wMIDhiQl5jEXQrTB03ePOSQfR8sA/KMrW0CIfFfXKEE'], 0); // rotate after too many messages Iterable.generate(300).forEach((_) { - sess.outboundGroupSession.encrypt('some string'); + sess.outboundGroupSession!.encrypt('some string'); }); - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); // rotate if device is blocked - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); - client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); + client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! .blocked = true; - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); - client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! .blocked = false; // lazy-create if it would rotate - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); - final oldSessKey = sess.outboundGroupSession.session_key(); - client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); + final oldSessKey = sess.outboundGroupSession!.session_key(); + client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! .blocked = true; - await client.encryption.keyManager.prepareOutboundGroupSession(roomId); + await client.encryption!.keyManager.prepareOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, true); expect( - client.encryption.keyManager - .getOutboundGroupSession(roomId) - .outboundGroupSession + client.encryption!.keyManager + .getOutboundGroupSession(roomId)! + .outboundGroupSession! .session_key() != oldSessKey, true); - client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! .blocked = false; // rotate if too far in the past - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); sess.creationTime = DateTime.now().subtract(Duration(days: 30)); - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); // rotate if user leaves - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); - final room = client.getRoomById(roomId); + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); + final room = client.getRoomById(roomId)!; final member = room.getState('m.room.member', '@alice:example.com'); - member.content['membership'] = 'leave'; - room.summary.mJoinedMemberCount--; - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + member!.content['membership'] = 'leave'; + room.summary.mJoinedMemberCount = room.summary.mJoinedMemberCount! - 1; + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); member.content['membership'] = 'join'; - room.summary.mJoinedMemberCount++; + room.summary.mJoinedMemberCount = room.summary.mJoinedMemberCount! + 1; // do not rotate if new device is added - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); - sess.outboundGroupSession.encrypt( + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); + sess.outboundGroupSession!.encrypt( 'foxies'); // so that the new device will have a different index - client.userDeviceKeys['@alice:example.com'].deviceKeys['NEWDEVICE'] = + client.userDeviceKeys['@alice:example.com']?.deviceKeys['NEWDEVICE'] = DeviceKeys.fromJson({ 'user_id': '@alice:example.com', 'device_id': 'NEWDEVICE', @@ -211,56 +215,58 @@ void main() { } } }, client); - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, true); - inbound = client.encryption.keyManager.getInboundGroupSession( - roomId, sess.outboundGroupSession.session_id(), client.identityKey); + inbound = client.encryption!.keyManager.getInboundGroupSession( + roomId, sess.outboundGroupSession!.session_id(), client.identityKey); expect( - inbound.allowedAtIndex['@alice:example.com'] - ['L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8'], + inbound!.allowedAtIndex['@alice:example.com'] + ?['L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8'], 0); expect( inbound.allowedAtIndex['@alice:example.com'] - ['wMIDhiQl5jEXQrTB03ePOSQfR8sA/KMrW0CIfFfXKEE'], + ?['wMIDhiQl5jEXQrTB03ePOSQfR8sA/KMrW0CIfFfXKEE'], 0); expect( inbound.allowedAtIndex['@alice:example.com'] - ['bnKQp6pPW0l9cGoIgHpBoK5OUi4h0gylJ7upc4asFV8'], + ?['bnKQp6pPW0l9cGoIgHpBoK5OUi4h0gylJ7upc4asFV8'], 1); // do not rotate if new user is added member.content['membership'] = 'leave'; - room.summary.mJoinedMemberCount--; - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); + room.summary.mJoinedMemberCount = room.summary.mJoinedMemberCount! - 1; + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); member.content['membership'] = 'join'; - room.summary.mJoinedMemberCount++; - await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); + room.summary.mJoinedMemberCount = room.summary.mJoinedMemberCount! + 1; + await client.encryption!.keyManager + .clearOrUseOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, true); // force wipe - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); - await client.encryption.keyManager + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); + await client.encryption!.keyManager .clearOrUseOutboundGroupSession(roomId, wipe: true); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); // load from database - sess = - await client.encryption.keyManager.createOutboundGroupSession(roomId); - client.encryption.keyManager.clearOutboundGroupSessions(); + sess = await client.encryption!.keyManager + .createOutboundGroupSession(roomId); + client.encryption!.keyManager.clearOutboundGroupSessions(); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, false); - await client.encryption.keyManager.loadOutboundGroupSession(roomId); + await client.encryption!.keyManager.loadOutboundGroupSession(roomId); expect( - client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + client.encryption!.keyManager.getOutboundGroupSession(roomId) != null, true); }); @@ -276,71 +282,71 @@ void main() { 'session_key': 'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw' }; - client.encryption.keyManager.clearInboundGroupSessions(); + client.encryption!.keyManager.clearInboundGroupSessions(); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, false); - client.encryption.keyManager + client.encryption!.keyManager .setInboundGroupSession(roomId, sessionId, senderKey, sessionContent); await Future.delayed(Duration(milliseconds: 10)); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, 'invalid') != null, false); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession('otherroom', sessionId, senderKey) != null, true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession('otherroom', sessionId, 'invalid') != null, false); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession('otherroom', 'invalid', senderKey) != null, false); - client.encryption.keyManager.clearInboundGroupSessions(); + client.encryption!.keyManager.clearInboundGroupSessions(); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, false); - await client.encryption.keyManager + await client.encryption!.keyManager .loadInboundGroupSession(roomId, sessionId, senderKey); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, true); - client.encryption.keyManager.clearInboundGroupSessions(); + client.encryption!.keyManager.clearInboundGroupSessions(); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, false); - await client.encryption.keyManager + await client.encryption!.keyManager .loadInboundGroupSession(roomId, sessionId, 'invalid'); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, 'invalid') != null, false); @@ -379,7 +385,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.type, 'm.room.encrypted'); + expect(room.lastEvent?.type, 'm.room.encrypted'); // set a payload... var sessionPayload = { 'algorithm': AlgorithmTypes.megolmV1AesSha2, @@ -390,19 +396,19 @@ void main() { 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; - client.encryption.keyManager.setInboundGroupSession( + client.encryption!.keyManager.setInboundGroupSession( roomId, sessionId, senderKey, sessionPayload, forwarded: true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .inboundGroupSession - .first_known_index(), + ?.inboundGroupSession + ?.first_known_index(), 1); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .forwardingCurve25519KeyChain + ?.forwardingCurve25519KeyChain .length, 1); @@ -416,19 +422,19 @@ void main() { 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; - client.encryption.keyManager.setInboundGroupSession( + client.encryption!.keyManager.setInboundGroupSession( roomId, sessionId, senderKey, sessionPayload, forwarded: true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .inboundGroupSession - .first_known_index(), + ?.inboundGroupSession + ?.first_known_index(), 1); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .forwardingCurve25519KeyChain + ?.forwardingCurve25519KeyChain .length, 1); @@ -442,19 +448,19 @@ void main() { 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; - client.encryption.keyManager.setInboundGroupSession( + client.encryption!.keyManager.setInboundGroupSession( roomId, sessionId, senderKey, sessionPayload, forwarded: true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .inboundGroupSession - .first_known_index(), + ?.inboundGroupSession + ?.first_known_index(), 0); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .forwardingCurve25519KeyChain + ?.forwardingCurve25519KeyChain .length, 1); @@ -468,19 +474,19 @@ void main() { 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; - client.encryption.keyManager.setInboundGroupSession( + client.encryption!.keyManager.setInboundGroupSession( roomId, sessionId, senderKey, sessionPayload, forwarded: true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .inboundGroupSession - .first_known_index(), + ?.inboundGroupSession + ?.first_known_index(), 0); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .forwardingCurve25519KeyChain + ?.forwardingCurve25519KeyChain .length, 1); @@ -494,25 +500,25 @@ void main() { 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; - client.encryption.keyManager.setInboundGroupSession( + client.encryption!.keyManager.setInboundGroupSession( roomId, sessionId, senderKey, sessionPayload, forwarded: true); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .inboundGroupSession - .first_known_index(), + ?.inboundGroupSession + ?.first_known_index(), 0); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) - .forwardingCurve25519KeyChain + ?.forwardingCurve25519KeyChain .length, 0); // test that it decrypted the last event - expect(room.lastEvent.type, 'm.room.message'); - expect(room.lastEvent.content['body'], 'foxies'); + expect(room.lastEvent?.type, 'm.room.message'); + expect(room.lastEvent?.content['body'], 'foxies'); inbound.free(); session.free(); @@ -521,22 +527,21 @@ void main() { test('Reused deviceID attack', () async { if (!olmEnabled) return; Logs().level = Level.warning; - client ??= await getClient(); // Ensure the device came from sync expect( client.userDeviceKeys['@alice:example.com'] - .deviceKeys['JLAFKJWSCS'] != + ?.deviceKeys['JLAFKJWSCS'] != null, true); // Alice removes her device - client.userDeviceKeys['@alice:example.com'].deviceKeys + client.userDeviceKeys['@alice:example.com']?.deviceKeys .remove('JLAFKJWSCS'); // Alice adds her device with same device ID but different keys - final oldResp = FakeMatrixApi.api['POST']['/client/r0/keys/query'](null); - FakeMatrixApi.api['POST']['/client/r0/keys/query'] = (_) { + final oldResp = FakeMatrixApi.api['POST']?['/client/r0/keys/query'](null); + FakeMatrixApi.api['POST']?['/client/r0/keys/query'] = (_) { oldResp['device_keys']['@alice:example.com']['JLAFKJWSCS'] = { 'user_id': '@alice:example.com', 'device_id': 'JLAFKJWSCS', @@ -558,10 +563,10 @@ void main() { }; return oldResp; }; - client.userDeviceKeys['@alice:example.com'].outdated = true; + client.userDeviceKeys['@alice:example.com']!.outdated = true; await client.updateUserDeviceKeys(); expect( - client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'], + client.userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS'], null); }); diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index 80d7dbd0..01d02657 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -58,8 +57,8 @@ void main() { if (!olmEnabled) return; final matrix = await getClient(); - final requestRoom = matrix.getRoomById('!726s6s6q:example.com'); - await matrix.encryption.keyManager.request( + final requestRoom = matrix.getRoomById('!726s6s6q:example.com')!; + await matrix.encryption!.keyManager.request( requestRoom, 'sessionId', validSenderKey, tryOnlineBackup: false); var foundEvent = false; @@ -89,12 +88,12 @@ void main() { matrix.setUserId('@alice:example.com'); // we need to pretend to be alice FakeMatrixApi.calledEndpoints.clear(); await matrix - .userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] + .userDeviceKeys['@alice:example.com']!.deviceKeys['OTHERDEVICE']! .setBlocked(false); await matrix - .userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] + .userDeviceKeys['@alice:example.com']!.deviceKeys['OTHERDEVICE']! .setVerified(true); - final session = await matrix.encryption.keyManager + final session = await matrix.encryption!.keyManager .loadInboundGroupSession( '!726s6s6q:example.com', validSessionId, validSenderKey); // test a successful share @@ -112,7 +111,7 @@ void main() { 'request_id': 'request_1', 'requesting_device_id': 'OTHERDEVICE', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); Logs().i(FakeMatrixApi.calledEndpoints.keys.toString()); expect( FakeMatrixApi.calledEndpoints.keys.any( @@ -121,9 +120,9 @@ void main() { // test a successful foreign share FakeMatrixApi.calledEndpoints.clear(); - session.allowedAtIndex['@test:fakeServer.notExisting'] = { - matrix.userDeviceKeys['@test:fakeServer.notExisting'] - .deviceKeys['OTHERDEVICE'].curve25519Key: 0, + session!.allowedAtIndex['@test:fakeServer.notExisting'] = { + matrix.userDeviceKeys['@test:fakeServer.notExisting']! + .deviceKeys['OTHERDEVICE']!.curve25519Key!: 0, }; event = ToDeviceEvent( sender: '@test:fakeServer.notExisting', @@ -139,7 +138,7 @@ void main() { 'request_id': 'request_a1', 'requesting_device_id': 'OTHERDEVICE', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); Logs().i(FakeMatrixApi.calledEndpoints.keys.toString()); expect( FakeMatrixApi.calledEndpoints.keys.any( @@ -165,7 +164,7 @@ void main() { 'request_id': 'request_a2', 'requesting_device_id': 'OTHERDEVICE', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); Logs().i(FakeMatrixApi.calledEndpoints.keys.toString()); expect( FakeMatrixApi.calledEndpoints.keys.any( @@ -182,7 +181,7 @@ void main() { 'request_id': 'request_2', 'requesting_device_id': 'OTHERDEVICE', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -204,7 +203,7 @@ void main() { 'request_id': 'request_3', 'requesting_device_id': 'JLAFKJWSCS', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -226,7 +225,7 @@ void main() { 'request_id': 'request_4', 'requesting_device_id': 'blubb', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -248,7 +247,7 @@ void main() { 'request_id': 'request_5', 'requesting_device_id': 'OTHERDEVICE', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -270,7 +269,7 @@ void main() { 'request_id': 'request_6', 'requesting_device_id': 'OTHERDEVICE', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -282,17 +281,17 @@ void main() { test('Receive shared keys', () async { if (!olmEnabled) return; final matrix = await getClient(); - final requestRoom = matrix.getRoomById('!726s6s6q:example.com'); - await matrix.encryption.keyManager.request( + final requestRoom = matrix.getRoomById('!726s6s6q:example.com')!; + await matrix.encryption!.keyManager.request( requestRoom, validSessionId, validSenderKey, tryOnlineBackup: false); - final session = await matrix.encryption.keyManager + final session = (await matrix.encryption!.keyManager .loadInboundGroupSession( - requestRoom.id, validSessionId, validSenderKey); - final sessionKey = session.inboundGroupSession - .export_session(session.inboundGroupSession.first_known_index()); - matrix.encryption.keyManager.clearInboundGroupSessions(); + requestRoom.id, validSessionId, validSenderKey))!; + final sessionKey = session.inboundGroupSession! + .export_session(session.inboundGroupSession!.first_known_index()); + matrix.encryption!.keyManager.clearInboundGroupSessions(); var event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', @@ -303,13 +302,15 @@ void main() { 'session_key': sessionKey, 'sender_key': validSenderKey, 'forwarding_curve25519_key_chain': [], + 'sender_claimed_ed25519_key': + 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', }, encryptedContent: { 'sender_key': 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( - matrix.encryption.keyManager.getInboundGroupSession( + matrix.encryption!.keyManager.getInboundGroupSession( requestRoom.id, validSessionId, validSenderKey) != null, true); @@ -317,7 +318,7 @@ void main() { // now test a few invalid scenarios // request not found - matrix.encryption.keyManager.clearInboundGroupSessions(); + matrix.encryption!.keyManager.clearInboundGroupSessions(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', @@ -328,22 +329,24 @@ void main() { 'session_key': sessionKey, 'sender_key': validSenderKey, 'forwarding_curve25519_key_chain': [], + 'sender_claimed_ed25519_key': + 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', }, encryptedContent: { 'sender_key': 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( - matrix.encryption.keyManager.getInboundGroupSession( + matrix.encryption!.keyManager.getInboundGroupSession( requestRoom.id, validSessionId, validSenderKey) != null, false); // unknown device - await matrix.encryption.keyManager.request( + await matrix.encryption!.keyManager.request( requestRoom, validSessionId, validSenderKey, tryOnlineBackup: false); - matrix.encryption.keyManager.clearInboundGroupSessions(); + matrix.encryption!.keyManager.clearInboundGroupSessions(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', @@ -354,22 +357,24 @@ void main() { 'session_key': sessionKey, 'sender_key': validSenderKey, 'forwarding_curve25519_key_chain': [], + 'sender_claimed_ed25519_key': + 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', }, encryptedContent: { 'sender_key': 'invalid', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( - matrix.encryption.keyManager.getInboundGroupSession( + matrix.encryption!.keyManager.getInboundGroupSession( requestRoom.id, validSessionId, validSenderKey) != null, false); // no encrypted content - await matrix.encryption.keyManager.request( + await matrix.encryption!.keyManager.request( requestRoom, validSessionId, validSenderKey, tryOnlineBackup: false); - matrix.encryption.keyManager.clearInboundGroupSessions(); + matrix.encryption!.keyManager.clearInboundGroupSessions(); event = ToDeviceEvent( sender: '@alice:example.com', type: 'm.forwarded_room_key', @@ -380,10 +385,12 @@ void main() { 'session_key': sessionKey, 'sender_key': validSenderKey, 'forwarding_curve25519_key_chain': [], + 'sender_claimed_ed25519_key': + 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', }); - await matrix.encryption.keyManager.handleToDeviceEvent(event); + await matrix.encryption!.keyManager.handleToDeviceEvent(event); expect( - matrix.encryption.keyManager.getInboundGroupSession( + matrix.encryption!.keyManager.getInboundGroupSession( requestRoom.id, validSessionId, validSenderKey) != null, false); diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 1b7aa433..8a622e44 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -34,7 +33,7 @@ class MockSSSS extends SSSS { bool requestedSecrets = false; @override - Future maybeRequestAll([List devices]) async { + Future maybeRequestAll([List? devices]) async { requestedSecrets = true; final handle = open(); await handle.unlock(recoveryKey: ssssKey); @@ -56,7 +55,7 @@ EventUpdate getLastSentEvent(KeyVerification req) { 'sender': req.client.userID, }, type: EventUpdateType.timeline, - roomID: req.room.id, + roomID: req.room!.id, ); } @@ -70,8 +69,8 @@ void main() { const otherPickledOlmAccount = 'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA'; - Client client1; - Client client2; + late Client client1; + late Client client2; test('setupClient', () async { try { @@ -117,24 +116,24 @@ void main() { // because then we can easily intercept the payloads and inject in the other client FakeMatrixApi.calledEndpoints.clear(); // make sure our master key is *not* verified to not triger SSSS for now - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(false); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(false); final req1 = - await client1.userDeviceKeys[client2.userID].startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification(); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); - KeyVerification req2; + late KeyVerification req2; final sub = client2.onKeyVerificationRequest.stream.listen((req) { req2 = req; }); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); - expect(req2 != null, true); expect( - client2.encryption.keyVerificationManager - .getRequest(req2.transactionId), + client2.encryption!.keyVerificationManager + .getRequest(req2.transactionId!), req2); // send ready @@ -145,27 +144,27 @@ void main() { // send start FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req1); // send accept FakeMatrixApi.calledEndpoints.clear(); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req2); // send key FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req1); // send key FakeMatrixApi.calledEndpoints.clear(); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req2); // receive last key FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); // compare emoji expect(req1.state, KeyVerificationState.askSas); @@ -194,64 +193,66 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); await req1.acceptSas(); evt = getLastSentEvent(req1); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); expect(req1.state, KeyVerificationState.waitingSas); // send mac FakeMatrixApi.calledEndpoints.clear(); await req2.acceptSas(); evt = getLastSentEvent(req2); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); expect(req1.state, KeyVerificationState.done); expect(req2.state, KeyVerificationState.done); expect( - client1.userDeviceKeys[client2.userID].deviceKeys[client2.deviceID] - .directVerified, + client1.userDeviceKeys[client2.userID]?.deviceKeys[client2.deviceID] + ?.directVerified, true); expect( - client2.userDeviceKeys[client1.userID].deviceKeys[client1.deviceID] - .directVerified, + client2.userDeviceKeys[client1.userID]?.deviceKeys[client1.deviceID] + ?.directVerified, true); - await client1.encryption.keyVerificationManager.cleanup(); - await client2.encryption.keyVerificationManager.cleanup(); + await client1.encryption!.keyVerificationManager.cleanup(); + await client2.encryption!.keyVerificationManager.cleanup(); }); test('ask SSSS start', () async { if (!olmEnabled) return; - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(true); - await client1.encryption.ssss.clearCache(); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(true); + await client1.encryption!.ssss.clearCache(); final req1 = - await client1.userDeviceKeys[client2.userID].startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification(); expect(req1.state, KeyVerificationState.askSSSS); await req1.openSSSS(recoveryKey: ssssKey); await Future.delayed(Duration(seconds: 1)); expect(req1.state, KeyVerificationState.waitingAccept); await req1.cancel(); - await client1.encryption.keyVerificationManager.cleanup(); + await client1.encryption!.keyVerificationManager.cleanup(); }); test('ask SSSS end', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); // make sure our master key is *not* verified to not triger SSSS for now - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(false); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(false); // the other one has to have their master key verified to trigger asking for ssss - client2.userDeviceKeys[client2.userID].masterKey.setDirectVerified(true); + client2.userDeviceKeys[client2.userID]!.masterKey! + .setDirectVerified(true); final req1 = - await client1.userDeviceKeys[client2.userID].startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification(); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); - KeyVerification req2; + late KeyVerification req2; final sub = client2.onKeyVerificationRequest.stream.listen((req) { req2 = req; }); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); - expect(req2 != null, true); // send ready FakeMatrixApi.calledEndpoints.clear(); @@ -261,27 +262,27 @@ void main() { // send start FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req1); // send accept FakeMatrixApi.calledEndpoints.clear(); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req2); // send key FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req1); // send key FakeMatrixApi.calledEndpoints.clear(); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req2); // receive last key FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); // compare emoji expect(req1.state, KeyVerificationState.askSas); @@ -301,21 +302,22 @@ void main() { } // alright, they match - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(true); - await client1.encryption.ssss.clearCache(); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(true); + await client1.encryption!.ssss.clearCache(); // send mac FakeMatrixApi.calledEndpoints.clear(); await req1.acceptSas(); evt = getLastSentEvent(req1); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); expect(req1.state, KeyVerificationState.waitingSas); // send mac FakeMatrixApi.calledEndpoints.clear(); await req2.acceptSas(); evt = getLastSentEvent(req2); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); expect(req1.state, KeyVerificationState.askSSSS); expect(req2.state, KeyVerificationState.done); @@ -324,66 +326,67 @@ void main() { await Future.delayed(Duration(milliseconds: 10)); expect(req1.state, KeyVerificationState.done); - client1.encryption.ssss = MockSSSS(client1.encryption); - (client1.encryption.ssss as MockSSSS).requestedSecrets = false; - await client1.encryption.ssss.clearCache(); + client1.encryption!.ssss = MockSSSS(client1.encryption!); + (client1.encryption!.ssss as MockSSSS).requestedSecrets = false; + await client1.encryption!.ssss.clearCache(); await req1.maybeRequestSSSSSecrets(); await Future.delayed(Duration(milliseconds: 10)); - expect((client1.encryption.ssss as MockSSSS).requestedSecrets, true); + expect((client1.encryption!.ssss as MockSSSS).requestedSecrets, true); // delay for 12 seconds to be sure no other tests clear the ssss cache await Future.delayed(Duration(seconds: 12)); - await client1.encryption.keyVerificationManager.cleanup(); - await client2.encryption.keyVerificationManager.cleanup(); + await client1.encryption!.keyVerificationManager.cleanup(); + await client2.encryption!.keyVerificationManager.cleanup(); }); test('reject verification', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); // make sure our master key is *not* verified to not triger SSSS for now - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(false); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(false); final req1 = - await client1.userDeviceKeys[client2.userID].startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification(); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); - KeyVerification req2; + late KeyVerification req2; final sub = client2.onKeyVerificationRequest.stream.listen((req) { req2 = req; }); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); FakeMatrixApi.calledEndpoints.clear(); await req2.rejectVerification(); evt = getLastSentEvent(req2); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); expect(req1.state, KeyVerificationState.error); expect(req2.state, KeyVerificationState.error); - await client1.encryption.keyVerificationManager.cleanup(); - await client2.encryption.keyVerificationManager.cleanup(); + await client1.encryption!.keyVerificationManager.cleanup(); + await client2.encryption!.keyVerificationManager.cleanup(); }); test('reject sas', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); // make sure our master key is *not* verified to not triger SSSS for now - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(false); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(false); final req1 = - await client1.userDeviceKeys[client2.userID].startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification(); var evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); - KeyVerification req2; + late KeyVerification req2; final sub = client2.onKeyVerificationRequest.stream.listen((req) { req2 = req; }); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); - expect(req2 != null, true); // send ready FakeMatrixApi.calledEndpoints.clear(); @@ -393,60 +396,60 @@ void main() { // send start FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req1); // send accept FakeMatrixApi.calledEndpoints.clear(); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req2); // send key FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req1); // send key FakeMatrixApi.calledEndpoints.clear(); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); evt = getLastSentEvent(req2); // receive last key FakeMatrixApi.calledEndpoints.clear(); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); await req1.acceptSas(); FakeMatrixApi.calledEndpoints.clear(); await req2.rejectSas(); evt = getLastSentEvent(req2); - await client1.encryption.keyVerificationManager.handleEventUpdate(evt); + await client1.encryption!.keyVerificationManager.handleEventUpdate(evt); expect(req1.state, KeyVerificationState.error); expect(req2.state, KeyVerificationState.error); - await client1.encryption.keyVerificationManager.cleanup(); - await client2.encryption.keyVerificationManager.cleanup(); + await client1.encryption!.keyVerificationManager.cleanup(); + await client2.encryption!.keyVerificationManager.cleanup(); }); test('other device accepted', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); // make sure our master key is *not* verified to not triger SSSS for now - client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(false); + client1.userDeviceKeys[client1.userID]!.masterKey! + .setDirectVerified(false); final req1 = - await client1.userDeviceKeys[client2.userID].startVerification(); + await client1.userDeviceKeys[client2.userID]!.startVerification(); final evt = getLastSentEvent(req1); expect(req1.state, KeyVerificationState.waitingAccept); - KeyVerification req2; + late KeyVerification req2; final sub = client2.onKeyVerificationRequest.stream.listen((req) { req2 = req; }); - await client2.encryption.keyVerificationManager.handleEventUpdate(evt); + await client2.encryption!.keyVerificationManager.handleEventUpdate(evt); await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); - expect(req2 != null, true); - await client2.encryption.keyVerificationManager + await client2.encryption!.keyVerificationManager .handleEventUpdate(EventUpdate( content: { 'event_id': req2.transactionId, @@ -463,13 +466,13 @@ void main() { 'sender': client2.userID, }, type: EventUpdateType.timeline, - roomID: req2.room.id, + roomID: req2.room!.id, )); expect(req2.state, KeyVerificationState.error); await req2.cancel(); - await client1.encryption.keyVerificationManager.cleanup(); - await client2.encryption.keyVerificationManager.cleanup(); + await client1.encryption!.keyVerificationManager.cleanup(); + await client2.encryption!.keyVerificationManager.cleanup(); }); test('dispose client', () async { diff --git a/test/encryption/olm_manager_test.dart b/test/encryption/olm_manager_test.dart index e6e7b5f6..40ebc08c 100644 --- a/test/encryption/olm_manager_test.dart +++ b/test/encryption/olm_manager_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH @@ -32,7 +31,7 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; test('setupClient', () async { try { @@ -53,37 +52,37 @@ void main() { final payload = { 'fox': 'floof', }; - final signedPayload = client.encryption.olmManager.signJson(payload); + final signedPayload = client.encryption!.olmManager.signJson(payload); expect( signedPayload.checkJsonSignature( - client.fingerprintKey, client.userID, client.deviceID), + client.fingerprintKey, client.userID!, client.deviceID!), true); }); test('uploadKeys', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); - final res = - await client.encryption.olmManager.uploadKeys(uploadDeviceKeys: true); + final res = await client.encryption!.olmManager + .uploadKeys(uploadDeviceKeys: true); expect(res, true); var sent = json.decode( - FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first); + FakeMatrixApi.calledEndpoints['/client/r0/keys/upload']!.first); expect(sent['device_keys'] != null, true); expect(sent['one_time_keys'] != null, true); expect(sent['one_time_keys'].keys.length, 66); expect(sent['fallback_keys'] != null, true); expect(sent['fallback_keys'].keys.length, 1); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.olmManager.uploadKeys(); + await client.encryption!.olmManager.uploadKeys(); sent = json.decode( - FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first); + FakeMatrixApi.calledEndpoints['/client/r0/keys/upload']!.first); expect(sent['device_keys'] != null, false); expect(sent['fallback_keys'].keys.length, 1); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.olmManager + await client.encryption!.olmManager .uploadKeys(oldKeyCount: 20, unusedFallbackKey: true); sent = json.decode( - FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first); + FakeMatrixApi.calledEndpoints['/client/r0/keys/upload']!.first); expect(sent['one_time_keys'].keys.length, 46); expect(sent['fallback_keys'].keys.length, 0); }); @@ -91,7 +90,7 @@ void main() { test('handleDeviceOneTimeKeysCount', () async { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); - client.encryption.olmManager + client.encryption!.olmManager .handleDeviceOneTimeKeysCount({'signed_curve25519': 20}, null); await Future.delayed(Duration(milliseconds: 50)); expect( @@ -99,7 +98,7 @@ void main() { true); FakeMatrixApi.calledEndpoints.clear(); - client.encryption.olmManager + client.encryption!.olmManager .handleDeviceOneTimeKeysCount({'signed_curve25519': 70}, null); await Future.delayed(Duration(milliseconds: 50)); expect( @@ -107,7 +106,7 @@ void main() { false); FakeMatrixApi.calledEndpoints.clear(); - client.encryption.olmManager.handleDeviceOneTimeKeysCount(null, []); + client.encryption!.olmManager.handleDeviceOneTimeKeysCount(null, []); await Future.delayed(Duration(milliseconds: 50)); expect( FakeMatrixApi.calledEndpoints.containsKey('/client/r0/keys/upload'), @@ -115,7 +114,7 @@ void main() { // this will upload keys because we assume the key count is 0, if the server doesn't send one FakeMatrixApi.calledEndpoints.clear(); - client.encryption.olmManager + client.encryption!.olmManager .handleDeviceOneTimeKeysCount(null, ['signed_curve25519']); await Future.delayed(Duration(milliseconds: 50)); expect( @@ -125,30 +124,31 @@ void main() { test('restoreOlmSession', () async { if (!olmEnabled) return; - client.encryption.olmManager.olmSessions.clear(); - await client.encryption.olmManager - .restoreOlmSession(client.userID, client.identityKey); - expect(client.encryption.olmManager.olmSessions.length, 1); + client.encryption!.olmManager.olmSessions.clear(); + await client.encryption!.olmManager + .restoreOlmSession(client.userID!, client.identityKey); + expect(client.encryption!.olmManager.olmSessions.length, 1); - client.encryption.olmManager.olmSessions.clear(); - await client.encryption.olmManager - .restoreOlmSession(client.userID, 'invalid'); - expect(client.encryption.olmManager.olmSessions.length, 0); + client.encryption!.olmManager.olmSessions.clear(); + await client.encryption!.olmManager + .restoreOlmSession(client.userID!, 'invalid'); + expect(client.encryption!.olmManager.olmSessions.length, 0); - client.encryption.olmManager.olmSessions.clear(); - await client.encryption.olmManager + client.encryption!.olmManager.olmSessions.clear(); + await client.encryption!.olmManager .restoreOlmSession('invalid', client.identityKey); - expect(client.encryption.olmManager.olmSessions.length, 0); + expect(client.encryption!.olmManager.olmSessions.length, 0); }); test('startOutgoingOlmSessions', () async { if (!olmEnabled) return; // start an olm session.....with ourself! - client.encryption.olmManager.olmSessions.clear(); - await client.encryption.olmManager.startOutgoingOlmSessions( - [client.userDeviceKeys[client.userID].deviceKeys[client.deviceID]]); + client.encryption!.olmManager.olmSessions.clear(); + await client.encryption!.olmManager.startOutgoingOlmSessions([ + client.userDeviceKeys[client.userID!]!.deviceKeys[client.deviceID]! + ]); expect( - client.encryption.olmManager.olmSessions + client.encryption!.olmManager.olmSessions .containsKey(client.identityKey), true); }); @@ -159,7 +159,7 @@ void main() { final deviceId = 'JLAFKJWSCS'; final senderKey = 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8'; FakeMatrixApi.calledEndpoints.clear(); - await client.database.setLastSentMessageUserDeviceKey( + await client.database!.setLastSentMessageUserDeviceKey( json.encode({ 'type': 'm.foxies', 'content': { @@ -176,7 +176,7 @@ void main() { 'sender_key': senderKey, }, ); - await client.encryption.olmManager.handleToDeviceEvent(event); + await client.encryption!.olmManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -186,7 +186,7 @@ void main() { // not encrypted FakeMatrixApi.calledEndpoints.clear(); - await client.database.setLastSentMessageUserDeviceKey( + await client.database!.setLastSentMessageUserDeviceKey( json.encode({ 'type': 'm.foxies', 'content': { @@ -201,7 +201,7 @@ void main() { content: {}, encryptedContent: null, ); - await client.encryption.olmManager.handleToDeviceEvent(event); + await client.encryption!.olmManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -209,7 +209,7 @@ void main() { // device not found FakeMatrixApi.calledEndpoints.clear(); - await client.database.setLastSentMessageUserDeviceKey( + await client.database!.setLastSentMessageUserDeviceKey( json.encode({ 'type': 'm.foxies', 'content': { @@ -226,7 +226,7 @@ void main() { 'sender_key': 'invalid', }, ); - await client.encryption.olmManager.handleToDeviceEvent(event); + await client.encryption!.olmManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -234,7 +234,7 @@ void main() { // don't replay if the last event is m.dummy itself FakeMatrixApi.calledEndpoints.clear(); - await client.database.setLastSentMessageUserDeviceKey( + await client.database!.setLastSentMessageUserDeviceKey( json.encode({ 'type': 'm.dummy', 'content': {}, @@ -249,7 +249,7 @@ void main() { 'sender_key': senderKey, }, ); - await client.encryption.olmManager.handleToDeviceEvent(event); + await client.encryption!.olmManager.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), diff --git a/test/encryption/online_key_backup_test.dart b/test/encryption/online_key_backup_test.dart index 4cd8feac..48f03efe 100644 --- a/test/encryption/online_key_backup_test.dart +++ b/test/encryption/online_key_backup_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -32,7 +31,7 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; final roomId = '!726s6s6q:example.com'; final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; @@ -54,21 +53,21 @@ void main() { test('basic things', () async { if (!olmEnabled) return; - expect(client.encryption.keyManager.enabled, true); - expect(await client.encryption.keyManager.isCached(), false); - final handle = client.encryption.ssss.open(); + expect(client.encryption!.keyManager.enabled, true); + expect(await client.encryption!.keyManager.isCached(), false); + final handle = client.encryption!.ssss.open(); await handle.unlock(recoveryKey: ssssKey); await handle.maybeCacheAll(); - expect(await client.encryption.keyManager.isCached(), true); + expect(await client.encryption!.keyManager.isCached(), true); }); test('load key', () async { if (!olmEnabled) return; - client.encryption.keyManager.clearInboundGroupSessions(); - await client.encryption.keyManager - .request(client.getRoomById(roomId), sessionId, senderKey); + client.encryption!.keyManager.clearInboundGroupSessions(); + await client.encryption!.keyManager + .request(client.getRoomById(roomId)!, sessionId, senderKey); expect( - client.encryption.keyManager + client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey) != null, true); @@ -94,25 +93,25 @@ void main() { 'sender_claimed_ed25519_key': client.fingerprintKey, }; FakeMatrixApi.calledEndpoints.clear(); - client.encryption.keyManager.setInboundGroupSession( + client.encryption!.keyManager.setInboundGroupSession( roomId, sessionId, senderKey, sessionPayload, forwarded: true); await Future.delayed(Duration(milliseconds: 500)); - var dbSessions = await client.database.getInboundGroupSessionsToUpload(); + var dbSessions = await client.database!.getInboundGroupSessionsToUpload(); expect(dbSessions.isNotEmpty, true); - await client.encryption.keyManager.backgroundTasks(); + await client.encryption!.keyManager.backgroundTasks(); final payload = FakeMatrixApi - .calledEndpoints['/client/unstable/room_keys/keys?version=5'].first; - dbSessions = await client.database.getInboundGroupSessionsToUpload(); + .calledEndpoints['/client/unstable/room_keys/keys?version=5']!.first; + dbSessions = await client.database!.getInboundGroupSessionsToUpload(); expect(dbSessions.isEmpty, true); final onlineKeys = RoomKeys.fromJson(json.decode(payload)); - client.encryption.keyManager.clearInboundGroupSessions(); - var ret = client.encryption.keyManager + client.encryption!.keyManager.clearInboundGroupSessions(); + var ret = client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey); expect(ret, null); - await client.encryption.keyManager.loadFromResponse(onlineKeys); - ret = client.encryption.keyManager + await client.encryption!.keyManager.loadFromResponse(onlineKeys); + ret = client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId, senderKey); expect(ret != null, true); }); diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index 4189e1f8..13de3af8 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -42,7 +41,7 @@ class MockSSSS extends SSSS { bool requestedSecrets = false; @override - Future maybeRequestAll([List devices]) async { + Future maybeRequestAll([List? devices]) async { requestedSecrets = true; final handle = open(); await handle.unlock(recoveryKey: ssssKey); @@ -55,7 +54,7 @@ void main() { Logs().level = Level.error; var olmEnabled = true; - Client client; + late Client client; test('setupClient', () async { try { @@ -73,7 +72,7 @@ void main() { test('basic things', () async { if (!olmEnabled) return; - expect(client.encryption.ssss.defaultKeyId, + expect(client.encryption!.ssss.defaultKeyId, '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3'); }); @@ -88,7 +87,7 @@ void main() { test('store', () async { if (!olmEnabled) return; - final handle = client.encryption.ssss.open(); + final handle = client.encryption!.ssss.open(); var failed = false; try { await handle.unlock(passphrase: 'invalid'); @@ -114,7 +113,7 @@ void main() { // account_data for this test final content = FakeMatrixApi .calledEndpoints[ - '/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best%20animal'] + '/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best%20animal']! .first; client.accountData['best animal'] = BasicEvent.fromJson({ 'type': 'best animal', @@ -130,77 +129,78 @@ void main() { final decoded = SSSS.decodeRecoveryKey(encoded); expect(key, decoded); - final handle = client.encryption.ssss.open(); + final handle = client.encryption!.ssss.open(); await handle.unlock(recoveryKey: ssssKey); expect(handle.recoveryKey, ssssKey); }); test('cache', () async { if (!olmEnabled) return; - await client.encryption.ssss.clearCache(); + await client.encryption!.ssss.clearCache(); final handle = - client.encryption.ssss.open(EventTypes.CrossSigningSelfSigning); + client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); await handle.unlock(recoveryKey: ssssKey, postUnlock: false); expect( - (await client.encryption.ssss + (await client.encryption!.ssss .getCached(EventTypes.CrossSigningSelfSigning)) != null, false); expect( - (await client.encryption.ssss + (await client.encryption!.ssss .getCached(EventTypes.CrossSigningUserSigning)) != null, false); await handle.getStored(EventTypes.CrossSigningSelfSigning); expect( - (await client.encryption.ssss + (await client.encryption!.ssss .getCached(EventTypes.CrossSigningSelfSigning)) != null, true); await handle.maybeCacheAll(); expect( - (await client.encryption.ssss + (await client.encryption!.ssss .getCached(EventTypes.CrossSigningUserSigning)) != null, true); expect( - (await client.encryption.ssss.getCached(EventTypes.MegolmBackup)) != + (await client.encryption!.ssss.getCached(EventTypes.MegolmBackup)) != null, true); }); test('postUnlock', () async { if (!olmEnabled) return; - await client.encryption.ssss.clearCache(); - client.userDeviceKeys[client.userID].masterKey.setDirectVerified(false); + await client.encryption!.ssss.clearCache(); + client.userDeviceKeys[client.userID!]!.masterKey! + .setDirectVerified(false); final handle = - client.encryption.ssss.open(EventTypes.CrossSigningSelfSigning); + client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); await handle.unlock(recoveryKey: ssssKey); expect( - (await client.encryption.ssss + (await client.encryption!.ssss .getCached(EventTypes.CrossSigningSelfSigning)) != null, true); expect( - (await client.encryption.ssss + (await client.encryption!.ssss .getCached(EventTypes.CrossSigningUserSigning)) != null, true); expect( - (await client.encryption.ssss.getCached(EventTypes.MegolmBackup)) != + (await client.encryption!.ssss.getCached(EventTypes.MegolmBackup)) != null, true); - expect( - client.userDeviceKeys[client.userID].masterKey.directVerified, true); + expect(client.userDeviceKeys[client.userID!]!.masterKey!.directVerified, + true); }); test('make share requests', () async { if (!olmEnabled) return; final key = - client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']; + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; key.setDirectVerified(true); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.ssss.request('some.type', [key]); + await client.encryption!.ssss.request('some.type', [key]); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -210,7 +210,7 @@ void main() { test('answer to share requests', () async { if (!olmEnabled) return; var event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.request', content: { 'action': 'request', @@ -220,7 +220,7 @@ void main() { }, ); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.ssss.handleToDeviceEvent(event); + await client.encryption!.ssss.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -240,7 +240,7 @@ void main() { }, ); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.ssss.handleToDeviceEvent(event); + await client.encryption!.ssss.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -248,7 +248,7 @@ void main() { // secret not cached event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.request', content: { 'action': 'request', @@ -258,7 +258,7 @@ void main() { }, ); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.ssss.handleToDeviceEvent(event); + await client.encryption!.ssss.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -266,7 +266,7 @@ void main() { // is a cancelation event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.request', content: { 'action': 'request_cancellation', @@ -276,7 +276,7 @@ void main() { }, ); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.ssss.handleToDeviceEvent(event); + await client.encryption!.ssss.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -284,11 +284,12 @@ void main() { // device not verified final key = - client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']; + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; key.setDirectVerified(false); - client.userDeviceKeys[client.userID].masterKey.setDirectVerified(false); + client.userDeviceKeys[client.userID!]!.masterKey! + .setDirectVerified(false); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.request', content: { 'action': 'request', @@ -298,7 +299,7 @@ void main() { }, ); FakeMatrixApi.calledEndpoints.clear(); - await client.encryption.ssss.handleToDeviceEvent(event); + await client.encryption!.ssss.handleToDeviceEvent(event); expect( FakeMatrixApi.calledEndpoints.keys.any( (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), @@ -309,28 +310,28 @@ void main() { test('receive share requests', () async { if (!olmEnabled) return; final key = - client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']; + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; key.setDirectVerified(true); final handle = - client.encryption.ssss.open(EventTypes.CrossSigningSelfSigning); + client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); await handle.unlock(recoveryKey: ssssKey); - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request('best animal', [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); var event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { - 'request_id': client.encryption.ssss.pendingShareRequests.keys.first, + 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, 'secret': 'foxies!', }, encryptedContent: { 'sender_key': key.curve25519Key, }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached('best animal'), 'foxies!'); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), 'foxies!'); // test the different validators for (final type in [ @@ -339,48 +340,48 @@ void main() { EventTypes.MegolmBackup ]) { final secret = await handle.getStored(type); - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request(type, [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request(type, [key]); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { 'request_id': - client.encryption.ssss.pendingShareRequests.keys.first, + client.encryption!.ssss.pendingShareRequests.keys.first, 'secret': secret, }, encryptedContent: { 'sender_key': key.curve25519Key, }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached(type), secret); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached(type), secret); } // test different fail scenarios // not encrypted - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request('best animal', [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { - 'request_id': client.encryption.ssss.pendingShareRequests.keys.first, + 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, 'secret': 'foxies!', }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached('best animal'), null); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); // unknown request id - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request('best animal', [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { 'request_id': 'invalid', @@ -390,103 +391,103 @@ void main() { 'sender_key': key.curve25519Key, }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached('best animal'), null); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); // not from a device we sent the request to - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request('best animal', [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { - 'request_id': client.encryption.ssss.pendingShareRequests.keys.first, + 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, 'secret': 'foxies!', }, encryptedContent: { 'sender_key': 'invalid', }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached('best animal'), null); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); // secret not a string - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request('best animal', [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { - 'request_id': client.encryption.ssss.pendingShareRequests.keys.first, + 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, 'secret': 42, }, encryptedContent: { 'sender_key': key.curve25519Key, }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached('best animal'), null); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); // validator doesn't check out - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.request(EventTypes.MegolmBackup, [key]); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request(EventTypes.MegolmBackup, [key]); event = ToDeviceEvent( - sender: client.userID, + sender: client.userID!, type: 'm.secret.send', content: { - 'request_id': client.encryption.ssss.pendingShareRequests.keys.first, + 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, 'secret': 'foxies!', }, encryptedContent: { 'sender_key': key.curve25519Key, }, ); - await client.encryption.ssss.handleToDeviceEvent(event); - expect(await client.encryption.ssss.getCached(EventTypes.MegolmBackup), + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached(EventTypes.MegolmBackup), null); }); test('request all', () async { if (!olmEnabled) return; final key = - client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']; + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; key.setDirectVerified(true); - await client.encryption.ssss.clearCache(); - client.encryption.ssss.pendingShareRequests.clear(); - await client.encryption.ssss.maybeRequestAll([key]); - expect(client.encryption.ssss.pendingShareRequests.length, 3); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.maybeRequestAll([key]); + expect(client.encryption!.ssss.pendingShareRequests.length, 3); }); test('periodicallyRequestMissingCache', () async { if (!olmEnabled) return; - client.userDeviceKeys[client.userID].masterKey.setDirectVerified(true); - client.encryption.ssss = MockSSSS(client.encryption); - (client.encryption.ssss as MockSSSS).requestedSecrets = false; - await client.encryption.ssss.periodicallyRequestMissingCache(); - expect((client.encryption.ssss as MockSSSS).requestedSecrets, true); + client.userDeviceKeys[client.userID!]!.masterKey!.setDirectVerified(true); + client.encryption!.ssss = MockSSSS(client.encryption!); + (client.encryption!.ssss as MockSSSS).requestedSecrets = false; + await client.encryption!.ssss.periodicallyRequestMissingCache(); + expect((client.encryption!.ssss as MockSSSS).requestedSecrets, true); // it should only retry once every 15 min - (client.encryption.ssss as MockSSSS).requestedSecrets = false; - await client.encryption.ssss.periodicallyRequestMissingCache(); - expect((client.encryption.ssss as MockSSSS).requestedSecrets, false); + (client.encryption!.ssss as MockSSSS).requestedSecrets = false; + await client.encryption!.ssss.periodicallyRequestMissingCache(); + expect((client.encryption!.ssss as MockSSSS).requestedSecrets, false); }); test('createKey', () async { if (!olmEnabled) return; // with passphrase - var newKey = await client.encryption.ssss.createKey('test'); - expect(client.encryption.ssss.isKeyValid(newKey.keyId), true); - var testKey = client.encryption.ssss.open(newKey.keyId); + var newKey = await client.encryption!.ssss.createKey('test'); + expect(client.encryption!.ssss.isKeyValid(newKey.keyId), true); + var testKey = client.encryption!.ssss.open(newKey.keyId); await testKey.unlock(passphrase: 'test'); - await testKey.setPrivateKey(newKey.privateKey); + await testKey.setPrivateKey(newKey.privateKey!); // without passphrase - newKey = await client.encryption.ssss.createKey(); - expect(client.encryption.ssss.isKeyValid(newKey.keyId), true); - testKey = client.encryption.ssss.open(newKey.keyId); - await testKey.setPrivateKey(newKey.privateKey); + newKey = await client.encryption!.ssss.createKey(); + expect(client.encryption!.ssss.isKeyValid(newKey.keyId), true); + testKey = client.encryption!.ssss.open(newKey.keyId); + await testKey.setPrivateKey(newKey.privateKey!); }); test('dispose client', () async { diff --git a/test/event_test.dart b/test/event_test.dart index 3433be93..6be6317b 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -29,7 +28,7 @@ import 'package:test/test.dart'; import 'fake_client.dart'; import 'fake_matrix_api.dart'; -import 'fake_matrix_localizations.dart'; +import 'matrix_default_localizations.dart'; void main() { /// All Tests related to the Event @@ -249,10 +248,10 @@ void main() { final event = Event.fromJson(redactJsonObj, room); event.setRedactionEvent(redactedBecause); expect(event.redacted, true); - expect(event.redactedBecause.toJson(), redactedBecause.toJson()); + expect(event.redactedBecause?.toJson(), redactedBecause.toJson()); expect(event.content.isEmpty, true); redactionEventJson.remove('redacts'); - expect(event.unsigned['redacted_because'], redactionEventJson); + expect(event.unsigned?['redacted_because'], redactionEventJson); } }); @@ -280,7 +279,7 @@ void main() { event.status = EventStatus.error; final resp2 = await event.sendAgain(txid: '1234'); expect(resp1, null); - expect(resp2.startsWith('\$event'), true); + expect(resp2?.startsWith('\$event'), true); await matrix.dispose(closeDatabase: true); }); @@ -295,7 +294,7 @@ void main() { final event = Event.fromJson( jsonObj, Room(id: '!1234:example.com', client: matrix)); - String exception; + String? exception; try { await event.requestKey(); } catch (e) { @@ -330,7 +329,7 @@ void main() { jsonObj['state_key'] = '@alice:example.com'; final event = Event.fromJson( jsonObj, Room(id: '!localpart:server.abc', client: client)); - expect(event.stateKeyUser.id, '@alice:example.com'); + expect(event.stateKeyUser?.id, '@alice:example.com'); }); test('canRedact', () async { expect(event.canRedact, true); @@ -364,7 +363,8 @@ void main() { } } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Removed by Example'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -392,7 +392,8 @@ void main() { 'type': 'm.sticker', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example sent a sticker'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -405,7 +406,8 @@ void main() { 'type': 'm.room.redaction', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example redacted an event'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -420,7 +422,8 @@ void main() { 'type': 'm.room.aliases', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the room aliases'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -435,7 +438,8 @@ void main() { 'type': 'm.room.aliases', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the room aliases'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -448,7 +452,8 @@ void main() { 'type': 'm.room.canonical_alias', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the room invitation link'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -469,7 +474,8 @@ void main() { 'type': 'm.room.create', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example created the chat'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -485,7 +491,8 @@ void main() { 'type': 'm.room.tombstone', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Room has been upgraded'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -498,7 +505,8 @@ void main() { 'type': 'm.room.join_rules', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the join rules to Anyone can join'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -515,7 +523,8 @@ void main() { 'type': 'm.room.member', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Alice joined the chat'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -527,7 +536,8 @@ void main() { 'state_key': '@alice:example.org', 'type': 'm.room.member' }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example has invited Alice'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -542,7 +552,8 @@ void main() { 'prev_content': {'membership': 'join'}, } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example kicked Alice'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -557,7 +568,8 @@ void main() { 'prev_content': {'membership': 'join'}, } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example banned Alice'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -572,7 +584,8 @@ void main() { 'prev_content': {'membership': 'invite'}, } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Alice accepted the invitation'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -587,7 +600,8 @@ void main() { 'prev_content': {'membership': 'join'}, } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example has invited Alice'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -602,7 +616,8 @@ void main() { 'prev_content': {'membership': 'invite'}, } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example has withdrawn the invitation for Alice'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -617,7 +632,8 @@ void main() { 'prev_content': {'membership': 'invite'}, } }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Alice rejected the invitation'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -641,7 +657,8 @@ void main() { 'type': 'm.room.power_levels', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the chat permissions'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -654,7 +671,8 @@ void main() { 'type': 'm.room.name', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the chat name to The room name'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -667,7 +685,8 @@ void main() { 'type': 'm.room.topic', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the chat description to A room topic'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -683,7 +702,8 @@ void main() { 'type': 'm.room.avatar', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the chat avatar'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -696,7 +716,8 @@ void main() { 'type': 'm.room.history_visibility', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example changed the history visibility to Visible for all participants'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -713,8 +734,8 @@ void main() { 'type': 'm.room.encryption', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), - 'Example activatedEndToEndEncryption. needPantalaimonWarning'); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example activated end to end encryption. Need pantalaimon'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -731,7 +752,7 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), 'This is an example text message'); expect(event.isEventTypeKnown, true); @@ -749,7 +770,7 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), '* thinks this is an example emote'); expect(event.isEventTypeKnown, true); @@ -767,7 +788,7 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), 'This is an example notice'); expect(event.isEventTypeKnown, true); @@ -785,7 +806,8 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example sent a picture'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -803,7 +825,8 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example sent a file'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -824,7 +847,8 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example sent an audio'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -849,7 +873,8 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example shared the location'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -879,7 +904,8 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Example sent a video'); expect(event.isEventTypeKnown, true); event = Event.fromJson({ @@ -891,7 +917,8 @@ void main() { 'type': 'unknown.event.type', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations()), null); + expect(event.getLocalizedBody(MatrixDefaultLocalizations()), + 'Unknown event unknown.event.type'); expect(event.isEventTypeKnown, false); }); @@ -913,7 +940,7 @@ void main() { 'unsigned': {'age': 1234} }, room); expect( - event.getLocalizedBody(FakeMatrixLocalizations(), + event.getLocalizedBody(MatrixDefaultLocalizations(), plaintextBody: true), '**This is an example text message**'); @@ -941,10 +968,11 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations(), hideEdit: true), + expect( + event.getLocalizedBody(MatrixDefaultLocalizations(), hideEdit: true), 'This is an example text message'); expect( - event.getLocalizedBody(FakeMatrixLocalizations(), + event.getLocalizedBody(MatrixDefaultLocalizations(), hideEdit: true, plaintextBody: true), '**This is an example text message**'); @@ -962,10 +990,11 @@ void main() { 'type': 'm.room.message', 'unsigned': {'age': 1234} }, room); - expect(event.getLocalizedBody(FakeMatrixLocalizations(), hideReply: true), + expect( + event.getLocalizedBody(MatrixDefaultLocalizations(), hideReply: true), 'hmm, fox'); expect( - event.getLocalizedBody(FakeMatrixLocalizations(), + event.getLocalizedBody(MatrixDefaultLocalizations(), hideReply: true, plaintextBody: true), 'hmm, *fox*'); }); @@ -976,6 +1005,8 @@ void main() { 'body': 'blah', 'msgtype': 'm.text', }, + 'type': 'm.room.message', + 'sender': '@example:example.org', 'event_id': '\$source', }, null); final edit1 = Event.fromJson({ @@ -987,6 +1018,8 @@ void main() { 'rel_type': RelationshipTypes.edit, }, }, + 'type': 'm.room.message', + 'sender': '@example:example.org', 'event_id': '\$edit1', }, null); final edit2 = Event.fromJson({ @@ -998,9 +1031,11 @@ void main() { 'rel_type': RelationshipTypes.edit, }, }, + 'type': 'm.room.message', + 'sender': '@example:example.org', 'event_id': '\$edit2', }, null); - final room = Room(client: client); + final room = Room(client: client, id: '!id:fakeserver.nonexisting'); final timeline = Timeline(events: [event, edit1, edit2], room: room); expect(event.hasAggregatedEvents(timeline, RelationshipTypes.edit), true); @@ -1096,7 +1131,7 @@ void main() { 'event_id': '\$edit3', 'sender': '@bob:example.org', }, null); - final room = Room(client: client); + final room = Room(id: '!localpart:server.abc', client: client); // no edits var displayEvent = event.getDisplayEvent(Timeline(events: [event], room: room)); @@ -1127,8 +1162,9 @@ void main() { 'sender': '@alice:example.org', 'unsigned': { 'redacted_because': { - 'evnet_id': '\$redact', + 'event_id': '\$redact', 'sender': '@alice:example.org', + 'type': 'm.room.redaction', }, }, }, null); @@ -1143,7 +1179,7 @@ void main() { return { '/_matrix/media/r0/download/example.org/file': FILE_BUFF, '/_matrix/media/r0/download/example.org/thumb': THUMBNAIL_BUFF, - }[uri.path]; + }[uri.path]!; }; await client.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); @@ -1160,7 +1196,7 @@ void main() { }, room); var buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); - expect(buffer.bytes, FILE_BUFF); + expect(buffer?.bytes, FILE_BUFF); expect(event.attachmentOrThumbnailMxcUrl().toString(), 'mxc://example.org/file'); expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true).toString(), @@ -1215,11 +1251,11 @@ void main() { buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); - expect(buffer.bytes, FILE_BUFF); + expect(buffer?.bytes, FILE_BUFF); buffer = await event.downloadAndDecryptAttachment( getThumbnail: true, downloadCallback: downloadCallback); - expect(buffer.bytes, THUMBNAIL_BUFF); + expect(buffer?.bytes, THUMBNAIL_BUFF); }); test('encrypted attachments', () async { if (!olmEnabled) return; @@ -1234,7 +1270,7 @@ void main() { return { '/_matrix/media/r0/download/example.com/file': FILE_BUFF_ENC, '/_matrix/media/r0/download/example.com/thumb': THUMB_BUFF_ENC, - }[uri.path]; + }[uri.path]!; }; final room = Room(id: '!localpart:server.abc', client: await getClient()); var event = Event.fromJson({ @@ -1262,7 +1298,7 @@ void main() { }, room); var buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); - expect(buffer.bytes, FILE_BUFF_DEC); + expect(buffer?.bytes, FILE_BUFF_DEC); event = Event.fromJson({ 'type': EventTypes.Message, @@ -1315,11 +1351,11 @@ void main() { expect(event.thumbnailMxcUrl.toString(), 'mxc://example.com/thumb'); buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); - expect(buffer.bytes, FILE_BUFF_DEC); + expect(buffer?.bytes, FILE_BUFF_DEC); buffer = await event.downloadAndDecryptAttachment( getThumbnail: true, downloadCallback: downloadCallback); - expect(buffer.bytes, THUMB_BUFF_DEC); + expect(buffer?.bytes, THUMB_BUFF_DEC); await room.client.dispose(closeDatabase: true); }); @@ -1330,7 +1366,7 @@ void main() { serverHits++; return { '/_matrix/media/r0/download/example.org/newfile': FILE_BUFF, - }[uri.path]; + }[uri.path]!; }; await client.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); @@ -1352,14 +1388,14 @@ void main() { var buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); expect(await event.isAttachmentInLocalStore(), - event.room.client.database.supportsFileStoring); - expect(buffer.bytes, FILE_BUFF); + event.room?.client.database?.supportsFileStoring); + expect(buffer?.bytes, FILE_BUFF); expect(serverHits, 1); buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); - expect(buffer.bytes, FILE_BUFF); + expect(buffer?.bytes, FILE_BUFF); expect( - serverHits, event.room.client.database.supportsFileStoring ? 1 : 2); + serverHits, event.room!.client.database!.supportsFileStoring ? 1 : 2); await room.client.dispose(closeDatabase: true); }); diff --git a/test/fake_client.dart b/test/fake_client.dart index 2563dcaa..0a13c708 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH diff --git a/test/fake_database.dart b/test/fake_database.dart index cd9f6714..a5dad91b 100644 --- a/test/fake_database.dart +++ b/test/fake_database.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -25,11 +24,11 @@ import 'package:matrix/src/database/hive_database.dart'; import 'package:file/memory.dart'; import 'package:hive/hive.dart'; -Future getDatabase(Client _) => getHiveDatabase(_); +Future getDatabase(Client? _) => getHiveDatabase(_); bool hiveInitialized = false; -Future getHiveDatabase(Client c) async { +Future getHiveDatabase(Client? c) async { if (!hiveInitialized) { final fileSystem = MemoryFileSystem(); final testHivePath = @@ -38,7 +37,7 @@ Future getHiveDatabase(Client c) async { Hive.init(testHivePath); hiveInitialized = true; } - final db = FamedlySdkHiveDatabase('unit_test.${c.hashCode}'); + final db = FamedlySdkHiveDatabase('unit_test.${c?.hashCode}'); await db.open(); return db; } diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index 539da514..bdb023e2 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -39,7 +38,7 @@ Map decodeJson(dynamic data) { class FakeMatrixApi extends MockClient { static final calledEndpoints = >{}; static int eventCounter = 0; - static sdk.Client client; + static sdk.Client? client; static bool failToDevice = false; FakeMatrixApi() @@ -85,9 +84,10 @@ class FakeMatrixApi extends MockClient { if (!calledEndpoints.containsKey(action)) { calledEndpoints[action] = []; } - calledEndpoints[action].add(data); - if (api.containsKey(method) && api[method].containsKey(action)) { - res = api[method][action](data); + calledEndpoints[action]?.add(data); + final act = api[method]?[action]; + if (act != null) { + res = act(data); if (res is Map && res.containsKey('errcode')) { if (res['errcode'] == 'M_NOT_FOUND') { statusCode = 404; @@ -126,12 +126,12 @@ class FakeMatrixApi extends MockClient { ..accountData = [ sdk.BasicEvent(content: decodeJson(data), type: type) ]; - if (client.database != null) { - await client.database.transaction(() async { - await client.handleSync(syncUpdate); + if (client?.database != null) { + await client?.database?.transaction(() async { + await client?.handleSync(syncUpdate); }); } else { - await client.handleSync(syncUpdate); + await client?.handleSync(syncUpdate); } res = {}; } else { @@ -2101,15 +2101,15 @@ class FakeMatrixApi extends MockClient { }) { if (jsonBody[keyType] != null) { final key = - sdk.CrossSigningKey.fromJson(jsonBody[keyType], client); - client.userDeviceKeys[client.userID].crossSigningKeys + sdk.CrossSigningKey.fromJson(jsonBody[keyType], client!); + client!.userDeviceKeys[client!.userID!]?.crossSigningKeys .removeWhere((k, v) => v.usage.contains(key.usage.first)); - client.userDeviceKeys[client.userID] - .crossSigningKeys[key.publicKey] = key; + client!.userDeviceKeys[client!.userID!] + ?.crossSigningKeys[key.publicKey!] = key; } } // and generate a fake sync - client.handleSync(sdk.SyncUpdate(nextBatch: '')); + client!.handleSync(sdk.SyncUpdate(nextBatch: '')); } return {}; }, @@ -2124,35 +2124,35 @@ class FakeMatrixApi extends MockClient { '/client/r0/pushrules/global/content/nocake/enabled': (var req) => {}, '/client/r0/pushrules/global/content/nocake/actions': (var req) => {}, '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.history_visibility': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.join_rules': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.guest_access': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.invite/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.answer/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.select_answer/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.reject/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.negotiate/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.candidates/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.hangup/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.replaces/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.asserted_identity/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/m.call.sdp_stream_metadata_changed/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/org.matrix.call.sdp_stream_metadata_changed/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!localpart%3Aserver.abc/send/org.matrix.call.asserted_identity/1234': - (var req) => {}, + (var req) => {'event_id': '1234'}, '/client/r0/rooms/!1234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234': (var req) => {'event_id': '1234'}, '/client/r0/pushrules/global/room/!localpart%3Aserver.abc': (var req) => diff --git a/test/fake_matrix_localizations.dart b/test/fake_matrix_localizations.dart deleted file mode 100644 index b9ca8aa7..00000000 --- a/test/fake_matrix_localizations.dart +++ /dev/null @@ -1,334 +0,0 @@ -// @dart=2.9 -/* - * Famedly Matrix SDK - * Copyright (C) 2019, 2020 Famedly GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import 'package:matrix/matrix.dart'; - -class FakeMatrixLocalizations extends MatrixLocalizations { - @override - String acceptedTheInvitation(String targetName) { - // TODO: implement acceptedTheInvitation - return null; - } - - @override - String activatedEndToEndEncryption(String senderName) { - // TODO: implement activatedEndToEndEncryption - return '$senderName activatedEndToEndEncryption'; - } - - @override - // TODO: implement anyoneCanJoin - String get anyoneCanJoin => null; - - @override - String bannedUser(String senderName, String targetName) { - // TODO: implement bannedUser - return null; - } - - @override - String changedTheChatAvatar(String senderName) { - // TODO: implement changedTheChatAvatar - return null; - } - - @override - String changedTheChatDescriptionTo(String senderName, String content) { - // TODO: implement changedTheChatDescriptionTo - return null; - } - - @override - String changedTheChatNameTo(String senderName, String content) { - // TODO: implement changedTheChatNameTo - return null; - } - - @override - String changedTheChatPermissions(String senderName) { - // TODO: implement changedTheChatPermissions - return null; - } - - @override - String changedTheDisplaynameTo(String targetName, String newDisplayname) { - // TODO: implement changedTheDisplaynameTo - return null; - } - - @override - String changedTheGuestAccessRules(String senderName) { - // TODO: implement changedTheGuestAccessRules - return null; - } - - @override - String changedTheGuestAccessRulesTo( - String senderName, String localizedString) { - // TODO: implement changedTheGuestAccessRulesTo - return null; - } - - @override - String changedTheHistoryVisibility(String senderName) { - // TODO: implement changedTheHistoryVisibility - return null; - } - - @override - String changedTheHistoryVisibilityTo( - String senderName, String localizedString) { - // TODO: implement changedTheHistoryVisibilityTo - return null; - } - - @override - String changedTheJoinRules(String senderName) { - // TODO: implement changedTheJoinRules - return null; - } - - @override - String changedTheJoinRulesTo(String senderName, String localizedString) { - // TODO: implement changedTheJoinRulesTo - return null; - } - - @override - String changedTheProfileAvatar(String targetName) { - // TODO: implement changedTheProfileAvatar - return null; - } - - @override - String changedTheRoomAliases(String senderName) { - // TODO: implement changedTheRoomAliases - return null; - } - - @override - String changedTheRoomInvitationLink(String senderName) { - // TODO: implement changedTheRoomInvitationLink - return null; - } - - @override - // TODO: implement channelCorruptedDecryptError - String get channelCorruptedDecryptError => null; - - @override - String couldNotDecryptMessage(String errorText) { - // TODO: implement couldNotDecryptMessage - return null; - } - - @override - String createdTheChat(String senderName) { - // TODO: implement createdTheChat - return null; - } - - @override - // TODO: implement emptyChat - String get emptyChat => null; - - @override - // TODO: implement encryptionNotEnabled - String get encryptionNotEnabled => null; - - @override - // TODO: implement fromJoining - String get fromJoining => null; - - @override - // TODO: implement fromTheInvitation - String get fromTheInvitation => null; - - @override - String groupWith(String displayname) { - // TODO: implement groupWith - return null; - } - - @override - // TODO: implement guestsAreForbidden - String get guestsAreForbidden => null; - - @override - // TODO: implement guestsCanJoin - String get guestsCanJoin => null; - - @override - String hasWithdrawnTheInvitationFor(String senderName, String targetName) { - // TODO: implement hasWithdrawnTheInvitationFor - return null; - } - - @override - String invitedUser(String senderName, String targetName) { - // TODO: implement invitedUser - return null; - } - - @override - // TODO: implement invitedUsersOnly - String get invitedUsersOnly => null; - - @override - String joinedTheChat(String targetName) { - // TODO: implement joinedTheChat - return null; - } - - @override - String kicked(String senderName, String targetName) { - // TODO: implement kicked - return null; - } - - @override - String kickedAndBanned(String senderName, String targetName) { - // TODO: implement kickedAndBanned - return null; - } - - @override - // TODO: implement needPantalaimonWarning - String get needPantalaimonWarning => 'needPantalaimonWarning'; - - @override - // TODO: implement noPermission - String get noPermission => 'noPermission'; - - @override - String redactedAnEvent(String senderName) { - // TODO: implement redactedAnEvent - return null; - } - - @override - String rejectedTheInvitation(String targetName) { - // TODO: implement rejectedTheInvitation - return null; - } - - @override - String removedBy(String calcDisplayname) { - // TODO: implement removedBy - return null; - } - - @override - // TODO: implement roomHasBeenUpgraded - String get roomHasBeenUpgraded => null; - - @override - String sentAFile(String senderName) { - // TODO: implement sentAFile - return null; - } - - @override - String sentAPicture(String senderName) { - // TODO: implement sentAPicture - return null; - } - - @override - String sentASticker(String senderName) { - // TODO: implement sentASticker - return null; - } - - @override - String sentAVideo(String senderName) { - // TODO: implement sentAVideo - return null; - } - - @override - String sentAnAudio(String senderName) { - // TODO: implement sentAnAudio - return null; - } - - @override - String sharedTheLocation(String senderName) { - // TODO: implement sharedTheLocation - return null; - } - - @override - String unbannedUser(String senderName, String targetName) { - // TODO: implement unbannedUser - return null; - } - - @override - // TODO: implement unknownEncryptionAlgorithm - String get unknownEncryptionAlgorithm => null; - - @override - String unknownEvent(String typeKey) { - // TODO: implement unknownEvent - return null; - } - - @override - String userLeftTheChat(String targetName) { - // TODO: implement userLeftTheChat - return null; - } - - @override - // TODO: implement visibleForAllParticipants - String get visibleForAllParticipants => null; - - @override - // TODO: implement visibleForEveryone - String get visibleForEveryone => null; - - @override - // TODO: implement you - String get you => null; - - @override - String answeredTheCall(String senderName) { - // TODO: implement answeredTheCall - return null; - } - - @override - String endedTheCall(String senderName) { - // TODO: implement endedTheCall - return null; - } - - @override - String sentCallInformations(String senderName) { - // TODO: implement sentCallInformations - return null; - } - - @override - String startedACall(String senderName) { - // TODO: implement startedACall - return null; - } -} diff --git a/test/html_to_text_test.dart b/test/html_to_text_test.dart index 2e142da7..fc29a6a3 100644 --- a/test/html_to_text_test.dart +++ b/test/html_to_text_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH diff --git a/test/image_pack_test.dart b/test/image_pack_test.dart index 8005732e..513dd7c5 100644 --- a/test/image_pack_test.dart +++ b/test/image_pack_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH @@ -23,9 +22,9 @@ import 'fake_client.dart'; void main() { group('Image Pack', () { - Client client; - Room room; - Room room2; + late Client client; + late Room room; + late Room room2; test('setupClient', () async { client = await getClient(); @@ -36,24 +35,36 @@ void main() { content: {}, room: room, stateKey: '', + senderId: client.userID!, + eventId: '\$fakeid1:fakeServer.notExisting', + originServerTs: DateTime.now(), )); room.setState(Event( type: 'm.room.member', content: {'membership': 'join'}, room: room, stateKey: client.userID, + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid2:fakeServer.notExisting', + originServerTs: DateTime.now(), )); room2.setState(Event( type: 'm.room.power_levels', content: {}, room: room, stateKey: '', + senderId: client.userID!, + eventId: '\$fakeid3:fakeServer.notExisting', + originServerTs: DateTime.now(), )); room2.setState(Event( type: 'm.room.member', content: {'membership': 'join'}, room: room, stateKey: client.userID, + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid4:fakeServer.notExisting', + originServerTs: DateTime.now(), )); client.rooms.add(room); client.rooms.add(room2); @@ -69,11 +80,14 @@ void main() { }, room: room, stateKey: '', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid5:fakeServer.notExisting', + originServerTs: DateTime.now(), )); final packs = room.getImagePacks(); expect(packs.length, 1); - expect(packs['room'].images.length, 1); - expect(packs['room'].images['room_plain'].url.toString(), + expect(packs['room']?.images.length, 1); + expect(packs['room']?.images['room_plain']?.url.toString(), 'mxc://room_plain'); var packsFlat = room.getImagePacksFlat(); expect(packsFlat, { @@ -95,6 +109,9 @@ void main() { }, room: room, stateKey: '', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid6:fakeServer.notExisting', + originServerTs: DateTime.now(), )); packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon); expect(packsFlat, { @@ -117,6 +134,9 @@ void main() { }, room: room, stateKey: '', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid7:fakeServer.notExisting', + originServerTs: DateTime.now(), )); packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon); expect(packsFlat, { @@ -137,6 +157,9 @@ void main() { }, room: room, stateKey: 'fox', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid8:fakeServer.notExisting', + originServerTs: DateTime.now(), )); packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon); expect(packsFlat, { @@ -177,6 +200,9 @@ void main() { }, room: room2, stateKey: '', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid9:fakeServer.notExisting', + originServerTs: DateTime.now(), )); client.accountData['im.ponies.emote_rooms'] = BasicEvent.fromJson({ 'type': 'im.ponies.emote_rooms', @@ -207,6 +233,9 @@ void main() { }, room: room2, stateKey: 'fox', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid10:fakeServer.notExisting', + originServerTs: DateTime.now(), )); client.accountData['im.ponies.emote_rooms'] = BasicEvent.fromJson({ 'type': 'im.ponies.emote_rooms', diff --git a/test/markdown_test.dart b/test/markdown_test.dart index 07477870..2c3dab85 100644 --- a/test/markdown_test.dart +++ b/test/markdown_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH diff --git a/test/matrix_api/map_copy_extension_test.dart b/test/matrix_api/map_copy_extension_test.dart index 6ecb62c2..e7027952 100644 --- a/test/matrix_api/map_copy_extension_test.dart +++ b/test/matrix_api/map_copy_extension_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH diff --git a/test/matrix_api/try_get_map_extension_test.dart b/test/matrix_api/try_get_map_extension_test.dart index f7adccb3..886700e3 100644 --- a/test/matrix_api/try_get_map_extension_test.dart +++ b/test/matrix_api/try_get_map_extension_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH diff --git a/test/matrix_database_test.dart b/test/matrix_database_test.dart index 360d6d39..03d9cc17 100644 --- a/test/matrix_database_test.dart +++ b/test/matrix_database_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -27,7 +26,7 @@ import 'fake_database.dart'; void main() { group('Databse', () { Logs().level = Level.error; - final room = Room(id: '!room:blubb'); + final room = Room(id: '!room:blubb', client: Client('testclient')); test('setupDatabase', () async { final database = await getDatabase(null); await database.insertClient( @@ -43,7 +42,8 @@ void main() { }); test('storeEventUpdate', () async { - final database = await getDatabase(null); + final client = Client('testclient'); + final database = await getDatabase(client); // store a simple update var update = EventUpdate( type: EventUpdateType.timeline, @@ -56,9 +56,9 @@ void main() { 'sender': '@blah:blubb', }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); var event = await database.getEventById('\$event-1', room); - expect(event.eventId, '\$event-1'); + expect(event?.eventId, '\$event-1'); // insert a transaction id update = EventUpdate( @@ -73,9 +73,9 @@ void main() { 'status': EventStatus.sending.intValue, }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); event = await database.getEventById('transaction-1', room); - expect(event.eventId, 'transaction-1'); + expect(event?.eventId, 'transaction-1'); update = EventUpdate( type: EventUpdateType.timeline, roomID: room.id, @@ -91,7 +91,7 @@ void main() { 'status': EventStatus.sent.intValue, }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); event = await database.getEventById('transaction-1', room); expect(event, null); event = await database.getEventById('\$event-2', room); @@ -109,9 +109,9 @@ void main() { 'status': EventStatus.sending.intValue, }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); event = await database.getEventById('\$event-3', room); - expect(event.eventId, '\$event-3'); + expect(event?.eventId, '\$event-3'); update = EventUpdate( type: EventUpdateType.timeline, roomID: room.id, @@ -127,10 +127,10 @@ void main() { }, }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); event = await database.getEventById('\$event-3', room); - expect(event.eventId, '\$event-3'); - expect(event.status, EventStatus.sent); + expect(event?.eventId, '\$event-3'); + expect(event?.status, EventStatus.sent); event = await database.getEventById('transaction-2', room); expect(event, null); @@ -147,9 +147,9 @@ void main() { 'status': EventStatus.synced.intValue, }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); event = await database.getEventById('\$event-4', room); - expect(event.eventId, '\$event-4'); + expect(event?.eventId, '\$event-4'); update = EventUpdate( type: EventUpdateType.timeline, roomID: room.id, @@ -165,10 +165,10 @@ void main() { }, }, ); - await database.storeEventUpdate(update); + await database.storeEventUpdate(update, client); event = await database.getEventById('\$event-4', room); - expect(event.eventId, '\$event-4'); - expect(event.status, EventStatus.synced); + expect(event?.eventId, '\$event-4'); + expect(event?.status, EventStatus.synced); event = await database.getEventById('transaction-3', room); expect(event, null); }); diff --git a/test/matrix_default_localizations.dart b/test/matrix_default_localizations.dart index d8389542..3ab973ff 100644 --- a/test/matrix_default_localizations.dart +++ b/test/matrix_default_localizations.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH diff --git a/test/matrix_exception_test.dart b/test/matrix_exception_test.dart index c49fd4d7..5378f325 100644 --- a/test/matrix_exception_test.dart +++ b/test/matrix_exception_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -34,11 +33,11 @@ void main() { ); expect(matrixException.errcode, 'M_FORBIDDEN'); final flows = matrixException.authenticationFlows; - expect(flows.length, 1); - expect(flows.first.stages.length, 1); - expect(flows.first.stages.first, 'example.type.foo'); + expect(flows?.length, 1); + expect(flows?.first.stages.length, 1); + expect(flows?.first.stages.first, 'example.type.foo'); expect( - matrixException.authenticationParams['example.type.baz'], + matrixException.authenticationParams?['example.type.baz'], {'example_key': 'foobar'}, ); expect(matrixException.completedAuthenticationFlows.length, 1); diff --git a/test/matrix_file_test.dart b/test/matrix_file_test.dart index dd0e6cca..d932bcbc 100644 --- a/test/matrix_file_test.dart +++ b/test/matrix_file_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -43,7 +42,7 @@ void main() { } if (olmEnabled) { final encryptedFile = await file.encrypt(); - expect(encryptedFile != null, true); + expect(encryptedFile.data.isNotEmpty, true); } }); }); diff --git a/test/matrix_id_string_extension_test.dart b/test/matrix_id_string_extension_test.dart index 52a24592..e5fd2842 100644 --- a/test/matrix_id_string_extension_test.dart +++ b/test/matrix_id_string_extension_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -48,77 +47,73 @@ void main() { expect('@user:domain:8448'.domain, 'domain:8448'); }); test('parseIdentifierIntoParts', () { - var res = '#alias:beep'.parseIdentifierIntoParts(); + var res = '#alias:beep'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, null); expect(res.queryString, null); - res = 'blha'.parseIdentifierIntoParts(); - expect(res, null); - res = '#alias:beep/\$event'.parseIdentifierIntoParts(); + expect('blha'.parseIdentifierIntoParts(), null); + res = '#alias:beep/\$event'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, '\$event'); expect(res.queryString, null); - res = '#alias:beep?blubb'.parseIdentifierIntoParts(); + res = '#alias:beep?blubb'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, null); expect(res.queryString, 'blubb'); - res = '#alias:beep/\$event?blubb'.parseIdentifierIntoParts(); + res = '#alias:beep/\$event?blubb'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, '\$event'); expect(res.queryString, 'blubb'); - res = '#/\$?:beep/\$event?blubb?b'.parseIdentifierIntoParts(); + res = '#/\$?:beep/\$event?blubb?b'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#/\$?:beep'); expect(res.secondaryIdentifier, '\$event'); expect(res.queryString, 'blubb?b'); - res = 'https://matrix.to/#/#alias:beep'.parseIdentifierIntoParts(); + res = 'https://matrix.to/#/#alias:beep'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, null); expect(res.queryString, null); - res = 'https://matrix.to/#/#๐ŸฆŠ:beep'.parseIdentifierIntoParts(); + res = 'https://matrix.to/#/#๐ŸฆŠ:beep'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#๐ŸฆŠ:beep'); expect(res.secondaryIdentifier, null); expect(res.queryString, null); - res = 'https://matrix.to/#/%23alias%3abeep'.parseIdentifierIntoParts(); + res = 'https://matrix.to/#/%23alias%3abeep'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, null); expect(res.queryString, null); res = 'https://matrix.to/#/%23alias%3abeep?boop%F0%9F%A7%A1%F0%9F%A6%8A' - .parseIdentifierIntoParts(); + .parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#alias:beep'); expect(res.secondaryIdentifier, null); expect(res.queryString, 'boop%F0%9F%A7%A1%F0%9F%A6%8A'); res = 'https://matrix.to/#/#alias:beep?via=fox.com&via=fox.org' - .parseIdentifierIntoParts(); + .parseIdentifierIntoParts()!; expect(res.via, {'fox.com', 'fox.org'}); - res = 'matrix:u/her:example.org'.parseIdentifierIntoParts(); + res = 'matrix:u/her:example.org'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '@her:example.org'); expect(res.secondaryIdentifier, null); - res = 'matrix:u/bad'.parseIdentifierIntoParts(); - expect(res, null); - res = 'matrix:roomid/rid:example.org'.parseIdentifierIntoParts(); + expect('matrix:u/bad'.parseIdentifierIntoParts(), null); + res = 'matrix:roomid/rid:example.org'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '!rid:example.org'); expect(res.secondaryIdentifier, null); expect(res.action, null); - res = 'matrix:r/us:example.org?action=chat'.parseIdentifierIntoParts(); + res = 'matrix:r/us:example.org?action=chat'.parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#us:example.org'); expect(res.secondaryIdentifier, null); expect(res.action, 'chat'); res = 'matrix:r/us:example.org/e/lol823y4bcp3qo4' - .parseIdentifierIntoParts(); + .parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '#us:example.org'); expect(res.secondaryIdentifier, '\$lol823y4bcp3qo4'); res = 'matrix:roomid/rid:example.org?via=fox.com&via=fox.org' - .parseIdentifierIntoParts(); + .parseIdentifierIntoParts()!; expect(res.primaryIdentifier, '!rid:example.org'); expect(res.secondaryIdentifier, null); expect(res.via, {'fox.com', 'fox.org'}); - res = 'matrix:beep/boop:example.org'.parseIdentifierIntoParts(); - expect(res, null); - res = 'matrix:boop'.parseIdentifierIntoParts(); - expect(res, null); + expect('matrix:beep/boop:example.org'.parseIdentifierIntoParts(), null); + expect('matrix:boop'.parseIdentifierIntoParts(), null); }); }); } diff --git a/test/matrix_localizations_test.dart b/test/matrix_localizations_test.dart index f9684e29..6f92b372 100644 --- a/test/matrix_localizations_test.dart +++ b/test/matrix_localizations_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH diff --git a/test/multilock_test.dart b/test/multilock_test.dart index 9f7da34b..292b1e4e 100644 --- a/test/multilock_test.dart +++ b/test/multilock_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH diff --git a/test/mxc_uri_extension_test.dart b/test/mxc_uri_extension_test.dart index b6319c03..b4d505e7 100644 --- a/test/mxc_uri_extension_test.dart +++ b/test/mxc_uri_extension_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH diff --git a/test/room_test.dart b/test/room_test.dart index f206f134..2bfb8147 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -33,8 +32,8 @@ import 'fake_client.dart'; import 'fake_matrix_api.dart'; void main() { - Client matrix; - Room room; + late Client matrix; + late Room room; /// All Tests related to the Event group('Room', () { @@ -96,8 +95,9 @@ void main() { expect(room.summary.mInvitedMemberCount, notificationCount); expect(room.summary.mHeroes, heroes); expect(room.displayname, 'Alice, Bob, Charley'); - expect(room.getState('m.room.join_rules').content['join_rule'], 'public'); - expect(room.roomAccountData['com.test.foo'].content['foo'], 'bar'); + expect( + room.getState('m.room.join_rules')?.content['join_rule'], 'public'); + expect(room.roomAccountData['com.test.foo']?.content['foo'], 'bar'); room.setState( Event( @@ -107,6 +107,7 @@ void main() { room: room, eventId: '123', content: {'alias': '#testalias:example.com'}, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.displayname, 'testalias'); @@ -120,6 +121,7 @@ void main() { room: room, eventId: '123', content: {'name': 'testname'}, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.displayname, 'testname'); @@ -133,6 +135,7 @@ void main() { room: room, eventId: '123', content: {'topic': 'testtopic'}, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.topic, 'testtopic'); @@ -146,6 +149,7 @@ void main() { room: room, eventId: '123', content: {'url': 'mxc://testurl'}, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.avatar.toString(), 'mxc://testurl'); @@ -161,6 +165,7 @@ void main() { content: { 'pinned': ['1234'] }, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.pinnedEventIds.first, '1234'); @@ -176,9 +181,9 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.eventId, '12345'); - expect(room.lastEvent.body, 'abc'); - expect(room.timeCreated, room.lastEvent.originServerTs); + expect(room.lastEvent?.eventId, '12345'); + expect(room.lastEvent?.body, 'abc'); + expect(room.timeCreated, room.lastEvent?.originServerTs); }); test('lastEvent is set properly', () { @@ -194,7 +199,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'cd'); + expect(room.lastEvent?.body, 'cd'); room.setState( Event( senderId: '@test:example.com', @@ -207,7 +212,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'cdc'); + expect(room.lastEvent?.body, 'cdc'); room.setState( Event( senderId: '@test:example.com', @@ -225,7 +230,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'cdc'); // because we edited the "cd" message + expect(room.lastEvent?.body, 'cdc'); // because we edited the "cd" message room.setState( Event( senderId: '@test:example.com', @@ -243,7 +248,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'edited cdc'); + expect(room.lastEvent?.body, 'edited cdc'); }); test('lastEvent when reply parent edited', () async { room.setState( @@ -258,7 +263,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'A'); + expect(room.lastEvent?.body, 'A'); room.setState( Event( @@ -276,7 +281,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'B'); + expect(room.lastEvent?.body, 'B'); room.setState( Event( senderId: '@test:example.com', @@ -294,7 +299,7 @@ void main() { stateKey: '', ), ); - expect(room.lastEvent.body, 'B'); + expect(room.lastEvent?.body, 'B'); }); test('sendReadMarker', () async { await room.setReadMarker('ยง1234:fakeServer.notExisting'); @@ -308,12 +313,12 @@ void main() { expect(user.displayName, 'Alice Margatroid'); expect(user.membership, Membership.join); expect(user.avatarUrl.toString(), 'mxc://example.org/SEsfnsuifSDFSSEF'); - expect(user.room.id, '!localpart:server.abc'); + expect(user.room?.id, '!localpart:server.abc'); }); test('getEventByID', () async { final event = await room.getEventById('1234'); - expect(event.eventId, '143273582443PhrSn:example.org'); + expect(event?.eventId, '143273582443PhrSn:example.org'); }); test('setName', () async { @@ -358,10 +363,11 @@ void main() { 'users': {'@test:fakeServer.notExisting': 100}, 'users_default': 10 }, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.ownPowerLevel, 100); - expect(room.getPowerLevelByUserId(matrix.userID), room.ownPowerLevel); + expect(room.getPowerLevelByUserId(matrix.userID!), room.ownPowerLevel); expect(room.getPowerLevelByUserId('@nouser:example.com'), 10); expect(room.ownPowerLevel, 100); expect(room.canBan, true); @@ -375,7 +381,7 @@ void main() { expect(room.canSendEvent('m.room.power_levels'), true); expect(room.canSendEvent('m.room.member'), true); expect(room.powerLevels, - room.getState('m.room.power_levels').content['users']); + room.getState('m.room.power_levels')?.content['users']); room.setState( Event( @@ -396,6 +402,7 @@ void main() { 'users': {}, 'users_default': 0 }, + originServerTs: DateTime.now(), stateKey: '', ), ); @@ -447,12 +454,12 @@ void main() { }); test('getUserByMXID', () async { - User user; + User? user; try { user = await room.requestUser('@getme:example.com'); } catch (_) {} - expect(user.stateKey, '@getme:example.com'); - expect(user.calcDisplayname(), 'Getme'); + expect(user?.stateKey, '@getme:example.com'); + expect(user?.calcDisplayname(), 'Getme'); }); test('setAvatar', () async { @@ -465,14 +472,14 @@ void main() { final dynamic resp = await room.sendEvent( {'msgtype': 'm.text', 'body': 'hello world'}, txid: 'testtxid'); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); }); test('sendEvent', () async { FakeMatrixApi.calledEndpoints.clear(); final dynamic resp = await room.sendTextEvent('Hello world', txid: 'testtxid'); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); final entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); final content = json.decode(entry.value.first); @@ -486,7 +493,7 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); final dynamic resp = await room.sendTextEvent('Hello world', txid: 'testtxid', editEventId: '\$otherEvent'); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); final entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); final content = json.decode(entry.value.first); @@ -517,7 +524,7 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); var resp = await room.sendTextEvent('Hello world', txid: 'testtxid', inReplyTo: event); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); var entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); var content = json.decode(entry.value.first); @@ -546,7 +553,7 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); resp = await room.sendTextEvent('Hello world\nfox', txid: 'testtxid', inReplyTo: event); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); content = json.decode(entry.value.first); @@ -578,7 +585,7 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); resp = await room.sendTextEvent('Hello world', txid: 'testtxid', inReplyTo: event); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); content = json.decode(entry.value.first); @@ -607,7 +614,7 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); resp = await room.sendTextEvent('Hello world', txid: 'testtxid', inReplyTo: event); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); content = json.decode(entry.value.first); @@ -629,7 +636,7 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); final dynamic resp = await room.sendReaction('\$otherEvent', '๐ŸฆŠ', txid: 'testtxid'); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); final entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.reaction/')); final content = json.decode(entry.value.first); @@ -649,7 +656,7 @@ void main() { final geoUri = 'geo:0.0,0.0'; final dynamic resp = await room.sendLocation(body, geoUri, txid: 'testtxid'); - expect(resp.startsWith('\$event'), true); + expect(resp?.startsWith('\$event'), true); final entry = FakeMatrixApi.calledEndpoints.entries .firstWhere((p) => p.key.contains('/send/m.room.message/')); @@ -677,8 +684,8 @@ void main() { test('pushRuleState', () async { expect(room.pushRuleState, PushRuleState.mentionsOnly); - matrix.accountData['m.push_rules'].content['global']['override'] - .add(matrix.accountData['m.push_rules'].content['global']['room'][0]); + matrix.accountData['m.push_rules']?.content['global']['override'].add( + matrix.accountData['m.push_rules']?.content['global']['room'][0]); expect(room.pushRuleState, PushRuleState.dontNotify); }); @@ -749,7 +756,7 @@ void main() { 'type': 'm.tag' }); expect(room.tags.length, 1); - expect(room.tags[TagType.favourite].order, 0.1); + expect(room.tags[TagType.favourite]?.order, 0.1); expect(room.isFavourite, true); await room.setFavourite(false); }); diff --git a/test/sync_filter_test.dart b/test/sync_filter_test.dart index 8a76b805..bb0b2630 100644 --- a/test/sync_filter_test.dart +++ b/test/sync_filter_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH @@ -119,6 +118,7 @@ const updates = { 'events': [ { 'type': 'beep', + 'sender': '@example:localhost', 'content': { 'blah': 'blubb', }, diff --git a/test/timeline_test.dart b/test/timeline_test.dart index 0532ae17..6e2be8b8 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -37,9 +36,9 @@ void main() { final insertList = []; var olmEnabled = true; - Client client; - Room room; - Timeline timeline; + late Client client; + late Room room; + late Timeline timeline; test('create stuff', () async { try { await olm.init(); @@ -233,18 +232,18 @@ void main() { test('getEventById', () async { var event = await timeline.getEventById('abc'); - expect(event.content, {'msgtype': 'm.text', 'body': 'Testcase'}); + expect(event?.content, {'msgtype': 'm.text', 'body': 'Testcase'}); event = await timeline.getEventById('not_found'); expect(event, null); event = await timeline.getEventById('unencrypted_event'); - expect(event.body, 'This is an example text message'); + expect(event?.body, 'This is an example text message'); if (olmEnabled) { event = await timeline.getEventById('encrypted_event'); // the event is invalid but should have traces of attempting to decrypt - expect(event.messageType, MessageTypes.BadEncrypted); + expect(event?.messageType, MessageTypes.BadEncrypted); } }); diff --git a/test/uia_test.dart b/test/uia_test.dart index 3d01e6b0..f8300936 100644 --- a/test/uia_test.dart +++ b/test/uia_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020 Famedly GmbH diff --git a/test/user_test.dart b/test/user_test.dart index 23c46fb3..f63d6d22 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020 Famedly GmbH @@ -123,6 +122,7 @@ void main() { }); test('getPresence', () async { await client.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fake', 'presence': { 'events': [ { @@ -133,7 +133,7 @@ void main() { ] } })); - expect(user1.presence.presence.presence, PresenceType.online); + expect(user1.presence?.presence.presence, PresenceType.online); }); test('canBan', () async { expect(user1.canBan, false); diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 1bab3ab5..2e3294bf 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH @@ -30,7 +29,7 @@ const String testMessage5 = 'Hello earth'; const String testMessage6 = 'Hello mars'; void test() async { - Client testClientA, testClientB; + Client? testClientA, testClientB; try { await olm.init(); @@ -56,7 +55,7 @@ void test() async { Logs().i('++++ (Alice) Leave all rooms ++++'); while (testClientA.rooms.isNotEmpty) { final room = testClientA.rooms.first; - if (room.canonicalAlias?.isNotEmpty ?? false) { + if (room.canonicalAlias.isNotEmpty) { break; } try { @@ -78,29 +77,28 @@ void test() async { Logs().i('++++ Check if own olm device is verified by default ++++'); assert(testClientA.userDeviceKeys.containsKey(TestUser.username)); - assert(testClientA.userDeviceKeys[TestUser.username].deviceKeys + assert(testClientA.userDeviceKeys[TestUser.username]!.deviceKeys .containsKey(testClientA.deviceID)); - assert(testClientA.userDeviceKeys[TestUser.username] - .deviceKeys[testClientA.deviceID].verified); - assert(!testClientA.userDeviceKeys[TestUser.username] - .deviceKeys[testClientA.deviceID].blocked); + assert(testClientA.userDeviceKeys[TestUser.username]! + .deviceKeys[testClientA.deviceID!]!.verified); + assert(!testClientA.userDeviceKeys[TestUser.username]! + .deviceKeys[testClientA.deviceID!]!.blocked); assert(testClientB.userDeviceKeys.containsKey(TestUser.username2)); - assert(testClientB.userDeviceKeys[TestUser.username2].deviceKeys + assert(testClientB.userDeviceKeys[TestUser.username2]!.deviceKeys .containsKey(testClientB.deviceID)); - assert(testClientB.userDeviceKeys[TestUser.username2] - .deviceKeys[testClientB.deviceID].verified); - assert(!testClientB.userDeviceKeys[TestUser.username2] - .deviceKeys[testClientB.deviceID].blocked); + assert(testClientB.userDeviceKeys[TestUser.username2]! + .deviceKeys[testClientB.deviceID!]!.verified); + assert(!testClientB.userDeviceKeys[TestUser.username2]! + .deviceKeys[testClientB.deviceID!]!.blocked); Logs().i('++++ (Alice) Create room and invite Bob ++++'); await testClientA.createRoom(invite: [TestUser.username2]); await Future.delayed(Duration(seconds: 1)); final room = testClientA.rooms.first; - assert(room != null); final roomId = room.id; Logs().i('++++ (Bob) Join room ++++'); - final inviteRoom = testClientB.getRoomById(roomId); + final inviteRoom = testClientB.getRoomById(roomId)!; await inviteRoom.join(); await Future.delayed(Duration(seconds: 1)); assert(inviteRoom.membership == Membership.join); @@ -110,116 +108,118 @@ void test() async { await room.enableEncryption(); await Future.delayed(Duration(seconds: 5)); assert(room.encrypted == true); - assert(room.client.encryption.keyManager.getOutboundGroupSession(room.id) == - null); + assert( + room.client.encryption!.keyManager.getOutboundGroupSession(room.id) == + null); Logs().i('++++ (Alice) Check known olm devices ++++'); assert(testClientA.userDeviceKeys.containsKey(TestUser.username2)); - assert(testClientA.userDeviceKeys[TestUser.username2].deviceKeys + assert(testClientA.userDeviceKeys[TestUser.username2]!.deviceKeys .containsKey(testClientB.deviceID)); - assert(!testClientA.userDeviceKeys[TestUser.username2] - .deviceKeys[testClientB.deviceID].verified); - assert(!testClientA.userDeviceKeys[TestUser.username2] - .deviceKeys[testClientB.deviceID].blocked); + assert(!testClientA.userDeviceKeys[TestUser.username2]! + .deviceKeys[testClientB.deviceID!]!.verified); + assert(!testClientA.userDeviceKeys[TestUser.username2]! + .deviceKeys[testClientB.deviceID!]!.blocked); assert(testClientB.userDeviceKeys.containsKey(TestUser.username)); - assert(testClientB.userDeviceKeys[TestUser.username].deviceKeys + assert(testClientB.userDeviceKeys[TestUser.username]!.deviceKeys .containsKey(testClientA.deviceID)); - assert(!testClientB.userDeviceKeys[TestUser.username] - .deviceKeys[testClientA.deviceID].verified); - assert(!testClientB.userDeviceKeys[TestUser.username] - .deviceKeys[testClientA.deviceID].blocked); + assert(!testClientB.userDeviceKeys[TestUser.username]! + .deviceKeys[testClientA.deviceID!]!.verified); + assert(!testClientB.userDeviceKeys[TestUser.username]! + .deviceKeys[testClientA.deviceID!]!.blocked); await testClientA - .userDeviceKeys[TestUser.username2].deviceKeys[testClientB.deviceID] + .userDeviceKeys[TestUser.username2]!.deviceKeys[testClientB.deviceID!]! .setVerified(true); Logs().i('++++ Check if own olm device is verified by default ++++'); assert(testClientA.userDeviceKeys.containsKey(TestUser.username)); - assert(testClientA.userDeviceKeys[TestUser.username].deviceKeys + assert(testClientA.userDeviceKeys[TestUser.username]!.deviceKeys .containsKey(testClientA.deviceID)); - assert(testClientA.userDeviceKeys[TestUser.username] - .deviceKeys[testClientA.deviceID].verified); + assert(testClientA.userDeviceKeys[TestUser.username]! + .deviceKeys[testClientA.deviceID!]!.verified); assert(testClientB.userDeviceKeys.containsKey(TestUser.username2)); - assert(testClientB.userDeviceKeys[TestUser.username2].deviceKeys + assert(testClientB.userDeviceKeys[TestUser.username2]!.deviceKeys .containsKey(testClientB.deviceID)); - assert(testClientB.userDeviceKeys[TestUser.username2] - .deviceKeys[testClientB.deviceID].verified); + assert(testClientB.userDeviceKeys[TestUser.username2]! + .deviceKeys[testClientB.deviceID!]!.verified); Logs().i("++++ (Alice) Send encrypted message: '$testMessage' ++++"); await room.sendTextEvent(testMessage); await Future.delayed(Duration(seconds: 5)); - assert(room.client.encryption.keyManager.getOutboundGroupSession(room.id) != - null); - var currentSessionIdA = room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + assert( + room.client.encryption!.keyManager.getOutboundGroupSession(room.id) != + null); + var currentSessionIdA = room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id(); /*assert(room.client.encryption.keyManager .getInboundGroupSession(room.id, currentSessionIdA, '') != null);*/ - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].length == + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.length == 1); - assert(testClientB.encryption.olmManager - .olmSessions[testClientA.identityKey].length == + assert(testClientB.encryption!.olmManager + .olmSessions[testClientA.identityKey]!.length == 1); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].first.sessionId == - testClientB.encryption.olmManager.olmSessions[testClientA.identityKey] + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.first.sessionId == + testClientB.encryption!.olmManager.olmSessions[testClientA.identityKey]! .first.sessionId); /*assert(inviteRoom.client.encryption.keyManager .getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') != null);*/ - assert(room.lastEvent.body == testMessage); - assert(inviteRoom.lastEvent.body == testMessage); + assert(room.lastEvent!.body == testMessage); + assert(inviteRoom.lastEvent!.body == testMessage); Logs().i( - "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent.body}' ++++"); + "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++"); Logs().i("++++ (Alice) Send again encrypted message: '$testMessage2' ++++"); await room.sendTextEvent(testMessage2); await Future.delayed(Duration(seconds: 5)); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].length == + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.length == 1); - assert(testClientB.encryption.olmManager - .olmSessions[testClientA.identityKey].length == + assert(testClientB.encryption!.olmManager + .olmSessions[testClientA.identityKey]!.length == 1); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].first.sessionId == - testClientB.encryption.olmManager.olmSessions[testClientA.identityKey] + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.first.sessionId == + testClientB.encryption!.olmManager.olmSessions[testClientA.identityKey]! .first.sessionId); - assert(room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + assert(room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id() == currentSessionIdA); /*assert(room.client.encryption.keyManager .getInboundGroupSession(room.id, currentSessionIdA, '') != null);*/ - assert(room.lastEvent.body == testMessage2); - assert(inviteRoom.lastEvent.body == testMessage2); + assert(room.lastEvent!.body == testMessage2); + assert(inviteRoom.lastEvent!.body == testMessage2); Logs().i( - "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent.body}' ++++"); + "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++"); Logs().i("++++ (Bob) Send again encrypted message: '$testMessage3' ++++"); await inviteRoom.sendTextEvent(testMessage3); await Future.delayed(Duration(seconds: 5)); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].length == + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.length == 1); - assert(testClientB.encryption.olmManager - .olmSessions[testClientA.identityKey].length == + assert(testClientB.encryption!.olmManager + .olmSessions[testClientA.identityKey]!.length == 1); - assert(room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + assert(room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id() == currentSessionIdA); final inviteRoomOutboundGroupSession = inviteRoom - .client.encryption.keyManager - .getOutboundGroupSession(inviteRoom.id); + .client.encryption!.keyManager + .getOutboundGroupSession(inviteRoom.id)!; - assert(inviteRoomOutboundGroupSession != null); + assert(inviteRoomOutboundGroupSession.isValid); /*assert(inviteRoom.client.encryption.keyManager.getInboundGroupSession( inviteRoom.id, inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), @@ -230,13 +230,13 @@ void test() async { inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), '') != null);*/ - assert(inviteRoom.lastEvent.body == testMessage3); - assert(room.lastEvent.body == testMessage3); + assert(inviteRoom.lastEvent!.body == testMessage3); + assert(room.lastEvent!.body == testMessage3); Logs().i( - "++++ (Alice) Received decrypted message: '${room.lastEvent.body}' ++++"); + "++++ (Alice) Received decrypted message: '${room.lastEvent!.body}' ++++"); Logs().i('++++ Login Bob in another client ++++'); - var testClientC = Client('TestClientC', databaseBuilder: getDatabase); + final testClientC = Client('TestClientC', databaseBuilder: getDatabase); await testClientC.checkHomeserver(TestUser.homeserver); await testClientC.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: TestUser.username2), @@ -246,78 +246,77 @@ void test() async { Logs().i("++++ (Alice) Send again encrypted message: '$testMessage4' ++++"); await room.sendTextEvent(testMessage4); await Future.delayed(Duration(seconds: 5)); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].length == + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.length == 1); - assert(testClientB.encryption.olmManager - .olmSessions[testClientA.identityKey].length == + assert(testClientB.encryption!.olmManager + .olmSessions[testClientA.identityKey]!.length == 1); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].first.sessionId == - testClientB.encryption.olmManager.olmSessions[testClientA.identityKey] + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.first.sessionId == + testClientB.encryption!.olmManager.olmSessions[testClientA.identityKey]! .first.sessionId); - assert(testClientA.encryption.olmManager - .olmSessions[testClientC.identityKey].length == + assert(testClientA.encryption!.olmManager + .olmSessions[testClientC.identityKey]!.length == 1); - assert(testClientC.encryption.olmManager - .olmSessions[testClientA.identityKey].length == + assert(testClientC.encryption!.olmManager + .olmSessions[testClientA.identityKey]!.length == 1); - assert(testClientA.encryption.olmManager - .olmSessions[testClientC.identityKey].first.sessionId == - testClientC.encryption.olmManager.olmSessions[testClientA.identityKey] + assert(testClientA.encryption!.olmManager + .olmSessions[testClientC.identityKey]!.first.sessionId == + testClientC.encryption!.olmManager.olmSessions[testClientA.identityKey]! .first.sessionId); - assert(room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + assert(room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id() != currentSessionIdA); - currentSessionIdA = room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + currentSessionIdA = room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id(); /*assert(inviteRoom.client.encryption.keyManager .getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') != null);*/ - assert(room.lastEvent.body == testMessage4); - assert(inviteRoom.lastEvent.body == testMessage4); + assert(room.lastEvent!.body == testMessage4); + assert(inviteRoom.lastEvent!.body == testMessage4); Logs().i( - "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent.body}' ++++"); + "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++"); Logs().i('++++ Logout Bob another client ++++'); await testClientC.dispose(closeDatabase: false); await testClientC.logout(); - testClientC = null; await Future.delayed(Duration(seconds: 5)); Logs().i("++++ (Alice) Send again encrypted message: '$testMessage6' ++++"); await room.sendTextEvent(testMessage6); await Future.delayed(Duration(seconds: 5)); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].length == + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.length == 1); - assert(testClientB.encryption.olmManager - .olmSessions[testClientA.identityKey].length == + assert(testClientB.encryption!.olmManager + .olmSessions[testClientA.identityKey]!.length == 1); - assert(testClientA.encryption.olmManager - .olmSessions[testClientB.identityKey].first.sessionId == - testClientB.encryption.olmManager.olmSessions[testClientA.identityKey] + assert(testClientA.encryption!.olmManager + .olmSessions[testClientB.identityKey]!.first.sessionId == + testClientB.encryption!.olmManager.olmSessions[testClientA.identityKey]! .first.sessionId); - assert(room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + assert(room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id() != currentSessionIdA); - currentSessionIdA = room.client.encryption.keyManager - .getOutboundGroupSession(room.id) - .outboundGroupSession + currentSessionIdA = room.client.encryption!.keyManager + .getOutboundGroupSession(room.id)! + .outboundGroupSession! .session_id(); /*assert(inviteRoom.client.encryption.keyManager .getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') != null);*/ - assert(room.lastEvent.body == testMessage6); - assert(inviteRoom.lastEvent.body == testMessage6); + assert(room.lastEvent!.body == testMessage6); + assert(inviteRoom.lastEvent!.body == testMessage6); Logs().i( - "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent.body}' ++++"); + "++++ (Bob) Received decrypted message: '${inviteRoom.lastEvent!.body}' ++++"); await room.leave(); await room.forget(); @@ -329,8 +328,8 @@ void test() async { rethrow; } finally { Logs().i('++++ Logout Alice and Bob ++++'); - if (testClientA?.isLogged() ?? false) await testClientA.logoutAll(); - if (testClientA?.isLogged() ?? false) await testClientB.logoutAll(); + if (testClientA?.isLogged() ?? false) await testClientA!.logoutAll(); + if (testClientA?.isLogged() ?? false) await testClientB!.logoutAll(); await testClientA?.dispose(closeDatabase: false); await testClientB?.dispose(closeDatabase: false); testClientA = null;