diff --git a/src/client.ts b/src/client.ts index 926f21c97..aff7be4d1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4839,12 +4839,11 @@ export class MatrixClient extends EventEmitter { * null. * @return {module:http-api.MatrixError} Rejects: with an error response. */ - public scrollback(room: Room, limit: number, callback?: Callback): Promise { + public scrollback(room: Room, limit = 30, callback?: Callback): Promise { if (utils.isFunction(limit)) { callback = limit as any as Callback; // legacy limit = undefined; } - limit = limit || 30; let timeToWaitMs = 0; let info = this.ongoingScrollbacks[room.roomId] || {}; @@ -5025,7 +5024,7 @@ export class MatrixClient extends EventEmitter { // XXX: Intended private, used in code. public createMessagesRequest( roomId: string, - fromToken: string, + fromToken: string | null, limit = 30, dir: Direction, timelineFilter?: Filter, @@ -5033,11 +5032,14 @@ export class MatrixClient extends EventEmitter { const path = utils.encodeUri("/rooms/$roomId/messages", { $roomId: roomId }); const params: Record = { - from: fromToken, limit: limit.toString(), - dir, + dir: dir, }; + if (fromToken) { + params.from = fromToken; + } + let filter = null; if (this.clientOpts.lazyLoadMembers) { // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER, @@ -5086,11 +5088,6 @@ export class MatrixClient extends EventEmitter { const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS; const token = eventTimeline.getPaginationToken(dir); - if (!token) { - // no token - no results. - return Promise.resolve(false); - } - const pendingRequest = eventTimeline.paginationRequests[dir]; if (pendingRequest) { diff --git a/src/filter-component.ts b/src/filter-component.ts index e5cdc2ec3..16665acb2 100644 --- a/src/filter-component.ts +++ b/src/filter-component.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { UNSTABLE_FILTER_RELATION_SENDERS, UNSTABLE_FILTER_RELATION_TYPES } from "./filter"; import { MatrixEvent } from "./models/event"; /** @@ -89,6 +90,8 @@ export class FilterComponent { senders: this.filterJson.senders || null, not_senders: this.filterJson.not_senders || [], contains_url: this.filterJson.contains_url || null, + [UNSTABLE_FILTER_RELATION_SENDERS.name]: UNSTABLE_FILTER_RELATION_SENDERS.findIn(this.filterJson), + [UNSTABLE_FILTER_RELATION_TYPES.name]: UNSTABLE_FILTER_RELATION_TYPES.findIn(this.filterJson), }; } diff --git a/src/filter.ts b/src/filter.ts index e42eea955..eb619e78d 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -65,7 +65,7 @@ export interface IFilterDefinition { export interface IRoomEventFilter extends IFilterComponent { lazy_load_members?: boolean; include_redundant_members?: boolean; - types?: EventType[] | string[]; + types?: Array; [UNSTABLE_FILTER_RELATION_TYPES.name]?: Array; [UNSTABLE_FILTER_RELATION_SENDERS.name]?: string[]; } diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index b5c6e4800..11dea131f 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -209,7 +209,7 @@ export class EventTimelineSet extends EventEmitter { * * @fires module:client~MatrixClient#event:"Room.timelineReset" */ - public resetLiveTimeline(backPaginationToken: string, forwardPaginationToken?: string): void { + public resetLiveTimeline(backPaginationToken?: string, forwardPaginationToken?: string): void { // Each EventTimeline has RoomState objects tracking the state at the start // and end of that timeline. The copies at the end of the live timeline are // special because they will have listeners attached to monitor changes to diff --git a/src/models/event-timeline.ts b/src/models/event-timeline.ts index f755fb647..fb0602735 100644 --- a/src/models/event-timeline.ts +++ b/src/models/event-timeline.ts @@ -34,13 +34,13 @@ export class EventTimeline { * Symbolic constant for methods which take a 'direction' argument: * refers to the start of the timeline, or backwards in time. */ - static BACKWARDS = Direction.Backward; + public static readonly BACKWARDS = Direction.Backward; /** * Symbolic constant for methods which take a 'direction' argument: * refers to the end of the timeline, or forwards in time. */ - static FORWARDS = Direction.Forward; + public static readonly FORWARDS = Direction.Forward; /** * Static helper method to set sender and target properties diff --git a/src/models/room.ts b/src/models/room.ts index 751bc97f6..0f4c9c5ce 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -20,8 +20,8 @@ limitations under the License. import { EventEmitter } from "events"; -import { DuplicateStrategy, EventTimelineSet } from "./event-timeline-set"; -import { EventTimeline } from "./event-timeline"; +import { EventTimelineSet, DuplicateStrategy } from "./event-timeline-set"; +import { Direction, EventTimeline } from "./event-timeline"; import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; import { normalize } from "../utils"; @@ -110,6 +110,13 @@ export enum NotificationCountType { Total = "total", } +export interface ICreateFilterOpts { + // Populate the filtered timeline with already loaded events in the room + // timeline. Useful to disable for some filters that can't be achieved by the + // client in an efficient manner + prepopulateTimeline?: boolean; +} + export class Room extends EventEmitter { private readonly reEmitter: ReEmitter; private txnToEvent: Record = {}; // Pending in-flight requests { string: MatrixEvent } @@ -793,7 +800,7 @@ export class Room extends EventEmitter { * timeline which would otherwise be unable to paginate forwards without this token). * Removing just the old live timeline whilst preserving previous ones is not supported. */ - public resetLiveTimeline(backPaginationToken: string, forwardPaginationToken: string): void { + public resetLiveTimeline(backPaginationToken: string | null, forwardPaginationToken: string | null): void { for (let i = 0; i < this.timelineSets.length; i++) { this.timelineSets[i].resetLiveTimeline( backPaginationToken, forwardPaginationToken, @@ -1222,9 +1229,14 @@ export class Room extends EventEmitter { /** * Add a timelineSet for this room with the given filter * @param {Filter} filter The filter to be applied to this timelineSet + * @param {Object=} opts Configuration options + * @param {*} opts.storageToken Optional. * @return {EventTimelineSet} The timelineSet */ - public getOrCreateFilteredTimelineSet(filter: Filter): EventTimelineSet { + public getOrCreateFilteredTimelineSet( + filter: Filter, + { prepopulateTimeline = true }: ICreateFilterOpts = {}, + ): EventTimelineSet { if (this.filteredTimelineSets[filter.filterId]) { return this.filteredTimelineSets[filter.filterId]; } @@ -1234,30 +1246,39 @@ export class Room extends EventEmitter { this.filteredTimelineSets[filter.filterId] = timelineSet; this.timelineSets.push(timelineSet); - // populate up the new timelineSet with filtered events from our live - // unfiltered timeline. - // - // XXX: This is risky as our timeline - // may have grown huge and so take a long time to filter. - // see https://github.com/vector-im/vector-web/issues/2109 - const unfilteredLiveTimeline = this.getLiveTimeline(); + // Not all filter are possible to replicate client-side only + // When that's the case we do not want to prepopulate from the live timeline + // as we would get incorrect results compared to what the server would send back + if (prepopulateTimeline) { + // populate up the new timelineSet with filtered events from our live + // unfiltered timeline. + // + // XXX: This is risky as our timeline + // may have grown huge and so take a long time to filter. + // see https://github.com/vector-im/vector-web/issues/2109 - unfilteredLiveTimeline.getEvents().forEach(function(event) { - timelineSet.addLiveEvent(event); - }); + unfilteredLiveTimeline.getEvents().forEach(function(event) { + timelineSet.addLiveEvent(event); + }); - // find the earliest unfiltered timeline - let timeline = unfilteredLiveTimeline; - while (timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)) { - timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS); + // find the earliest unfiltered timeline + let timeline = unfilteredLiveTimeline; + while (timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)) { + timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS); + } + + timelineSet.getLiveTimeline().setPaginationToken( + timeline.getPaginationToken(EventTimeline.BACKWARDS), + EventTimeline.BACKWARDS, + ); + } else { + const livePaginationToken = unfilteredLiveTimeline.getPaginationToken(Direction.Forward); + timelineSet + .getLiveTimeline() + .setPaginationToken(livePaginationToken, Direction.Backward); } - timelineSet.getLiveTimeline().setPaginationToken( - timeline.getPaginationToken(EventTimeline.BACKWARDS), - EventTimeline.BACKWARDS, - ); - // alternatively, we could try to do something like this to try and re-paginate // in the filtered events from nothing, but Mark says it's an abuse of the API // to do so: