diff --git a/src/models/event.ts b/src/models/event.ts index cc4255888..e61c411c7 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -193,7 +193,9 @@ export class MatrixEvent extends EventEmitter { */ private txnId: string = null; - /** A reference to the event ID making the root of the thread + /** + * @experimental + * A reference to the thread this event belongs to */ private thread: Thread = null; @@ -391,6 +393,7 @@ export class MatrixEvent extends EventEmitter { } /** + * @experimental * Get the event ID of the replied event */ public get replyEventId(): string { @@ -1289,11 +1292,17 @@ export class MatrixEvent extends EventEmitter { return this.txnId; } + /** + * @experimental + */ public setThread(thread: Thread): void { this.thread = thread; this.reEmitter.reEmit(thread, ["Thread.ready", "Thread.update"]); } + /** + * @experimental + */ public getThread(): Thread { return this.thread; } diff --git a/src/models/room.ts b/src/models/room.ts index c415adf03..59ef279e6 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -146,6 +146,9 @@ export class Room extends EventEmitter { public oldState: RoomState; public currentState: RoomState; + /** + * @experimental + */ public threads = new Set(); /** @@ -860,7 +863,7 @@ export class Room extends EventEmitter { } /** - * Get an event which is stored in our unfiltered timeline set + * Get an event which is stored in our unfiltered timeline set or in a thread * * @param {string} eventId event ID to look for * @return {?module:models/event.MatrixEvent} the given event, or undefined if unknown @@ -1065,6 +1068,9 @@ export class Room extends EventEmitter { ); } + /** + * @experimental + */ public addThread(thread: Thread): Set { this.threads.add(thread); if (!thread.ready) { @@ -1075,16 +1081,28 @@ export class Room extends EventEmitter { return this.threads; } + /** + * @experimental + */ public getThread(eventId: string): Thread { return this.getThreads().find(thread => { return thread.id === eventId; }); } + /** + * @experimental + */ public getThreads(): Thread[] { return Array.from(this.threads.values()); } + /** + * Two threads starting from a different child event can end up + * with the same event root. This method ensures that the duplicates + * are removed + * @experimental + */ private dedupeThreads = (readyThread): void => { const threads = Array.from(this.threads); if (threads.includes(readyThread)) { @@ -1274,6 +1292,10 @@ export class Room extends EventEmitter { } } + /** + * Add an event to a thread's timeline. Will fire "Thread.update" + * @experimental + */ public addThreadedEvent(event: MatrixEvent): void { if (event.getUnsigned().transaction_id) { const existingEvent = this.txnToEvent[event.getUnsigned().transaction_id]; diff --git a/src/models/thread.ts b/src/models/thread.ts index 1932120b3..043ff7dc7 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -20,14 +20,20 @@ import { MatrixEvent } from "./event"; import { EventTimelineSet } from './event-timeline-set'; import { Room } from './room'; +/** + * @experimental + */ export class Thread extends EventEmitter { + /** + * A reference to the event ID at the top of the thread + */ private root: string; + /** + * A reference to all the events ID at the bottom of the threads + */ public tail = new Set(); - private events = new Map(); private _timelineSet: EventTimelineSet; - private decrypted = false; - constructor( events: MatrixEvent[] = [], public readonly room: Room, @@ -44,10 +50,11 @@ export class Thread extends EventEmitter { /** * Add an event to the thread and updates * the tail/root references if needed + * Will fire "Thread.update" * @param event The event to add */ public async addEvent(event: MatrixEvent): Promise { - if (this.events.has(event.getId()) || event.status !== null) { + if (this._timelineSet.findEventById(event.getId()) || event.status !== null) { return; } @@ -56,12 +63,11 @@ export class Thread extends EventEmitter { } this.tail.add(event.getId()); - if (!event.replyEventId || !this.events.has(event.replyEventId)) { + if (!event.replyEventId || !this._timelineSet.findEventById(event.replyEventId)) { this.root = event.getId(); } event.setThread(this); - this.events.set(event.getId(), event); this._timelineSet.addLiveEvent(event); if (this.ready) { @@ -72,6 +78,11 @@ export class Thread extends EventEmitter { } } + /** + * Completes the reply chain with all events + * missing from the current sync data + * Will fire "Thread.ready" + */ public async fetchReplyChain(): Promise { if (!this.ready) { let mxEvent = this.room.findEventById(this.rootEvent.replyEventId); @@ -94,14 +105,15 @@ export class Thread extends EventEmitter { private async decryptEvents(): Promise { await Promise.allSettled( - Array.from(this.events.values()).map(event => { + Array.from(this._timelineSet.getLiveTimeline().getEvents()).map(event => { return this.client.decryptEventIfNeeded(event, {}); }), ); - - this.decrypted = true; } + /** + * Fetches an event over the network + */ private async fetchEventById(roomId: string, eventId: string): Promise { const response = await this.client.http.authedRequest( undefined, @@ -111,10 +123,16 @@ export class Thread extends EventEmitter { return new MatrixEvent(response); } + /** + * Finds an event by ID in the current thread + */ public findEventById(eventId: string) { - return this.events.get(eventId); + return this._timelineSet.findEventById(eventId); } + /** + * Determines thread's ready status + */ public get ready(): boolean { return this.rootEvent.replyEventId === undefined; } @@ -126,8 +144,11 @@ export class Thread extends EventEmitter { return this.root; } + /** + * The thread root event + */ public get rootEvent(): MatrixEvent { - return this.events.get(this.root); + return this.findEventById(this.root); } /** @@ -142,16 +163,22 @@ export class Thread extends EventEmitter { */ public get participants(): Set { const participants = new Set(); - this.events.forEach(event => { + this._timelineSet.getLiveTimeline().getEvents().forEach(event => { participants.add(event.getSender()); }); return participants; } + /** + * A read-only getter to access the timeline set + */ public get timelineSet(): EventTimelineSet { return this._timelineSet; } + /** + * A getter for the last event added to the thread + */ public get replyToEvent(): MatrixEvent { const events = this._timelineSet.getLiveTimeline().getEvents(); return events[events.length -1]; diff --git a/src/sync.ts b/src/sync.ts index 29baab39a..f4655d872 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -307,6 +307,11 @@ export class SyncApi { }); } + /** + * Split events between the ones that will end up in the main + * room timeline versus the one that need to be processed in a thread + * @experimental + */ public partitionThreadedEvents(events: MatrixEvent[]): [MatrixEvent[], MatrixEvent[]] { return events.reduce((memo, event: MatrixEvent) => { memo[event.replyEventId ? 1 : 0].push(event); @@ -1700,6 +1705,9 @@ export class SyncApi { room.addLiveEvents(timelineEventList || [], null, fromCache); } + /** + * @experimental + */ private processThreadEvents(room: Room, threadedEvents: MatrixEvent[]): void { threadedEvents.forEach(event => { room.addThreadedEvent(event);