Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MSC 3981 #3248

Merged
merged 10 commits into from
Apr 27, 2023
1 change: 1 addition & 0 deletions spec/unit/event-timeline-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ describe("EventTimelineSet", () => {
beforeEach(() => {
client = utils.mock(MatrixClient, "MatrixClient");
client.reEmitter = utils.mock(ReEmitter, "ReEmitter");
client.canSupport = new Map();
room = new Room(roomId, client, userA);
eventTimelineSet = new EventTimelineSet(room);
eventTimeline = new EventTimeline(eventTimelineSet);
Expand Down
1 change: 1 addition & 0 deletions src/@types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export interface IRelationsRequestOpts {
to?: string;
limit?: number;
dir?: Direction;
recurse?: boolean; // MSC3981 Relations Recursion https://github.com/matrix-org/matrix-spec-proposals/pull/3981
}

export interface IRelationsResponse {
Expand Down
17 changes: 12 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5736,6 +5736,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return undefined;
}

const recurse = this.canSupport.get(Feature.RelationsRecursion) !== ServerSupport.Unsupported;
if (Thread.hasServerSideSupport) {
if (Thread.hasServerSideFwdPaginationSupport) {
if (!timelineSet.thread) {
Expand All @@ -5748,14 +5749,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
thread.id,
THREAD_RELATION_TYPE.name,
null,
{ dir: Direction.Backward, from: res.start },
{ dir: Direction.Backward, from: res.start, recurse: recurse || undefined },
);
const resNewer: IRelationsResponse = await this.fetchRelations(
timelineSet.room.roomId,
thread.id,
THREAD_RELATION_TYPE.name,
null,
{ dir: Direction.Forward, from: res.end },
{ dir: Direction.Forward, from: res.end, recurse: recurse || undefined },
);
const events = [
// Order events from most recent to oldest (reverse-chronological).
Expand Down Expand Up @@ -5803,7 +5804,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
thread.id,
THREAD_RELATION_TYPE.name,
null,
{ dir: Direction.Backward, from: res.start },
{ dir: Direction.Backward, from: res.start, recurse: recurse || undefined },
);
const eventsNewer: IEvent[] = [];
let nextBatch: Optional<string> = res.end;
Expand All @@ -5813,7 +5814,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
thread.id,
THREAD_RELATION_TYPE.name,
null,
{ dir: Direction.Forward, from: nextBatch },
{ dir: Direction.Forward, from: nextBatch, recurse: recurse || undefined },
);
nextBatch = resNewer.next_batch ?? null;
eventsNewer.push(...resNewer.chunk);
Expand Down Expand Up @@ -5884,12 +5885,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
);
event = res.chunk?.[0];
} else if (timelineSet.thread && Thread.hasServerSideSupport) {
const recurse = this.canSupport.get(Feature.RelationsRecursion) !== ServerSupport.Unsupported;
const res = await this.fetchRelations(
timelineSet.room.roomId,
timelineSet.thread.id,
THREAD_RELATION_TYPE.name,
null,
{ dir: Direction.Backward, limit: 1 },
{ dir: Direction.Backward, limit: 1, recurse: recurse || undefined },
);
event = res.chunk?.[0];
} else {
Expand Down Expand Up @@ -6164,10 +6166,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
throw new Error("Unknown room " + eventTimeline.getRoomId());
}

const recurse = this.canSupport.get(Feature.RelationsRecursion) !== ServerSupport.Unsupported;
promise = this.fetchRelations(eventTimeline.getRoomId() ?? "", thread.id, THREAD_RELATION_TYPE.name, null, {
dir,
limit: opts.limit,
from: token ?? undefined,
recurse: recurse || undefined,
})
.then(async (res) => {
const mapper = this.getEventMapper();
Expand Down Expand Up @@ -7956,6 +7960,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (Thread.hasServerSideFwdPaginationSupport === FeatureSupport.Experimental) {
params = replaceParam("dir", "org.matrix.msc3715.dir", params);
}
if (this.canSupport.get(Feature.RelationsRecursion) === ServerSupport.Unstable) {
params = replaceParam("recurse", "org.matrix.msc3981.recurse", params);
}
const queryString = utils.encodeParams(params);

let templatedUrl = "/rooms/$roomId/relations/$eventId";
Expand Down
4 changes: 4 additions & 0 deletions src/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum Feature {
LoginTokenRequest = "LoginTokenRequest",
RelationBasedRedactions = "RelationBasedRedactions",
AccountDataDeletion = "AccountDataDeletion",
RelationsRecursion = "RelationsRecursion",
}

type FeatureSupportCondition = {
Expand All @@ -56,6 +57,9 @@ const featureSupportResolver: Record<string, FeatureSupportCondition> = {
[Feature.AccountDataDeletion]: {
unstablePrefixes: ["org.matrix.msc3391"],
},
[Feature.RelationsRecursion]: {
unstablePrefixes: ["org.matrix.msc3981"],
},
};

export async function buildFeatureSupportMap(versions: IServerVersions): Promise<Map<Feature, ServerSupport>> {
Expand Down
42 changes: 23 additions & 19 deletions src/models/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { ServerControlledNamespacedValue } from "../NamespacedValue";
import { logger } from "../logger";
import { ReadReceipt } from "./read-receipt";
import { CachedReceiptStructure, ReceiptType } from "../@types/read_receipts";
import { Feature, ServerSupport } from "../feature";

export enum ThreadEvent {
New = "Thread.new",
Expand Down Expand Up @@ -458,25 +459,28 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {

// XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084
private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> {
return Promise.all(
events
.filter((e) => e.isEncrypted())
.map((event: MatrixEvent) => {
if (event.isRelation()) return; // skip - relations don't get edits
return this.client
.relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), {
limit: 1,
})
.then((relations) => {
if (relations.events.length) {
event.makeReplaced(relations.events[0]);
}
})
.catch((e) => {
logger.error("Failed to load edits for encrypted thread event", e);
});
}),
);
const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported;
if (recursionSupport !== ServerSupport.Unsupported) {
return Promise.all(
events
.filter((e) => e.isEncrypted())
.map((event: MatrixEvent) => {
if (event.isRelation()) return; // skip - relations don't get edits
return this.client
.relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), {
limit: 1,
})
.then((relations) => {
if (relations.events.length) {
event.makeReplaced(relations.events[0]);
}
})
.catch((e) => {
logger.error("Failed to load edits for encrypted thread event", e);
});
}),
);
}
}

public setEventMetadata(event: Optional<MatrixEvent>): void {
Expand Down