You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Fix edge cases around 2nd order relations and threads (#3437)
* Fix tests oversimplifying threads fixtures * Check for unsigned thread_id in MatrixEvent::threadRootId * Fix threads order being racy * Make Sonar happier * Iterate
This commit is contained in:
committed by
GitHub
parent
3351c4f57a
commit
0329824cab
@ -1274,6 +1274,7 @@ describe("MatrixClient event timelines", function () {
|
|||||||
THREAD_ROOT.event_id,
|
THREAD_ROOT.event_id,
|
||||||
THREAD_REPLY.event_id,
|
THREAD_REPLY.event_id,
|
||||||
THREAD_REPLY2.getId(),
|
THREAD_REPLY2.getId(),
|
||||||
|
THREAD_ROOT_REACTION.getId(),
|
||||||
THREAD_REPLY3.getId(),
|
THREAD_REPLY3.getId(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@ -1322,7 +1323,7 @@ describe("MatrixClient event timelines", function () {
|
|||||||
request.respond(200, function () {
|
request.respond(200, function () {
|
||||||
return {
|
return {
|
||||||
original_event: root,
|
original_event: root,
|
||||||
chunk: [replies],
|
chunk: replies,
|
||||||
// no next batch as this is the oldest end of the timeline
|
// no next batch as this is the oldest end of the timeline
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -1479,7 +1480,7 @@ describe("MatrixClient event timelines", function () {
|
|||||||
user: userId,
|
user: userId,
|
||||||
type: "m.room.message",
|
type: "m.room.message",
|
||||||
content: {
|
content: {
|
||||||
"body": "thread reply",
|
"body": "thread2 reply",
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
// We can't use the const here because we change server support mode for test
|
// We can't use the const here because we change server support mode for test
|
||||||
@ -1499,7 +1500,7 @@ describe("MatrixClient event timelines", function () {
|
|||||||
user: userId,
|
user: userId,
|
||||||
type: "m.room.message",
|
type: "m.room.message",
|
||||||
content: {
|
content: {
|
||||||
"body": "thread reply",
|
"body": "thread reply2",
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
// We can't use the const here because we change server support mode for test
|
// We can't use the const here because we change server support mode for test
|
||||||
@ -1567,7 +1568,7 @@ describe("MatrixClient event timelines", function () {
|
|||||||
// Test adding a second event to the first thread
|
// Test adding a second event to the first thread
|
||||||
const thread = room.getThread(THREAD_ROOT.event_id!)!;
|
const thread = room.getThread(THREAD_ROOT.event_id!)!;
|
||||||
thread.initialEventsFetched = true;
|
thread.initialEventsFetched = true;
|
||||||
const prom = emitPromise(room, ThreadEvent.NewReply);
|
const prom = emitPromise(room, ThreadEvent.Update);
|
||||||
respondToEvent(THREAD_ROOT_UPDATED);
|
respondToEvent(THREAD_ROOT_UPDATED);
|
||||||
respondToEvent(THREAD_ROOT_UPDATED);
|
respondToEvent(THREAD_ROOT_UPDATED);
|
||||||
respondToEvent(THREAD_ROOT_UPDATED);
|
respondToEvent(THREAD_ROOT_UPDATED);
|
||||||
|
@ -142,13 +142,6 @@ describe("EventTimelineSet", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("addEventToTimeline", () => {
|
describe("addEventToTimeline", () => {
|
||||||
let thread: Thread;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
(client.supportsThreads as jest.Mock).mockReturnValue(true);
|
|
||||||
thread = new Thread("!thread_id:server", messageEvent, { room, client });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Adds event to timeline", () => {
|
it("Adds event to timeline", () => {
|
||||||
const liveTimeline = eventTimelineSet.getLiveTimeline();
|
const liveTimeline = eventTimelineSet.getLiveTimeline();
|
||||||
expect(liveTimeline.getEvents().length).toStrictEqual(0);
|
expect(liveTimeline.getEvents().length).toStrictEqual(0);
|
||||||
@ -167,6 +160,15 @@ describe("EventTimelineSet", () => {
|
|||||||
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, true, false);
|
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, true, false);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addEventToTimeline (thread timeline)", () => {
|
||||||
|
let thread: Thread;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(client.supportsThreads as jest.Mock).mockReturnValue(true);
|
||||||
|
thread = new Thread("!thread_id:server", messageEvent, { room, client });
|
||||||
|
});
|
||||||
|
|
||||||
it("should not add an event to a timeline that does not belong to the timelineSet", () => {
|
it("should not add an event to a timeline that does not belong to the timelineSet", () => {
|
||||||
const eventTimelineSet2 = new EventTimelineSet(room);
|
const eventTimelineSet2 = new EventTimelineSet(room);
|
||||||
@ -197,7 +199,14 @@ describe("EventTimelineSet", () => {
|
|||||||
const liveTimeline = eventTimelineSetForThread.getLiveTimeline();
|
const liveTimeline = eventTimelineSetForThread.getLiveTimeline();
|
||||||
expect(liveTimeline.getEvents().length).toStrictEqual(0);
|
expect(liveTimeline.getEvents().length).toStrictEqual(0);
|
||||||
|
|
||||||
eventTimelineSetForThread.addEventToTimeline(messageEvent, liveTimeline, {
|
const normalMessage = utils.mkMessage({
|
||||||
|
room: roomId,
|
||||||
|
user: userA,
|
||||||
|
msg: "Hello!",
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
eventTimelineSetForThread.addEventToTimeline(normalMessage, liveTimeline, {
|
||||||
toStartOfTimeline: true,
|
toStartOfTimeline: true,
|
||||||
});
|
});
|
||||||
expect(liveTimeline.getEvents().length).toStrictEqual(0);
|
expect(liveTimeline.getEvents().length).toStrictEqual(0);
|
||||||
@ -336,7 +345,9 @@ describe("EventTimelineSet", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return true if the timeline set is not for a thread and the event is a thread root", () => {
|
it("should return true if the timeline set is not for a thread and the event is a thread root", () => {
|
||||||
|
const thread = new Thread(messageEvent.getId()!, messageEvent, { room, client });
|
||||||
const eventTimelineSet = new EventTimelineSet(room, {}, client);
|
const eventTimelineSet = new EventTimelineSet(room, {}, client);
|
||||||
|
messageEvent.setThread(thread);
|
||||||
expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy();
|
expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -579,9 +579,18 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
const relatesTo = this.getWireContent()?.["m.relates_to"];
|
const relatesTo = this.getWireContent()?.["m.relates_to"];
|
||||||
if (relatesTo?.rel_type === THREAD_RELATION_TYPE.name) {
|
if (relatesTo?.rel_type === THREAD_RELATION_TYPE.name) {
|
||||||
return relatesTo.event_id;
|
return relatesTo.event_id;
|
||||||
} else {
|
|
||||||
return this.getThread()?.id || this.threadId;
|
|
||||||
}
|
}
|
||||||
|
if (this.thread) {
|
||||||
|
return this.thread.id;
|
||||||
|
}
|
||||||
|
if (this.threadId !== undefined) {
|
||||||
|
return this.threadId;
|
||||||
|
}
|
||||||
|
const unsigned = this.getUnsigned();
|
||||||
|
if (typeof unsigned[UNSIGNED_THREAD_ID_FIELD.name] === "string") {
|
||||||
|
return unsigned[UNSIGNED_THREAD_ID_FIELD.name];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -593,7 +602,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
// Bundled relationships only returned when the sync response is limited
|
// Bundled relationships only returned when the sync response is limited
|
||||||
// hence us having to check both bundled relation and inspect the thread
|
// hence us having to check both bundled relation and inspect the thread
|
||||||
// model
|
// model
|
||||||
return !!threadDetails || this.getThread()?.id === this.getId();
|
return !!threadDetails || this.threadRootId === this.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get replyEventId(): string | undefined {
|
public get replyEventId(): string | undefined {
|
||||||
|
@ -1957,7 +1957,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on(ThreadEvent.NewReply, this.onThreadNewReply);
|
this.on(ThreadEvent.Update, this.onThreadUpdate);
|
||||||
this.on(ThreadEvent.Delete, this.onThreadDelete);
|
this.on(ThreadEvent.Delete, this.onThreadDelete);
|
||||||
this.threadsReady = true;
|
this.threadsReady = true;
|
||||||
}
|
}
|
||||||
@ -2055,7 +2055,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onThreadNewReply(thread: Thread): void {
|
private onThreadUpdate(thread: Thread): void {
|
||||||
this.updateThreadRootEvents(thread, false, true);
|
this.updateThreadRootEvents(thread, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2113,12 +2113,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// A thread relation is always only shown in a thread
|
// A thread relation (1st and 2nd order) is always only shown in a thread
|
||||||
if (event.isRelation(THREAD_RELATION_TYPE.name)) {
|
const threadRootId = event.threadRootId;
|
||||||
|
if (threadRootId != undefined) {
|
||||||
return {
|
return {
|
||||||
shouldLiveInRoom: false,
|
shouldLiveInRoom: false,
|
||||||
shouldLiveInThread: true,
|
shouldLiveInThread: true,
|
||||||
threadId: event.threadRootId,
|
threadId: threadRootId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2149,15 +2150,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned = event.getUnsigned();
|
|
||||||
if (typeof unsigned[UNSIGNED_THREAD_ID_FIELD.name] === "string") {
|
|
||||||
return {
|
|
||||||
shouldLiveInRoom: false,
|
|
||||||
shouldLiveInThread: true,
|
|
||||||
threadId: unsigned[UNSIGNED_THREAD_ID_FIELD.name],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've exhausted all scenarios,
|
// We've exhausted all scenarios,
|
||||||
// we cannot assume that it lives in the main timeline as this may be a relation for an unknown thread
|
// we cannot assume that it lives in the main timeline as this may be a relation for an unknown thread
|
||||||
// adding the event in the wrong timeline causes stuck notifications and can break ability to send read receipts
|
// adding the event in the wrong timeline causes stuck notifications and can break ability to send read receipts
|
||||||
@ -2890,12 +2882,9 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
private findThreadRoots(events: MatrixEvent[]): Set<string> {
|
private findThreadRoots(events: MatrixEvent[]): Set<string> {
|
||||||
const threadRoots = new Set<string>();
|
const threadRoots = new Set<string>();
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (event.isRelation(THREAD_RELATION_TYPE.name)) {
|
const threadRootId = event.threadRootId;
|
||||||
threadRoots.add(event.relationEventId ?? "");
|
if (threadRootId != undefined) {
|
||||||
}
|
threadRoots.add(threadRootId);
|
||||||
const unsigned = event.getUnsigned();
|
|
||||||
if (typeof unsigned[UNSIGNED_THREAD_ID_FIELD.name] === "string") {
|
|
||||||
threadRoots.add(unsigned[UNSIGNED_THREAD_ID_FIELD.name]!);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return threadRoots;
|
return threadRoots;
|
||||||
|
Reference in New Issue
Block a user