You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-13 07:42:14 +03:00
Refactor Space Hierarchy stuff in preparation for pagination
This commit is contained in:
@@ -43,8 +43,9 @@ export interface ISpaceSummaryEvent {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IRoomChildState extends IStrippedState {
|
||||
export interface IHierarchyRelation extends IStrippedState {
|
||||
room_id: string;
|
||||
origin_server_ts: number;
|
||||
content: {
|
||||
order?: string;
|
||||
suggested?: boolean;
|
||||
@@ -52,8 +53,8 @@ export interface IRoomChildState extends IStrippedState {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IRoomChild extends IPublicRoomsChunkRoom {
|
||||
export interface IHierarchyRoom extends IPublicRoomsChunkRoom {
|
||||
room_type?: RoomType | string;
|
||||
children_state: IRoomChildState[];
|
||||
children_state: IHierarchyRelation[];
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
@@ -140,7 +140,7 @@ import {
|
||||
SearchOrderBy,
|
||||
} from "./@types/search";
|
||||
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse";
|
||||
import { IRoomChild, ISpaceSummaryEvent, ISpaceSummaryRoom } from "./@types/spaces";
|
||||
import { IHierarchyRoom, ISpaceSummaryEvent, ISpaceSummaryRoom } from "./@types/spaces";
|
||||
import { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, RuleId } from "./@types/PushRules";
|
||||
import { IThreepid } from "./@types/threepids";
|
||||
import { CryptoStore } from "./crypto/store/base";
|
||||
@@ -7981,7 +7981,7 @@ export class MatrixClient extends EventEmitter {
|
||||
* @param {number?} limit The maximum number of rooms to return in total.
|
||||
* @param {string?} batch The opaque token to paginate a previous summary request.
|
||||
* @returns {Promise} the response, with next_token, rooms fields.
|
||||
* @deprecated in favour of `getRoomChildren` due to the MSC changing paths.
|
||||
* @deprecated in favour of `getRoomHierarchy` due to the MSC changing paths.
|
||||
*/
|
||||
public getSpaceSummary(
|
||||
roomId: string,
|
||||
@@ -8010,7 +8010,7 @@ export class MatrixClient extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches or paginates a summary of a space as defined by MSC2946.
|
||||
* Fetches or paginates a room hierarchy as defined by MSC2946.
|
||||
* Falls back gracefully to sourcing its data from `getSpaceSummary` if this API is not yet supported by the server.
|
||||
* @param {string} roomId The ID of the space-room to use as the root of the summary.
|
||||
* @param {number?} limit The maximum number of rooms to return per page.
|
||||
@@ -8019,17 +8019,17 @@ export class MatrixClient extends EventEmitter {
|
||||
* @param {string?} fromToken The opaque token to paginate a previous request.
|
||||
* @returns {Promise} the response, with next_token & rooms fields.
|
||||
*/
|
||||
public getRoomChildren(
|
||||
public getRoomHierarchy(
|
||||
roomId: string,
|
||||
limit?: number,
|
||||
maxDepth?: number,
|
||||
suggestedOnly = false,
|
||||
fromToken?: string,
|
||||
): Promise<{
|
||||
rooms: IRoomChild[];
|
||||
rooms: IHierarchyRoom[];
|
||||
next_token?: string; // eslint-disable-line camelcase
|
||||
}> {
|
||||
const path = utils.encodeUri("/rooms/$roomId/children", {
|
||||
const path = utils.encodeUri("/rooms/$roomId/hierarchy", {
|
||||
$roomId: roomId,
|
||||
});
|
||||
|
||||
@@ -8039,14 +8039,16 @@ export class MatrixClient extends EventEmitter {
|
||||
from: fromToken,
|
||||
limit,
|
||||
}, undefined, {
|
||||
prefix: "/_matrix/client/unstable/org.matrix.msc2946.v2",
|
||||
prefix: "/_matrix/client/unstable/org.matrix.msc2946",
|
||||
}).catch(e => {
|
||||
if (e.errcode === "M_UNRECOGNIZED") {
|
||||
// fall back to the older space summary API as it exposes the same data just in a different shape.
|
||||
return this.getSpaceSummary(roomId, undefined, suggestedOnly, undefined, limit)
|
||||
.then(({ rooms, events }) => {
|
||||
// Translate response from `/spaces` to that we expect in this API.
|
||||
const roomMap = new Map(rooms.map(r => [r.room_id, <IRoomChild>{ ...r, children_state: [] }]));
|
||||
const roomMap = new Map(rooms.map(r => {
|
||||
return [r.room_id, <IHierarchyRoom>{ ...r, children_state: [] }];
|
||||
}));
|
||||
events.forEach(e => {
|
||||
roomMap.get(e.room_id)?.children_state.push(e);
|
||||
});
|
||||
|
||||
157
src/room-hierarchy.ts
Normal file
157
src/room-hierarchy.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module room-hierarchy
|
||||
*/
|
||||
|
||||
import { Room } from "./models/room";
|
||||
import { IHierarchyRoom, IHierarchyRelation } from "./@types/spaces";
|
||||
import { MatrixClient } from "./client";
|
||||
import { EventType } from "./@types/event";
|
||||
|
||||
export class RoomHierarchy {
|
||||
// Map from room id to list of servers which are listed as a via somewhere in the loaded hierarchy
|
||||
public readonly viaMap = new Map<string, Set<string>>();
|
||||
// Map from room id to list of rooms which claim this room as their child
|
||||
public readonly backRefs = new Map<string, string[]>();
|
||||
// Map from room id to object
|
||||
public readonly roomMap = new Map<string, IHierarchyRoom>();
|
||||
private loadRequest: ReturnType<MatrixClient["getRoomHierarchy"]>;
|
||||
private nextToken?: string;
|
||||
private _rooms?: IHierarchyRoom[];
|
||||
private serverSupportError?: Error;
|
||||
|
||||
/**
|
||||
* Construct a new EventTimeline
|
||||
*
|
||||
* TODO
|
||||
* <p>An EventTimeline represents a contiguous sequence of events in a room.
|
||||
*
|
||||
* <p>As well as keeping track of the events themselves, it stores the state of
|
||||
* the room at the beginning and end of the timeline, and pagination tokens for
|
||||
* going backwards and forwards in the timeline.
|
||||
*
|
||||
* <p>In order that clients can meaningfully maintain an index into a timeline,
|
||||
* the EventTimeline object tracks a 'baseIndex'. This starts at zero, but is
|
||||
* incremented when events are prepended to the timeline. The index of an event
|
||||
* relative to baseIndex therefore remains constant.
|
||||
*
|
||||
* <p>Once a timeline joins up with its neighbour, they are linked together into a
|
||||
* doubly-linked list.
|
||||
*
|
||||
* @param {Room} root the root of this hierarchy
|
||||
* @param {number} pageSize the maximum number of rooms to return per page, can be overridden per load request.
|
||||
* @param {number} maxDepth the maximum depth to traverse the hierarchy to
|
||||
* @param {boolean} suggestedOnly whether to only return rooms with suggested=true.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(
|
||||
private readonly root: Room,
|
||||
private readonly pageSize?: number,
|
||||
private readonly maxDepth?: number,
|
||||
private readonly suggestedOnly = false,
|
||||
) {}
|
||||
|
||||
public get noSupport(): boolean {
|
||||
return !!this.serverSupportError;
|
||||
}
|
||||
|
||||
public get canLoadMore(): boolean {
|
||||
return !!this.serverSupportError || !!this.nextToken || !this._rooms;
|
||||
}
|
||||
|
||||
public get rooms(): IHierarchyRoom[] {
|
||||
return this._rooms;
|
||||
}
|
||||
|
||||
public async load(pageSize = this.pageSize): Promise<IHierarchyRoom[]> {
|
||||
if (this.loadRequest) return this.loadRequest.then(r => r.rooms);
|
||||
|
||||
this.loadRequest = this.root.client.getRoomHierarchy(
|
||||
this.root.roomId,
|
||||
pageSize,
|
||||
this.maxDepth,
|
||||
this.suggestedOnly,
|
||||
this.nextToken,
|
||||
);
|
||||
|
||||
let rooms: IHierarchyRoom[];
|
||||
try {
|
||||
({ rooms, next_token: this.nextToken } = await this.loadRequest);
|
||||
} catch (e) {
|
||||
if (e.errcode === "M_UNRECOGNIZED") {
|
||||
this.serverSupportError = e;
|
||||
} else {
|
||||
// TODO retry?
|
||||
}
|
||||
|
||||
return [];
|
||||
} finally {
|
||||
this.loadRequest = null;
|
||||
}
|
||||
|
||||
this._rooms = rooms; // TODO merge
|
||||
|
||||
rooms.forEach(room => {
|
||||
this.roomMap.set(room.room_id, room);
|
||||
|
||||
room.children_state.forEach(ev => {
|
||||
if (ev.type !== EventType.SpaceChild) return;
|
||||
const childRoomId = ev.state_key;
|
||||
|
||||
// track backrefs for quicker hierarchy navigation
|
||||
if (!this.backRefs.has(childRoomId)) {
|
||||
this.backRefs.set(childRoomId, []);
|
||||
}
|
||||
this.backRefs.get(childRoomId).push(ev.room_id);
|
||||
|
||||
// fill viaMap
|
||||
if (Array.isArray(ev.content.via)) {
|
||||
if (!this.viaMap.has(childRoomId)) {
|
||||
this.viaMap.set(childRoomId, new Set());
|
||||
}
|
||||
const vias = this.viaMap.get(childRoomId);
|
||||
ev.content.via.forEach(via => vias.add(via));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
public getRelation(parentId: string, childId: string): IHierarchyRelation {
|
||||
return this.roomMap.get(parentId)?.children_state.find(e => e.state_key === childId);
|
||||
}
|
||||
|
||||
public isSuggested(parentId: string, childId: string): boolean {
|
||||
return this.getRelation(parentId, childId)?.content.suggested;
|
||||
}
|
||||
|
||||
public removeRelation(parentId: string, childId: string): void {
|
||||
const backRefs = this.backRefs.get(childId);
|
||||
if (backRefs?.length === 1) {
|
||||
this.backRefs.delete(childId);
|
||||
} else if (backRefs?.length) {
|
||||
this.backRefs.set(childId, backRefs.filter(ref => ref !== parentId));
|
||||
}
|
||||
|
||||
const room = this.roomMap.get(parentId);
|
||||
if (room) {
|
||||
room.children_state = room.children_state.filter(ev => ev.state_key !== childId);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user