1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Support MSC4222 state_after (#4487)

* WIP support for state_after

* Fix sliding sync sdk / embedded tests

* Allow both state & state_after to be undefined

Since it must have allowed state to be undefined previously: the test
had it as such.

* Fix limited sync handling

* Need to use state_after being undefined

if state can be undefined anyway

* Make sliding sync sdk tests pass

* Remove deprecated interfaces & backwards-compat code

* Remove useless assignment

* Use updates unstable prefix

* Clarify docs

* Remove additional semi-backwards compatible overload

* Update unstable prefixes

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test for MSC4222 behaviour

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tidy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add comments to explain why things work as they are.

* Fix sync accumulator for state_after sync handling

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Revert "Fix room state being updated with old (now overwritten) state and emitting for those updates. (#4242)"

This reverts commit 957329b218.

* Fix Sync Accumulator toJSON putting start timeline state in state_after field

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test case

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Hugh Nimmo-Smith <hughns@matrix.org>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Timo <toger5@hotmail.de>
This commit is contained in:
David Baker
2024-11-27 11:40:41 +00:00
committed by GitHub
parent 66f099b2e7
commit 5bcd26e506
32 changed files with 1343 additions and 735 deletions

View File

@ -1327,7 +1327,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
const syncResponse = getSyncResponse(["@bob:xyz"]);
// Every 2 messages in the room, the session should be rotated
syncResponse.rooms[Category.Join][ROOM_ID].state.events[0].content = {
syncResponse.rooms[Category.Join][ROOM_ID].state!.events[0].content = {
algorithm: "m.megolm.v1.aes-sha2",
rotation_period_msgs: 2,
};
@ -1383,7 +1383,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
const oneHourInMs = 60 * 60 * 1000;
// Every 1h the session should be rotated
syncResponse.rooms[Category.Join][ROOM_ID].state.events[0].content = {
syncResponse.rooms[Category.Join][ROOM_ID].state!.events[0].content = {
algorithm: "m.megolm.v1.aes-sha2",
rotation_period_ms: oneHourInMs,
};

View File

@ -1144,7 +1144,7 @@ describe("MatrixClient event timelines", function () {
const prom = emitPromise(room, ThreadEvent.Update);
// Assume we're seeing the reply while loading backlog
await room.addLiveEvents([THREAD_REPLY2]);
await room.addLiveEvents([THREAD_REPLY2], { addToState: false });
httpBackend
.when(
"GET",
@ -1155,7 +1155,7 @@ describe("MatrixClient event timelines", function () {
});
await flushHttp(prom);
// but while loading the metadata, a new reply has arrived
await room.addLiveEvents([THREAD_REPLY3]);
await room.addLiveEvents([THREAD_REPLY3], { addToState: false });
const thread = room.getThread(THREAD_ROOT_UPDATED.event_id!)!;
// then the events should still be all in the right order
expect(thread.events.map((it) => it.getId())).toEqual([
@ -1247,7 +1247,7 @@ describe("MatrixClient event timelines", function () {
const prom = emitPromise(room, ThreadEvent.Update);
// Assume we're seeing the reply while loading backlog
await room.addLiveEvents([THREAD_REPLY2]);
await room.addLiveEvents([THREAD_REPLY2], { addToState: false });
httpBackend
.when(
"GET",
@ -1263,7 +1263,7 @@ describe("MatrixClient event timelines", function () {
});
await flushHttp(prom);
// but while loading the metadata, a new reply has arrived
await room.addLiveEvents([THREAD_REPLY3]);
await room.addLiveEvents([THREAD_REPLY3], { addToState: false });
const thread = room.getThread(THREAD_ROOT_UPDATED.event_id!)!;
// then the events should still be all in the right order
expect(thread.events.map((it) => it.getId())).toEqual([
@ -1560,7 +1560,7 @@ describe("MatrixClient event timelines", function () {
thread.initialEventsFetched = true;
const prom = emitPromise(room, ThreadEvent.NewReply);
respondToEvent(THREAD_ROOT_UPDATED);
await room.addLiveEvents([THREAD_REPLY2]);
await room.addLiveEvents([THREAD_REPLY2], { addToState: false });
await httpBackend.flushAllExpected();
await prom;
expect(thread.length).toBe(2);
@ -1685,7 +1685,7 @@ describe("MatrixClient event timelines", function () {
thread.initialEventsFetched = true;
const prom = emitPromise(room, ThreadEvent.Update);
respondToEvent(THREAD_ROOT_UPDATED);
await room.addLiveEvents([THREAD_REPLY_REACTION]);
await room.addLiveEvents([THREAD_REPLY_REACTION], { addToState: false });
await httpBackend.flushAllExpected();
await prom;
expect(thread.length).toBe(1); // reactions don't count towards the length of a thread

View File

@ -168,14 +168,17 @@ describe("MatrixClient", function () {
type: "test",
content: {},
});
room.addLiveEvents([
utils.mkMembership({
user: userId,
room: roomId,
mship: KnownMembership.Join,
event: true,
}),
]);
room.addLiveEvents(
[
utils.mkMembership({
user: userId,
room: roomId,
mship: KnownMembership.Join,
event: true,
}),
],
{ addToState: true },
);
httpBackend.verifyNoOutstandingRequests();
store.storeRoom(room);
@ -188,14 +191,17 @@ describe("MatrixClient", function () {
const roomId = "!roomId:server";
const roomAlias = "#my-fancy-room:server";
const room = new Room(roomId, client, userId);
room.addLiveEvents([
utils.mkMembership({
user: userId,
room: roomId,
mship: KnownMembership.Join,
event: true,
}),
]);
room.addLiveEvents(
[
utils.mkMembership({
user: userId,
room: roomId,
mship: KnownMembership.Join,
event: true,
}),
],
{ addToState: true },
);
store.storeRoom(room);
// The method makes a request to resolve the alias
@ -275,14 +281,17 @@ describe("MatrixClient", function () {
content: {},
});
room.addLiveEvents([
utils.mkMembership({
user: userId,
room: roomId,
mship: KnownMembership.Knock,
event: true,
}),
]);
room.addLiveEvents(
[
utils.mkMembership({
user: userId,
room: roomId,
mship: KnownMembership.Knock,
event: true,
}),
],
{ addToState: true },
);
httpBackend.verifyNoOutstandingRequests();
store.storeRoom(room);

View File

@ -556,7 +556,7 @@ describe("MatrixClient syncing", () => {
});
it("should resolve incoming invites from /sync", () => {
syncData.rooms.join[roomOne].state.events.push(
syncData.rooms.join[roomOne].state!.events.push(
utils.mkMembership({
room: roomOne,
mship: KnownMembership.Invite,
@ -589,7 +589,7 @@ describe("MatrixClient syncing", () => {
name: "The Ghost",
}) as IMinimalEvent,
];
syncData.rooms.join[roomOne].state.events.push(
syncData.rooms.join[roomOne].state!.events.push(
utils.mkMembership({
room: roomOne,
mship: KnownMembership.Invite,
@ -617,7 +617,7 @@ describe("MatrixClient syncing", () => {
name: "The Ghost",
}) as IMinimalEvent,
];
syncData.rooms.join[roomOne].state.events.push(
syncData.rooms.join[roomOne].state!.events.push(
utils.mkMembership({
room: roomOne,
mship: KnownMembership.Invite,
@ -644,7 +644,7 @@ describe("MatrixClient syncing", () => {
});
it("should no-op if resolveInvitesToProfiles is not set", () => {
syncData.rooms.join[roomOne].state.events.push(
syncData.rooms.join[roomOne].state!.events.push(
utils.mkMembership({
room: roomOne,
mship: KnownMembership.Invite,
@ -1373,6 +1373,114 @@ describe("MatrixClient syncing", () => {
expect(stateEventEmitCount).toEqual(2);
});
});
describe("msc4222", () => {
const roomOneSyncOne = {
"timeline": {
events: [
utils.mkMessage({
room: roomOne,
user: otherUserId,
msg: "hello",
}),
],
},
"org.matrix.msc4222.state_after": {
events: [
utils.mkEvent({
type: "m.room.name",
room: roomOne,
user: otherUserId,
content: {
name: "Initial room name",
},
}),
utils.mkMembership({
room: roomOne,
mship: KnownMembership.Join,
user: otherUserId,
}),
utils.mkMembership({
room: roomOne,
mship: KnownMembership.Join,
user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create",
room: roomOne,
user: selfUserId,
content: {},
}),
],
},
};
const roomOneSyncTwo = {
"org.matrix.msc4222.state_after": {
events: [
utils.mkEvent({
type: "m.room.topic",
room: roomOne,
user: selfUserId,
content: { topic: "A new room topic" },
}),
],
},
"state": {
events: [
utils.mkEvent({
type: "m.room.name",
room: roomOne,
user: selfUserId,
content: { name: "A new room name" },
}),
],
},
};
it("should ignore state events in timeline when state_after is present", async () => {
httpBackend!.when("GET", "/sync").respond(200, {
rooms: {
join: { [roomOne]: roomOneSyncOne },
},
});
httpBackend!.when("GET", "/sync").respond(200, {
rooms: {
join: { [roomOne]: roomOneSyncTwo },
},
});
client!.startClient();
return Promise.all([httpBackend!.flushAllExpected(), awaitSyncEvent(2)]).then(() => {
const room = client!.getRoom(roomOne)!;
expect(room.name).toEqual("Initial room name");
expect(room.currentState.getStateEvents("m.room.topic", "")?.getContent().topic).toBe(
"A new room topic",
);
});
});
it("should respect state events in state_after for left rooms", async () => {
httpBackend!.when("GET", "/sync").respond(200, {
rooms: {
join: { [roomOne]: roomOneSyncOne },
},
});
httpBackend!.when("GET", "/sync").respond(200, {
rooms: {
leave: { [roomOne]: roomOneSyncTwo },
},
});
client!.startClient();
return Promise.all([httpBackend!.flushAllExpected(), awaitSyncEvent(2)]).then(() => {
const room = client!.getRoom(roomOne)!;
expect(room.name).toEqual("Initial room name");
expect(room.currentState.getStateEvents("m.room.topic", "")?.getContent().topic).toBe(
"A new room topic",
);
});
});
});
});
describe("timeline", () => {
@ -2274,6 +2382,57 @@ describe("MatrixClient syncing", () => {
}),
]);
});
describe("msc4222", () => {
it("should respect state events in state_after for left rooms", async () => {
httpBackend!.when("POST", "/filter").respond(200, {
filter_id: "another_id",
});
httpBackend!.when("GET", "/sync").respond(200, {
rooms: {
leave: {
[roomOne]: {
"org.matrix.msc4222.state_after": {
events: [
utils.mkEvent({
type: "m.room.topic",
room: roomOne,
user: selfUserId,
content: { topic: "A new room topic" },
}),
],
},
"state": {
events: [
utils.mkEvent({
type: "m.room.name",
room: roomOne,
user: selfUserId,
content: { name: "A new room name" },
}),
],
},
},
},
},
});
const [[room]] = await Promise.all([
client!.syncLeftRooms(),
// first flush the filter request; this will make syncLeftRooms make its /sync call
httpBackend!.flush("/filter").then(() => {
return httpBackend!.flushAllExpected();
}),
]);
expect(room.name).toEqual("Empty room");
expect(room.currentState.getStateEvents("m.room.topic", "")?.getContent().topic).toBe(
"A new room topic",
);
});
});
});
describe("peek", () => {

View File

@ -128,7 +128,7 @@ describe("MatrixClient syncing", () => {
const thread = mkThread({ room, client: client!, authorId: selfUserId, participantUserIds: [selfUserId] });
const threadReply = thread.events.at(-1)!;
await room.addLiveEvents([thread.rootEvent]);
await room.addLiveEvents([thread.rootEvent], { addToState: false });
// Initialize read receipt datastructure before testing the reaction
room.addReceiptToStructure(thread.rootEvent.getId()!, ReceiptType.Read, selfUserId, { ts: 1 }, false);

View File

@ -601,13 +601,13 @@ describe("SlidingSyncSdk", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
initial: true,
name: "Room with Invite",
required_state: [],
timeline: [
required_state: [
mkOwnStateEvent(EventType.RoomCreate, {}, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: KnownMembership.Join }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: KnownMembership.Invite }, invitee),
],
timeline: [],
});
await httpBackend!.flush("/profile", 1, 1000);
await emitPromise(client!, RoomMemberEvent.Name);
@ -921,13 +921,12 @@ describe("SlidingSyncSdk", () => {
const roomId = "!room:id";
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
name: "Room with typing",
required_state: [],
timeline: [
required_state: [
mkOwnStateEvent(EventType.RoomCreate, {}, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: KnownMembership.Join }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
],
timeline: [mkOwnEvent(EventType.RoomMessage, { body: "hello" })],
initial: true,
});
await emitPromise(client!, ClientEvent.Room);
@ -962,13 +961,12 @@ describe("SlidingSyncSdk", () => {
const roomId = "!room:id";
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
name: "Room with typing",
required_state: [],
timeline: [
required_state: [
mkOwnStateEvent(EventType.RoomCreate, {}, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: KnownMembership.Join }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
],
timeline: [mkOwnEvent(EventType.RoomMessage, { body: "hello" })],
initial: true,
});
const room = client!.getRoom(roomId)!;

View File

@ -86,7 +86,7 @@ export function getSyncResponse(roomMembers: string[], roomId = TEST_ROOM_ID): I
};
for (let i = 0; i < roomMembers.length; i++) {
roomResponse.state.events.push(
roomResponse.state!.events.push(
mkMembershipCustom({
membership: KnownMembership.Join,
sender: roomMembers[i],

View File

@ -178,6 +178,6 @@ export const populateThread = ({
}: MakeThreadProps): MakeThreadResult => {
const ret = mkThread({ room, client, authorId, participantUserIds, length, ts });
ret.thread.initialEventsFetched = true;
room.addLiveEvents(ret.events);
room.addLiveEvents(ret.events, { addToState: false });
return ret;
};

View File

@ -261,7 +261,7 @@ describe("RoomWidgetClient", () => {
expect(injectSpy).toHaveBeenCalled();
const call = injectSpy.mock.calls[0] as any;
const injectedEv = call[2][0];
const injectedEv = call[3][0];
expect(injectedEv.getType()).toBe("org.matrix.rageshake_request");
expect(injectedEv.getUnsigned().transaction_id).toBe("widgetTxId");
});
@ -287,7 +287,7 @@ describe("RoomWidgetClient", () => {
expect(injectSpy).toHaveBeenCalled();
const call = injectSpy.mock.calls[0] as any;
const injectedEv = call[2][0];
const injectedEv = call[3][0];
expect(injectedEv.getType()).toBe("org.matrix.rageshake_request");
expect(injectedEv.getUnsigned().transaction_id).toBe("widgetTxId");
});
@ -326,13 +326,13 @@ describe("RoomWidgetClient", () => {
// it has been called with the event sent by ourselves
const call = injectSpy.mock.calls[0] as any;
const injectedEv = call[2][0];
const injectedEv = call[3][0];
expect(injectedEv.getType()).toBe("org.matrix.rageshake_request");
expect(injectedEv.getUnsigned().transaction_id).toBe("widgetTxId");
// It has been called by the event we blocked because of our send right afterwards
const call2 = injectSpy.mock.calls[1] as any;
const injectedEv2 = call2[2][0];
const injectedEv2 = call2[3][0];
expect(injectedEv2.getType()).toBe("org.matrix.rageshake_request");
expect(injectedEv2.getUnsigned().transaction_id).toBe("4567");
});

View File

@ -74,7 +74,7 @@ describe("eventMapperFor", function () {
const event = mapper(eventDefinition);
expect(event).toBeInstanceOf(MatrixEvent);
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
expect(room.findEventById(eventId)).toBe(event);
const event2 = mapper(eventDefinition);
@ -109,7 +109,7 @@ describe("eventMapperFor", function () {
room.oldState.setStateEvents([event]);
room.currentState.setStateEvents([event]);
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
expect(room.findEventById(eventId)).toBe(event);
const event2 = mapper(eventDefinition);

View File

@ -104,7 +104,7 @@ describe("EventTimelineSet", () => {
it("Adds event to the live timeline in the timeline set", () => {
const liveTimeline = eventTimelineSet.getLiveTimeline();
expect(liveTimeline.getEvents().length).toStrictEqual(0);
eventTimelineSet.addLiveEvent(messageEvent);
eventTimelineSet.addLiveEvent(messageEvent, { addToState: false });
expect(liveTimeline.getEvents().length).toStrictEqual(1);
});
@ -113,6 +113,7 @@ describe("EventTimelineSet", () => {
expect(liveTimeline.getEvents().length).toStrictEqual(0);
eventTimelineSet.addLiveEvent(messageEvent, {
duplicateStrategy: DuplicateStrategy.Replace,
addToState: false,
});
expect(liveTimeline.getEvents().length).toStrictEqual(1);
@ -130,6 +131,7 @@ describe("EventTimelineSet", () => {
// replace.
eventTimelineSet.addLiveEvent(duplicateMessageEvent, {
duplicateStrategy: DuplicateStrategy.Replace,
addToState: false,
});
const eventsInLiveTimeline = liveTimeline.getEvents();
@ -144,6 +146,7 @@ describe("EventTimelineSet", () => {
expect(liveTimeline.getEvents().length).toStrictEqual(0);
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, {
toStartOfTimeline: true,
addToState: false,
});
expect(liveTimeline.getEvents().length).toStrictEqual(1);
});
@ -151,10 +154,17 @@ describe("EventTimelineSet", () => {
it("Make sure legacy overload passing options directly as parameters still works", () => {
const liveTimeline = eventTimelineSet.getLiveTimeline();
expect(() => {
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, true);
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, {
toStartOfTimeline: true,
addToState: false,
});
}).not.toThrow();
expect(() => {
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, true, false);
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, {
toStartOfTimeline: true,
fromCache: false,
addToState: false,
});
}).not.toThrow();
});
@ -167,11 +177,13 @@ describe("EventTimelineSet", () => {
expect(liveTimeline.getEvents().length).toStrictEqual(0);
eventTimelineSet.addEventToTimeline(reactionEvent, liveTimeline, {
toStartOfTimeline: true,
addToState: false,
});
expect(liveTimeline.getEvents().length).toStrictEqual(0);
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, {
toStartOfTimeline: true,
addToState: false,
});
expect(liveTimeline.getEvents()).toHaveLength(1);
const [event] = liveTimeline.getEvents();
@ -202,6 +214,7 @@ describe("EventTimelineSet", () => {
expect(() => {
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline2, {
toStartOfTimeline: true,
addToState: false,
});
}).toThrow();
});
@ -214,6 +227,7 @@ describe("EventTimelineSet", () => {
eventTimelineSet.addEventToTimeline(threadedReplyEvent, liveTimeline, {
toStartOfTimeline: true,
addToState: false,
});
expect(liveTimeline.getEvents().length).toStrictEqual(0);
});
@ -232,6 +246,7 @@ describe("EventTimelineSet", () => {
eventTimelineSetForThread.addEventToTimeline(normalMessage, liveTimeline, {
toStartOfTimeline: true,
addToState: false,
});
expect(liveTimeline.getEvents().length).toStrictEqual(0);
});
@ -248,6 +263,7 @@ describe("EventTimelineSet", () => {
expect(nonRoomEventTimeline.getEvents().length).toStrictEqual(0);
nonRoomEventTimelineSet.addEventToTimeline(messageEvent, nonRoomEventTimeline, {
toStartOfTimeline: true,
addToState: false,
});
expect(nonRoomEventTimeline.getEvents().length).toStrictEqual(1);
});
@ -257,7 +273,7 @@ describe("EventTimelineSet", () => {
describe("aggregateRelations", () => {
describe("with unencrypted events", () => {
beforeEach(() => {
eventTimelineSet.addEventsToTimeline([messageEvent, replyEvent], true, eventTimeline, "foo");
eventTimelineSet.addEventsToTimeline([messageEvent, replyEvent], true, false, eventTimeline, "foo");
});
itShouldReturnTheRelatedEvents();
@ -279,7 +295,7 @@ describe("EventTimelineSet", () => {
replyEventShouldAttemptDecryptionSpy.mockReturnValue(true);
replyEventIsDecryptionFailureSpy = jest.spyOn(messageEvent, "isDecryptionFailure");
eventTimelineSet.addEventsToTimeline([messageEvent, replyEvent], true, eventTimeline, "foo");
eventTimelineSet.addEventsToTimeline([messageEvent, replyEvent], true, false, eventTimeline, "foo");
});
it("should not return the related events", () => {

View File

@ -98,7 +98,7 @@ describe("EventTimeline", function () {
expect(function () {
timeline.initialiseState(state);
}).not.toThrow();
timeline.addEvent(event, { toStartOfTimeline: false });
timeline.addEvent(event, { toStartOfTimeline: false, addToState: false });
expect(function () {
timeline.initialiseState(state);
}).toThrow();
@ -182,9 +182,9 @@ describe("EventTimeline", function () {
];
it("should be able to add events to the end", function () {
timeline.addEvent(events[0], { toStartOfTimeline: false });
timeline.addEvent(events[0], { toStartOfTimeline: false, addToState: false });
const initialIndex = timeline.getBaseIndex();
timeline.addEvent(events[1], { toStartOfTimeline: false });
timeline.addEvent(events[1], { toStartOfTimeline: false, addToState: false });
expect(timeline.getBaseIndex()).toEqual(initialIndex);
expect(timeline.getEvents().length).toEqual(2);
expect(timeline.getEvents()[0]).toEqual(events[0]);
@ -192,9 +192,9 @@ describe("EventTimeline", function () {
});
it("should be able to add events to the start", function () {
timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.addEvent(events[0], { toStartOfTimeline: true, addToState: false });
const initialIndex = timeline.getBaseIndex();
timeline.addEvent(events[1], { toStartOfTimeline: true });
timeline.addEvent(events[1], { toStartOfTimeline: true, addToState: false });
expect(timeline.getBaseIndex()).toEqual(initialIndex + 1);
expect(timeline.getEvents().length).toEqual(2);
expect(timeline.getEvents()[0]).toEqual(events[1]);
@ -238,9 +238,9 @@ describe("EventTimeline", function () {
content: { name: "Old Room Name" },
});
timeline.addEvent(newEv, { toStartOfTimeline: false });
timeline.addEvent(newEv, { toStartOfTimeline: false, addToState: false });
expect(newEv.sender).toEqual(sentinel);
timeline.addEvent(oldEv, { toStartOfTimeline: true });
timeline.addEvent(oldEv, { toStartOfTimeline: true, addToState: false });
expect(oldEv.sender).toEqual(oldSentinel);
});
@ -280,9 +280,9 @@ describe("EventTimeline", function () {
skey: userA,
event: true,
});
timeline.addEvent(newEv, { toStartOfTimeline: false });
timeline.addEvent(newEv, { toStartOfTimeline: false, addToState: false });
expect(newEv.target).toEqual(sentinel);
timeline.addEvent(oldEv, { toStartOfTimeline: true });
timeline.addEvent(oldEv, { toStartOfTimeline: true, addToState: false });
expect(oldEv.target).toEqual(oldSentinel);
});
@ -308,8 +308,8 @@ describe("EventTimeline", function () {
}),
];
timeline.addEvent(events[0], { toStartOfTimeline: false });
timeline.addEvent(events[1], { toStartOfTimeline: false });
timeline.addEvent(events[0], { toStartOfTimeline: false, addToState: true });
timeline.addEvent(events[1], { toStartOfTimeline: false, addToState: true });
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
@ -347,8 +347,8 @@ describe("EventTimeline", function () {
}),
];
timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.addEvent(events[1], { toStartOfTimeline: true });
timeline.addEvent(events[0], { toStartOfTimeline: true, addToState: true });
timeline.addEvent(events[1], { toStartOfTimeline: true, addToState: true });
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
@ -365,11 +365,15 @@ describe("EventTimeline", function () {
);
it("Make sure legacy overload passing options directly as parameters still works", () => {
expect(() => timeline.addEvent(events[0], { toStartOfTimeline: true })).not.toThrow();
expect(() => timeline.addEvent(events[0], { toStartOfTimeline: true, addToState: false })).not.toThrow();
// @ts-ignore stateContext is not a valid param
expect(() => timeline.addEvent(events[0], { stateContext: new RoomState(roomId) })).not.toThrow();
expect(() =>
timeline.addEvent(events[0], { toStartOfTimeline: false, roomState: new RoomState(roomId) }),
timeline.addEvent(events[0], {
toStartOfTimeline: false,
addToState: false,
roomState: new RoomState(roomId),
}),
).not.toThrow();
});
});
@ -397,8 +401,8 @@ describe("EventTimeline", function () {
];
it("should remove events", function () {
timeline.addEvent(events[0], { toStartOfTimeline: false });
timeline.addEvent(events[1], { toStartOfTimeline: false });
timeline.addEvent(events[0], { toStartOfTimeline: false, addToState: false });
timeline.addEvent(events[1], { toStartOfTimeline: false, addToState: false });
expect(timeline.getEvents().length).toEqual(2);
let ev = timeline.removeEvent(events[0].getId()!);
@ -411,9 +415,9 @@ describe("EventTimeline", function () {
});
it("should update baseIndex", function () {
timeline.addEvent(events[0], { toStartOfTimeline: false });
timeline.addEvent(events[1], { toStartOfTimeline: true });
timeline.addEvent(events[2], { toStartOfTimeline: false });
timeline.addEvent(events[0], { toStartOfTimeline: false, addToState: false });
timeline.addEvent(events[1], { toStartOfTimeline: true, addToState: false });
timeline.addEvent(events[2], { toStartOfTimeline: false, addToState: false });
expect(timeline.getEvents().length).toEqual(3);
expect(timeline.getBaseIndex()).toEqual(1);
@ -430,11 +434,11 @@ describe("EventTimeline", function () {
// - removing the last event got baseIndex into such a state that
// further addEvent(ev, false) calls made the index increase.
it("should not make baseIndex assplode when removing the last event", function () {
timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.addEvent(events[0], { toStartOfTimeline: true, addToState: false });
timeline.removeEvent(events[0].getId()!);
const initialIndex = timeline.getBaseIndex();
timeline.addEvent(events[1], { toStartOfTimeline: false });
timeline.addEvent(events[2], { toStartOfTimeline: false });
timeline.addEvent(events[1], { toStartOfTimeline: false, addToState: false });
timeline.addEvent(events[2], { toStartOfTimeline: false, addToState: false });
expect(timeline.getBaseIndex()).toEqual(initialIndex);
expect(timeline.getEvents().length).toEqual(2);
});

View File

@ -2799,24 +2799,28 @@ describe("MatrixClient", function () {
roomCreateEvent(room1.roomId, replacedByCreate1.roomId),
predecessorEvent(room1.roomId, replacedByDynamicPredecessor1.roomId),
],
{},
{ addToState: true },
);
room2.addLiveEvents(
[
roomCreateEvent(room2.roomId, replacedByCreate2.roomId),
predecessorEvent(room2.roomId, replacedByDynamicPredecessor2.roomId),
],
{},
{ addToState: true },
);
replacedByCreate1.addLiveEvents([tombstoneEvent(room1.roomId, replacedByCreate1.roomId)], {});
replacedByCreate2.addLiveEvents([tombstoneEvent(room2.roomId, replacedByCreate2.roomId)], {});
replacedByCreate1.addLiveEvents([tombstoneEvent(room1.roomId, replacedByCreate1.roomId)], {
addToState: true,
});
replacedByCreate2.addLiveEvents([tombstoneEvent(room2.roomId, replacedByCreate2.roomId)], {
addToState: true,
});
replacedByDynamicPredecessor1.addLiveEvents(
[tombstoneEvent(room1.roomId, replacedByDynamicPredecessor1.roomId)],
{},
{ addToState: true },
);
replacedByDynamicPredecessor2.addLiveEvents(
[tombstoneEvent(room2.roomId, replacedByDynamicPredecessor2.roomId)],
{},
{ addToState: true },
);
return {
@ -2854,10 +2858,10 @@ describe("MatrixClient", function () {
const room2 = new Room("room2", client, "@daryl:alexandria.example.com");
client.store = new StubStore();
client.store.getRooms = () => [room1, replacedRoom1, replacedRoom2, room2];
room1.addLiveEvents([roomCreateEvent(room1.roomId, replacedRoom1.roomId)], {});
room2.addLiveEvents([roomCreateEvent(room2.roomId, replacedRoom2.roomId)], {});
replacedRoom1.addLiveEvents([tombstoneEvent(room1.roomId, replacedRoom1.roomId)], {});
replacedRoom2.addLiveEvents([tombstoneEvent(room2.roomId, replacedRoom2.roomId)], {});
room1.addLiveEvents([roomCreateEvent(room1.roomId, replacedRoom1.roomId)], { addToState: true });
room2.addLiveEvents([roomCreateEvent(room2.roomId, replacedRoom2.roomId)], { addToState: true });
replacedRoom1.addLiveEvents([tombstoneEvent(room1.roomId, replacedRoom1.roomId)], { addToState: true });
replacedRoom2.addLiveEvents([tombstoneEvent(room2.roomId, replacedRoom2.roomId)], { addToState: true });
// When we ask for the visible rooms
const rooms = client.getVisibleRooms();
@ -2937,15 +2941,15 @@ describe("MatrixClient", function () {
const room4 = new Room("room4", client, "@michonne:hawthorne.example.com");
if (creates) {
room2.addLiveEvents([roomCreateEvent(room2.roomId, room1.roomId)]);
room3.addLiveEvents([roomCreateEvent(room3.roomId, room2.roomId)]);
room4.addLiveEvents([roomCreateEvent(room4.roomId, room3.roomId)]);
room2.addLiveEvents([roomCreateEvent(room2.roomId, room1.roomId)], { addToState: true });
room3.addLiveEvents([roomCreateEvent(room3.roomId, room2.roomId)], { addToState: true });
room4.addLiveEvents([roomCreateEvent(room4.roomId, room3.roomId)], { addToState: true });
}
if (tombstones) {
room1.addLiveEvents([tombstoneEvent(room2.roomId, room1.roomId)], {});
room2.addLiveEvents([tombstoneEvent(room3.roomId, room2.roomId)], {});
room3.addLiveEvents([tombstoneEvent(room4.roomId, room3.roomId)], {});
room1.addLiveEvents([tombstoneEvent(room2.roomId, room1.roomId)], { addToState: true });
room2.addLiveEvents([tombstoneEvent(room3.roomId, room2.roomId)], { addToState: true });
room3.addLiveEvents([tombstoneEvent(room4.roomId, room3.roomId)], { addToState: true });
}
mocked(store.getRoom).mockImplementation((roomId: string) => {
@ -2980,17 +2984,17 @@ describe("MatrixClient", function () {
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)]);
dynRoom1.addLiveEvents([tombstoneEvent(dynRoom2.roomId, dynRoom1.roomId)], { addToState: true });
dynRoom2.addLiveEvents([predecessorEvent(dynRoom2.roomId, dynRoom1.roomId)], { addToState: true });
dynRoom2.addLiveEvents([tombstoneEvent(room3.roomId, dynRoom2.roomId)], {});
room3.addLiveEvents([predecessorEvent(room3.roomId, dynRoom2.roomId)]);
dynRoom2.addLiveEvents([tombstoneEvent(room3.roomId, dynRoom2.roomId)], { addToState: true });
room3.addLiveEvents([predecessorEvent(room3.roomId, dynRoom2.roomId)], { addToState: true });
room3.addLiveEvents([tombstoneEvent(dynRoom4.roomId, room3.roomId)], {});
dynRoom4.addLiveEvents([predecessorEvent(dynRoom4.roomId, room3.roomId)]);
room3.addLiveEvents([tombstoneEvent(dynRoom4.roomId, room3.roomId)], { addToState: true });
dynRoom4.addLiveEvents([predecessorEvent(dynRoom4.roomId, room3.roomId)], { addToState: true });
dynRoom4.addLiveEvents([tombstoneEvent(dynRoom5.roomId, dynRoom4.roomId)], {});
dynRoom5.addLiveEvents([predecessorEvent(dynRoom5.roomId, dynRoom4.roomId)]);
dynRoom4.addLiveEvents([tombstoneEvent(dynRoom5.roomId, dynRoom4.roomId)], { addToState: true });
dynRoom5.addLiveEvents([predecessorEvent(dynRoom5.roomId, dynRoom4.roomId)], { addToState: true });
mocked(store.getRoom)
.mockClear()

View File

@ -99,7 +99,7 @@ describe("MatrixEvent", () => {
const room = new Room("!roomid:e.xyz", mockClient, "myname");
const ev = createEvent("$event1:server");
await room.addLiveEvents([ev]);
await room.addLiveEvents([ev], { addToState: false });
await room.createThreadsTimelineSets();
expect(ev.threadRootId).toBeUndefined();
expect(mainTimelineLiveEventIds(room)).toEqual([ev.getId()]);
@ -120,7 +120,7 @@ describe("MatrixEvent", () => {
const threadRoot = createEvent("$threadroot:server");
const ev = createThreadedEvent("$event1:server", threadRoot.getId()!);
await room.addLiveEvents([threadRoot, ev]);
await room.addLiveEvents([threadRoot, ev], { addToState: false });
await room.createThreadsTimelineSets();
expect(threadRoot.threadRootId).toEqual(threadRoot.getId());
expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]);
@ -143,7 +143,7 @@ describe("MatrixEvent", () => {
const threadRoot = createEvent("$threadroot:server");
const ev = createThreadedEvent("$event1:server", threadRoot.getId()!);
await room.addLiveEvents([threadRoot, ev]);
await room.addLiveEvents([threadRoot, ev], { addToState: false });
await room.createThreadsTimelineSets();
expect(ev.threadRootId).toEqual(threadRoot.getId());
expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]);
@ -167,7 +167,7 @@ describe("MatrixEvent", () => {
const ev = createThreadedEvent("$event1:server", threadRoot.getId()!);
const reaction = createReactionEvent("$reaction:server", ev.getId()!);
await room.addLiveEvents([threadRoot, ev, reaction]);
await room.addLiveEvents([threadRoot, ev, reaction], { addToState: false });
await room.createThreadsTimelineSets();
expect(reaction.threadRootId).toEqual(threadRoot.getId());
expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]);
@ -191,7 +191,7 @@ describe("MatrixEvent", () => {
const ev = createThreadedEvent("$event1:server", threadRoot.getId()!);
const edit = createEditEvent("$edit:server", ev.getId()!);
await room.addLiveEvents([threadRoot, ev, edit]);
await room.addLiveEvents([threadRoot, ev, edit], { addToState: false });
await room.createThreadsTimelineSets();
expect(edit.threadRootId).toEqual(threadRoot.getId());
expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]);
@ -217,7 +217,7 @@ describe("MatrixEvent", () => {
const reply2 = createReplyEvent("$reply2:server", reply1.getId()!);
const reaction = createReactionEvent("$reaction:server", reply2.getId()!);
await room.addLiveEvents([threadRoot, ev, reply1, reply2, reaction]);
await room.addLiveEvents([threadRoot, ev, reply1, reply2, reaction], { addToState: false });
await room.createThreadsTimelineSets();
expect(reaction.threadRootId).toEqual(threadRoot.getId());
expect(mainTimelineLiveEventIds(room)).toEqual([threadRoot.getId()]);

View File

@ -36,7 +36,7 @@ describe("RoomReceipts", () => {
// Given there are no receipts in the room
const room = createRoom();
const [event] = createEvent();
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
// When I ask about any event, then it is unread
expect(room.hasUserReadEvent(readerId, event.getId()!)).toBe(false);
@ -46,7 +46,7 @@ describe("RoomReceipts", () => {
// Given there are no receipts in the room
const room = createRoom();
const [event] = createEventSentBy(readerId);
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
// When I ask about an event I sent, it is read (because a synthetic
// receipt was created and stored in RoomReceipts)
@ -57,7 +57,7 @@ describe("RoomReceipts", () => {
// Given my event exists and is unread
const room = createRoom();
const [event, eventId] = createEvent();
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
expect(room.hasUserReadEvent(readerId, eventId)).toBe(false);
// When we receive a receipt for this event+user
@ -72,7 +72,7 @@ describe("RoomReceipts", () => {
const room = createRoom();
const [event1, event1Id] = createEvent();
const [event2] = createEvent();
room.addLiveEvents([event1, event2]);
room.addLiveEvents([event1, event2], { addToState: false });
// When we receive a receipt for the later event
room.addReceipt(createReceipt(readerId, event2));
@ -86,7 +86,7 @@ describe("RoomReceipts", () => {
const room = createRoom();
const [oldEvent, oldEventId] = createEvent();
const [liveEvent] = createEvent();
room.addLiveEvents([liveEvent]);
room.addLiveEvents([liveEvent], { addToState: false });
createOldTimeline(room, [oldEvent]);
// When we receive a receipt for the live event
@ -120,7 +120,7 @@ describe("RoomReceipts", () => {
const room = createRoom();
const [event1] = createEvent();
const [event2, event2Id] = createEvent();
room.addLiveEvents([event1, event2]);
room.addLiveEvents([event1, event2], { addToState: false });
// When we receive a receipt for the earlier event
room.addReceipt(createReceipt(readerId, event1));
@ -133,7 +133,7 @@ describe("RoomReceipts", () => {
// Given my event exists and is unread
const room = createRoom();
const [event, eventId] = createEvent();
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
expect(room.hasUserReadEvent(readerId, eventId)).toBe(false);
// When we receive a receipt for another user
@ -151,7 +151,7 @@ describe("RoomReceipts", () => {
const room = createRoom();
const [previousEvent] = createEvent();
const [myEvent] = createEventSentBy(readerId);
room.addLiveEvents([previousEvent, myEvent]);
room.addLiveEvents([previousEvent, myEvent], { addToState: false });
// And I just received a receipt for the previous event
room.addReceipt(createReceipt(readerId, previousEvent));
@ -165,7 +165,7 @@ describe("RoomReceipts", () => {
const room = createRoom();
const [myEvent] = createEventSentBy(readerId);
const [laterEvent] = createEvent();
room.addLiveEvents([myEvent, laterEvent]);
room.addLiveEvents([myEvent, laterEvent], { addToState: false });
// When I ask about the later event, it is unread (because it's after the synthetic receipt)
expect(room.hasUserReadEvent(readerId, laterEvent.getId()!)).toBe(false);
@ -177,7 +177,7 @@ describe("RoomReceipts", () => {
const [event1] = createEvent();
const [event2, event2Id] = createEvent();
const [event3, event3Id] = createEvent();
room.addLiveEvents([event1, event2, event3]);
room.addLiveEvents([event1, event2, event3], { addToState: false });
// When we receive receipts for the older events out of order
room.addReceipt(createReceipt(readerId, event2));
@ -192,7 +192,7 @@ describe("RoomReceipts", () => {
// Given my event exists and is unread
const room = createRoom();
const [event, eventId] = createEvent();
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
expect(room.hasUserReadEvent(readerId, eventId)).toBe(false);
// When we receive a receipt for this event+user
@ -208,7 +208,7 @@ describe("RoomReceipts", () => {
const [root, rootId] = createEvent();
const [event, eventId] = createThreadedEvent(root);
setupThread(room, root);
room.addLiveEvents([root, event]);
room.addLiveEvents([root, event], { addToState: false });
expect(room.hasUserReadEvent(readerId, eventId)).toBe(false);
// When we receive a receipt for this event on this thread
@ -225,7 +225,7 @@ describe("RoomReceipts", () => {
const [event1, event1Id] = createThreadedEvent(root);
const [event2] = createThreadedEvent(root);
setupThread(room, root);
room.addLiveEvents([root, event1, event2]);
room.addLiveEvents([root, event1, event2], { addToState: false });
// When we receive a receipt for the later event
room.addReceipt(createThreadedReceipt(readerId, event2, rootId));
@ -241,7 +241,7 @@ describe("RoomReceipts", () => {
const [event1] = createThreadedEvent(root);
const [event2, event2Id] = createThreadedEvent(root);
setupThread(room, root);
room.addLiveEvents([root, event1, event2]);
room.addLiveEvents([root, event1, event2], { addToState: false });
// When we receive a receipt for the earlier event
room.addReceipt(createThreadedReceipt(readerId, event1, rootId));
@ -256,7 +256,7 @@ describe("RoomReceipts", () => {
const [root, rootId] = createEvent();
const [event, eventId] = createThreadedEvent(root);
setupThread(room, root);
room.addLiveEvents([root, event]);
room.addLiveEvents([root, event], { addToState: false });
expect(room.hasUserReadEvent(readerId, eventId)).toBe(false);
// When we receive a receipt for another user
@ -278,7 +278,7 @@ describe("RoomReceipts", () => {
const [thread2] = createThreadedEvent(root2);
setupThread(room, root1);
setupThread(room, root2);
room.addLiveEvents([root1, root2, thread1, thread2]);
room.addLiveEvents([root1, root2, thread1, thread2], { addToState: false });
// When we receive a receipt for the later event
room.addReceipt(createThreadedReceipt(readerId, thread2, root2.getId()!));
@ -295,7 +295,7 @@ describe("RoomReceipts", () => {
const [event2, event2Id] = createThreadedEvent(root);
const [event3, event3Id] = createThreadedEvent(root);
setupThread(room, root);
room.addLiveEvents([root, event1, event2, event3]);
room.addLiveEvents([root, event1, event2, event3], { addToState: false });
// When we receive receipts for the older events out of order
room.addReceipt(createThreadedReceipt(readerId, event2, rootId));
@ -329,7 +329,7 @@ describe("RoomReceipts", () => {
const [thread2b, thread2bId] = createThreadedEvent(main2);
setupThread(room, main1);
setupThread(room, main2);
room.addLiveEvents([main1, thread1a, thread1b, main2, thread2a, main3, thread2b]);
room.addLiveEvents([main1, thread1a, thread1b, main2, thread2a, main3, thread2b], { addToState: false });
// And the timestamps on the events are consistent with the order above
main1.event.origin_server_ts = 1;
@ -377,7 +377,7 @@ describe("RoomReceipts", () => {
// Add the event to the room
// The receipt is removed from the dangling state
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
// Then the event is read
expect(room.hasUserReadEvent(readerId, eventId)).toBe(true);
@ -398,7 +398,7 @@ describe("RoomReceipts", () => {
// Add the events to the room
// The receipt is removed from the dangling state
room.addLiveEvents([root, event]);
room.addLiveEvents([root, event], { addToState: false });
// Then the event is read
expect(room.hasUserReadEvent(readerId, eventId)).toBe(true);
@ -418,7 +418,7 @@ describe("RoomReceipts", () => {
// Add the event to the room
// The two receipts should be processed
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
// Then the event is read
// We expect that the receipt of `otherUserId` didn't replace/erase the receipt of `readerId`
@ -528,7 +528,7 @@ function createThreadedReceipt(userId: string, referencedEvent: MatrixEvent, thr
*/
function createOldTimeline(room: Room, events: MatrixEvent[]) {
const oldTimeline = room.getUnfilteredTimelineSet().addTimeline();
room.getUnfilteredTimelineSet().addEventsToTimeline(events, true, oldTimeline);
room.getUnfilteredTimelineSet().addEventsToTimeline(events, true, false, oldTimeline);
}
/**

View File

@ -801,7 +801,7 @@ async function createThread(client: MatrixClient, user: string, roomId: string):
// Ensure the root is in the room timeline
root.setThreadId(root.getId());
await room.addLiveEvents([root]);
await room.addLiveEvents([root], { addToState: false });
// Create the thread and wait for it to be initialised
const thread = room.createThread(root.getId()!, root, [], false);

View File

@ -106,7 +106,7 @@ describe("fixNotificationCountOnDecryption", () => {
mockClient,
);
room.addLiveEvents([event]);
room.addLiveEvents([event], { addToState: false });
THREAD_ID = event.getId()!;
threadEvent = mkEvent({

View File

@ -198,8 +198,8 @@ describe("Relations", function () {
});
const timelineSet = new EventTimelineSet(room);
timelineSet.addLiveEvent(targetEvent);
timelineSet.addLiveEvent(relationEvent);
timelineSet.addLiveEvent(targetEvent, { addToState: false });
timelineSet.addLiveEvent(relationEvent, { addToState: false });
await relationsCreated;
}
@ -212,8 +212,8 @@ describe("Relations", function () {
});
const timelineSet = new EventTimelineSet(room);
timelineSet.addLiveEvent(relationEvent);
timelineSet.addLiveEvent(targetEvent);
timelineSet.addLiveEvent(relationEvent, { addToState: false });
timelineSet.addLiveEvent(targetEvent, { addToState: false });
await relationsCreated;
}

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,10 @@ limitations under the License.
import { ReceiptType } from "../../src/@types/read_receipts";
import {
IJoinedRoom,
Category,
IInvitedRoom,
IInviteState,
IJoinedRoom,
IKnockedRoom,
IKnockState,
ILeftRoom,
@ -27,7 +29,6 @@ import {
IStrippedState,
ISyncResponse,
SyncAccumulator,
IInviteState,
} from "../../src/sync-accumulator";
import { IRoomSummary } from "../../src";
import * as utils from "../test-utils/test-utils";
@ -85,6 +86,7 @@ describe("SyncAccumulator", function () {
// technically cheating since we also cheekily pre-populate keys we
// know that the sync accumulator will pre-populate.
// It isn't 100% transitive.
const events = [member("alice", KnownMembership.Join), member("bob", KnownMembership.Join)];
const res = {
next_batch: "abc",
rooms: {
@ -92,18 +94,17 @@ describe("SyncAccumulator", function () {
leave: {},
join: {
"!foo:bar": {
account_data: { events: [] },
ephemeral: { events: [] },
unread_notifications: {},
state: {
events: [member("alice", KnownMembership.Join), member("bob", KnownMembership.Join)],
},
summary: {
"account_data": { events: [] },
"ephemeral": { events: [] },
"unread_notifications": {},
"org.matrix.msc4222.state_after": { events },
"state": { events },
"summary": {
"m.heroes": undefined,
"m.joined_member_count": undefined,
"m.invited_member_count": undefined,
},
timeline: {
"timeline": {
events: [msg("alice", "hi")],
prev_batch: "something",
},
@ -882,6 +883,147 @@ describe("SyncAccumulator", function () {
).not.toBeUndefined();
});
});
describe("msc4222", () => {
it("should accumulate state_after events", () => {
const initState = {
events: [member("alice", KnownMembership.Knock)],
};
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": initState,
}),
);
expect(sa.getJSON().roomsData[Category.Join]["!foo:bar"].state).toEqual(initState);
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": {
events: [
utils.mkEvent({
user: "alice",
room: "!knock:bar",
type: "m.room.name",
content: {
name: "Room 1",
},
skey: "",
}) as IStateEvent,
],
},
}),
);
expect(
sa.getJSON().roomsData[Category.Join]["!foo:bar"].state?.events.find((e) => e.type === "m.room.name")
?.content.name,
).toEqual("Room 1");
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": {
events: [
utils.mkEvent({
user: "alice",
room: "!knock:bar",
type: "m.room.name",
content: {
name: "Room 2",
},
skey: "",
}) as IStateEvent,
],
},
}),
);
expect(
sa.getJSON().roomsData[Category.Join]["!foo:bar"].state?.events.find((e) => e.type === "m.room.name")
?.content.name,
).toEqual("Room 2");
});
it("should ignore state events in timeline", () => {
const initState = {
events: [member("alice", KnownMembership.Knock)],
};
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": initState,
}),
);
expect(sa.getJSON().roomsData[Category.Join]["!foo:bar"].state).toEqual(initState);
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": {
events: [],
},
"timeline": {
events: [
utils.mkEvent({
user: "alice",
room: "!knock:bar",
type: "m.room.name",
content: {
name: "Room 1",
},
skey: "",
}) as IStateEvent,
],
prev_batch: "something",
},
}),
);
expect(
sa.getJSON().roomsData[Category.Join]["!foo:bar"].state?.events.find((e) => e.type === "m.room.name")
?.content.name,
).not.toEqual("Room 1");
});
it("should not rewind state_after to start of timeline in toJSON", () => {
const initState = {
events: [member("alice", KnownMembership.Knock)],
};
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": initState,
"timeline": {
events: initState.events,
prev_batch: null,
},
}),
);
expect(sa.getJSON().roomsData[Category.Join]["!foo:bar"].state).toEqual(initState);
const joinEvent = member("alice", KnownMembership.Join);
joinEvent.unsigned = { prev_content: initState.events[0].content, prev_sender: initState.events[0].sender };
sa.accumulate(
syncSkeleton({
"org.matrix.msc4222.state_after": {
events: [joinEvent],
},
"timeline": {
events: [joinEvent],
prev_batch: "something",
},
}),
);
const roomData = sa.getJSON().roomsData[Category.Join]["!foo:bar"];
expect(roomData.state?.events.find((e) => e.type === "m.room.member")?.content.membership).toEqual(
KnownMembership.Knock,
);
expect(
roomData["org.matrix.msc4222.state_after"]?.events.find((e) => e.type === "m.room.member")?.content
.membership,
).toEqual(KnownMembership.Join);
expect(roomData.timeline?.events.find((e) => e.type === "m.room.member")?.content.membership).toEqual(
KnownMembership.Join,
);
});
});
});
function syncSkeleton(
@ -961,5 +1103,6 @@ function member(localpart: string, membership: Membership) {
state_key: "@" + localpart + ":localhost",
sender: "@" + localpart + ":localhost",
type: "m.room.member",
unsigned: {},
};
}

View File

@ -62,7 +62,7 @@ function addEventsToTimeline(timeline: EventTimeline, numEvents: number, toStart
user: USER_ID,
event: true,
}),
{ toStartOfTimeline },
{ toStartOfTimeline, addToState: false },
);
}
}
@ -451,8 +451,8 @@ describe("TimelineWindow", function () {
const liveEvents = createEvents(5);
const [, , e3, e4, e5] = oldEvents;
const [, e7, e8, e9, e10] = liveEvents;
room.addLiveEvents(liveEvents);
room.addEventsToTimeline(oldEvents, true, oldTimeline);
room.addLiveEvents(liveEvents, { addToState: false });
room.addEventsToTimeline(oldEvents, true, false, oldTimeline);
// And 2 windows over the timelines in this room
const oldWindow = new TimelineWindow(mockClient, timelineSet);

View File

@ -1566,16 +1566,19 @@ describe("Group Call", function () {
async (roomId, eventType, content, stateKey) => {
const eventId = `$${Math.random()}`;
if (roomId === room.roomId) {
room.addLiveEvents([
new MatrixEvent({
event_id: eventId,
type: eventType,
room_id: roomId,
sender: FAKE_USER_ID_2,
content,
state_key: stateKey,
}),
]);
room.addLiveEvents(
[
new MatrixEvent({
event_id: eventId,
type: eventType,
room_id: roomId,
sender: FAKE_USER_ID_2,
content,
state_key: stateKey,
}),
],
{ addToState: true },
);
}
return { event_id: eventId };
},

View File

@ -6136,7 +6136,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
room.partitionThreadedEvents(matrixEvents);
this.processAggregatedTimelineEvents(room, timelineEvents);
room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
room.addEventsToTimeline(timelineEvents, true, true, room.getLiveTimeline());
this.processThreadEvents(room, threadedEvents, true);
unknownRelations.forEach((event) => room.relations.aggregateChildEvent(event));
@ -6248,7 +6248,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const [timelineEvents, threadedEvents, unknownRelations] = timelineSet.room.partitionThreadedEvents(events);
timelineSet.addEventsToTimeline(timelineEvents, true, timeline, res.start);
timelineSet.addEventsToTimeline(timelineEvents, true, false, timeline, res.start);
// The target event is not in a thread but process the contextual events, so we can show any threads around it.
this.processThreadEvents(timelineSet.room, threadedEvents, true);
this.processAggregatedTimelineEvents(timelineSet.room, timelineEvents);
@ -6342,10 +6342,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
timeline.initialiseState(res.state.map(mapper));
}
timelineSet.addEventsToTimeline(events, true, timeline, resNewer.next_batch);
timelineSet.addEventsToTimeline(events, true, false, timeline, resNewer.next_batch);
if (!resOlder.next_batch) {
const originalEvent = await this.fetchRoomEvent(timelineSet.room.roomId, thread.id);
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, timeline, null);
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, false, timeline, null);
}
timeline.setPaginationToken(resOlder.next_batch ?? null, Direction.Backward);
timeline.setPaginationToken(resNewer.next_batch ?? null, Direction.Forward);
@ -6399,10 +6399,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const timeline = timelineSet.getLiveTimeline();
timeline.getState(EventTimeline.BACKWARDS)!.setUnknownStateEvents(res.state.map(mapper));
timelineSet.addEventsToTimeline(events, true, timeline, null);
timelineSet.addEventsToTimeline(events, true, false, timeline, null);
if (!resOlder.next_batch) {
const originalEvent = await this.fetchRoomEvent(timelineSet.room.roomId, thread.id);
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, timeline, null);
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, false, timeline, null);
}
timeline.setPaginationToken(resOlder.next_batch ?? null, Direction.Backward);
timeline.setPaginationToken(null, Direction.Forward);
@ -6665,7 +6665,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// No need to partition events for threads here, everything lives
// in the notification timeline set
const timelineSet = eventTimeline.getTimelineSet();
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, token);
this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents);
// if we've hit the end of the timeline, we need to stop trying to
@ -6708,7 +6708,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const matrixEvents = res.chunk.filter(noUnsafeEventProps).map(this.getEventMapper());
const timelineSet = eventTimeline.getTimelineSet();
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, token);
this.processAggregatedTimelineEvents(room, matrixEvents);
this.processThreadRoots(room, matrixEvents, backwards);
@ -6756,12 +6756,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const newToken = res.next_batch;
const timelineSet = eventTimeline.getTimelineSet();
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, newToken ?? null);
timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, newToken ?? null);
if (!newToken && backwards) {
const originalEvent =
thread.rootEvent ??
mapper(await this.fetchRoomEvent(eventTimeline.getRoomId() ?? "", thread.id));
timelineSet.addEventsToTimeline([originalEvent], true, eventTimeline, null);
timelineSet.addEventsToTimeline([originalEvent], true, false, eventTimeline, null);
}
this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents);
@ -6800,7 +6800,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const timelineSet = eventTimeline.getTimelineSet();
const [timelineEvents, , unknownRelations] = room.partitionThreadedEvents(matrixEvents);
timelineSet.addEventsToTimeline(timelineEvents, backwards, eventTimeline, token);
timelineSet.addEventsToTimeline(timelineEvents, backwards, false, eventTimeline, token);
this.processAggregatedTimelineEvents(room, timelineEvents);
this.processThreadRoots(
room,

View File

@ -284,7 +284,13 @@ export class RoomWidgetClient extends MatrixClient {
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]);
const events = rawEvents.map((rawEvent) => new MatrixEvent(rawEvent as Partial<IEvent>));
await this.syncApi!.injectRoomEvents(this.room!, [], events);
if (this.syncApi instanceof SyncApi) {
// Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode
// -> state events in `timelineEventList` will update the state.
await this.syncApi.injectRoomEvents(this.room!, undefined, events);
} else {
await this.syncApi!.injectRoomEvents(this.room!, events); // Sliding Sync
}
events.forEach((event) => {
this.emit(ClientEvent.Event, event);
logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
@ -567,7 +573,34 @@ export class RoomWidgetClient extends MatrixClient {
// Only inject once we have update the txId
await this.updateTxId(event);
await this.syncApi!.injectRoomEvents(this.room!, [], [event]);
// The widget API does not tell us whether a state event came from `state_after` or not so we assume legacy behaviour for now.
if (this.syncApi instanceof SyncApi) {
// The code will want to be something like:
// ```
// if (!params.addToTimeline && !params.addToState) {
// // Passing undefined for `stateAfterEventList` makes `injectRoomEvents` run in "legacy mode"
// // -> state events part of the `timelineEventList` parameter will update the state.
// this.injectRoomEvents(this.room!, [], undefined, [event]);
// } else {
// this.injectRoomEvents(this.room!, undefined, params.addToState ? [event] : [], params.addToTimeline ? [event] : []);
// }
// ```
// Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode
// -> state events in `timelineEventList` will update the state.
await this.syncApi.injectRoomEvents(this.room!, [], undefined, [event]);
} else {
// The code will want to be something like:
// ```
// if (!params.addToTimeline && !params.addToState) {
// this.injectRoomEvents(this.room!, [], [event]);
// } else {
// this.injectRoomEvents(this.room!, params.addToState ? [event] : [], params.addToTimeline ? [event] : []);
// }
// ```
await this.syncApi!.injectRoomEvents(this.room!, [], [event]); // Sliding Sync
}
this.emit(ClientEvent.Event, event);
this.setSyncState(SyncState.Syncing);
logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);

View File

@ -58,13 +58,13 @@ export interface IRoomTimelineData {
}
export interface IAddEventToTimelineOptions
extends Pick<IAddEventOptions, "toStartOfTimeline" | "roomState" | "timelineWasEmpty"> {
extends Pick<IAddEventOptions, "toStartOfTimeline" | "roomState" | "timelineWasEmpty" | "addToState"> {
/** Whether the sync response came from cache */
fromCache?: boolean;
}
export interface IAddLiveEventOptions
extends Pick<IAddEventToTimelineOptions, "fromCache" | "roomState" | "timelineWasEmpty"> {
extends Pick<IAddEventToTimelineOptions, "fromCache" | "roomState" | "timelineWasEmpty" | "addToState"> {
/** Applies to events in the timeline only. If this is 'replace' then if a
* duplicate is encountered, the event passed to this function will replace
* the existing event in the timeline. If this is not specified, or is
@ -391,6 +391,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
public addEventsToTimeline(
events: MatrixEvent[],
toStartOfTimeline: boolean,
addToState: boolean,
timeline: EventTimeline,
paginationToken?: string | null,
): void {
@ -495,6 +496,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
// we don't know about this event yet. Just add it to the timeline.
this.addEventToTimeline(event, timeline, {
toStartOfTimeline,
addToState,
});
lastEventWasNew = true;
didUpdate = true;
@ -592,7 +594,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
*/
public addLiveEvent(
event: MatrixEvent,
{ duplicateStrategy, fromCache, roomState, timelineWasEmpty }: IAddLiveEventOptions = {},
{ duplicateStrategy, fromCache, roomState, timelineWasEmpty, addToState }: IAddLiveEventOptions,
): void {
if (this.filter) {
const events = this.filter.filterRoomTimeline([event]);
@ -630,6 +632,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
fromCache,
roomState,
timelineWasEmpty,
addToState,
});
}
@ -649,40 +652,8 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
public addEventToTimeline(
event: MatrixEvent,
timeline: EventTimeline,
{ toStartOfTimeline, fromCache, roomState, timelineWasEmpty }: IAddEventToTimelineOptions,
): void;
/**
* @deprecated In favor of the overload with `IAddEventToTimelineOptions`
*/
public addEventToTimeline(
event: MatrixEvent,
timeline: EventTimeline,
toStartOfTimeline: boolean,
fromCache?: boolean,
roomState?: RoomState,
): void;
public addEventToTimeline(
event: MatrixEvent,
timeline: EventTimeline,
toStartOfTimelineOrOpts: boolean | IAddEventToTimelineOptions,
fromCache = false,
roomState?: RoomState,
{ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty, addToState }: IAddEventToTimelineOptions,
): void {
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
let timelineWasEmpty: boolean | undefined;
if (typeof toStartOfTimelineOrOpts === "object") {
({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
} else if (toStartOfTimelineOrOpts !== undefined) {
// Deprecation warning
// FIXME: Remove after 2023-06-01 (technical debt)
logger.warn(
"Overload deprecated: " +
"`EventTimelineSet.addEventToTimeline(event, timeline, toStartOfTimeline, fromCache?, roomState?)` " +
"is deprecated in favor of the overload with " +
"`EventTimelineSet.addEventToTimeline(event, timeline, IAddEventToTimelineOptions)`",
);
}
if (timeline.getTimelineSet() !== this) {
throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong " +
"in timelineSet(threadId=${this.thread?.id})`);
@ -713,6 +684,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
toStartOfTimeline,
roomState,
timelineWasEmpty,
addToState,
});
this._eventIdToTimeline.set(eventId, timeline);
@ -741,7 +713,12 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
* @remarks
* Fires {@link RoomEvent.Timeline}
*/
public insertEventIntoTimeline(event: MatrixEvent, timeline: EventTimeline, roomState: RoomState): void {
public insertEventIntoTimeline(
event: MatrixEvent,
timeline: EventTimeline,
roomState: RoomState,
addToState: boolean,
): void {
if (timeline.getTimelineSet() !== this) {
throw new Error(`EventTimelineSet.insertEventIntoTimeline: Timeline=${timeline.toString()} does not belong " +
"in timelineSet(threadId=${this.thread?.id})`);
@ -777,6 +754,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
fromCache: false,
timelineWasEmpty: false,
roomState,
addToState,
});
return;
}
@ -799,7 +777,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
// If we got to the end of the loop, insertIndex points at the end of
// the list.
timeline.insertEvent(event, insertIndex, roomState);
timeline.insertEvent(event, insertIndex, roomState, addToState);
this._eventIdToTimeline.set(eventId, timeline);
const data: IRoomTimelineData = {
@ -832,6 +810,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
} else if (!this.filter || this.filter.filterRoomTimeline([localEvent]).length) {
this.addEventToTimeline(localEvent, this.liveTimeline, {
toStartOfTimeline: false,
addToState: false,
});
}
}

View File

@ -35,6 +35,11 @@ export interface IAddEventOptions extends Pick<IMarkerFoundOptions, "timelineWas
toStartOfTimeline: boolean;
/** The state events to reconcile metadata from */
roomState?: RoomState;
/** Whether to add timeline events to the state as was done in legacy sync v2.
* If true then timeline events will be added to the state.
* In sync v2 with org.matrix.msc4222.use_state_after and simplified sliding sync,
* all state arrives explicitly and timeline events should not be added. */
addToState: boolean;
}
export enum Direction {
@ -362,7 +367,7 @@ export class EventTimeline {
*/
public addEvent(
event: MatrixEvent,
{ toStartOfTimeline, roomState, timelineWasEmpty }: IAddEventOptions = { toStartOfTimeline: false },
{ toStartOfTimeline, roomState, timelineWasEmpty, addToState }: IAddEventOptions,
): void {
if (!roomState) {
roomState = toStartOfTimeline ? this.startState : this.endState;
@ -374,7 +379,7 @@ export class EventTimeline {
EventTimeline.setEventMetadata(event, roomState!, toStartOfTimeline);
// modify state but only on unfiltered timelineSets
if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
if (addToState && event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
roomState?.setStateEvents([event], { timelineWasEmpty });
// it is possible that the act of setting the state event means we
// can set more metadata (specifically sender/target props), so try
@ -417,14 +422,14 @@ export class EventTimeline {
*
* @internal
*/
public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState): void {
public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState, addToState: boolean): void {
const timelineSet = this.getTimelineSet();
if (timelineSet.room) {
EventTimeline.setEventMetadata(event, roomState, false);
// modify state but only on unfiltered timelineSets
if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
if (addToState && event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
roomState.setStateEvents([event], {});
// it is possible that the act of setting the state event means we
// can set more metadata (specifically sender/target props), so try

View File

@ -1250,7 +1250,9 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
const timeline = room.getLiveTimeline();
// We use insertEventIntoTimeline to insert it in timestamp order,
// because we don't know where it should go (until we have MSC4033).
timeline.getTimelineSet().insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!);
timeline
.getTimelineSet()
.insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!, false);
}
/**

View File

@ -1739,10 +1739,11 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
public addEventsToTimeline(
events: MatrixEvent[],
toStartOfTimeline: boolean,
addToState: boolean,
timeline: EventTimeline,
paginationToken?: string,
): void {
timeline.getTimelineSet().addEventsToTimeline(events, toStartOfTimeline, timeline, paginationToken);
timeline.getTimelineSet().addEventsToTimeline(events, toStartOfTimeline, addToState, timeline, paginationToken);
}
/**
@ -1907,7 +1908,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// see https://github.com/vector-im/vector-web/issues/2109
unfilteredLiveTimeline.getEvents().forEach(function (event) {
timelineSet.addLiveEvent(event);
timelineSet.addLiveEvent(event, { addToState: false }); // Filtered timeline sets should not track state
});
// find the earliest unfiltered timeline
@ -1994,6 +1995,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
if (filterType !== ThreadFilterType.My || currentUserParticipated) {
timelineSet.getLiveTimeline().addEvent(thread.rootEvent!, {
toStartOfTimeline: false,
addToState: false,
});
}
});
@ -2068,6 +2070,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
const opts = {
duplicateStrategy: DuplicateStrategy.Ignore,
fromCache: false,
addToState: false,
roomState,
};
this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, opts);
@ -2190,6 +2193,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
duplicateStrategy: DuplicateStrategy.Replace,
fromCache: false,
roomState,
addToState: false,
});
}
}
@ -2381,9 +2385,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
duplicateStrategy: DuplicateStrategy.Replace,
fromCache: false,
roomState: this.currentState,
addToState: false,
});
} else {
timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), { toStartOfTimeline });
timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), {
toStartOfTimeline,
addToState: false,
});
}
}
};
@ -2540,7 +2548,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* Fires {@link RoomEvent.Timeline}
*/
private addLiveEvent(event: MatrixEvent, addLiveEventOptions: IAddLiveEventOptions): void {
const { duplicateStrategy, timelineWasEmpty, fromCache } = addLiveEventOptions;
const { duplicateStrategy, timelineWasEmpty, fromCache, addToState } = addLiveEventOptions;
// add to our timeline sets
for (const timelineSet of this.timelineSets) {
@ -2548,6 +2556,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
duplicateStrategy,
fromCache,
timelineWasEmpty,
addToState,
});
}
@ -2631,11 +2640,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
if (timelineSet.getFilter()!.filterRoomTimeline([event]).length) {
timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), {
toStartOfTimeline: false,
addToState: false, // We don't support localEcho of state events yet
});
}
} else {
timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), {
toStartOfTimeline: false,
addToState: false, // We don't support localEcho of state events yet
});
}
}
@ -2886,8 +2897,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @param addLiveEventOptions - addLiveEvent options
* @throws If `duplicateStrategy` is not falsey, 'replace' or 'ignore'.
*/
public async addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): Promise<void> {
const { duplicateStrategy, fromCache, timelineWasEmpty = false } = addLiveEventOptions ?? {};
public async addLiveEvents(events: MatrixEvent[], addLiveEventOptions: IAddLiveEventOptions): Promise<void> {
const { duplicateStrategy, fromCache, timelineWasEmpty = false, addToState } = addLiveEventOptions;
if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) {
throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'");
}
@ -2902,6 +2913,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
duplicateStrategy,
fromCache,
timelineWasEmpty,
addToState,
};
// List of extra events to check for being parents of any relations encountered

View File

@ -208,6 +208,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
public static setServerSideSupport(status: FeatureSupport): void {
Thread.hasServerSideSupport = status;
// XXX: This global latching behaviour is really unexpected and means that you can't undo when moving to a server without support
if (status !== FeatureSupport.Stable) {
FILTER_RELATED_BY_SENDERS.setPreferUnstable(true);
FILTER_RELATED_BY_REL_TYPES.setPreferUnstable(true);
@ -317,6 +318,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
toStartOfTimeline,
fromCache: false,
roomState: this.roomState,
addToState: false,
});
}
}
@ -343,7 +345,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
if (this.findEventById(eventId)) {
return;
}
this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState);
this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState, false);
}
public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {
@ -618,7 +620,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
// if the thread has regular events, this will just load the last reply.
// if the thread is newly created, this will load the root event.
if (this.replyCount === 0 && this.rootEvent) {
this.timelineSet.addEventsToTimeline([this.rootEvent], true, this.liveTimeline, null);
this.timelineSet.addEventsToTimeline([this.rootEvent], true, false, this.liveTimeline, null);
this.liveTimeline.setPaginationToken(null, Direction.Backward);
} else {
this.initalEventFetchProm = this.client.paginateEventTimeline(this.liveTimeline, {

View File

@ -612,7 +612,7 @@ export class SlidingSyncSdk {
timelineEvents = newEvents;
if (oldEvents.length > 0) {
// old events are scrollback, insert them now
room.addEventsToTimeline(oldEvents, true, room.getLiveTimeline(), roomData.prev_batch);
room.addEventsToTimeline(oldEvents, true, false, room.getLiveTimeline(), roomData.prev_batch);
}
}
@ -754,7 +754,7 @@ export class SlidingSyncSdk {
/**
* Injects events into a room's model.
* @param stateEventList - A list of state events. This is the state
* at the *START* of the timeline list if it is supplied.
* at the *END* of the timeline list if it is supplied.
* @param timelineEventList - A list of timeline events. Lower index
* is earlier in time. Higher index is later.
* @param numLive - the number of events in timelineEventList which just happened,
@ -763,13 +763,9 @@ export class SlidingSyncSdk {
public async injectRoomEvents(
room: Room,
stateEventList: MatrixEvent[],
timelineEventList?: MatrixEvent[],
numLive?: number,
timelineEventList: MatrixEvent[] = [],
numLive: number = 0,
): Promise<void> {
timelineEventList = timelineEventList || [];
stateEventList = stateEventList || [];
numLive = numLive || 0;
// If there are no events in the timeline yet, initialise it with
// the given state events
const liveTimeline = room.getLiveTimeline();
@ -820,16 +816,17 @@ export class SlidingSyncSdk {
timelineEventList = timelineEventList.slice(0, -1 * liveTimelineEvents.length);
}
// execute the timeline events. This will continue to diverge the current state
// if the timeline has any state events in it.
// Execute the timeline events.
// This also needs to be done before running push rules on the events as they need
// to be decorated with sender etc.
await room.addLiveEvents(timelineEventList, {
fromCache: true,
addToState: false,
});
if (liveTimelineEvents.length > 0) {
await room.addLiveEvents(liveTimelineEvents, {
fromCache: false,
addToState: false,
});
}
@ -966,7 +963,7 @@ export class SlidingSyncSdk {
return a.getTs() - b.getTs();
});
this.notifEvents.forEach((event) => {
this.client.getNotifTimelineSet()?.addLiveEvent(event);
this.client.getNotifTimelineSet()?.addLiveEvent(event, { addToState: false });
});
this.notifEvents = [];
}

View File

@ -77,7 +77,9 @@ export interface ITimeline {
export interface IJoinedRoom {
"summary": IRoomSummary;
"state": IState;
// One of `state` or `state_after` is required.
"state"?: IState;
"org.matrix.msc4222.state_after"?: IState; // https://github.com/matrix-org/matrix-spec-proposals/pull/4222
"timeline": ITimeline;
"ephemeral": IEphemeral;
"account_data": IAccountData;
@ -106,9 +108,11 @@ export interface IInvitedRoom {
}
export interface ILeftRoom {
state: IState;
timeline: ITimeline;
account_data: IAccountData;
// One of `state` or `state_after` is required.
"state"?: IState;
"org.matrix.msc4222.state_after"?: IState;
"timeline": ITimeline;
"account_data": IAccountData;
}
export interface IKnockedRoom {
@ -481,13 +485,18 @@ export class SyncAccumulator {
// Work out the current state. The deltas need to be applied in the order:
// - existing state which didn't come down /sync.
// - State events under the 'state' key.
// - State events in the 'timeline'.
// - State events under the 'state_after' key OR state events in the 'timeline' if 'state_after' is not present.
data.state?.events?.forEach((e) => {
setState(currentData._currentState, e);
});
data.timeline?.events?.forEach((e, index) => {
// this nops if 'e' isn't a state event
data["org.matrix.msc4222.state_after"]?.events?.forEach((e) => {
setState(currentData._currentState, e);
});
data.timeline?.events?.forEach((e, index) => {
if (!data["org.matrix.msc4222.state_after"]) {
// this nops if 'e' isn't a state event
setState(currentData._currentState, e);
}
// append the event to the timeline. The back-pagination token
// corresponds to the first event in the timeline
let transformedEvent: TaggedEvent;
@ -563,17 +572,22 @@ export class SyncAccumulator {
});
Object.keys(this.joinRooms).forEach((roomId) => {
const roomData = this.joinRooms[roomId];
const roomJson: IJoinedRoom = {
ephemeral: { events: [] },
account_data: { events: [] },
state: { events: [] },
timeline: {
const roomJson: IJoinedRoom & {
// We track both `state` and `state_after` for downgrade compatibility
"state": IState;
"org.matrix.msc4222.state_after": IState;
} = {
"ephemeral": { events: [] },
"account_data": { events: [] },
"state": { events: [] },
"org.matrix.msc4222.state_after": { events: [] },
"timeline": {
events: [],
prev_batch: null,
},
unread_notifications: roomData._unreadNotifications,
unread_thread_notifications: roomData._unreadThreadNotifications,
summary: roomData._summary as IRoomSummary,
"unread_notifications": roomData._unreadNotifications,
"unread_thread_notifications": roomData._unreadThreadNotifications,
"summary": roomData._summary as IRoomSummary,
};
// Add account data
Object.keys(roomData._accountData).forEach((evType) => {
@ -650,8 +664,11 @@ export class SyncAccumulator {
Object.keys(roomData._currentState).forEach((evType) => {
Object.keys(roomData._currentState[evType]).forEach((stateKey) => {
let ev = roomData._currentState[evType][stateKey];
// Push to both fields to provide downgrade compatibility in the sync accumulator db
// the code will prefer `state_after` if it is present
roomJson["org.matrix.msc4222.state_after"].events.push(ev);
// Roll the state back to the value at the start of the timeline if it was changed
if (rollBackState[evType] && rollBackState[evType][stateKey]) {
// use the reverse clobbered event instead.
ev = rollBackState[evType][stateKey];
}
roomJson.state.events.push(ev);

View File

@ -175,14 +175,15 @@ export enum SetPresence {
}
interface ISyncParams {
filter?: string;
timeout: number;
since?: string;
"filter"?: string;
"timeout": number;
"since"?: string;
// eslint-disable-next-line camelcase
full_state?: boolean;
"full_state"?: boolean;
// eslint-disable-next-line camelcase
set_presence?: SetPresence;
_cacheBuster?: string | number; // not part of the API itself
"set_presence"?: SetPresence;
"_cacheBuster"?: string | number; // not part of the API itself
"org.matrix.msc4222.use_state_after"?: boolean; // https://github.com/matrix-org/matrix-spec-proposals/pull/4222
}
type WrappedRoom<T> = T & {
@ -344,8 +345,9 @@ export class SyncApi {
);
const qps: ISyncParams = {
timeout: 0, // don't want to block since this is a single isolated req
filter: filterId,
"timeout": 0, // don't want to block since this is a single isolated req
"filter": filterId,
"org.matrix.msc4222.use_state_after": true,
};
const data = await client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
@ -375,21 +377,18 @@ export class SyncApi {
prev_batch: null,
events: [],
};
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
// set the back-pagination token. Do this *before* adding any
// events so that clients can start back-paginating.
room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);
await this.injectRoomEvents(room, stateEvents, events);
const { timelineEvents } = await this.mapAndInjectRoomEvents(leaveObj);
room.recalculate();
client.store.storeRoom(room);
client.emit(ClientEvent.Room, room);
this.processEventsForNotifs(room, events);
this.processEventsForNotifs(room, timelineEvents);
return room;
}),
);
@ -464,6 +463,7 @@ export class SyncApi {
this._peekRoom.addEventsToTimeline(
messages.reverse(),
true,
true,
this._peekRoom.getLiveTimeline(),
response.messages.start,
);
@ -551,7 +551,7 @@ export class SyncApi {
})
.map(this.client.getEventMapper());
await peekRoom.addLiveEvents(events);
await peekRoom.addLiveEvents(events, { addToState: true });
this.peekPoll(peekRoom, res.end);
},
(err) => {
@ -976,7 +976,11 @@ export class SyncApi {
filter = this.getGuestFilter();
}
const qps: ISyncParams = { filter, timeout };
const qps: ISyncParams = {
filter,
timeout,
"org.matrix.msc4222.use_state_after": true,
};
if (this.opts.disablePresence) {
qps.set_presence = SetPresence.Offline;
@ -1242,7 +1246,7 @@ export class SyncApi {
const room = inviteObj.room;
const stateEvents = this.mapSyncEventsFormat(inviteObj.invite_state, room);
await this.injectRoomEvents(room, stateEvents);
await this.injectRoomEvents(room, stateEvents, undefined);
const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId()!)?.getSender();
@ -1282,15 +1286,24 @@ export class SyncApi {
await promiseMapSeries(joinRooms, async (joinObj) => {
const room = joinObj.room;
const stateEvents = this.mapSyncEventsFormat(joinObj.state, room);
const stateAfterEvents = this.mapSyncEventsFormat(joinObj["org.matrix.msc4222.state_after"], room);
// Prevent events from being decrypted ahead of time
// this helps large account to speed up faster
// room::decryptCriticalEvent is in charge of decrypting all the events
// required for a client to function properly
const events = this.mapSyncEventsFormat(joinObj.timeline, room, false);
const timelineEvents = this.mapSyncEventsFormat(joinObj.timeline, room, false);
const ephemeralEvents = this.mapSyncEventsFormat(joinObj.ephemeral);
const accountDataEvents = this.mapSyncEventsFormat(joinObj.account_data);
const encrypted = this.isRoomEncrypted(room, stateEvents, events);
// If state_after is present, this is the events that form the state at the end of the timeline block and
// regular timeline events do *not* count towards state. If it's not present, then the state is formed by
// the state events plus the timeline events. Note mapSyncEventsFormat returns an empty array if the field
// is absent so we explicitly check the field on the original object.
const eventsFormingFinalState = joinObj["org.matrix.msc4222.state_after"]
? stateAfterEvents
: stateEvents.concat(timelineEvents);
const encrypted = this.isRoomEncrypted(room, eventsFormingFinalState);
// We store the server-provided value first so it's correct when any of the events fire.
if (joinObj.unread_notifications) {
/**
@ -1378,8 +1391,8 @@ export class SyncApi {
// which we'll try to paginate but not get any new events (which
// will stop us linking the empty timeline into the chain).
//
for (let i = events.length - 1; i >= 0; i--) {
const eventId = events[i].getId()!;
for (let i = timelineEvents.length - 1; i >= 0; i--) {
const eventId = timelineEvents[i].getId()!;
if (room.getTimelineForEvent(eventId)) {
debuglog(`Already have event ${eventId} in limited sync - not resetting`);
limited = false;
@ -1387,7 +1400,7 @@ export class SyncApi {
// we might still be missing some of the events before i;
// we don't want to be adding them to the end of the
// timeline because that would put them out of order.
events.splice(0, i);
timelineEvents.splice(0, i);
// XXX: there's a problem here if the skipped part of the
// timeline modifies the state set in stateEvents, because
@ -1419,8 +1432,9 @@ export class SyncApi {
// avoids a race condition if the application tries to send a message after the
// state event is processed, but before crypto is enabled, which then causes the
// crypto layer to complain.
if (this.syncOpts.cryptoCallbacks) {
for (const e of stateEvents.concat(events)) {
for (const e of eventsFormingFinalState) {
if (e.isState() && e.getType() === EventType.RoomEncryption && e.getStateKey() === "") {
await this.syncOpts.cryptoCallbacks.onCryptoEvent(room, e);
}
@ -1428,7 +1442,17 @@ export class SyncApi {
}
try {
await this.injectRoomEvents(room, stateEvents, events, syncEventData.fromCache);
if ("org.matrix.msc4222.state_after" in joinObj) {
await this.injectRoomEvents(
room,
undefined,
stateAfterEvents,
timelineEvents,
syncEventData.fromCache,
);
} else {
await this.injectRoomEvents(room, stateEvents, undefined, timelineEvents, syncEventData.fromCache);
}
} catch (e) {
logger.error(`Failed to process events on room ${room.roomId}:`, e);
}
@ -1452,11 +1476,11 @@ export class SyncApi {
client.emit(ClientEvent.Room, room);
}
this.processEventsForNotifs(room, events);
this.processEventsForNotifs(room, timelineEvents);
const emitEvent = (e: MatrixEvent): boolean => client.emit(ClientEvent.Event, e);
stateEvents.forEach(emitEvent);
events.forEach(emitEvent);
timelineEvents.forEach(emitEvent);
ephemeralEvents.forEach(emitEvent);
accountDataEvents.forEach(emitEvent);
@ -1469,11 +1493,9 @@ export class SyncApi {
// Handle leaves (e.g. kicked rooms)
await promiseMapSeries(leaveRooms, async (leaveObj) => {
const room = leaveObj.room;
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
const { timelineEvents, stateEvents, stateAfterEvents } = await this.mapAndInjectRoomEvents(leaveObj);
const accountDataEvents = this.mapSyncEventsFormat(leaveObj.account_data);
await this.injectRoomEvents(room, stateEvents, events);
room.addAccountData(accountDataEvents);
room.recalculate();
@ -1482,12 +1504,15 @@ export class SyncApi {
client.emit(ClientEvent.Room, room);
}
this.processEventsForNotifs(room, events);
this.processEventsForNotifs(room, timelineEvents);
stateEvents.forEach(function (e) {
stateEvents?.forEach(function (e) {
client.emit(ClientEvent.Event, e);
});
events.forEach(function (e) {
stateAfterEvents?.forEach(function (e) {
client.emit(ClientEvent.Event, e);
});
timelineEvents.forEach(function (e) {
client.emit(ClientEvent.Event, e);
});
accountDataEvents.forEach(function (e) {
@ -1500,7 +1525,7 @@ export class SyncApi {
const room = knockObj.room;
const stateEvents = this.mapSyncEventsFormat(knockObj.knock_state, room);
await this.injectRoomEvents(room, stateEvents);
await this.injectRoomEvents(room, stateEvents, undefined);
if (knockObj.isBrandNewRoom) {
room.recalculate();
@ -1525,7 +1550,7 @@ export class SyncApi {
return a.getTs() - b.getTs();
});
this.notifEvents.forEach(function (event) {
client.getNotifTimelineSet()?.addLiveEvent(event);
client.getNotifTimelineSet()?.addLiveEvent(event, { addToState: true });
});
}
@ -1669,7 +1694,7 @@ export class SyncApi {
}
private mapSyncEventsFormat(
obj: IInviteState | ITimeline | IEphemeral,
obj: IInviteState | ITimeline | IEphemeral | undefined,
room?: Room,
decrypt = true,
): MatrixEvent[] {
@ -1737,28 +1762,69 @@ export class SyncApi {
// When processing the sync response we cannot rely on Room.hasEncryptionStateEvent we actually
// inject the events into the room object, so we have to inspect the events themselves.
private isRoomEncrypted(room: Room, stateEventList: MatrixEvent[], timelineEventList?: MatrixEvent[]): boolean {
return (
room.hasEncryptionStateEvent() ||
!!this.findEncryptionEvent(stateEventList) ||
!!this.findEncryptionEvent(timelineEventList)
private isRoomEncrypted(room: Room, eventsFormingFinalState: MatrixEvent[]): boolean {
return room.hasEncryptionStateEvent() || !!this.findEncryptionEvent(eventsFormingFinalState);
}
private async mapAndInjectRoomEvents(wrappedRoom: WrappedRoom<ILeftRoom>): Promise<{
timelineEvents: MatrixEvent[];
stateEvents?: MatrixEvent[];
stateAfterEvents?: MatrixEvent[];
}> {
const stateEvents = this.mapSyncEventsFormat(wrappedRoom.state, wrappedRoom.room);
const stateAfterEvents = this.mapSyncEventsFormat(
wrappedRoom["org.matrix.msc4222.state_after"],
wrappedRoom.room,
);
const timelineEvents = this.mapSyncEventsFormat(wrappedRoom.timeline, wrappedRoom.room);
if ("org.matrix.msc4222.state_after" in wrappedRoom) {
await this.injectRoomEvents(wrappedRoom.room, undefined, stateAfterEvents, timelineEvents);
} else {
await this.injectRoomEvents(wrappedRoom.room, stateEvents, undefined, timelineEvents);
}
return { timelineEvents, stateEvents, stateAfterEvents };
}
/**
* Injects events into a room's model.
* @param stateEventList - A list of state events. This is the state
* at the *START* of the timeline list if it is supplied.
* @param stateAfterEventList - A list of state events. This is the state
* at the *END* of the timeline list if it is supplied.
* @param timelineEventList - A list of timeline events, including threaded. Lower index
* is earlier in time. Higher index is later.
* @param fromCache - whether the sync response came from cache
*
* No more than one of stateEventList and stateAfterEventList must be supplied. If
* stateEventList is supplied, the events in timelineEventList are added to the state
* after stateEventList. If stateAfterEventList is supplied, the events in timelineEventList
* are not added to the state.
*/
public async injectRoomEvents(
room: Room,
stateEventList: MatrixEvent[],
stateAfterEventList: undefined,
timelineEventList?: MatrixEvent[],
fromCache?: boolean,
): Promise<void>;
public async injectRoomEvents(
room: Room,
stateEventList: undefined,
stateAfterEventList: MatrixEvent[],
timelineEventList?: MatrixEvent[],
fromCache?: boolean,
): Promise<void>;
public async injectRoomEvents(
room: Room,
stateEventList: MatrixEvent[] | undefined,
stateAfterEventList: MatrixEvent[] | undefined,
timelineEventList?: MatrixEvent[],
fromCache = false,
): Promise<void> {
const eitherStateEventList = stateAfterEventList ?? stateEventList!;
// If there are no events in the timeline yet, initialise it with
// the given state events
const liveTimeline = room.getLiveTimeline();
@ -1772,10 +1838,11 @@ export class SyncApi {
// push actions cache elsewhere so we can freeze MatrixEvents, or otherwise
// find some solution where MatrixEvents are immutable but allow for a cache
// field.
for (const ev of stateEventList) {
for (const ev of eitherStateEventList) {
this.client.getPushActionsForEvent(ev);
}
liveTimeline.initialiseState(stateEventList, {
liveTimeline.initialiseState(eitherStateEventList, {
timelineWasEmpty,
});
}
@ -1807,17 +1874,18 @@ export class SyncApi {
// XXX: As above, don't do this...
//room.addLiveEvents(stateEventList || []);
// Do this instead...
room.oldState.setStateEvents(stateEventList || []);
room.currentState.setStateEvents(stateEventList || []);
room.oldState.setStateEvents(eitherStateEventList);
room.currentState.setStateEvents(eitherStateEventList);
}
// Execute the timeline events. This will continue to diverge the current state
// if the timeline has any state events in it.
// Execute the timeline events. If addToState is true the timeline has any state
// events in it, this will continue to diverge the current state.
// This also needs to be done before running push rules on the events as they need
// to be decorated with sender etc.
await room.addLiveEvents(timelineEventList || [], {
fromCache,
timelineWasEmpty,
addToState: stateAfterEventList === undefined,
});
this.client.processBeaconEvents(room, timelineEventList);
}