diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 68c6ded9f..58e6a5799 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -22,11 +22,13 @@ import { Filter } from "../../src/filter"; import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE } from "../../src/models/MSC3089TreeSpace"; import { EventType, + RelationType, RoomCreateTypeField, RoomType, UNSTABLE_MSC3088_ENABLED, UNSTABLE_MSC3088_PURPOSE, UNSTABLE_MSC3089_TREE_SUBTYPE, + MSC3912_RELATION_BASED_REDACTIONS_PROP, } from "../../src/@types/event"; import { MEGOLM_ALGORITHM } from "../../src/crypto/olmlib"; import { Crypto } from "../../src/crypto"; @@ -121,6 +123,10 @@ describe("MatrixClient", function () { data: SYNC_DATA, }; + const unstableFeatures: Record = { + "org.matrix.msc3440.stable": true, + }; + // items are popped off when processed and block if no items left. let httpLookups: HttpLookup[] = []; let acceptKeepalives: boolean; @@ -132,9 +138,7 @@ describe("MatrixClient", function () { function httpReq(method: Method, path: string, qp?: QueryDict, data?: BodyInit, opts?: IRequestOpts) { if (path === KEEP_ALIVE_PATH && acceptKeepalives) { return Promise.resolve({ - unstable_features: { - "org.matrix.msc3440.stable": true, - }, + unstable_features: unstableFeatures, versions: ["r0.6.0", "r0.6.1"], }); } @@ -1085,6 +1089,59 @@ describe("MatrixClient", function () { await client.redactEvent(roomId, eventId, txnId, { reason }); }); + + describe("when calling with with_relations", () => { + const eventId = "$event42:example.org"; + + it("should raise an error if server has no support for relation based redactions", async () => { + // load supported features + await client.getVersions(); + + const txnId = client.makeTxnId(); + + expect(() => { + client.redactEvent(roomId, eventId, txnId, { + with_relations: [RelationType.Reference], + }); + }).toThrowError( + new Error( + "Server does not support relation based redactions " + + `roomId ${roomId} eventId ${eventId} txnId: ${txnId} threadId null`, + ), + ); + }); + + describe("and the server supports relation based redactions (unstable)", () => { + beforeEach(async () => { + unstableFeatures["org.matrix.msc3912"] = true; + // load supported features + await client.getVersions(); + }); + + it("should send with_relations in the request body", async () => { + const txnId = client.makeTxnId(); + + httpLookups = [ + { + method: "PUT", + path: + `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}` + + `/${encodeURIComponent(txnId)}`, + expectBody: { + reason: "redaction test", + [MSC3912_RELATION_BASED_REDACTIONS_PROP.unstable!]: [RelationType.Reference], + }, + data: { event_id: eventId }, + }, + ]; + + await client.redactEvent(roomId, eventId, txnId, { + reason: "redaction test", + with_relations: [RelationType.Reference], + }); + }); + }); + }); }); describe("cancelPendingEvent", () => { diff --git a/src/@types/event.ts b/src/@types/event.ts index 230cb0503..4d7cd5d51 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -165,6 +165,15 @@ export const UNSTABLE_MSC3089_BRANCH = new UnstableValue("m.branch", "org.matrix */ export const UNSTABLE_MSC2716_MARKER = new UnstableValue("m.room.marker", "org.matrix.msc2716.marker"); +/** + * Name of the "with_relations" request property for relation based redactions. + * {@link https://github.com/matrix-org/matrix-spec-proposals/pull/3912} + */ +export const MSC3912_RELATION_BASED_REDACTIONS_PROP = new UnstableValue( + "with_relations", + "org.matrix.msc3912.with_relations", +); + /** * Functional members type for declaring a purpose of room members (e.g. helpful bots). * Note that this reference is UNSTABLE and subject to breaking changes, including its diff --git a/src/@types/requests.ts b/src/@types/requests.ts index 75296940a..12f4d8eb0 100644 --- a/src/@types/requests.ts +++ b/src/@types/requests.ts @@ -21,7 +21,7 @@ import { IRoomEventFilter } from "../filter"; import { Direction } from "../models/event-timeline"; import { PushRuleAction } from "./PushRules"; import { IRoomEvent } from "../sync-accumulator"; -import { EventType, RoomType } from "./event"; +import { EventType, RelationType, RoomType } from "./event"; // allow camelcase as these are things that go onto the wire /* eslint-disable camelcase */ @@ -47,6 +47,18 @@ export interface IJoinRoomOpts { export interface IRedactOpts { reason?: string; + /** + * Whether events related to the redacted event should be redacted. + * + * If specified, then any events which relate to the event being redacted with + * any of the relationship types listed will also be redacted. + * + * Raises an Error if the server does not support it. + * Check for server-side support before using this param with + * client.canSupport.get(Feature.RelationBasedRedactions). + * {@link https://github.com/matrix-org/matrix-spec-proposals/pull/3912} + */ + with_relations?: Array; } export interface ISendEventResponse { diff --git a/src/client.ts b/src/client.ts index 8066b22c8..26d31ef0c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -154,6 +154,7 @@ import { UNSTABLE_MSC3088_ENABLED, UNSTABLE_MSC3088_PURPOSE, UNSTABLE_MSC3089_TREE_SUBTYPE, + MSC3912_RELATION_BASED_REDACTIONS_PROP, } from "./@types/event"; import { IdServerUnbindResult, IImageInfo, Preset, Visibility } from "./@types/partials"; import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper"; @@ -4444,9 +4445,11 @@ export class MatrixClient extends TypedEventEmitter = { [Feature.LoginTokenRequest]: { unstablePrefixes: ["org.matrix.msc3882"], }, + [Feature.RelationBasedRedactions]: { + unstablePrefixes: ["org.matrix.msc3912"], + }, [Feature.AccountDataDeletion]: { unstablePrefixes: ["org.matrix.msc3391"], },