diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index fd93e9cd..4b2bc662 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -44,7 +44,8 @@ class KeyManager { final keyObj = olm.PkDecryption(); try { final info = await getRoomKeysBackupInfo(false); - if (info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2) { + if (info.algorithm != + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) { return false; } return keyObj.init_with_private_key(base64.decode(secret)) == @@ -523,9 +524,9 @@ class KeyManager { return (await encryption.ssss.getCached(megolmKey)) != null; } - RoomKeysVersionResponse _roomKeysVersionCache; + GetRoomKeysVersionCurrentResponse _roomKeysVersionCache; DateTime _roomKeysVersionCacheDate; - Future getRoomKeysBackupInfo( + Future getRoomKeysBackupInfo( [bool useCache = true]) async { if (_roomKeysVersionCache != null && _roomKeysVersionCacheDate != null && @@ -535,7 +536,7 @@ class KeyManager { .isBefore(_roomKeysVersionCacheDate)) { return _roomKeysVersionCache; } - _roomKeysVersionCache = await client.getRoomKeysBackup(); + _roomKeysVersionCache = await client.getRoomKeysVersionCurrent(); _roomKeysVersionCacheDate = DateTime.now(); return _roomKeysVersionCache; } @@ -553,7 +554,7 @@ class KeyManager { backupPubKey = decryption.init_with_private_key(privateKey); if (backupPubKey == null || - info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2 || + info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || info.authData['public_key'] != backupPubKey) { return; } @@ -706,7 +707,8 @@ class KeyManager { backupPubKey = decryption.init_with_private_key(privateKey); if (backupPubKey == null || - info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2 || + info.algorithm != + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || info.authData['public_key'] != backupPubKey) { return; } @@ -737,7 +739,7 @@ class KeyManager { _generateUploadKeys, args); Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...'); // upload the payload... - await client.storeRoomKeys(info.version, roomKeys); + await client.postRoomKeysKey(info.version, roomKeys); // 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) { @@ -1011,7 +1013,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) { try { enc.set_recipient_key(args.pubkey); // first we generate the payload to upload all the session keys in this chunk - final roomKeys = RoomKeys(); + final roomKeys = RoomKeys(rooms: {}); for (final dbSession in args.dbSessions) { final sess = SessionKey.fromDb(dbSession.dbSession, args.userId); if (!sess.isValid) { @@ -1019,7 +1021,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) { } // create the room if it doesn't exist if (!roomKeys.rooms.containsKey(sess.roomId)) { - roomKeys.rooms[sess.roomId] = RoomKeysRoom(); + roomKeys.rooms[sess.roomId] = RoomKeyBackup(sessions: {}); } // generate the encrypted content final payload = { @@ -1035,7 +1037,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) { // fetch the device, if available... //final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey); // aaaand finally add the session key to our payload - roomKeys.rooms[sess.roomId].sessions[sess.sessionId] = RoomKeysSingleKey( + roomKeys.rooms[sess.roomId].sessions[sess.sessionId] = KeyBackupData( firstMessageIndex: sess.inboundGroupSession.first_known_index(), forwardedCount: sess.forwardingCurve25519KeyChain.length, isVerified: dbSession.verified, //device?.verified ?? false, diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index 63b4d681..47f8ed48 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -23,9 +23,9 @@ import 'package:matrix/matrix.dart'; import 'package:olm/olm.dart' as olm; import '../encryption/utils/json_signature_check_extension.dart'; +import '../src/utils/run_in_root.dart'; import 'encryption.dart'; import 'utils/olm_session.dart'; -import '../src/utils/run_in_root.dart'; class OlmManager { final Encryption encryption; @@ -264,7 +264,7 @@ class OlmManager { // and generate and upload more if not. // If the server did not send us a count, assume it is 0 - final keyCount = countJson?.tryGet('signed_curve25519', 0) ?? 0; + final keyCount = countJson?.tryGet('signed_curve25519') ?? 0; // If the server does not support fallback keys, it will not tell us about them. // If the server supports them but has no key, upload a new one. @@ -644,7 +644,7 @@ class OlmManager { return; } final device = client.getUserDeviceKeysByCurve25519Key( - event.encryptedContent.tryGet('sender_key', '')); + event.encryptedContent.tryGet('sender_key') ?? ''); if (device == null) { return; // device not found } diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 1d9819a4..95f64d82 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import 'dart:core'; -import 'dart:convert'; -import 'dart:typed_data'; import 'dart:async'; +import 'dart:convert'; +import 'dart:core'; +import 'dart:typed_data'; import 'package:base58check/base58.dart'; import 'package:crypto/crypto.dart'; @@ -51,11 +51,13 @@ const pbkdf2SaltLength = 64; /// https://matrix.org/docs/guides/implementing-more-advanced-e-2-ee-features-such-as-cross-signing#3-implementing-ssss class SSSS { final Encryption encryption; + Client get client => encryption.client; final pendingShareRequests = {}; final _validators = Function(String)>{}; final _cacheCallbacks = Function(String)>{}; final Map _cache = {}; + SSSS(this.encryption); // for testing @@ -174,7 +176,7 @@ class SSSS { await client.setAccountData( client.userID, EventTypes.SecretStorageDefaultKey, - (SecretStorageDefaultKeyContent()..key = keyId).toJson(), + SecretStorageDefaultKeyContent(key: keyId).toJson(), ); } @@ -193,13 +195,12 @@ class SSSS { final content = SecretStorageKeyContent(); if (passphrase != null) { // we need to derive the key off of the passphrase - content.passphrase = PassphraseInfo(); - content.passphrase.algorithm = AlgorithmTypes.pbkdf2; - content.passphrase.salt = base64 - .encode(uc.secureRandomBytes(pbkdf2SaltLength)); // generate salt - content.passphrase.iterations = pbkdf2DefaultIterations; - ; - content.passphrase.bits = ssssKeyLength * 8; + content.passphrase = PassphraseInfo( + iterations: pbkdf2DefaultIterations, + salt: base64.encode(uc.secureRandomBytes(pbkdf2SaltLength)), + algorithm: AlgorithmTypes.pbkdf2, + bits: ssssKeyLength * 8, + ); privateKey = await client .runInBackground( _keyFromPassphrase, @@ -431,6 +432,7 @@ class SSSS { DateTime _lastCacheRequest; bool _isPeriodicallyRequestingMissingCache = false; + Future periodicallyRequestMissingCache() async { if (_isPeriodicallyRequestingMissingCache || (_lastCacheRequest != null && @@ -606,10 +608,13 @@ class OpenSSSS { final SSSS ssss; final String keyId; final SecretStorageKeyContent keyData; + OpenSSSS({this.ssss, this.keyId, this.keyData}); + Uint8List privateKey; bool get isUnlocked => privateKey != null; + bool get hasPassphrase => keyData.passphrase != null; String get recoveryKey => @@ -708,6 +713,7 @@ class OpenSSSS { class _KeyFromPassphraseArgs { final String passphrase; final PassphraseInfo info; + _KeyFromPassphraseArgs({this.passphrase, this.info}); } diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart index 63e912ad..aab657a8 100644 --- a/lib/encryption/utils/bootstrap.dart +++ b/lib/encryption/utils/bootstrap.dart @@ -563,8 +563,8 @@ class Bootstrap { keyObj.free(); } Logs().v('Create the new backup version...'); - await client.createRoomKeysBackup( - RoomKeysAlgorithmType.v1Curve25519AesSha2, + await client.postRoomKeysVersion( + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2, { 'public_key': pubKey, }, diff --git a/lib/src/client.dart b/lib/src/client.dart index b503458f..ad650669 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -21,8 +21,8 @@ import 'dart:convert'; import 'dart:core'; import 'dart:typed_data'; -import 'package:matrix/src/utils/run_in_root.dart'; import 'package:http/http.dart' as http; +import 'package:matrix/src/utils/run_in_root.dart'; import 'package:olm/olm.dart' as olm; import '../encryption.dart'; @@ -44,6 +44,12 @@ typedef RoomSorter = int Function(Room a, Room b); enum LoginState { logged, loggedOut } +extension TrailingSlash on Uri { + Uri stripTrailingSlash() => path.endsWith('/') + ? replace(path: path.substring(0, path.length - 1)) + : this; +} + /// Represents a Matrix client to communicate with a /// [Matrix](https://matrix.org) homeserver and is the entry point for this /// SDK. @@ -158,12 +164,13 @@ class Client extends MatrixApi { this.compute, Filter syncFilter, @deprecated bool debug, - }) : syncFilter = syncFilter ?? + }) : syncFilter = syncFilter ?? Filter( room: RoomFilter( state: StateFilter(lazyLoadMembers: true), ), - ) { + ), + super(httpClient: httpClient) { supportedLoginTypes ??= {AuthenticationTypes.password}; verificationMethods ??= {}; importantStateEvents ??= {}; @@ -182,7 +189,6 @@ class Client extends MatrixApi { EventTypes.Encrypted, EventTypes.Sticker, ]); - this.httpClient = httpClient ?? http.Client(); // register all the default commands registerDefaultCommands(); @@ -308,7 +314,7 @@ class Client extends MatrixApi { } /// Gets discovery information about the domain. The file may include additional keys. - Future getWellKnownInformationsByUserId( + Future getDiscoveryInformationsByUserId( String MatrixIdOrDomain, ) async { try { @@ -321,15 +327,14 @@ class Client extends MatrixApi { // No-OP } final rawJson = json.decode(respBody); - return WellKnownInformation.fromJson(rawJson); + return DiscoveryInformation.fromJson(rawJson); } catch (_) { // we got an error processing or fetching the well-known information, let's // provide a reasonable fallback. - return WellKnownInformation.fromJson({ - 'm.homeserver': { - 'base_url': Uri.https(MatrixIdOrDomain.domain, '').toString(), - }, - }); + return DiscoveryInformation( + mHomeserver: HomeserverInformation( + baseUrl: Uri.https(MatrixIdOrDomain.domain, '')), + ); } } @@ -347,35 +352,19 @@ 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 { - if (homeserverUrl is Uri) { - homeserver = homeserverUrl; - } else { - // URLs allow to have whitespace surrounding them, see https://www.w3.org/TR/2011/WD-html5-20110525/urls.html - // As we want to strip a trailing slash, though, we have to trim the url ourself - // and thus can't let Uri.parse() deal with it. - homeserverUrl = homeserverUrl.trim(); - // strip a trailing slash - if (homeserverUrl.endsWith('/')) { - homeserverUrl = homeserverUrl.substring(0, homeserverUrl.length - 1); - } - homeserver = Uri.parse(homeserverUrl); - } + homeserver = + (homeserverUrl is Uri) ? homeserverUrl : Uri.parse(homeserverUrl); + homeserver = homeserver.stripTrailingSlash(); // Look up well known - WellKnownInformation wellKnown; + DiscoveryInformation wellKnown; if (checkWellKnown) { try { wellKnown = await getWellknown(); - homeserverUrl = wellKnown.mHomeserver.baseUrl.trim(); - // strip a trailing slash - if (homeserverUrl.endsWith('/')) { - homeserverUrl = - homeserverUrl.substring(0, homeserverUrl.length - 1); - } - homeserver = Uri.parse(homeserverUrl); + homeserver = wellKnown.mHomeserver.baseUrl.stripTrailingSlash(); } catch (e) { Logs().v('Found no well known information', e); } @@ -390,9 +379,9 @@ class Client extends MatrixApi { } final loginTypes = await getLoginFlows(); - if (!loginTypes.flows.any((f) => supportedLoginTypes.contains(f.type))) { + if (!loginTypes.any((f) => supportedLoginTypes.contains(f.type))) { throw BadServerLoginTypesException( - loginTypes.flows.map((f) => f.type).toSet(), supportedLoginTypes); + loginTypes.map((f) => f.type).toSet(), supportedLoginTypes); } return wellKnown; @@ -406,14 +395,14 @@ class Client extends MatrixApi { /// Returns the fully-qualified Matrix user ID (MXID) that has been registered. /// You have to call [checkHomeserver] first to set a homeserver. @override - Future register({ + Future register({ String username, String password, String deviceId, String initialDeviceDisplayName, bool inhibitLogin, AuthenticationData auth, - String kind, + AccountKind kind, }) async { final response = await super.register( username: username, @@ -447,8 +436,8 @@ class Client extends MatrixApi { /// Maybe you want to set [user] to the same String to stay compatible with /// older server versions. @override - Future login({ - String type = AuthenticationTypes.password, + Future login( + LoginType type, { AuthenticationIdentifier identifier, String password, String token, @@ -463,13 +452,12 @@ class Client extends MatrixApi { await checkHomeserver(user.domain); } final loginResp = await super.login( - type: type, + type, identifier: identifier, password: password, token: token, deviceId: deviceId, initialDeviceDisplayName: initialDeviceDisplayName, - auth: auth, // ignore: deprecated_member_use user: user, // ignore: deprecated_member_use @@ -553,7 +541,7 @@ class Client extends MatrixApi { roomId = await createRoom( invite: [mxid], isDirect: true, - preset: CreateRoomPreset.trusted_private_chat, + preset: CreateRoomPreset.trustedPrivateChat, ); if (roomId == null) return roomId; @@ -576,7 +564,7 @@ class Client extends MatrixApi { Visibility visibility = Visibility.public, String spaceAliasName, List invite, - List> invite3pid, + List invite3pid, String roomVersion, }) => createRoom( @@ -607,7 +595,7 @@ class Client extends MatrixApi { return getProfileFromUserId(userID); } - final Map _profileCache = {}; + final Map _profileCache = {}; /// Get the combined profile information for this user. /// If [getFromRooms] is true then the profile will first be searched from the @@ -629,15 +617,25 @@ class Client extends MatrixApi { if (room != null) { final user = room.getParticipants().firstWhere((User user) => user.id == userId); - return Profile(user.displayName, user.avatarUrl); + return Profile( + userId: userId, + displayName: user.displayName, + avatarUrl: user.avatarUrl); } } if (cache && _profileCache.containsKey(userId)) { - return _profileCache[userId]; + final profile = _profileCache[userId]; + return Profile( + userId: userId, + displayName: profile.displayname, + avatarUrl: profile.avatarUrl); } final profile = await getUserProfile(userId); _profileCache[userId] = profile; - return profile; + return Profile( + userId: userId, + displayName: profile.displayname, + avatarUrl: profile.avatarUrl); } Future> get archive async { @@ -685,10 +683,10 @@ class Client extends MatrixApi { /// Uploads a file and automatically caches it in the database, if it is small enough /// and returns the mxc url as a string. @override - Future uploadContent(Uint8List file, String fileName, - {String contentType}) async { - final mxc = - await super.uploadContent(file, fileName, contentType: contentType); + Future uploadContent(Uint8List file, + {String filename, String contentType}) async { + final mxc = await super + .uploadContent(file, filename: filename, contentType: contentType); final storeable = database != null && file.length <= database.maxFileSize; if (storeable) { await database.storeFile( @@ -715,7 +713,7 @@ class Client extends MatrixApi { /// Uploads a new user avatar for this user. Future setAvatar(MatrixFile file) async { - final uploadResp = await uploadContent(file.bytes, file.name); + final uploadResp = await uploadContent(file.bytes, filename: file.name); await setAvatarUrl(userID, Uri.parse(uploadResp)); return; } @@ -2006,16 +2004,16 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); /// Changes the password. You should either set oldPasswort or another authentication flow. @override Future changePassword(String newPassword, - {String oldPassword, AuthenticationData auth}) async { + {String oldPassword, AuthenticationData auth, bool logoutDevices}) async { try { if (oldPassword != null) { auth = AuthenticationPassword( - user: userID, identifier: AuthenticationUserIdentifier(user: userID), password: oldPassword, ); } - await super.changePassword(newPassword, auth: auth); + await super.changePassword(newPassword, + auth: auth, logoutDevices: logoutDevices); } on MatrixException catch (matrixException) { if (!matrixException.requireAdditionalAuthentication) { rethrow; @@ -2031,11 +2029,11 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); return changePassword( newPassword, auth: AuthenticationPassword( - user: userID, identifier: AuthenticationUserIdentifier(user: userID), password: oldPassword, session: matrixException.session, ), + logoutDevices: logoutDevices, ); } catch (_) { rethrow; diff --git a/lib/src/room.dart b/lib/src/room.dart index baaf106e..ede90143 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -19,8 +19,8 @@ import 'dart:async'; import 'dart:convert'; -import 'package:matrix/src/utils/space_child.dart'; import 'package:html_unescape/html_unescape.dart'; +import 'package:matrix/src/utils/space_child.dart'; import '../matrix.dart'; import 'client.dart'; @@ -288,7 +288,7 @@ class Room { if (!aliases.contains(canonicalAlias)) { await client.setRoomAlias(canonicalAlias, id); } - await client.setRoomStateWithKey(id, EventTypes.RoomCanonicalAlias, { + await client.setRoomStateWithKey(id, EventTypes.RoomCanonicalAlias, '', { 'alias': canonicalAlias, }); } @@ -455,6 +455,7 @@ class Room { Future setName(String newName) => client.setRoomStateWithKey( id, EventTypes.RoomName, + '', {'name': newName}, ); @@ -462,6 +463,7 @@ class Room { Future setDescription(String newName) => client.setRoomStateWithKey( id, EventTypes.RoomTopic, + '', {'topic': newName}, ); @@ -506,14 +508,15 @@ class Room { /// this works if there is no connection to the homeserver. Future setUnread(bool unread) async { final content = MarkedUnread(unread).toJson(); - await _handleFakeSync(SyncUpdate() + await _handleFakeSync(SyncUpdate(nextBatch: '') ..rooms = (RoomsUpdate() ..join = (({}..[id] = (JoinedRoomUpdate() ..accountData = [ - BasicRoomEvent() - ..content = content - ..roomId = id - ..type = EventType.markedUnread + BasicRoomEvent( + content: content, + roomId: id, + type: EventType.markedUnread, + ) ]))))); await client.setAccountDataPerRoom( client.userID, @@ -524,23 +527,24 @@ class Room { if (unread == false && lastEvent != null) { await setReadMarker( lastEvent.eventId, - readReceiptLocationEventId: lastEvent.eventId, + mRead: lastEvent.eventId, ); } } /// Returns true if this room has a m.favourite tag. - bool get isFavourite => tags[TagType.Favourite] != null; + bool get isFavourite => tags[TagType.favourite] != null; /// Sets the m.favourite tag for this room. Future setFavourite(bool favourite) => - favourite ? addTag(TagType.Favourite) : removeTag(TagType.Favourite); + favourite ? addTag(TagType.favourite) : removeTag(TagType.favourite); /// Call the Matrix API to change the pinned events of this room. Future setPinnedEvents(List pinnedEventIds) => client.setRoomStateWithKey( id, EventTypes.RoomPinnedEvents, + '', {'pinned': pinnedEventIds}, ); @@ -635,13 +639,13 @@ class Room { } final uploadResp = await client.uploadContent( uploadFile.bytes, - uploadFile.name, + filename: uploadFile.name, contentType: uploadFile.mimeType, ); final thumbnailUploadResp = uploadThumbnail != null ? await client.uploadContent( uploadThumbnail.bytes, - uploadThumbnail.name, + filename: uploadThumbnail.name, contentType: uploadThumbnail.mimeType, ) : null; @@ -751,17 +755,16 @@ class Room { RegExp(r'.*<\/mx-reply>', caseSensitive: false, multiLine: false, dotAll: true), ''); - final repliedHtml = content.tryGet( - 'formatted_body', + final repliedHtml = content.tryGet('formatted_body') ?? htmlEscape - .convert(content.tryGet('body', '')) - .replaceAll('\n', '
')); + .convert(content.tryGet('body') ?? '') + .replaceAll('\n', '
'); content['formatted_body'] = '
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'] = - '${replyText.replaceAll('@room', '@\u200broom')}\n\n${content.tryGet('body', '')}'; + '${replyText.replaceAll('@room', '@\u200broom')}\n\n${content.tryGet('body') ?? ''}'; content['m.relates_to'] = { 'm.in_reply_to': { 'event_id': inReplyTo.eventId, @@ -783,21 +786,22 @@ class Room { } } final sentDate = DateTime.now(); - final syncUpdate = SyncUpdate() + final syncUpdate = SyncUpdate(nextBatch: '') ..rooms = (RoomsUpdate() ..join = ({}..[id] = (JoinedRoomUpdate() ..timeline = (TimelineUpdate() ..events = [ - MatrixEvent() - ..content = content - ..type = type - ..eventId = messageID - ..senderId = client.userID - ..originServerTs = sentDate - ..unsigned = { + MatrixEvent( + content: content, + type: type, + eventId: messageID, + senderId: client.userID, + originServerTs: sentDate, + unsigned: { messageSendingStatusKey: 0, 'transaction_id': messageID, }, + ) ])))); await _handleFakeSync(syncUpdate); @@ -868,7 +872,7 @@ class Room { if ([MatrixError.M_NOT_FOUND, MatrixError.M_UNKNOWN] .contains(exception.error)) { await _handleFakeSync( - SyncUpdate() + SyncUpdate(nextBatch: '') ..rooms = (RoomsUpdate() ..leave = { '$id': (LeftRoomUpdate()), @@ -909,12 +913,13 @@ class Room { return await client.setRoomStateWithKey( id, EventTypes.RoomPowerLevels, + '', powerMap, ); } /// Call the Matrix API to invite a user to this room. - Future invite(String userID) => client.inviteToRoom(id, userID); + Future invite(String userID) => client.inviteUser(id, userID); /// Request more previous events from the server. [historyCount] defines how much events should /// be received maximum. When the request is answered, [onHistoryReceived] will be triggered **before** @@ -936,7 +941,7 @@ class Room { if (!((resp.chunk?.isNotEmpty ?? false) && resp.end != null)) return; await client.handleSync( - SyncUpdate() + SyncUpdate(nextBatch: '') ..rooms = (RoomsUpdate() ..join = ({}..[id] = (JoinedRoomUpdate() ..state = resp.state @@ -1000,16 +1005,15 @@ 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 readReceiptLocationEventId}) async { - if (readReceiptLocationEventId != null) { + Future setReadMarker(String eventId, {String mRead}) async { + if (mRead != null) { notificationCount = 0; await client.database?.resetNotificationCount(client.id, id); } await client.setReadMarker( id, eventId, - readReceiptLocationEventId: readReceiptLocationEventId, + mRead: mRead, ); return; } @@ -1021,7 +1025,9 @@ class Room { await client.database?.resetNotificationCount(client.id, id); await client.postReceipt( id, + ReceiptType.mRead, eventId, + {}, ); return; } @@ -1034,7 +1040,7 @@ class Room { await client.setReadMarker( id, eventID, - readReceiptLocationEventId: eventID, + mRead: eventID, ); return; } @@ -1294,10 +1300,12 @@ class Room { /// Uploads a new user avatar for this room. Returns the event ID of the new /// m.room.avatar event. Future setAvatar(MatrixFile file) async { - final uploadResp = await client.uploadContent(file.bytes, file.name); + final uploadResp = + await client.uploadContent(file.bytes, filename: file.name); return await client.setRoomStateWithKey( id, EventTypes.RoomAvatar, + '', {'url': uploadResp}, ); } @@ -1402,14 +1410,14 @@ class Room { 'global', PushRuleKind.room, id, - [PushRuleAction.dont_notify], + [PushRuleAction.dontNotify], ); } else if (pushRuleState == PushRuleState.notify) { await client.setPushRule( 'global', PushRuleKind.room, id, - [PushRuleAction.dont_notify], + [PushRuleAction.dontNotify], ); } break; @@ -1422,9 +1430,9 @@ class Room { 'global', PushRuleKind.override, id, - [PushRuleAction.dont_notify], + [PushRuleAction.dontNotify], conditions: [ - PushConditions('event_match', key: 'room_id', pattern: id) + PushCondition(kind: 'event_match', key: 'room_id', pattern: id) ], ); } @@ -1578,6 +1586,7 @@ class Room { await client.setRoomStateWithKey( id, EventTypes.RoomJoinRules, + '', { 'join_rule': joinRules.toString().replaceAll('JoinRules.', ''), }, @@ -1600,6 +1609,7 @@ class Room { await client.setRoomStateWithKey( id, EventTypes.GuestAccess, + '', { 'guest_access': _guestAccessMap[guestAccess], }, @@ -1623,6 +1633,7 @@ class Room { await client.setRoomStateWithKey( id, EventTypes.HistoryVisibility, + '', { 'history_visibility': _historyVisibilityMap[historyVisibility], }, @@ -1648,6 +1659,7 @@ class Room { await client.setRoomStateWithKey( id, EventTypes.Encryption, + '', { 'algorithm': algorithm, }, @@ -1742,22 +1754,14 @@ class Room { }) async { if (!isSpace) throw Exception('Room is not a space!'); via ??= [roomId.domain]; - await client.setRoomStateWithKey( - id, - EventTypes.spaceChild, - { - 'via': via, - if (order != null) 'order': order, - if (suggested != null) 'suggested': suggested, - }, - roomId); - await client.setRoomStateWithKey( - roomId, - EventTypes.spaceParent, - { - 'via': via, - }, - id); + await client.setRoomStateWithKey(id, EventTypes.spaceChild, roomId, { + 'via': via, + if (order != null) 'order': order, + if (suggested != null) 'suggested': suggested, + }); + await client.setRoomStateWithKey(roomId, EventTypes.spaceParent, id, { + 'via': via, + }); return; } diff --git a/lib/src/utils/commands_extension.dart b/lib/src/utils/commands_extension.dart index 2babaf83..820cf9dc 100644 --- a/lib/src/utils/commands_extension.dart +++ b/lib/src/utils/commands_extension.dart @@ -172,8 +172,8 @@ extension CommandsClientExtension on Client { return await args.room.client.setRoomStateWithKey( args.room.id, EventTypes.RoomMember, - currentEventJson, args.room.client.userID, + currentEventJson, ); }); addCommand('myroomavatar', (CommandArgs args) async { @@ -185,8 +185,8 @@ extension CommandsClientExtension on Client { return await args.room.client.setRoomStateWithKey( args.room.id, EventTypes.RoomMember, - currentEventJson, args.room.client.userID, + currentEventJson, ); }); } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index bc8088f1..d0adeda9 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -23,7 +23,6 @@ import 'package:matrix/matrix.dart'; import 'package:olm/olm.dart' as olm; import '../../encryption.dart'; - import '../client.dart'; import '../event.dart'; import '../room.dart'; @@ -132,12 +131,22 @@ class DeviceKeysList { DeviceKeysList(this.userId, this.client); } +class SimpleSignableKey extends MatrixSignableKey { + @override + String identifier; + + SimpleSignableKey.fromJson(Map json) : super.fromJson(json); +} + abstract class SignableKey extends MatrixSignableKey { Client client; Map validSignatures; bool _verified; bool blocked; + @override + String identifier; + String get ed25519Key => keys['ed25519:$identifier']; bool get verified => (directVerified || crossVerified) && !blocked; bool get encryptToDevice => @@ -161,8 +170,8 @@ abstract class SignableKey extends MatrixSignableKey { blocked = false; } - MatrixSignableKey cloneForSigning() { - final newKey = MatrixSignableKey.fromJson(toJson().copy()); + SimpleSignableKey cloneForSigning() { + final newKey = SimpleSignableKey.fromJson(toJson().copy()); newKey.identifier = identifier; newKey.signatures ??= >{}; newKey.signatures.clear(); diff --git a/lib/src/utils/space_child.dart b/lib/src/utils/space_child.dart index be045b5a..01905ecc 100644 --- a/lib/src/utils/space_child.dart +++ b/lib/src/utils/space_child.dart @@ -30,7 +30,7 @@ class SpaceChild { : assert(state.type == EventTypes.spaceChild), roomId = state.stateKey, via = state.content.tryGetList('via'), - order = state.content.tryGet('order', ''), + order = state.content.tryGet('order') ?? '', suggested = state.content.tryGet('suggested'); } diff --git a/lib/src/utils/uia_request.dart b/lib/src/utils/uia_request.dart index 2c40d698..e753d5a4 100644 --- a/lib/src/utils/uia_request.dart +++ b/lib/src/utils/uia_request.dart @@ -44,6 +44,7 @@ class UiaRequest { Map params = {}; UiaRequestState get state => _state; + set state(UiaRequestState newState) { if (_state == newState) return; _state = newState; @@ -57,7 +58,8 @@ class UiaRequest { Future _run([AuthenticationData auth]) async { state = UiaRequestState.loading; try { - auth ??= AuthenticationData(session: session); + auth ??= + AuthenticationData(session: session, type: AuthenticationTypes.token); final res = await request(auth); state = UiaRequestState.done; result = res; diff --git a/pubspec.yaml b/pubspec.yaml index a02b66c6..028c9f02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: crypto: ^3.0.0 base58check: ^2.0.0 olm: ^2.0.0 - matrix_api_lite: ^0.3.5 + matrix_api_lite: ^0.4.0 hive: ^2.0.4 ffi: ^1.0.0 js: ^0.6.3 diff --git a/test/client_test.dart b/test/client_test.dart index 1c5ba715..6ef1c308 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -84,7 +84,7 @@ void main() { try { await matrix.checkHomeserver('https://fakeserver.wrongaddress'); - } on MatrixConnectionException catch (exception) { + } catch (exception) { expect(exception != null, true); } await matrix.checkHomeserver('https://fakeserver.notexisting', @@ -323,7 +323,7 @@ void main() { await matrix.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); - final loginResp = await matrix.login( + final loginResp = await matrix.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: 'test'), password: '1234'); @@ -368,12 +368,12 @@ void main() { final profile = await matrix.getProfileFromUserId('@getme:example.com', getFromRooms: false); expect(profile.avatarUrl.toString(), 'mxc://test'); - expect(profile.displayname, 'You got me'); + expect(profile.displayName, 'You got me'); final aliceProfile = await matrix.getProfileFromUserId('@alice:example.com'); expect(aliceProfile.avatarUrl.toString(), 'mxc://example.org/SEsfnsuifSDFSSEF'); - expect(aliceProfile.displayname, 'Alice Margatroid'); + expect(aliceProfile.displayName, 'Alice Margatroid'); }); test('sendToDeviceEncrypted', () async { if (!olmEnabled) { @@ -642,7 +642,8 @@ void main() { }); test('upload', () async { final client = await getClient(); - final response = await client.uploadContent(Uint8List(0), 'file.jpeg'); + final response = + await client.uploadContent(Uint8List(0), filename: 'file.jpeg'); expect(response, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); expect(await client.database.getFile(response) != null, client.database.supportsFileStoring); diff --git a/test/event_test.dart b/test/event_test.dart index 657c6104..a0cb2c57 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -269,7 +269,7 @@ void main() { final matrix = Client('testclient', httpClient: FakeMatrixApi()); await matrix.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); - await matrix.login( + await matrix.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: 'test'), password: '1234'); @@ -288,7 +288,7 @@ void main() { final matrix = Client('testclient', httpClient: FakeMatrixApi()); await matrix.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); - await matrix.login( + await matrix.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: 'test'), password: '1234'); diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index f3fb3cdf..67f352d0 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -16,15 +16,14 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart' as sdk; -import 'package:matrix/matrix.dart'; - import 'dart:convert'; import 'dart:core'; import 'dart:math'; import 'package:http/http.dart'; import 'package:http/testing.dart'; +import 'package:matrix/matrix.dart' as sdk; +import 'package:matrix/matrix.dart'; Map decodeJson(dynamic data) { if (data is String) { @@ -122,11 +121,9 @@ class FakeMatrixApi extends MockClient { action.contains('/account_data/') && !action.contains('/room/')) { final type = Uri.decodeComponent(action.split('/').last); - final syncUpdate = sdk.SyncUpdate() + final syncUpdate = sdk.SyncUpdate(nextBatch: '') ..accountData = [ - sdk.BasicEvent() - ..content = decodeJson(data) - ..type = type + sdk.BasicEvent(content: decodeJson(data), type: type) ]; if (client.database != null) { await client.database.transaction(() async { @@ -2111,7 +2108,7 @@ class FakeMatrixApi extends MockClient { } } // and generate a fake sync - client.handleSync(sdk.SyncUpdate()); + client.handleSync(sdk.SyncUpdate(nextBatch: '')); } return {}; }, diff --git a/test/room_test.dart b/test/room_test.dart index 68ec4238..e9142ab4 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -648,8 +648,8 @@ void main() { }); test('Test tag methods', () async { - await room.addTag(TagType.Favourite, order: 0.1); - await room.removeTag(TagType.Favourite); + await room.addTag(TagType.favourite, order: 0.1); + await room.removeTag(TagType.favourite); expect(room.isFavourite, false); room.roomAccountData['m.tag'] = BasicRoomEvent.fromJson({ 'content': { @@ -661,7 +661,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/user_test.dart b/test/user_test.dart index e83d1937..10bb17a6 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -49,7 +49,7 @@ void main() { setUp(() async { await client.checkHomeserver('https://fakeserver.notexisting', checkWellKnown: false); - await client.login( + await client.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: 'test'), password: '1234'); }); diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 3ceb8a5b..67d1b485 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -22,7 +22,7 @@ void test() async { Logs().i('++++ Login Alice at ++++'); testClientA = Client('TestClientA', databaseBuilder: getDatabase); await testClientA.checkHomeserver(TestUser.homeserver); - await testClientA.login( + await testClientA.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: TestUser.username), password: TestUser.password); assert(testClientA.encryptionEnabled); @@ -30,7 +30,7 @@ void test() async { Logs().i('++++ Login Bob ++++'); testClientB = Client('TestClientB', databaseBuilder: getDatabase); await testClientB.checkHomeserver(TestUser.homeserver); - await testClientB.login( + await testClientB.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: TestUser.username2), password: TestUser.password); assert(testClientB.encryptionEnabled); @@ -220,7 +220,7 @@ void test() async { Logs().i('++++ Login Bob in another client ++++'); var testClientC = Client('TestClientC', databaseBuilder: getDatabase); await testClientC.checkHomeserver(TestUser.homeserver); - await testClientC.login( + await testClientC.login(LoginType.mLoginPassword, identifier: AuthenticationUserIdentifier(user: TestUser.username2), password: TestUser.password); await Future.delayed(Duration(seconds: 3));