From 17fd1f22b3b1e6db2a0055c0f2429cd40bc58034 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 14 Oct 2021 00:50:06 +0200 Subject: [PATCH] refactor: make event nullsafe --- lib/encryption/encryption.dart | 2 +- lib/src/event.dart | 189 ++++++++++-------- lib/src/timeline.dart | 22 +- lib/src/user.dart | 48 +++-- lib/src/utils/event_localizations.dart | 24 +-- lib/src/utils/image_pack_extension.dart | 4 +- lib/src/utils/space_child.dart | 4 +- test/commands_test.dart | 19 +- .../encrypt_decrypt_room_message_test.dart | 1 + test/image_pack_test.dart | 30 +++ test/room_test.dart | 7 + 11 files changed, 220 insertions(+), 130 deletions(-) diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index b2a2c761..ebcee139 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -308,7 +308,7 @@ class Encryption { await client.database?.storeEventUpdate( EventUpdate( content: event.toJson(), - roomID: event.roomId, + roomID: roomId, type: updateType, ), ); diff --git a/lib/src/event.dart b/lib/src/event.dart index 853bd683..2a21d9ad 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,7 +114,6 @@ 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 @@ -113,7 +123,8 @@ class Event extends MatrixEvent { 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']); @@ -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; } @@ -420,7 +443,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 +451,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 +476,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 +499,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; } @@ -489,10 +520,15 @@ class Event extends MatrixEvent { /// true to download the thumbnail instead. 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 +536,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,7 +577,7 @@ 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); } @@ -565,7 +598,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 +640,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,18 +657,18 @@ 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 { + String? get relationshipType { if (content?.tryGet>('m.relates_to') == null) { return null; } @@ -643,11 +677,11 @@ 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 { + String? get relationshipEventId { if (content == null || !(content['m.relates_to'] is Map)) { return null; } @@ -664,15 +698,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/timeline.dart b/lib/src/timeline.dart index 6ace2a09..9147bba6 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -190,9 +190,10 @@ class Timeline { 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 txnid = events[i].unsigned?['transaction_id']; + if (txnid != null) { + searchHaystack.add(txnid); } if (searchNeedle.intersection(searchHaystack).isNotEmpty) { break; @@ -205,19 +206,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 +227,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..87c67e89 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,7 +68,7 @@ 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 => @@ -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 @@ -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/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/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/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/test/commands_test.dart b/test/commands_test.dart index f9b3b211..ed269395 100644 --- a/test/commands_test.dart +++ b/test/commands_test.dart @@ -55,12 +55,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 +141,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': { diff --git a/test/encryption/encrypt_decrypt_room_message_test.dart b/test/encryption/encrypt_decrypt_room_message_test.dart index 24d1fb2d..cfa61aa7 100644 --- a/test/encryption/encrypt_decrypt_room_message_test.dart +++ b/test/encryption/encrypt_decrypt_room_message_test.dart @@ -72,6 +72,7 @@ void main() { room: room, originServerTs: now, eventId: '\$event', + senderId: client.userID, ); final decryptedEvent = await client.encryption.decryptRoomEvent(roomId, encryptedEvent); diff --git a/test/image_pack_test.dart b/test/image_pack_test.dart index 8005732e..0f48e6ab 100644 --- a/test/image_pack_test.dart +++ b/test/image_pack_test.dart @@ -36,24 +36,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,6 +81,9 @@ void main() { }, room: room, stateKey: '', + senderId: '\@fakeuser:fakeServer.notExisting', + eventId: '\$fakeid5:fakeServer.notExisting', + originServerTs: DateTime.now(), )); final packs = room.getImagePacks(); expect(packs.length, 1); @@ -95,6 +110,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 +135,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 +158,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 +201,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 +234,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/room_test.dart b/test/room_test.dart index f206f134..b044d4af 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -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'); @@ -358,6 +363,7 @@ void main() { 'users': {'@test:fakeServer.notExisting': 100}, 'users_default': 10 }, + originServerTs: DateTime.now(), stateKey: ''), ); expect(room.ownPowerLevel, 100); @@ -396,6 +402,7 @@ void main() { 'users': {}, 'users_default': 0 }, + originServerTs: DateTime.now(), stateKey: '', ), );