diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index f01ac1b8..a57d51e1 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -193,22 +193,9 @@ class KeyManager { event.content['session_id'] == sessionId) { final decrypted = encryption.decryptRoomEventSync(roomId, event); if (decrypted.type != EventTypes.Encrypted) { - // Remove this from the state to make sure it does not appear as last event - room.states.remove(EventTypes.Encrypted); - // Set the decrypted event as last event by adding it to the state - room.states[decrypted.type] = {'': decrypted}; - // Also store in database - final database = client.database; - if (database != null) { - await database.storeEventUpdate( - EventUpdate( - roomID: room.id, - type: EventUpdateType.state, - content: decrypted.toJson(), - ), - client, - ); - } + // No need to persist it as the lastEvent is persisted in the sync + // right after processing to-device messages: + room.lastEvent = decrypted; } } // and finally broadcast the new session diff --git a/lib/src/client.dart b/lib/src/client.dart index 03971d8c..ef4d747d 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -233,12 +233,8 @@ class Client extends MatrixApi { EventTypes.CallAnswer, EventTypes.CallReject, EventTypes.CallHangup, - - /// hack because having them both in important events and roomPreivew - /// makes the statekey '' which means you can only have one event of that - /// type - // EventTypes.GroupCallPrefix, - // EventTypes.GroupCallMemberPrefix, + EventTypes.GroupCallPrefix, + EventTypes.GroupCallMemberPrefix, ]); // register all the default commands @@ -2131,7 +2127,12 @@ class Client extends MatrixApi { final id = entry.key; final syncRoomUpdate = entry.value; - await database?.storeRoomUpdate(id, syncRoomUpdate, this); + // Is the timeline limited? Then all previous messages should be + // removed from the database! + if (syncRoomUpdate is JoinedRoomUpdate && + syncRoomUpdate.timeline?.limited == true) { + await database?.deleteTimelineForRoom(id); + } final room = await _updateRoomsByRoomUpdate(id, syncRoomUpdate); final timelineUpdateType = direction != null @@ -2202,6 +2203,7 @@ class Client extends MatrixApi { await _handleRoomEvents(room, state, EventUpdateType.inviteState); } } + await database?.storeRoomUpdate(id, syncRoomUpdate, room.lastEvent, this); } } @@ -2478,50 +2480,55 @@ class Client extends MatrixApi { if (eventUpdate.type == EventUpdateType.history) return; switch (eventUpdate.type) { - case EventUpdateType.timeline: - case EventUpdateType.state: case EventUpdateType.inviteState: - final stateEvent = Event.fromJson(eventUpdate.content, room); - if (stateEvent.type == EventTypes.Redaction) { - final String? redacts = eventUpdate.content.tryGet('redacts'); - if (redacts != null) { - room.states.forEach( - (String key, Map states) => states.forEach( - (String key, Event state) { - if (state.eventId == redacts) { - state.setRedactionEvent(stateEvent); - } - }, - ), - ); - } - } else { - // We want to set state the in-memory cache for the room with the new event. - // To do this, we have to respect to not save edits, unless they edit the - // current last event. - // Additionally, we only store the event in-memory if the room has either been - // post-loaded or the event is animportant state event. - final noMessageOrNoEdit = stateEvent.type != EventTypes.Message || - stateEvent.relationshipType != RelationshipTypes.edit; - final editingLastEvent = - stateEvent.relationshipEventId == room.lastEvent?.eventId; - final consecutiveEdit = - room.lastEvent?.relationshipType == RelationshipTypes.edit && - stateEvent.relationshipEventId == - room.lastEvent?.relationshipEventId; - final importantOrRoomLoaded = - eventUpdate.type == EventUpdateType.inviteState || - !room.partial || - // make sure we do overwrite events we have already loaded. - room.states[stateEvent.type] - ?.containsKey(stateEvent.stateKey ?? '') == - true || - importantStateEvents.contains(stateEvent.type); - if ((noMessageOrNoEdit || editingLastEvent || consecutiveEdit) && - importantOrRoomLoaded) { - room.setState(stateEvent); - } + room.setState(Event.fromJson(eventUpdate.content, room)); + break; + case EventUpdateType.state: + case EventUpdateType.timeline: + final event = Event.fromJson(eventUpdate.content, room); + + // Update the room state: + if (!room.partial || + // make sure we do overwrite events we have already loaded. + room.states[event.type]?.containsKey(event.stateKey ?? '') == + true || + importantStateEvents.contains(event.type)) { + room.setState(event); } + if (eventUpdate.type != EventUpdateType.timeline) break; + + // If last event is null or not a valid room preview event anyway, + // just use this: + if (room.lastEvent == null || + !roomPreviewLastEvents.contains(room.lastEvent?.type)) { + room.lastEvent = event; + break; + } + + // Is this event redacting the last event? + if (event.type == EventTypes.Redaction && + event.redacts == room.lastEvent?.eventId) { + room.lastEvent?.setRedactionEvent(event); + break; + } + + // Is this event an edit of the last event? Otherwise ignore it. + if (event.relationshipType == RelationshipTypes.edit) { + if (event.relationshipEventId == room.lastEvent?.eventId || + (room.lastEvent?.relationshipType == RelationshipTypes.edit && + event.relationshipEventId == + room.lastEvent?.relationshipEventId)) { + room.lastEvent = event; + } + break; + } + + // Is this event of an important type for the last event? + if (!roomPreviewLastEvents.contains(event.type)) break; + + // Event is a valid new lastEvent: + room.lastEvent = event; + break; case EventUpdateType.accountData: room.roomAccountData[eventUpdate.content['type']] = diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index b7e79c16..6f74ca88 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -65,7 +65,13 @@ abstract class DatabaseApi { /// Stores a RoomUpdate object in the database. Must be called inside of /// [transaction]. Future storeRoomUpdate( - String roomId, SyncRoomUpdate roomUpdate, Client client); + String roomId, + SyncRoomUpdate roomUpdate, + Event? lastEvent, + Client client, + ); + + Future deleteTimelineForRoom(String roomId); /// Stores an EventUpdate object in the database. Must be called inside of /// [transaction]. diff --git a/lib/src/database/hive_collections_database.dart b/lib/src/database/hive_collections_database.dart index 1577ac38..58404349 100644 --- a/lib/src/database/hive_collections_database.dart +++ b/lib/src/database/hive_collections_database.dart @@ -1130,12 +1130,7 @@ class HiveCollectionsDatabase extends DatabaseApi { ? '' : eventUpdate.content['state_key']; // Store a common state event - if ({ - EventUpdateType.timeline, - EventUpdateType.state, - EventUpdateType.inviteState - }.contains(eventUpdate.type) && - stateKey != null) { + if (stateKey != null) { if (eventUpdate.content['type'] == EventTypes.RoomMember) { await _roomMembersBox.put( TupleKey( @@ -1149,45 +1144,9 @@ class HiveCollectionsDatabase extends DatabaseApi { eventUpdate.content['type'], ).toString(); final stateMap = copyMap(await _roomStateBox.get(key) ?? {}); - // store state events and new messages, that either are not an edit or an edit of the lastest message - // An edit is an event, that has an edit relation to the latest event. In some cases for the second edit, we need to compare if both have an edit relation to the same event instead. - if (eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') == - null) { - stateMap[stateKey] = eventUpdate.content; - await _roomStateBox.put(key, stateMap); - } else { - final editedEventRelationshipEventId = eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') - ?.tryGet('event_id'); - final tmpRoom = client.getRoomById(eventUpdate.roomID) ?? - Room(id: eventUpdate.roomID, client: client); - - if (eventUpdate.content['type'] != - EventTypes - .Message || // send anything other than a message - eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') - ?.tryGet('rel_type') != - RelationshipTypes - .edit || // replies are always latest anyway - editedEventRelationshipEventId == - tmpRoom.lastEvent - ?.eventId || // edit of latest (original event) event - (tmpRoom.lastEvent?.relationshipType == - RelationshipTypes.edit && - editedEventRelationshipEventId == - tmpRoom.lastEvent - ?.relationshipEventId) // edit of latest (edited event) event - ) { - stateMap[stateKey] = eventUpdate.content; - await _roomStateBox.put(key, stateMap); - } - } + stateMap[stateKey] = eventUpdate.content; + await _roomStateBox.put(key, stateMap); } } @@ -1257,7 +1216,11 @@ class HiveCollectionsDatabase extends DatabaseApi { @override Future storeRoomUpdate( - String roomId, SyncRoomUpdate roomUpdate, Client client) async { + String roomId, + SyncRoomUpdate roomUpdate, + Event? lastEvent, + Client client, + ) async { // Leave room if membership is leave if (roomUpdate is LeftRoomUpdate) { await forgetRoom(roomId); @@ -1287,11 +1250,13 @@ class HiveCollectionsDatabase extends DatabaseApi { 0, prev_batch: roomUpdate.timeline?.prevBatch, summary: roomUpdate.summary, + lastEvent: lastEvent, ).toJson() : Room( client: client, id: roomId, membership: membership, + lastEvent: lastEvent, ).toJson()); } else if (roomUpdate is JoinedRoomUpdate) { final currentRoom = Room.fromJson(copyMap(currentRawRoom), client); @@ -1311,17 +1276,15 @@ class HiveCollectionsDatabase extends DatabaseApi { roomUpdate.timeline?.prevBatch ?? currentRoom.prev_batch, summary: RoomSummary.fromJson(currentRoom.summary.toJson() ..addAll(roomUpdate.summary?.toJson() ?? {})), + lastEvent: lastEvent, ).toJson()); } - - // Is the timeline limited? Then all previous messages should be - // removed from the database! - if (roomUpdate is JoinedRoomUpdate && - roomUpdate.timeline?.limited == true) { - await _timelineFragmentsBox.delete(TupleKey(roomId, '').toString()); - } } + @override + Future deleteTimelineForRoom(String roomId) => + _timelineFragmentsBox.delete(TupleKey(roomId, '').toString()); + @override Future storeSSSSCache( String type, String keyId, String ciphertext, String content) async { diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index 8370de50..a13b4b55 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -1062,12 +1062,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin { ? '' : eventUpdate.content['state_key']; // Store a common state event - if ({ - EventUpdateType.timeline, - EventUpdateType.state, - EventUpdateType.inviteState - }.contains(eventUpdate.type) && - stateKey != null) { + if (stateKey != null) { if (eventUpdate.content['type'] == EventTypes.RoomMember) { await _roomMembersBox.put( MultiKey( @@ -1082,45 +1077,8 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin { ).toString(); final Map stateMap = await _roomStateBox.get(key) ?? {}; - // store state events and new messages, that either are not an edit or an edit of the lastest message - // An edit is an event, that has an edit relation to the latest event. In some cases for the second edit, we need to compare if both have an edit relation to the same event instead. - if (eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') == - null) { - stateMap[stateKey] = eventUpdate.content; - await _roomStateBox.put(key, stateMap); - } else { - final editedEventRelationshipEventId = eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') - ?.tryGet('event_id'); - - final tmpRoom = client.getRoomById(eventUpdate.roomID) ?? - Room(id: eventUpdate.roomID, client: client); - - if (eventUpdate.content['type'] != - EventTypes - .Message || // send anything other than a message - eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') - ?.tryGet('rel_type') != - RelationshipTypes - .edit || // replies are always latest anyway - editedEventRelationshipEventId == - tmpRoom.lastEvent - ?.eventId || // edit of latest (original event) event - (tmpRoom.lastEvent?.relationshipType == - RelationshipTypes.edit && - editedEventRelationshipEventId == - tmpRoom.lastEvent - ?.relationshipEventId) // edit of latest (edited event) event - ) { - stateMap[stateKey] = eventUpdate.content; - await _roomStateBox.put(key, stateMap); - } - } + stateMap[stateKey] = eventUpdate.content; + await _roomStateBox.put(key, stateMap); } } @@ -1189,8 +1147,8 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin { } @override - Future storeRoomUpdate( - String roomId, SyncRoomUpdate roomUpdate, Client client) async { + Future storeRoomUpdate(String roomId, SyncRoomUpdate roomUpdate, + Event? lastEvent, Client client) async { // Leave room if membership is leave if (roomUpdate is LeftRoomUpdate) { await forgetRoom(roomId); @@ -1219,11 +1177,13 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin { 0, prev_batch: roomUpdate.timeline?.prevBatch, summary: roomUpdate.summary, + lastEvent: lastEvent, ).toJson() : Room( client: client, id: roomId, membership: membership, + lastEvent: lastEvent, ).toJson()); } else if (roomUpdate is JoinedRoomUpdate) { final currentRawRoom = await _roomsBox.get(roomId.toHiveKey); @@ -1246,15 +1206,12 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin { ..addAll(roomUpdate.summary?.toJson() ?? {})), ).toJson()); } - - // Is the timeline limited? Then all previous messages should be - // removed from the database! - if (roomUpdate is JoinedRoomUpdate && - roomUpdate.timeline?.limited == true) { - await _timelineFragmentsBox.delete(MultiKey(roomId, '').toString()); - } } + @override + Future deleteTimelineForRoom(String roomId) => + _timelineFragmentsBox.delete(TupleKey(roomId, '').toString()); + @override Future storeSSSSCache( String type, String keyId, String ciphertext, String content) async { diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart index ee0bc961..e0db746f 100644 --- a/lib/src/database/matrix_sdk_database.dart +++ b/lib/src/database/matrix_sdk_database.dart @@ -1089,12 +1089,7 @@ class MatrixSdkDatabase extends DatabaseApi { ? '' : eventUpdate.content['state_key']; // Store a common state event - if ({ - EventUpdateType.timeline, - EventUpdateType.state, - EventUpdateType.inviteState - }.contains(eventUpdate.type) && - stateKey != null) { + if (stateKey != null) { if (eventUpdate.content['type'] == EventTypes.RoomMember) { await _roomMembersBox.put( TupleKey( @@ -1112,45 +1107,9 @@ class MatrixSdkDatabase extends DatabaseApi { type, ).toString(); final stateMap = copyMap(await roomStateBox.get(key) ?? {}); - // store state events and new messages, that either are not an edit or an edit of the lastest message - // An edit is an event, that has an edit relation to the latest event. In some cases for the second edit, we need to compare if both have an edit relation to the same event instead. - if (eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') == - null) { - stateMap[stateKey] = eventUpdate.content; - await roomStateBox.put(key, stateMap); - } else { - final editedEventRelationshipEventId = eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') - ?.tryGet('event_id'); - final tmpRoom = client.getRoomById(eventUpdate.roomID) ?? - Room(id: eventUpdate.roomID, client: client); - - if (eventUpdate.content['type'] != - EventTypes - .Message || // send anything other than a message - eventUpdate.content - .tryGetMap('content') - ?.tryGetMap('m.relates_to') - ?.tryGet('rel_type') != - RelationshipTypes - .edit || // replies are always latest anyway - editedEventRelationshipEventId == - tmpRoom.lastEvent - ?.eventId || // edit of latest (original event) event - (tmpRoom.lastEvent?.relationshipType == - RelationshipTypes.edit && - editedEventRelationshipEventId == - tmpRoom.lastEvent - ?.relationshipEventId) // edit of latest (edited event) event - ) { - stateMap[stateKey] = eventUpdate.content; - await roomStateBox.put(key, stateMap); - } - } + stateMap[stateKey] = eventUpdate.content; + await roomStateBox.put(key, stateMap); } } @@ -1226,8 +1185,8 @@ class MatrixSdkDatabase extends DatabaseApi { } @override - Future storeRoomUpdate( - String roomId, SyncRoomUpdate roomUpdate, Client client) async { + Future storeRoomUpdate(String roomId, SyncRoomUpdate roomUpdate, + Event? lastEvent, Client client) async { // Leave room if membership is leave if (roomUpdate is LeftRoomUpdate) { await forgetRoom(roomId); @@ -1257,11 +1216,13 @@ class MatrixSdkDatabase extends DatabaseApi { 0, prev_batch: roomUpdate.timeline?.prevBatch, summary: roomUpdate.summary, + lastEvent: lastEvent, ).toJson() : Room( client: client, id: roomId, membership: membership, + lastEvent: lastEvent, ).toJson()); } else if (roomUpdate is JoinedRoomUpdate) { final currentRoom = Room.fromJson(copyMap(currentRawRoom), client); @@ -1281,17 +1242,15 @@ class MatrixSdkDatabase extends DatabaseApi { roomUpdate.timeline?.prevBatch ?? currentRoom.prev_batch, summary: RoomSummary.fromJson(currentRoom.summary.toJson() ..addAll(roomUpdate.summary?.toJson() ?? {})), + lastEvent: lastEvent, ).toJson()); } - - // Is the timeline limited? Then all previous messages should be - // removed from the database! - if (roomUpdate is JoinedRoomUpdate && - roomUpdate.timeline?.limited == true) { - await _timelineFragmentsBox.delete(TupleKey(roomId, '').toString()); - } } + @override + Future deleteTimelineForRoom(String roomId) => + _timelineFragmentsBox.delete(TupleKey(roomId, '').toString()); + @override Future storeSSSSCache( String type, String keyId, String ciphertext, String content) async { diff --git a/lib/src/room.dart b/lib/src/room.dart index a0e4e7a0..d5caf99b 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -111,21 +111,27 @@ class Room { 'notification_count': notificationCount, 'prev_batch': prev_batch, 'summary': summary.toJson(), + 'last_event': lastEvent?.toJson(), }; - factory Room.fromJson(Map json, Client client) => Room( - client: client, - id: json['id'], - membership: Membership.values.singleWhere( - (m) => m.toString() == 'Membership.${json['membership']}', - orElse: () => Membership.join, - ), - notificationCount: json['notification_count'], - highlightCount: json['highlight_count'], - prev_batch: json['prev_batch'], - summary: - RoomSummary.fromJson(Map.from(json['summary'])), - ); + factory Room.fromJson(Map json, Client client) { + final room = Room( + client: client, + id: json['id'], + membership: Membership.values.singleWhere( + (m) => m.toString() == 'Membership.${json['membership']}', + orElse: () => Membership.join, + ), + notificationCount: json['notification_count'], + highlightCount: json['highlight_count'], + prev_batch: json['prev_batch'], + summary: RoomSummary.fromJson(Map.from(json['summary'])), + ); + if (json['last_event'] != null) { + room._lastEvent = Event.fromJson(json['last_event'], room); + } + return room; + } /// Flag if the room is partial, meaning not all state events have been loaded yet bool partial = true; @@ -157,50 +163,25 @@ class Room { /// Adds the [state] to this room and overwrites a state with the same /// typeKey/stateKey key pair if there is one. void setState(Event state) { - // Decrypt if necessary - if (state.type == EventTypes.Encrypted && client.encryptionEnabled) { - try { - state = client.encryption?.decryptRoomEventSync(id, state) ?? state; - } catch (e, s) { - Logs().e('[LibOlm] Could not decrypt room state', e, s); - } - } - - // We ignore room verification events for lastEvents - if (state.type == EventTypes.Message && - state.messageType.startsWith('m.room.verification.')) { - return; - } - - final isMessageEvent = { - EventTypes.Message, - EventTypes.Encrypted, - EventTypes.Sticker - }.contains(state.type); - - // We ignore events editing events older than the current-latest here so - // i.e. newly sent edits for older events don't show up in room preview - final lastEvent = this.lastEvent; - if (isMessageEvent && - state.relationshipEventId != null && - state.relationshipType == RelationshipTypes.edit && - lastEvent != null && - !state.matchesEventOrTransactionId(lastEvent.eventId) && - lastEvent.eventId != state.relationshipEventId && - !(lastEvent.relationshipType == RelationshipTypes.edit && - lastEvent.relationshipEventId == state.relationshipEventId)) { - return; - } - // Ignore other non-state events - final stateKey = state.stateKey ?? - (client.roomPreviewLastEvents.contains(state.type) ? '' : null); + final stateKey = state.stateKey; final roomId = state.roomId; - if (stateKey == null || roomId == null) { + if (roomId == null || roomId != id) { + Logs().w('Tried to set state event for wrong room!'); + return; + } + if (stateKey == null && !client.importantStateEvents.contains(state.type)) { + Logs().w( + 'Tried to set a non state event with type "${state.type}" as state event for a room', + ); return; } - (states[state.type] ??= {})[stateKey] = state; + // We still store events without a state key to load legacy lastEvent + // candidates from the database. This can be changed once we either + // implemented a database migration for legacy lastEvent candidates or + // we just waited some time (written at March 13th 2024). + (states[state.type] ??= {})[stateKey ?? ''] = state; client.onRoomState.add(state); } @@ -384,7 +365,17 @@ class Room { /// Must be one of [all, mention] String? notificationSettings; + Event? _lastEvent; + + set lastEvent(Event? event) { + _lastEvent = event; + } + Event? get lastEvent { + if (_lastEvent != null) return _lastEvent; + + // !Everything below is the deprecated way of fetching the last event! + // 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, @@ -447,7 +438,9 @@ class Room { this.notificationSettings, Map? roomAccountData, RoomSummary? summary, + Event? lastEvent, }) : roomAccountData = roomAccountData ?? {}, + _lastEvent = lastEvent, summary = summary ?? RoomSummary.fromJson({ 'm.joined_member_count': 0, diff --git a/test/client_test.dart b/test/client_test.dart index 8f41d652..bcbc5e0f 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -537,7 +537,7 @@ void main() { } } })); - expect(room.getState('m.room.message')!.content['body'], 'meow'); + expect(room.lastEvent!.content['body'], 'meow'); // ignore edits await matrix.handleSync(SyncUpdate.fromJson({ @@ -571,7 +571,7 @@ void main() { } } })); - expect(room.getState('m.room.message')!.content['body'], 'meow'); + expect(room.lastEvent!.content['body'], 'meow'); // accept edits to the last event await matrix.handleSync(SyncUpdate.fromJson({ @@ -605,7 +605,7 @@ void main() { } } })); - expect(room.getState('m.room.message')!.content['body'], '* floooof'); + expect(room.lastEvent!.content['body'], '* floooof'); // accepts a consecutive edit await matrix.handleSync(SyncUpdate.fromJson({ @@ -639,7 +639,7 @@ void main() { } } })); - expect(room.getState('m.room.message')!.content['body'], '* foxies'); + expect(room.lastEvent!.content['body'], '* foxies'); }); test('getProfileFromUserId', () async { diff --git a/test/database_api_test.dart b/test/database_api_test.dart index c2a48566..d4d13536 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -101,7 +101,7 @@ void main() { 'membership': Membership.join, }); final client = Client('testclient'); - await database.storeRoomUpdate('!testroom', roomUpdate, client); + await database.storeRoomUpdate('!testroom', roomUpdate, null, client); final rooms = await database.getRoomList(client); expect(rooms.single.id, '!testroom'); }); diff --git a/test/room_test.dart b/test/room_test.dart index fe0e4d85..bbbcf506 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; @@ -25,6 +26,26 @@ import 'package:matrix/matrix.dart'; import 'fake_client.dart'; import 'fake_matrix_api.dart'; +Future updateLastEvent(Event event) { + if (event.room.client.getRoomById(event.room.id) == null) { + event.room.client.rooms.add(event.room); + } + return event.room.client.handleSync( + SyncUpdate( + rooms: RoomsUpdate( + join: { + event.room.id: JoinedRoomUpdate( + timeline: TimelineUpdate( + events: [event], + ), + ), + }, + ), + nextBatch: '', + ), + ); +} + void main() { late Client matrix; late Room room; @@ -193,18 +214,19 @@ void main() { expect(room.pinnedEventIds, []); room.setState( Event( - senderId: '@test:example.com', - type: 'm.room.pinned_events', - room: room, - eventId: '123', - content: { - 'pinned': ['1234'] - }, - originServerTs: DateTime.now(), - stateKey: ''), + senderId: '@test:example.com', + type: 'm.room.pinned_events', + room: room, + eventId: '123', + content: { + 'pinned': ['1234'] + }, + originServerTs: DateTime.now(), + stateKey: '', + ), ); expect(room.pinnedEventIds.first, '1234'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.message', @@ -212,7 +234,6 @@ void main() { eventId: '12345', originServerTs: DateTime.now(), content: {'msgtype': 'm.text', 'body': 'abc'}, - stateKey: '', ), ); expect(room.lastEvent?.eventId, '12345'); @@ -220,8 +241,19 @@ void main() { expect(room.timeCreated, room.lastEvent?.originServerTs); }); - test('lastEvent is set properly', () { - room.setState( + test('lastEvent is set properly', () async { + await updateLastEvent( + Event( + senderId: '@test:example.com', + type: 'm.room.message', + room: room, + eventId: '0', + originServerTs: DateTime.now(), + content: {'msgtype': 'm.text', 'body': 'meow'}, + ), + ); + expect(room.lastEvent?.body, 'meow'); + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -229,13 +261,12 @@ void main() { eventId: '1', originServerTs: DateTime.now(), content: {'msgtype': 'm.text', 'body': 'cd'}, - stateKey: '', ), ); - expect(room.hasNewMessages, isTrue); - expect(room.isUnreadOrInvited, isTrue); + expect(room.hasNewMessages, true); + expect(room.isUnreadOrInvited, false); expect(room.lastEvent?.body, 'cd'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -243,11 +274,10 @@ void main() { eventId: '2', originServerTs: DateTime.now(), content: {'msgtype': 'm.text', 'body': 'cdc'}, - stateKey: '', ), ); expect(room.lastEvent?.body, 'cdc'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -260,11 +290,10 @@ void main() { 'msgtype': 'm.text', 'body': '* test ok', }, - stateKey: '', ), ); expect(room.lastEvent?.body, 'cdc'); // because we edited the "cd" message - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -277,7 +306,10 @@ void main() { 'm.new_content': {'msgtype': 'm.text', 'body': 'edited cdc'}, 'm.relates_to': {'rel_type': 'm.replace', 'event_id': '2'}, }, - stateKey: '', + unsigned: { + messageSendingStatusKey: EventStatus.sending.intValue, + 'transaction_id': 'messageID', + }, status: EventStatus.sending, ), ); @@ -286,13 +318,16 @@ void main() { expect(room.lastEvent?.eventId, '4'); // Status update on edits working? - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', room: room, eventId: '5', - unsigned: {'transaction_id': '4'}, + unsigned: { + 'transaction_id': '4', + messageSendingStatusKey: EventStatus.sent.intValue, + }, originServerTs: DateTime.now(), content: { 'msgtype': 'm.text', @@ -308,7 +343,7 @@ void main() { expect(room.lastEvent?.body, 'edited cdc'); expect(room.lastEvent?.status, EventStatus.sent); // Are reactions coming through? - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: EventTypes.Reaction, @@ -322,15 +357,15 @@ void main() { 'key': ':-)', } }, - stateKey: '', ), ); expect(room.lastEvent?.eventId, '5'); expect(room.lastEvent?.body, 'edited cdc'); expect(room.lastEvent?.status, EventStatus.sent); }); + test('lastEvent when reply parent edited', () async { - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -338,12 +373,11 @@ void main() { eventId: '5', originServerTs: DateTime.now(), content: {'msgtype': 'm.text', 'body': 'A'}, - stateKey: '', ), ); expect(room.lastEvent?.body, 'A'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -355,11 +389,10 @@ void main() { 'body': 'B', 'm.relates_to': {'rel_type': 'm.in_reply_to', 'event_id': '5'} }, - stateKey: '', ), ); expect(room.lastEvent?.body, 'B'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -372,14 +405,13 @@ void main() { 'm.new_content': {'msgtype': 'm.text', 'body': 'edited A'}, 'm.relates_to': {'rel_type': 'm.replace', 'event_id': '5'}, }, - stateKey: '', ), ); expect(room.lastEvent?.body, 'B'); }); test('lastEvent with deleted message', () async { - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -392,7 +424,7 @@ void main() { ); expect(room.lastEvent?.body, 'AA'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -409,7 +441,7 @@ void main() { ); expect(room.lastEvent?.body, 'B'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -428,7 +460,7 @@ void main() { ), ); expect(room.lastEvent?.eventId, '10'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -440,7 +472,7 @@ void main() { ), ); expect(room.lastEvent?.body, 'BB'); - room.setState( + await updateLastEvent( Event( senderId: '@test:example.com', type: 'm.room.encrypted', @@ -815,7 +847,7 @@ void main() { test('getTimeline', () async { final timeline = await room.getTimeline(); - expect(timeline.events.length, 0); + expect(timeline.events.length, 14); }); test('getUserByMXID', () async { @@ -1260,7 +1292,7 @@ void main() { }, room, )); - expect(room.getState('m.room.message') != null, true); + expect(room.getState('m.room.message') == null, false); }); test('Widgets', () { diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index cf1fc8ba..f510dc95 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -132,7 +132,14 @@ void main() => group('Integration tests', () { Logs().i('++++ (Alice) Enable encryption ++++'); expect(room.encrypted, false); await room.enableEncryption(); - await Future.delayed(Duration(seconds: 5)); + var waitSeconds = 0; + while (!room.encrypted) { + await Future.delayed(Duration(seconds: 1)); + waitSeconds++; + if (waitSeconds >= 60) { + throw Exception('Unable to enable encryption'); + } + } expect(room.encrypted, isTrue); expect( room.client.encryption!.keyManager