From fc5f0e8047e2ccf349db05167b81ae59e430abf0 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 3 Mar 2022 15:21:17 +0000 Subject: [PATCH] Fix message ordering in threads (#2215) --- src/client.ts | 17 ++++++++++------- src/event-mapper.ts | 6 ++++++ src/models/room.ts | 4 ++-- src/models/thread.ts | 36 +++++++++++++++++++++--------------- src/sync.ts | 14 +++++++++----- 5 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/client.ts b/src/client.ts index 0fd099c71e2..b56c2052bb9 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5197,7 +5197,7 @@ export class MatrixClient extends TypedEventEmitter { - threadedEvents.sort((a, b) => a.getTs() - b.getTs()); + public async processThreadEvents( + room: Room, + threadedEvents: MatrixEvent[], + toStartOfTimeline: boolean, + ): Promise { for (const event of threadedEvents) { - await room.addThreadedEvent(event); + await room.addThreadedEvent(event, toStartOfTimeline); } } diff --git a/src/event-mapper.ts b/src/event-mapper.ts index e6942cbd47b..53873d11333 100644 --- a/src/event-mapper.ts +++ b/src/event-mapper.ts @@ -30,6 +30,12 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event function mapper(plainOldJsObject: Partial) { const event = new MatrixEvent(plainOldJsObject); + + const room = client.getRoom(event.getRoomId()); + if (room?.threads.has(event.getId())) { + event.setThread(room.threads.get(event.getId())); + } + if (event.isEncrypted()) { if (!preventReEmit) { client.reEmitter.reEmit(event, [ diff --git a/src/models/room.ts b/src/models/room.ts index e8aad9292f0..7b019190cdf 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -1406,10 +1406,10 @@ export class Room extends TypedEventEmitter * Add an event to a thread's timeline. Will fire "Thread.update" * @experimental */ - public async addThreadedEvent(event: MatrixEvent): Promise { + public async addThreadedEvent(event: MatrixEvent, toStartOfTimeline: boolean): Promise { let thread = this.findThreadForEvent(event); if (thread) { - thread.addEvent(event); + thread.addEvent(event, toStartOfTimeline); } else { const events = [event]; let rootEvent = this.findEventById(event.threadRootId); diff --git a/src/models/thread.ts b/src/models/thread.ts index 94f12a3ebb3..5255914b248 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -113,7 +113,7 @@ export class Thread extends TypedEventEmitter { } this.initialiseThread(this.rootEvent); - opts?.initialEvents?.forEach(event => this.addEvent(event)); + opts?.initialEvents?.forEach(event => this.addEvent(event, false)); this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho); this.room.on(RoomEvent.Timeline, this.onEcho); @@ -158,7 +158,7 @@ export class Thread extends TypedEventEmitter { * @param {boolean} toStartOfTimeline whether the event is being added * to the start (and not the end) of the timeline. */ - public async addEvent(event: MatrixEvent, toStartOfTimeline = false): Promise { + public async addEvent(event: MatrixEvent, toStartOfTimeline: boolean): Promise { if (Thread.hasServerSideSupport === undefined) { await Thread.serverSupportPromise; } @@ -232,22 +232,28 @@ export class Thread extends TypedEventEmitter { this.setEventMetadata(event); this.lastEvent = event; } - - if (!bundledRelationship && rootEvent) { - this.addEvent(rootEvent); - } } - public async fetchInitialEvents(): Promise { + public async fetchInitialEvents(): Promise<{ + originalEvent: MatrixEvent; + events: MatrixEvent[]; + nextBatch?: string; + prevBatch?: string; + } | null> { if (Thread.hasServerSideSupport === undefined) { await Thread.serverSupportPromise; } + + if (!Thread.hasServerSideSupport) { + this.initialEventsFetched = true; + return null; + } try { - await this.fetchEvents(); + const response = await this.fetchEvents(); this.initialEventsFetched = true; - return true; + return response; } catch (e) { - return false; + return null; } } @@ -317,7 +323,7 @@ export class Thread extends TypedEventEmitter { nextBatch?: string; prevBatch?: string; }> { - if (Thread.serverSupportPromise) { + if (Thread.hasServerSideSupport === undefined) { await Thread.serverSupportPromise; } @@ -337,13 +343,13 @@ export class Thread extends TypedEventEmitter { // When there's no nextBatch returned with a `from` request we have reached // the end of the thread, and therefore want to return an empty one if (!opts.to && !nextBatch) { - events = [originalEvent, ...events]; + events = [...events, originalEvent]; } - for (const event of events) { - await this.client.decryptEventIfNeeded(event); + await Promise.all(events.map(event => { this.setEventMetadata(event); - } + return this.client.decryptEventIfNeeded(event); + })); const prependEvents = !opts.direction || opts.direction === Direction.Backward; diff --git a/src/sync.ts b/src/sync.ts index f275e4f34b3..afb66262705 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -320,7 +320,7 @@ export class SyncApi { EventTimeline.BACKWARDS); this.processRoomEvents(room, stateEvents, timelineEvents); - await this.processThreadEvents(room, threadedEvents); + await this.processThreadEvents(room, threadedEvents, false); room.recalculate(); client.store.storeRoom(room); @@ -1317,7 +1317,7 @@ export class SyncApi { const [timelineEvents, threadedEvents] = this.client.partitionThreadedEvents(events); this.processRoomEvents(room, stateEvents, timelineEvents, syncEventData.fromCache); - await this.processThreadEvents(room, threadedEvents); + await this.processThreadEvents(room, threadedEvents, false); // set summary after processing events, // because it will trigger a name calculation @@ -1385,7 +1385,7 @@ export class SyncApi { const [timelineEvents, threadedEvents] = this.client.partitionThreadedEvents(events); this.processRoomEvents(room, stateEvents, timelineEvents); - await this.processThreadEvents(room, threadedEvents); + await this.processThreadEvents(room, threadedEvents, false); room.addAccountData(accountDataEvents); room.recalculate(); @@ -1730,8 +1730,12 @@ export class SyncApi { /** * @experimental */ - private processThreadEvents(room: Room, threadedEvents: MatrixEvent[]): Promise { - return this.client.processThreadEvents(room, threadedEvents); + private processThreadEvents( + room: Room, + threadedEvents: MatrixEvent[], + toStartOfTimeline: boolean, + ): Promise { + return this.client.processThreadEvents(room, threadedEvents, toStartOfTimeline); } // extractRelatedEvents(event: MatrixEvent, events: MatrixEvent[], relatedEvents: MatrixEvent[] = []): MatrixEvent[] {