diff --git a/spec/unit/matrix-client.spec.js b/spec/unit/matrix-client.spec.js index 82e166f67..6d4024430 100644 --- a/spec/unit/matrix-client.spec.js +++ b/spec/unit/matrix-client.spec.js @@ -728,4 +728,128 @@ describe("MatrixClient", function() { expect(httpLookups.length).toEqual(0); }); }); + + describe("sendEvent", () => { + const roomId = "!room:example.org"; + const body = "This is the body"; + const content = { body }; + + it("overload without threadId works", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`, + data: { event_id: eventId }, + expectBody: content, + }]; + + await client.sendEvent(roomId, EventType.RoomMessage, content, txnId); + }); + + it("overload with null threadId works", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`, + data: { event_id: eventId }, + expectBody: content, + }]; + + await client.sendEvent(roomId, null, EventType.RoomMessage, content, txnId); + }); + + it("overload with threadId works", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`, + data: { event_id: eventId }, + expectBody: content, + }]; + + await client.sendEvent(roomId, "$threadId:server", EventType.RoomMessage, content, txnId); + }); + }); + + describe("redactEvent", () => { + const roomId = "!room:example.org"; + const mockRoom = { + getMyMembership: () => "join", + currentState: { + getStateEvents: (eventType, stateKey) => { + if (eventType === EventType.RoomEncryption) { + expect(stateKey).toEqual(""); + return new MatrixEvent({ content: {} }); + } else { + throw new Error("Unexpected event type or state key"); + } + }, + }, + threads: { + get: jest.fn(), + }, + addPendingEvent: jest.fn(), + updatePendingEvent: jest.fn(), + }; + + beforeEach(() => { + client.getRoom = (getRoomId) => { + expect(getRoomId).toEqual(roomId); + return mockRoom; + }; + }); + + it("overload without threadId works", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, + data: { event_id: eventId }, + }]; + + await client.redactEvent(roomId, eventId, txnId); + }); + + it("overload with null threadId works", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, + data: { event_id: eventId }, + }]; + + await client.redactEvent(roomId, null, eventId, txnId); + }); + + it("overload with threadId works", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, + data: { event_id: eventId }, + }]; + + await client.redactEvent(roomId, "$threadId:server", eventId, txnId); + }); + + it("does not get wrongly encrypted", async () => { + const eventId = "$eventId:example.org"; + const txnId = client.makeTxnId(); + const reason = "This is the redaction reason"; + httpLookups = [{ + method: "PUT", + path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, + expectBody: { reason }, // NOT ENCRYPTED + data: { event_id: eventId }, + }]; + + await client.redactEvent(roomId, eventId, txnId, { reason }); + }); + }); }); diff --git a/src/client.ts b/src/client.ts index b5d59936e..0859b1b4f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3726,6 +3726,12 @@ export class MatrixClient extends EventEmitter { return null; } + if (event.isRedaction()) { + // Redactions do not support encryption in the spec at this time, + // whilst it mostly worked in some clients, it wasn't compliant. + return null; + } + if (!this.isRoomEncrypted(event.getRoomId())) { return null; } @@ -3852,7 +3858,7 @@ export class MatrixClient extends EventEmitter { txnId?: string | Callback | IRedactOpts, cbOrOpts?: Callback | IRedactOpts, ): Promise { - if (!eventId || eventId.startsWith(EVENT_ID_PREFIX)) { + if (!eventId?.startsWith(EVENT_ID_PREFIX)) { cbOrOpts = txnId as (Callback | IRedactOpts); txnId = eventId; eventId = threadId; @@ -3863,7 +3869,7 @@ export class MatrixClient extends EventEmitter { const callback = typeof (cbOrOpts) === 'function' ? cbOrOpts : undefined; return this.sendCompleteEvent(roomId, threadId, { type: EventType.RoomRedaction, - content: { reason: reason }, + content: { reason }, redacts: eventId, }, txnId as string, callback); } diff --git a/src/models/event.ts b/src/models/event.ts index f488d7434..df0ad5aec 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -771,7 +771,7 @@ export class MatrixEvent extends EventEmitter { private badEncryptedMessage(reason: string): IEventDecryptionResult { return { clearEvent: { - type: "m.room.message", + type: EventType.RoomMessage, content: { msgtype: "m.bad.encrypted", body: "** Unable to decrypt: " + reason + " **", @@ -818,7 +818,7 @@ export class MatrixEvent extends EventEmitter { * @return {boolean} True if this event is encrypted. */ public isEncrypted(): boolean { - return !this.isState() && this.event.type === "m.room.encrypted"; + return !this.isState() && this.event.type === EventType.RoomMessageEncrypted; } /** @@ -989,7 +989,7 @@ export class MatrixEvent extends EventEmitter { * @return {boolean} True if this event is a redaction */ public isRedaction(): boolean { - return this.getType() === "m.room.redaction"; + return this.getType() === EventType.RoomRedaction; } /** @@ -1371,15 +1371,15 @@ const REDACT_KEEP_KEYS = new Set([ // a map from event type to the .content keys we keep when an event is redacted const REDACT_KEEP_CONTENT_MAP = { - 'm.room.member': { 'membership': 1 }, - 'm.room.create': { 'creator': 1 }, - 'm.room.join_rules': { 'join_rule': 1 }, - 'm.room.power_levels': { + [EventType.RoomMember]: { 'membership': 1 }, + [EventType.RoomCreate]: { 'creator': 1 }, + [EventType.RoomJoinRules]: { 'join_rule': 1 }, + [EventType.RoomPowerLevels]: { 'ban': 1, 'events': 1, 'events_default': 1, 'kick': 1, 'redact': 1, 'state_default': 1, 'users': 1, 'users_default': 1, }, - 'm.room.aliases': { 'aliases': 1 }, + [EventType.RoomAliases]: { 'aliases': 1 }, }; /**