diff --git a/lib/src/client.dart b/lib/src/client.dart index 41875642..960bd832 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -2037,14 +2037,18 @@ class Client extends MatrixApi { if (join != null) { await _handleRooms(join, direction: direction); } - final invite = sync.rooms?.invite; - if (invite != null) { - await _handleRooms(invite, direction: direction); - } + // We need to handle leave before invite. If you decline an invite and + // then get another invite to the same room, Synapse will include the + // room both in invite and leave. If you get an invite and then leave, it + // will only be included in leave. final leave = sync.rooms?.leave; if (leave != null) { await _handleRooms(leave, direction: direction); } + final invite = sync.rooms?.invite; + if (invite != null) { + await _handleRooms(invite, direction: direction); + } } for (final newPresence in sync.presence ?? []) { final cachedPresence = CachedPresence.fromMatrixEvent(newPresence); diff --git a/test/client_test.dart b/test/client_test.dart index 6324f1cc..3ec967fb 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -709,6 +709,66 @@ void main() { await client.database?.clearCache(); await client.dispose(closeDatabase: true); }); + test('leaveThenInvite should be invited', () async { + // Synapse includes a room in both invite and leave if you leave and get + // reinvited while you are offline. The other direction only contains the + // room in leave. Verify that we actually store the invite in the first + // case. See also + // https://github.com/famedly/product-management/issues/2283 + final client = await getClient(); + await client.abortSync(); + client.rooms.clear(); + await client.database?.clearCache(); + + final roomId = '!inviteLeaveRoom:example.com'; + await client.handleSync( + SyncUpdate( + nextBatch: 'ABCDEF', + rooms: RoomsUpdate( + invite: { + roomId: InvitedRoomUpdate( + inviteState: [ + StrippedStateEvent( + type: EventTypes.RoomMember, + senderId: '@bob:example.com', + stateKey: client.userID, + content: { + 'membership': 'invite', + }, + ), + ], + ), + }, + leave: { + roomId: LeftRoomUpdate( + state: [ + MatrixEvent( + type: EventTypes.RoomMember, + senderId: client.userID!, + stateKey: client.userID, + originServerTs: DateTime.now(), + eventId: + '\$abcdefwsjaskdfabsjfhabfsjgbahsjfkgbasjffsajfgsfd', + content: { + 'membership': 'leave', + }, + ), + ], + ), + }, + ), + ), + ); + + final room = client.getRoomById(roomId); + + expect(room?.membership, Membership.invite); + + await client.abortSync(); + client.rooms.clear(); + await client.database?.clearCache(); + await client.dispose(closeDatabase: true); + }); test('ownProfile', () async { final client = await getClient(); await client.abortSync();