Skip to content

Commit

Permalink
Implement MSC3946 for getRoomUpgradeHistory
Browse files Browse the repository at this point in the history
  • Loading branch information
andybalaam committed Jan 24, 2023
1 parent e664807 commit d2937df
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 9 deletions.
70 changes: 70 additions & 0 deletions spec/unit/matrix-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2458,6 +2458,52 @@ describe("MatrixClient", function () {
return [room1, room2, room3, room4];
}

/**
* Creates 2 alternate chains of room history: one using create
* events, and one using MSC2946 predecessor+tombstone events.
*
* Using create, history looks like:
* room1->room2->room3->room4 (but note we do not create tombstones)
*
* Using predecessor+tombstone, history looks like:
* dynRoom1->dynRoom2->room3->dynRoom4->dynRoom4
*
* @returns [room1, room2, room3, room4, dynRoom1, dynRoom2,
* dynRoom4, dynRoom5].
*/
function createDynamicRoomHistory(): [Room, Room, Room, Room, Room, Room, Room, Room] {
// Don't create tombstones for the old versions - we generally
// expect only one tombstone in a room, and we are confused by
// anything else.
const creates = true;
const tombstones = false;
const [room1, room2, room3, room4] = createRoomHistory(creates, tombstones);
const dynRoom1 = new Room("dynRoom1", client, "@rick:grimes.example.com");
const dynRoom2 = new Room("dynRoom2", client, "@rick:grimes.example.com");
const dynRoom4 = new Room("dynRoom4", client, "@rick:grimes.example.com");
const dynRoom5 = new Room("dynRoom5", client, "@rick:grimes.example.com");

dynRoom1.addLiveEvents([tombstoneEvent(dynRoom2.roomId, dynRoom1.roomId)], {});
dynRoom2.addLiveEvents([predecessorEvent(dynRoom2.roomId, dynRoom1.roomId)]);

dynRoom2.addLiveEvents([tombstoneEvent(room3.roomId, dynRoom2.roomId)], {});
room3.addLiveEvents([predecessorEvent(room3.roomId, dynRoom2.roomId)]);

room3.addLiveEvents([tombstoneEvent(dynRoom4.roomId, room3.roomId)], {});
dynRoom4.addLiveEvents([predecessorEvent(dynRoom4.roomId, room3.roomId)]);

dynRoom4.addLiveEvents([tombstoneEvent(dynRoom5.roomId, dynRoom4.roomId)], {});
dynRoom5.addLiveEvents([predecessorEvent(dynRoom5.roomId, dynRoom4.roomId)]);

mocked(store.getRoom)
.mockClear()
.mockImplementation((roomId: string) => {
return { room1, room2, room3, room4, dynRoom1, dynRoom2, dynRoom4, dynRoom5 }[roomId] || null;
});

return [room1, room2, room3, room4, dynRoom1, dynRoom2, dynRoom4, dynRoom5];
}

it("Returns an empty list if room does not exist", () => {
const history = client.getRoomUpgradeHistory("roomthatdoesnotexist");
expect(history).toHaveLength(0);
Expand Down Expand Up @@ -2600,6 +2646,30 @@ describe("MatrixClient", function () {
room4.roomId,
]);
});

it("Returns the predecessors and subsequent rooms using MSC3945 dynamic room predecessors", () => {
const [, , room3, , dynRoom1, dynRoom2, dynRoom4, dynRoom5] = createDynamicRoomHistory();
const useMsc3946 = true;
const verifyLinks = false;
const history = client.getRoomUpgradeHistory(room3.roomId, verifyLinks, useMsc3946);
expect(history.map((room) => room.roomId)).toEqual([
dynRoom1.roomId,
dynRoom2.roomId,
room3.roomId,
dynRoom4.roomId,
dynRoom5.roomId,
]);
});

it("When not asking for MSC3946, verified history without tombstones is empty", () => {
// There no tombstones to match the create events
const [, , room3] = createDynamicRoomHistory();
const useMsc3946 = false;
const verifyLinks = true;
const history = client.getRoomUpgradeHistory(room3.roomId, verifyLinks, useMsc3946);
// So we get no history back
expect(history.map((room) => room.roomId)).toEqual([room3.roomId]);
});
});
});
});
25 changes: 16 additions & 9 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4990,24 +4990,31 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* which can be proven to be linked. For example, rooms which have a create
* event pointing to an old room which the client is not aware of or doesn't
* have a matching tombstone would not be returned.
* @param msc3946ProcessDynamicPredecessor - if true, look for
* m.room.predecessor state events as well as create events, and prefer
* predecessor events where they exist (MSC3946).
* @returns An array of rooms representing the upgrade
* history.
*/
public getRoomUpgradeHistory(roomId: string, verifyLinks = false): Room[] {
public getRoomUpgradeHistory(
roomId: string,
verifyLinks = false,
msc3946ProcessDynamicPredecessor = false,
): Room[] {
const currentRoom = this.getRoom(roomId);
if (!currentRoom) return [];

const before = this.findPredecessorRooms(currentRoom, verifyLinks);
const after = this.findSuccessorRooms(currentRoom, verifyLinks);
const before = this.findPredecessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor);
const after = this.findSuccessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor);

return [...before, currentRoom, ...after];
}

private findPredecessorRooms(room: Room, verifyLinks: boolean): Room[] {
private findPredecessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {
const ret: Room[] = [];

// Work backwards from newer to older rooms
let predecessorRoomId = room.findPredecessorRoomId();
let predecessorRoomId = room.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
while (predecessorRoomId !== null) {
const predecessorRoom = this.getRoom(predecessorRoomId);
if (predecessorRoom === null) {
Expand All @@ -5024,23 +5031,23 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
ret.splice(0, 0, predecessorRoom);

room = predecessorRoom;
predecessorRoomId = room.findPredecessorRoomId();
predecessorRoomId = room.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
}
return ret;
}

private findSuccessorRooms(room: Room, verifyLinks: boolean): Room[] {
private findSuccessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {
const ret: Room[] = [];

// Work forwards, looking at tombstone events
let tombstoneEvent = room.currentState.getStateEvents(EventType.RoomTombstone, "");
while (tombstoneEvent) {
const successorRoom = this.getRoom(tombstoneEvent.getContent()["replacement_room"]);
if (!successorRoom) break; // end of the chain
if (successorRoom.roomId === room.roomId) break; // Tombstone is referencing it's own room
if (successorRoom.roomId === room.roomId) break; // Tombstone is referencing its own room

if (verifyLinks) {
const predecessorRoomId = successorRoom.findPredecessorRoomId();
const predecessorRoomId = successorRoom.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
if (!predecessorRoomId || predecessorRoomId !== room.roomId) {
break;
}
Expand Down

0 comments on commit d2937df

Please sign in to comment.