diff --git a/src/models/room.ts b/src/models/room.ts index f81c63a1e..8b88b9842 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -2079,8 +2079,13 @@ export class Room extends ReadReceipt { room: this, client: this.client, pendingEventOrdering: this.opts.pendingEventOrdering, + receipts: this.cachedThreadReadReceipts.get(threadId) ?? [], }); + // All read receipts should now come down from sync, we do not need to keep + // a reference to the cached receipts anymore. + this.cachedThreadReadReceipts.delete(threadId); + // This is necessary to be able to jump to events in threads: // If we jump to an event in a thread where neither the event, nor the root, // nor any thread event are loaded yet, we'll load the event as well as the thread root, create the thread, @@ -2110,17 +2115,6 @@ export class Room extends ReadReceipt { if (this.threadsReady) { this.updateThreadRootEvents(thread, toStartOfTimeline, false); } - - // Pulling all the cached thread read receipts we've discovered when we - // did an initial sync, and applying them to the thread now that it exists - // on the client side - if (this.cachedThreadReadReceipts.has(threadId)) { - for (const { event, synthetic } of this.cachedThreadReadReceipts.get(threadId)!) { - this.addReceipt(event, synthetic); - } - this.cachedThreadReadReceipts.delete(threadId); - } - this.emit(ThreadEvent.New, thread, toStartOfTimeline); return thread; diff --git a/src/models/thread.ts b/src/models/thread.ts index 998c21569..8237e0a5b 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -27,7 +27,7 @@ import { RoomState } from "./room-state"; import { ServerControlledNamespacedValue } from "../NamespacedValue"; import { logger } from "../logger"; import { ReadReceipt } from "./read-receipt"; -import { ReceiptType } from "../@types/read_receipts"; +import { Receipt, ReceiptContent, ReceiptType } from "../@types/read_receipts"; export enum ThreadEvent { New = "Thread.new", @@ -50,6 +50,7 @@ interface IThreadOpts { room: Room; client: MatrixClient; pendingEventOrdering?: PendingEventOrdering; + receipts?: { event: MatrixEvent; synthetic: boolean }[]; } export enum FeatureSupport { @@ -127,6 +128,8 @@ export class Thread extends ReadReceipt { this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho); this.timelineSet.on(RoomEvent.Timeline, this.onTimelineEvent); + this.processReceipts(opts.receipts); + // even if this thread is thought to be originating from this client, we initialise it as we may be in a // gappy sync and a thread around this event may already exist. this.updateThreadMetadata(); @@ -284,6 +287,26 @@ export class Thread extends ReadReceipt { this.timeline = this.events; } + /** + * Processes the receipts that were caught during initial sync + * When clients become aware of a thread, they try to retrieve those read receipts + * and apply them to the current thread + * @param receipts - A collection of the receipts cached from initial sync + */ + private processReceipts(receipts: { event: MatrixEvent; synthetic: boolean }[] = []): void { + for (const { event, synthetic } of receipts) { + const content = event.getContent(); + Object.keys(content).forEach((eventId: string) => { + Object.keys(content[eventId]).forEach((receiptType: ReceiptType | string) => { + Object.keys(content[eventId][receiptType]).forEach((userId: string) => { + const receipt = content[eventId][receiptType][userId] as Receipt; + this.addReceiptToStructure(eventId, receiptType as ReceiptType, userId, receipt, synthetic); + }); + }); + }); + } + } + private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined { return rootEvent?.getServerAggregatedRelation(THREAD_RELATION_TYPE.name); }