@@ -90,7 +90,40 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
90
90
91
91
private reEmitter : TypedReEmitter < ThreadEmittedEvents , ThreadEventHandlerMap > ;
92
92
93
+ /**
94
+ * The last event in this thread, if we don't yet have this in the timeline.
95
+ *
96
+ * When we run {@link processRootEvent} (which I think happens during the
97
+ * setting-up of the thread), we set this to the event pointed to by the
98
+ * server in `latest_event` [1] that came through with the thread root.
99
+ *
100
+ * [1]: https://spec.matrix.org/v1.8/client-server-api/#server-side-aggregation-of-mthread-relationships
101
+ *
102
+ * Later, when we have populated the timeline, this is set to undefined, so
103
+ * that methods like {@link replyToEvent} fall through to use lastReply,
104
+ * which looks in the timeline for the latest event that is a "thread reply"
105
+ * i.e. directly refers to the thread root with an m.thread relation.
106
+ *
107
+ * So it looks like this is only really relevant when initialEventsFetched
108
+ * is false, because as soon as the initial events have been fetched, we
109
+ * should have a timeline (I think).
110
+ *
111
+ * If all replies in this thread are redacted, this is set to the root
112
+ * event. I'm not clear what the meaning of this is, since usually after the
113
+ * initial events have been fetched, lastEvent should be undefined.
114
+ * In fact, the whole usage inside onRedaction looks suspect - it may be
115
+ * that we were thinking lastEvent always refers to the actual last event,
116
+ * but it only does so before initialEventsFetched becomes true.
117
+ *
118
+ * The usage of lastEvent inside {@link onEcho} looks suspicious, since I'd
119
+ * think we probably mean {@link replyToEvent} there - we are trying not to
120
+ * echo a duplicate event, and we probably want that behaviour even after
121
+ * initialEventsFetched has become true.
122
+ *
123
+ * -- andyb
124
+ */
93
125
private lastEvent : MatrixEvent | undefined ;
126
+
94
127
private replyCount = 0 ;
95
128
private lastPendingEvent : MatrixEvent | undefined ;
96
129
private pendingReplyCount = 0 ;
@@ -324,25 +357,34 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
324
357
* @param emit - whether to emit the Update event if the thread was updated or not.
325
358
*/
326
359
public async addEvent ( event : MatrixEvent , toStartOfTimeline : boolean , emit = true ) : Promise < void > {
360
+ // Modify this event to point at our room's state, and mark its thread
361
+ // as this.
327
362
this . setEventMetadata ( event ) ;
328
363
364
+ // Decide whether this event is going to be added at the end of the timeline.
329
365
const lastReply = this . lastReply ( ) ;
330
366
const isNewestReply = ! lastReply || event . localTimestamp >= lastReply ! . localTimestamp ;
331
367
332
- // Add all incoming events to the thread's timeline set when there's no server support
333
368
if ( ! Thread . hasServerSideSupport ) {
334
- // all the relevant membership info to hydrate events with a sender
335
- // is held in the main room timeline
336
- // We want to fetch the room state from there and pass it down to this thread
337
- // timeline set to let it reconcile an event with its relevant RoomMember
369
+ // When there's no server-side support, just add it to the end of the timeline.
338
370
this . addEventToTimeline ( event , toStartOfTimeline ) ;
339
-
340
371
this . client . decryptEventIfNeeded ( event , { } ) ;
341
372
} else if ( ! toStartOfTimeline && this . initialEventsFetched && isNewestReply ) {
373
+ // When we've asked for the event to be added to the end, and we're
374
+ // not in the initial state, and this event belongs at the end, add it.
342
375
this . addEventToTimeline ( event , false ) ;
343
376
this . fetchEditsWhereNeeded ( event ) ;
344
377
} else if ( event . isRelation ( RelationType . Annotation ) || event . isRelation ( RelationType . Replace ) ) {
378
+ // If this event is not a direct member of the thread, but is a
379
+ // reference to something that is, then we have two cases:
380
+
345
381
if ( ! this . initialEventsFetched ) {
382
+ // Case 1: we haven't yet fetched events from the server. In
383
+ // this case, when we do, the events we get back might only be
384
+ // the first-order ones, so this event (which is second-order -
385
+ // a reference to something directly in the thread) needs to be
386
+ // kept so we can replay it when the first-order ones turn up.
387
+
346
388
/**
347
389
* A thread can be fully discovered via a single sync response
348
390
* And when that's the case we still ask the server to do an initialisation
@@ -354,6 +396,23 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
354
396
*/
355
397
this . replayEvents ?. push ( event ) ;
356
398
} else {
399
+ // Case 2: this is happening later, and we have a timeline. In
400
+ // this case, these events might be out-of order.
401
+ //
402
+ // Specifically, if the server doesn't support recursion, so we
403
+ // only get these events through sync, they might be coming
404
+ // later than the first-order ones, so we insert them based on
405
+ // timestamp (despite the problems with this documented in
406
+ // #3325).
407
+ //
408
+ // If the server does support recursion, we should have got all
409
+ // the interspersed events from the server when we fetched the
410
+ // initial events, so if they are coming via sync they should be
411
+ // the latest ones, so we can add them as normal.
412
+ //
413
+ // (Note that both insertEventIntoTimeline and addEventToTimeline
414
+ // do nothing if we have seen this event before.)
415
+
357
416
const recursionSupport =
358
417
this . client . canSupport . get ( Feature . RelationsRecursion ) ?? ServerSupport . Unsupported ;
359
418
0 commit comments