Skip to content

Commit cd26ba6

Browse files
authored
Attempt a potential workaround for stuck notifs (#3384)
* Attempt a potential workaround for stuck notifs * Remove TODOs * Fix backwards logic about server support for MSC3981 in fetchEditsWhereNeeded * Check for lack of MSC3981 server support before calling insertEventIntoTimeline * If no parent event is found, insert purely based on timestamp * Mark temporary methods as internal
1 parent 4883903 commit cd26ba6

File tree

3 files changed

+164
-2
lines changed

3 files changed

+164
-2
lines changed

src/models/event-timeline-set.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,94 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
756756
this.emit(RoomEvent.Timeline, event, this.room, Boolean(toStartOfTimeline), false, data);
757757
}
758758

759+
/**
760+
* Insert event to the given timeline, and emit Room.timeline. Assumes
761+
* we have already checked we don't know about this event.
762+
*
763+
* TEMPORARY: until we have recursive relations, we need this function
764+
* to exist to allow us to insert events in timeline order, which is our
765+
* best guess for Sync Order.
766+
* This is a copy of addEventToTimeline above, modified to insert the event
767+
* after the event it relates to, and before any event with a later
768+
* timestamp. This is our best guess at Sync Order.
769+
*
770+
* Will fire "Room.timeline" for each event added.
771+
*
772+
* @internal
773+
*
774+
* @param options - addEventToTimeline options
775+
*
776+
* @remarks
777+
* Fires {@link RoomEvent.Timeline}
778+
*/
779+
public insertEventIntoTimeline(event: MatrixEvent, timeline: EventTimeline, roomState: RoomState): void {
780+
if (timeline.getTimelineSet() !== this) {
781+
throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong " +
782+
"in timelineSet(threadId=${this.thread?.id})`);
783+
}
784+
785+
// Make sure events don't get mixed in timelines they shouldn't be in (e.g. a
786+
// threaded message should not be in the main timeline).
787+
//
788+
// We can only run this check for timelines with a `room` because `canContain`
789+
// requires it
790+
if (this.room && !this.canContain(event)) {
791+
let eventDebugString = `event=${event.getId()}`;
792+
if (event.threadRootId) {
793+
eventDebugString += `(belongs to thread=${event.threadRootId})`;
794+
}
795+
logger.warn(
796+
`EventTimelineSet.addEventToTimeline: Ignoring ${eventDebugString} that does not belong ` +
797+
`in timeline=${timeline.toString()} timelineSet(threadId=${this.thread?.id})`,
798+
);
799+
return;
800+
}
801+
802+
// Find the event that this event is related to - the "parent"
803+
const parentEventId = event.relationEventId;
804+
if (!parentEventId) {
805+
// Not related to anything - we just append
806+
this.addEventToTimeline(event, timeline, {
807+
toStartOfTimeline: false,
808+
fromCache: false,
809+
timelineWasEmpty: false,
810+
roomState,
811+
});
812+
return;
813+
}
814+
815+
const parentEvent = this.findEventById(parentEventId);
816+
817+
const timelineEvents = timeline.getEvents();
818+
819+
// Start searching from the parent event, or if it's not loaded, start
820+
// at the beginning and insert purely using timestamp order.
821+
const parentIndex = parentEvent !== undefined ? timelineEvents.indexOf(parentEvent) : 0;
822+
let insertIndex = parentIndex;
823+
for (; insertIndex < timelineEvents.length; insertIndex++) {
824+
const nextEvent = timelineEvents[insertIndex];
825+
if (nextEvent.getTs() > event.getTs()) {
826+
// We found an event later than ours, so insert before that.
827+
break;
828+
}
829+
}
830+
// If we got to the end of the loop, insertIndex points at the end of
831+
// the list.
832+
833+
const eventId = event.getId()!;
834+
timeline.insertEvent(event, insertIndex, roomState);
835+
this._eventIdToTimeline.set(eventId, timeline);
836+
837+
this.relations.aggregateParentEvent(event);
838+
this.relations.aggregateChildEvent(event, this);
839+
840+
const data: IRoomTimelineData = {
841+
timeline: timeline,
842+
liveEvent: timeline == this.liveTimeline,
843+
};
844+
this.emit(RoomEvent.Timeline, event, this.room, false, false, data);
845+
}
846+
759847
/**
760848
* Replaces event with ID oldEventId with one with newEventId, if oldEventId is
761849
* recognised. Otherwise, add to the live timeline. Used to handle remote echos.

src/models/event-timeline.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,45 @@ export class EventTimeline {
427427
}
428428
}
429429

430+
/**
431+
* Insert a new event into the timeline, and update the state.
432+
*
433+
* TEMPORARY: until we have recursive relations, we need this function
434+
* to exist to allow us to insert events in timeline order, which is our
435+
* best guess for Sync Order.
436+
* This is a copy of addEvent above, modified to allow inserting an event at
437+
* a specific index.
438+
*
439+
* @internal
440+
*/
441+
public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState): void {
442+
const timelineSet = this.getTimelineSet();
443+
444+
if (timelineSet.room) {
445+
EventTimeline.setEventMetadata(event, roomState, false);
446+
447+
// modify state but only on unfiltered timelineSets
448+
if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
449+
roomState.setStateEvents([event], {});
450+
// it is possible that the act of setting the state event means we
451+
// can set more metadata (specifically sender/target props), so try
452+
// it again if the prop wasn't previously set. It may also mean that
453+
// the sender/target is updated (if the event set was a room member event)
454+
// so we want to use the *updated* member (new avatar/name) instead.
455+
//
456+
// However, we do NOT want to do this on member events if we're going
457+
// back in time, else we'll set the .sender value for BEFORE the given
458+
// member event, whereas we want to set the .sender value for the ACTUAL
459+
// member event itself.
460+
if (!event.sender || event.getType() === EventType.RoomMember) {
461+
EventTimeline.setEventMetadata(event, roomState!, false);
462+
}
463+
}
464+
}
465+
466+
this.events.splice(insertIndex, 0, event); // insert element
467+
}
468+
430469
/**
431470
* Remove an event from the timeline
432471
*

src/models/thread.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,33 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
236236
}
237237
}
238238

239+
/**
240+
* TEMPORARY. Only call this when MSC3981 is not available, and we have some
241+
* late-arriving events to insert, because we recursively found them as part
242+
* of populating a thread. When we have MSC3981 we won't need it, because
243+
* they will all be supplied by the homeserver in one request, and they will
244+
* already be in the right order in that response.
245+
* This is a copy of addEventToTimeline above, modified to call
246+
* insertEventIntoTimeline so this event is inserted into our best guess of
247+
* the right place based on timestamp. (We should be using Sync Order but we
248+
* don't have it.)
249+
*
250+
* @internal
251+
*/
252+
public insertEventIntoTimeline(event: MatrixEvent): void {
253+
const eventId = event.getId();
254+
if (!eventId) {
255+
return;
256+
}
257+
if (this.findEventById(eventId)) {
258+
return;
259+
}
260+
this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState);
261+
262+
// As far as we know, timeline should always be the same as events
263+
this.timeline = this.events;
264+
}
265+
239266
public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {
240267
events.forEach((ev) => this.addEvent(ev, toStartOfTimeline, false));
241268
this.updateThreadMetadata();
@@ -281,7 +308,14 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
281308
*/
282309
this.replayEvents?.push(event);
283310
} else {
284-
this.addEventToTimeline(event, toStartOfTimeline);
311+
const recursionSupport =
312+
this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported;
313+
314+
if (recursionSupport === ServerSupport.Unsupported) {
315+
this.insertEventIntoTimeline(event);
316+
} else {
317+
this.addEventToTimeline(event, toStartOfTimeline);
318+
}
285319
}
286320
// Apply annotations and replace relations to the relations of the timeline only
287321
this.timelineSet.relations?.aggregateParentEvent(event);
@@ -460,7 +494,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
460494
// XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084
461495
private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> {
462496
const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported;
463-
if (recursionSupport !== ServerSupport.Unsupported) {
497+
if (recursionSupport === ServerSupport.Unsupported) {
464498
return Promise.all(
465499
events
466500
.filter((e) => e.isEncrypted())
@@ -473,6 +507,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
473507
.then((relations) => {
474508
if (relations.events.length) {
475509
event.makeReplaced(relations.events[0]);
510+
this.insertEventIntoTimeline(event);
476511
}
477512
})
478513
.catch((e) => {

0 commit comments

Comments
 (0)