@@ -19,7 +19,7 @@ limitations under the License.
1919 * @module client
2020 */
2121
22- import { EmoteEvent , IPartialEvent , MessageEvent , NoticeEvent } from "matrix-events-sdk" ;
22+ import { EmoteEvent , IPartialEvent , MessageEvent , NoticeEvent , Optional } from "matrix-events-sdk" ;
2323
2424import { ISyncStateData , SyncApi , SyncState } from "./sync" ;
2525import {
@@ -33,7 +33,7 @@ import {
3333} from "./models/event" ;
3434import { StubStore } from "./store/stub" ;
3535import { CallEvent , CallEventHandlerMap , createNewMatrixCall , MatrixCall , supportsMatrixCall } from "./webrtc/call" ;
36- import { Filter , IFilterDefinition } from "./filter" ;
36+ import { Filter , IFilterDefinition , IRoomEventFilter } from "./filter" ;
3737import { CallEventHandlerEvent , CallEventHandler , CallEventHandlerEventHandlerMap } from './webrtc/callEventHandler' ;
3838import * as utils from './utils' ;
3939import { sleep } from './utils' ;
@@ -591,6 +591,13 @@ interface IMessagesResponse {
591591 state : IStateEvent [ ] ;
592592}
593593
594+ interface IThreadedMessagesResponse {
595+ prev_batch : string ;
596+ next_batch : string ;
597+ chunk : IRoomEvent [ ] ;
598+ state : IStateEvent [ ] ;
599+ }
600+
594601export interface IRequestTokenResponse {
595602 sid : string ;
596603 submit_url ?: string ;
@@ -5343,13 +5350,17 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
53435350 * @return {Promise } Resolves:
53445351 * {@link module:models/event-timeline~EventTimeline} including the given event
53455352 */
5346- public async getEventTimeline ( timelineSet : EventTimelineSet , eventId : string ) : Promise < EventTimeline | undefined > {
5353+ public async getEventTimeline ( timelineSet : EventTimelineSet , eventId : string ) : Promise < Optional < EventTimeline > > {
53475354 // don't allow any timeline support unless it's been enabled.
53485355 if ( ! this . timelineSupport ) {
53495356 throw new Error ( "timeline support is disabled. Set the 'timelineSupport'" +
53505357 " parameter to true when creating MatrixClient to enable it." ) ;
53515358 }
53525359
5360+ if ( ! timelineSet . room ) {
5361+ throw new Error ( "getEventTimeline only supports room timelines" ) ;
5362+ }
5363+
53535364 if ( timelineSet . getTimelineForEvent ( eventId ) ) {
53545365 return timelineSet . getTimelineForEvent ( eventId ) ;
53555366 }
@@ -5361,7 +5372,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
53615372 } ,
53625373 ) ;
53635374
5364- let params : Record < string , string | string [ ] > = undefined ;
5375+ let params : Record < string , string | string [ ] > | undefined = undefined ;
53655376 if ( this . clientOpts . lazyLoadMembers ) {
53665377 params = { filter : JSON . stringify ( Filter . LAZY_LOADING_MESSAGES_FILTER ) } ;
53675378 }
@@ -5454,27 +5465,36 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
54545465 * @return {Promise } Resolves:
54555466 * {@link module:models/event-timeline~EventTimeline} timeline with the latest events in the room
54565467 */
5457- public async getLatestTimeline ( timelineSet : EventTimelineSet ) : Promise < EventTimeline > {
5468+ public async getLatestTimeline ( timelineSet : EventTimelineSet ) : Promise < Optional < EventTimeline > > {
54585469 // don't allow any timeline support unless it's been enabled.
54595470 if ( ! this . timelineSupport ) {
54605471 throw new Error ( "timeline support is disabled. Set the 'timelineSupport'" +
54615472 " parameter to true when creating MatrixClient to enable it." ) ;
54625473 }
54635474
5464- const messagesPath = utils . encodeUri (
5465- "/rooms/$roomId/messages" , {
5466- $roomId : timelineSet . room . roomId ,
5467- } ,
5468- ) ;
5469-
5470- const params : Record < string , string | string [ ] > = {
5471- dir : 'b' ,
5472- } ;
5473- if ( this . clientOpts . lazyLoadMembers ) {
5474- params . filter = JSON . stringify ( Filter . LAZY_LOADING_MESSAGES_FILTER ) ;
5475+ if ( ! timelineSet . room ) {
5476+ throw new Error ( "getLatestTimeline only supports room timelines" ) ;
54755477 }
54765478
5477- const res = await this . http . authedRequest < IMessagesResponse > ( undefined , Method . Get , messagesPath , params ) ;
5479+ let res : IMessagesResponse ;
5480+ const roomId = timelineSet . room ?. roomId ;
5481+ if ( timelineSet . isThreadTimeline ) {
5482+ res = await this . createThreadListMessagesRequest (
5483+ roomId ,
5484+ null ,
5485+ 1 ,
5486+ Direction . Backward ,
5487+ timelineSet . getFilter ( ) ,
5488+ ) ;
5489+ } else {
5490+ res = await this . createMessagesRequest (
5491+ roomId ,
5492+ null ,
5493+ 1 ,
5494+ Direction . Backward ,
5495+ timelineSet . getFilter ( ) ,
5496+ ) ;
5497+ }
54785498 const event = res . chunk ?. [ 0 ] ;
54795499 if ( ! event ) {
54805500 throw new Error ( "No message returned from /messages when trying to construct getLatestTimeline" ) ;
@@ -5514,7 +5534,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
55145534 params . from = fromToken ;
55155535 }
55165536
5517- let filter = null ;
5537+ let filter : IRoomEventFilter | null = null ;
55185538 if ( this . clientOpts . lazyLoadMembers ) {
55195539 // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
55205540 // so the timelineFilter doesn't get written into it below
@@ -5532,6 +5552,67 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
55325552 return this . http . authedRequest ( undefined , Method . Get , path , params ) ;
55335553 }
55345554
5555+ /**
5556+ * Makes a request to /messages with the appropriate lazy loading filter set.
5557+ * XXX: if we do get rid of scrollback (as it's not used at the moment),
5558+ * we could inline this method again in paginateEventTimeline as that would
5559+ * then be the only call-site
5560+ * @param {string } roomId
5561+ * @param {string } fromToken
5562+ * @param {number } limit the maximum amount of events the retrieve
5563+ * @param {string } dir 'f' or 'b'
5564+ * @param {Filter } timelineFilter the timeline filter to pass
5565+ * @return {Promise }
5566+ */
5567+ // XXX: Intended private, used by room.fetchRoomThreads
5568+ public createThreadListMessagesRequest (
5569+ roomId : string ,
5570+ fromToken : string | null ,
5571+ limit = 30 ,
5572+ dir = Direction . Backward ,
5573+ timelineFilter ?: Filter ,
5574+ ) : Promise < IMessagesResponse > {
5575+ const path = utils . encodeUri ( "/rooms/$roomId/threads" , { $roomId : roomId } ) ;
5576+
5577+ const params : Record < string , string > = {
5578+ limit : limit . toString ( ) ,
5579+ dir : dir ,
5580+ include : 'all' ,
5581+ } ;
5582+
5583+ if ( fromToken ) {
5584+ params . from = fromToken ;
5585+ }
5586+
5587+ let filter : IRoomEventFilter | null = null ;
5588+ if ( this . clientOpts . lazyLoadMembers ) {
5589+ // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
5590+ // so the timelineFilter doesn't get written into it below
5591+ filter = Object . assign ( { } , Filter . LAZY_LOADING_MESSAGES_FILTER ) ;
5592+ }
5593+ if ( timelineFilter ) {
5594+ // XXX: it's horrific that /messages' filter parameter doesn't match
5595+ // /sync's one - see https://matrix.org/jira/browse/SPEC-451
5596+ filter = filter || { } ;
5597+ Object . assign ( filter , timelineFilter . getRoomTimelineFilterComponent ( ) ?. toJSON ( ) ) ;
5598+ }
5599+ if ( filter ) {
5600+ params . filter = JSON . stringify ( filter ) ;
5601+ }
5602+
5603+ const opts : { prefix ?: string } = { } ;
5604+ if ( Thread . hasServerSideListSupport === FeatureSupport . Experimental ) {
5605+ opts . prefix = "/_matrix/client/unstable/org.matrix.msc3856" ;
5606+ }
5607+
5608+ return this . http . authedRequest < IThreadedMessagesResponse > ( undefined , Method . Get , path , params , undefined , opts )
5609+ . then ( res => ( {
5610+ ...res ,
5611+ start : res . prev_batch ,
5612+ end : res . next_batch ,
5613+ } ) ) ;
5614+ }
5615+
55355616 /**
55365617 * Take an EventTimeline, and back/forward-fill results.
55375618 *
@@ -5547,6 +5628,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
55475628 */
55485629 public paginateEventTimeline ( eventTimeline : EventTimeline , opts : IPaginateOpts ) : Promise < boolean > {
55495630 const isNotifTimeline = ( eventTimeline . getTimelineSet ( ) === this . notifTimelineSet ) ;
5631+ const room = this . getRoom ( eventTimeline . getRoomId ( ) ) ;
5632+ const isThreadTimeline = eventTimeline . getTimelineSet ( ) . isThreadTimeline ;
55505633
55515634 // TODO: we should implement a backoff (as per scrollback()) to deal more
55525635 // nicely with HTTP errors.
@@ -5580,15 +5663,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
55805663 only : 'highlight' ,
55815664 } ;
55825665
5583- if ( token !== "end" ) {
5666+ if ( token && token !== "end" ) {
55845667 params . from = token ;
55855668 }
55865669
55875670 promise = this . http . authedRequest < INotificationsResponse > (
55885671 undefined , Method . Get , path , params ,
55895672 ) . then ( async ( res ) => {
55905673 const token = res . next_token ;
5591- const matrixEvents = [ ] ;
5674+ const matrixEvents : MatrixEvent [ ] = [ ] ;
55925675
55935676 for ( let i = 0 ; i < res . notifications . length ; i ++ ) {
55945677 const notification = res . notifications [ i ] ;
@@ -5612,13 +5695,48 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
56125695 if ( backwards && ! res . next_token ) {
56135696 eventTimeline . setPaginationToken ( null , dir ) ;
56145697 }
5615- return res . next_token ? true : false ;
5698+ return Boolean ( res . next_token ) ;
5699+ } ) . finally ( ( ) => {
5700+ eventTimeline . paginationRequests [ dir ] = null ;
5701+ } ) ;
5702+ eventTimeline . paginationRequests [ dir ] = promise ;
5703+ } else if ( isThreadTimeline ) {
5704+ if ( ! room ) {
5705+ throw new Error ( "Unknown room " + eventTimeline . getRoomId ( ) ) ;
5706+ }
5707+
5708+ promise = this . createThreadListMessagesRequest (
5709+ eventTimeline . getRoomId ( ) ,
5710+ token ,
5711+ opts . limit ,
5712+ dir ,
5713+ eventTimeline . getFilter ( ) ,
5714+ ) . then ( ( res ) => {
5715+ if ( res . state ) {
5716+ const roomState = eventTimeline . getState ( dir ) ;
5717+ const stateEvents = res . state . map ( this . getEventMapper ( ) ) ;
5718+ roomState . setUnknownStateEvents ( stateEvents ) ;
5719+ }
5720+ const token = res . end ;
5721+ const matrixEvents = res . chunk . map ( this . getEventMapper ( ) ) ;
5722+
5723+ const timelineSet = eventTimeline . getTimelineSet ( ) ;
5724+ timelineSet . addEventsToTimeline ( matrixEvents , backwards , eventTimeline , token ) ;
5725+ this . processBeaconEvents ( room , matrixEvents ) ;
5726+ this . processThreadRoots ( room , matrixEvents , backwards ) ;
5727+
5728+ // if we've hit the end of the timeline, we need to stop trying to
5729+ // paginate. We need to keep the 'forwards' token though, to make sure
5730+ // we can recover from gappy syncs.
5731+ if ( backwards && res . end == res . start ) {
5732+ eventTimeline . setPaginationToken ( null , dir ) ;
5733+ }
5734+ return res . end != res . start ;
56165735 } ) . finally ( ( ) => {
56175736 eventTimeline . paginationRequests [ dir ] = null ;
56185737 } ) ;
56195738 eventTimeline . paginationRequests [ dir ] = promise ;
56205739 } else {
5621- const room = this . getRoom ( eventTimeline . getRoomId ( ) ) ;
56225740 if ( ! room ) {
56235741 throw new Error ( "Unknown room " + eventTimeline . getRoomId ( ) ) ;
56245742 }
@@ -5639,9 +5757,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
56395757 const matrixEvents = res . chunk . map ( this . getEventMapper ( ) ) ;
56405758
56415759 const timelineSet = eventTimeline . getTimelineSet ( ) ;
5642- const [ timelineEvents , threadedEvents ] = timelineSet . room . partitionThreadedEvents ( matrixEvents ) ;
5760+ const [ timelineEvents , threadedEvents ] = room . partitionThreadedEvents ( matrixEvents ) ;
56435761 timelineSet . addEventsToTimeline ( timelineEvents , backwards , eventTimeline , token ) ;
5644- this . processBeaconEvents ( timelineSet . room , timelineEvents ) ;
5762+ this . processBeaconEvents ( room , timelineEvents ) ;
56455763 this . processThreadEvents ( room , threadedEvents , backwards ) ;
56465764
56475765 const atEnd = res . end === undefined || res . end === res . start ;
@@ -9183,6 +9301,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
91839301 room . processThreadedEvents ( threadedEvents , toStartOfTimeline ) ;
91849302 }
91859303
9304+ /**
9305+ * @experimental
9306+ */
9307+ public processThreadRoots ( room : Room , threadedEvents : MatrixEvent [ ] , toStartOfTimeline : boolean ) : void {
9308+ room . processThreadRoots ( threadedEvents , toStartOfTimeline ) ;
9309+ }
9310+
91869311 public processBeaconEvents (
91879312 room ?: Room ,
91889313 events ?: MatrixEvent [ ] ,
0 commit comments