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;
|
room_id: string;
|
||||||
|
origin_server_ts: number;
|
||||||
content: {
|
content: {
|
||||||
order?: string;
|
order?: string;
|
||||||
suggested?: boolean;
|
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;
|
room_type?: RoomType | string;
|
||||||
children_state: IRoomChildState[];
|
children_state: IHierarchyRelation[];
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ import {
|
|||||||
SearchOrderBy,
|
SearchOrderBy,
|
||||||
} from "./@types/search";
|
} from "./@types/search";
|
||||||
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse";
|
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 { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, RuleId } from "./@types/PushRules";
|
||||||
import { IThreepid } from "./@types/threepids";
|
import { IThreepid } from "./@types/threepids";
|
||||||
import { CryptoStore } from "./crypto/store/base";
|
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 {number?} limit The maximum number of rooms to return in total.
|
||||||
* @param {string?} batch The opaque token to paginate a previous summary request.
|
* @param {string?} batch The opaque token to paginate a previous summary request.
|
||||||
* @returns {Promise} the response, with next_token, rooms fields.
|
* @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(
|
public getSpaceSummary(
|
||||||
roomId: string,
|
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.
|
* 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 {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.
|
* @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.
|
* @param {string?} fromToken The opaque token to paginate a previous request.
|
||||||
* @returns {Promise} the response, with next_token & rooms fields.
|
* @returns {Promise} the response, with next_token & rooms fields.
|
||||||
*/
|
*/
|
||||||
public getRoomChildren(
|
public getRoomHierarchy(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
limit?: number,
|
limit?: number,
|
||||||
maxDepth?: number,
|
maxDepth?: number,
|
||||||
suggestedOnly = false,
|
suggestedOnly = false,
|
||||||
fromToken?: string,
|
fromToken?: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
rooms: IRoomChild[];
|
rooms: IHierarchyRoom[];
|
||||||
next_token?: string; // eslint-disable-line camelcase
|
next_token?: string; // eslint-disable-line camelcase
|
||||||
}> {
|
}> {
|
||||||
const path = utils.encodeUri("/rooms/$roomId/children", {
|
const path = utils.encodeUri("/rooms/$roomId/hierarchy", {
|
||||||
$roomId: roomId,
|
$roomId: roomId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -8039,14 +8039,16 @@ export class MatrixClient extends EventEmitter {
|
|||||||
from: fromToken,
|
from: fromToken,
|
||||||
limit,
|
limit,
|
||||||
}, undefined, {
|
}, undefined, {
|
||||||
prefix: "/_matrix/client/unstable/org.matrix.msc2946.v2",
|
prefix: "/_matrix/client/unstable/org.matrix.msc2946",
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
if (e.errcode === "M_UNRECOGNIZED") {
|
if (e.errcode === "M_UNRECOGNIZED") {
|
||||||
// fall back to the older space summary API as it exposes the same data just in a different shape.
|
// 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)
|
return this.getSpaceSummary(roomId, undefined, suggestedOnly, undefined, limit)
|
||||||
.then(({ rooms, events }) => {
|
.then(({ rooms, events }) => {
|
||||||
// Translate response from `/spaces` to that we expect in this API.
|
// 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 => {
|
events.forEach(e => {
|
||||||
roomMap.get(e.room_id)?.children_state.push(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