diff --git a/src/client.ts b/src/client.ts index efd6f536ec3..54f75358141 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5243,6 +5243,10 @@ export class MatrixClient extends TypedEventEmitter = res.end; while (nextBatch) { - const resNewer = await this.fetchRelations( + const resNewer: IRelationsResponse = await this.fetchRelations( timelineSet.room.roomId, thread.id, THREAD_RELATION_TYPE.name, @@ -5532,7 +5536,6 @@ export class MatrixClient extends TypedEventEmitter { const mapper = this.getEventMapper(); const matrixEvents = res.chunk.map(mapper); @@ -6933,12 +6936,12 @@ export class MatrixClient extends TypedEventEmitter { - const fetchedEventType = this.getEncryptedIfNeededEventType(roomId, eventType); + const fetchedEventType = eventType ? this.getEncryptedIfNeededEventType(roomId, eventType) : null; const result = await this.fetchRelations( roomId, eventId, @@ -6962,7 +6965,7 @@ export class MatrixClient extends TypedEventEmitter e.getSender() === originalEvent.getSender()); } return { - originalEvent, + originalEvent: originalEvent ?? null, events, nextBatch: result.next_batch, prevBatch: result.prev_batch, diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 83a96b5b449..5ef710c37f0 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -297,8 +297,8 @@ export class EventTimelineSet extends TypedEventEmitter { } private onThreadNewReply(thread: Thread): void { - if (thread.length && thread.rootEvent) { - this.updateThreadRootEvents(thread, false); - } + this.updateThreadRootEvents(thread, false); } private onThreadDelete(thread: Thread): void { @@ -1957,9 +1955,11 @@ export class Room extends ReadReceipt { } private updateThreadRootEvents = (thread: Thread, toStartOfTimeline: boolean) => { - this.updateThreadRootEvent(this.threadsTimelineSets?.[0], thread, toStartOfTimeline); - if (thread.hasCurrentUserParticipated) { - this.updateThreadRootEvent(this.threadsTimelineSets?.[1], thread, toStartOfTimeline); + if (thread.length) { + this.updateThreadRootEvent(this.threadsTimelineSets?.[0], thread, toStartOfTimeline); + if (thread.hasCurrentUserParticipated) { + this.updateThreadRootEvent(this.threadsTimelineSets?.[1], thread, toStartOfTimeline); + } } }; @@ -1968,18 +1968,20 @@ export class Room extends ReadReceipt { thread: Thread, toStartOfTimeline: boolean, ) => { - if (Thread.hasServerSideSupport) { - timelineSet.addLiveEvent(thread.rootEvent, { - duplicateStrategy: DuplicateStrategy.Replace, - fromCache: false, - roomState: this.currentState, - }); - } else { - timelineSet.addEventToTimeline( - thread.rootEvent, - timelineSet.getLiveTimeline(), - { toStartOfTimeline }, - ); + if (timelineSet && thread.rootEvent) { + if (Thread.hasServerSideSupport) { + timelineSet.addLiveEvent(thread.rootEvent, { + duplicateStrategy: DuplicateStrategy.Replace, + fromCache: false, + roomState: this.currentState, + }); + } else { + timelineSet.addEventToTimeline( + thread.rootEvent, + timelineSet.getLiveTimeline(), + { toStartOfTimeline }, + ); + } } }; @@ -2020,15 +2022,16 @@ export class Room extends ReadReceipt { RoomEvent.Timeline, RoomEvent.TimelineReset, ]); + const isNewer = this.lastThread?.rootEvent + && rootEvent?.localTimestamp + && this.lastThread.rootEvent?.localTimestamp < rootEvent?.localTimestamp; - if (!this.lastThread || this.lastThread.rootEvent?.localTimestamp < rootEvent?.localTimestamp) { + if (!this.lastThread || isNewer) { this.lastThread = thread; } if (this.threadsReady) { - if (thread.length && thread.rootEvent) { - this.updateThreadRootEvents(thread, toStartOfTimeline); - } + this.updateThreadRootEvents(thread, toStartOfTimeline); } this.emit(ThreadEvent.New, thread, toStartOfTimeline); diff --git a/src/models/thread.ts b/src/models/thread.ts index 38c26d83239..19bf7415a07 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -234,6 +234,9 @@ export class Thread extends ReadReceipt { public async addEvent(event: MatrixEvent, toStartOfTimeline: boolean, emit = true): Promise { this.setEventMetadata(event); + const lastReply = this.lastReply(); + const isNewestReply = !lastReply || event.localTimestamp > lastReply?.localTimestamp; + // Add all incoming events to the thread's timeline set when there's no server support if (!Thread.hasServerSideSupport) { // all the relevant membership info to hydrate events with a sender @@ -243,16 +246,13 @@ export class Thread extends ReadReceipt { this.addEventToTimeline(event, toStartOfTimeline); this.client.decryptEventIfNeeded(event, {}); - } else if (!toStartOfTimeline && - this.initialEventsFetched && - event.localTimestamp > this.lastReply()?.localTimestamp - ) { + } else if (!toStartOfTimeline && this.initialEventsFetched && isNewestReply) { await this.fetchEditsWhereNeeded(event); this.addEventToTimeline(event, false); } else if (event.isRelation(RelationType.Annotation) || event.isRelation(RelationType.Replace)) { // Apply annotations and replace relations to the relations of the timeline only - this.timelineSet.relations.aggregateParentEvent(event); - this.timelineSet.relations.aggregateChildEvent(event, this.timelineSet); + this.timelineSet.relations?.aggregateParentEvent(event); + this.timelineSet.relations?.aggregateChildEvent(event, this.timelineSet); return; } @@ -268,12 +268,14 @@ export class Thread extends ReadReceipt { } } - public async processEvent(event: MatrixEvent): Promise { - this.setEventMetadata(event); - await this.fetchEditsWhereNeeded(event); + public async processEvent(event: Optional): Promise { + if (event) { + this.setEventMetadata(event); + await this.fetchEditsWhereNeeded(event); + } } - private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship { + private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined { return rootEvent?.getServerAggregatedRelation(THREAD_RELATION_TYPE.name); } @@ -286,7 +288,7 @@ export class Thread extends ReadReceipt { if (Thread.hasServerSideSupport && bundledRelationship) { this.replyCount = bundledRelationship.count; - this._currentUserParticipated = bundledRelationship.current_user_participated; + this._currentUserParticipated = bundledRelationship.current_user_participated ?? false; const mapper = this.client.getEventMapper(); this.lastEvent = mapper(bundledRelationship.latest_event); diff --git a/src/utils.ts b/src/utils.ts index dc109128f7e..6f3a47dbd45 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,6 +22,7 @@ limitations under the License. import unhomoglyph from "unhomoglyph"; import promiseRetry from "p-retry"; +import { Optional } from "matrix-events-sdk"; import type * as NodeCrypto from "crypto"; import { MatrixEvent } from "./models/event"; @@ -123,13 +124,17 @@ export function decodeParams(query: string): Record { * variables with. E.g. { "$bar": "baz" }. * @return {string} The result of replacing all template variables e.g. '/foo/baz'. */ -export function encodeUri(pathTemplate: string, variables: Record): string { +export function encodeUri(pathTemplate: string, variables: Record>): string { for (const key in variables) { if (!variables.hasOwnProperty(key)) { continue; } + const value = variables[key]; + if (value === undefined || value === null) { + continue; + } pathTemplate = pathTemplate.replace( - key, encodeURIComponent(variables[key]), + key, encodeURIComponent(value), ); } return pathTemplate;