From cefea750ff93704e318e78fc2121afc09d0864aa Mon Sep 17 00:00:00 2001 From: Germain Date: Fri, 9 Dec 2022 15:54:28 +0100 Subject: [PATCH 1/2] Fix infinite loop when restoring cached read receipts --- src/models/room.ts | 16 +++++----------- src/models/thread.ts | 31 ++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/models/room.ts b/src/models/room.ts index f81c63a1e91..22a2e4d3607 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 998c2156912..7fec1e638a8 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,32 @@ 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); } From 92a7076296927e39e2b076b08f3dd4bed57bc0ea Mon Sep 17 00:00:00 2001 From: Germain Date: Fri, 9 Dec 2022 16:07:28 +0100 Subject: [PATCH 2/2] lint fix --- src/models/room.ts | 2 +- src/models/thread.ts | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/models/room.ts b/src/models/room.ts index 22a2e4d3607..8b88b984226 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -2079,7 +2079,7 @@ export class Room extends ReadReceipt { room: this, client: this.client, pendingEventOrdering: this.opts.pendingEventOrdering, - receipts: this.cachedThreadReadReceipts.get(threadId) ?? [] + receipts: this.cachedThreadReadReceipts.get(threadId) ?? [], }); // All read receipts should now come down from sync, we do not need to keep diff --git a/src/models/thread.ts b/src/models/thread.ts index 7fec1e638a8..8237e0a5bc7 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -50,7 +50,7 @@ interface IThreadOpts { room: Room; client: MatrixClient; pendingEventOrdering?: PendingEventOrdering; - receipts?: { event: MatrixEvent, synthetic: boolean }[]; + receipts?: { event: MatrixEvent; synthetic: boolean }[]; } export enum FeatureSupport { @@ -291,22 +291,16 @@ export class Thread extends ReadReceipt { * 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 + * @param receipts - A collection of the receipts cached from initial sync */ - private processReceipts(receipts: { event: MatrixEvent, synthetic: boolean }[] = []): void { + 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, - ); + this.addReceiptToStructure(eventId, receiptType as ReceiptType, userId, receipt, synthetic); }); }); });