From 8cd5aac128d7d715d5daf3b7b63b756651f41461 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 24 Oct 2022 12:11:52 -0400 Subject: [PATCH] Add event and message capabilities to RoomWidgetClient --- spec/unit/embedded.spec.ts | 67 ++++++++++++++++++++++++++++- src/client.ts | 5 +-- src/embedded.ts | 88 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 153 insertions(+), 7 deletions(-) diff --git a/spec/unit/embedded.spec.ts b/spec/unit/embedded.spec.ts index f704e847d..3a144215c 100644 --- a/spec/unit/embedded.spec.ts +++ b/spec/unit/embedded.spec.ts @@ -30,7 +30,7 @@ import { ITurnServer, } from "matrix-widget-api"; -import { createRoomWidgetClient } from "../../src/matrix"; +import { createRoomWidgetClient, MsgType } from "../../src/matrix"; import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "../../src/client"; import { SyncState } from "../../src/sync"; import { ICapabilities } from "../../src/embedded"; @@ -43,10 +43,15 @@ class MockWidgetApi extends EventEmitter { public requestCapability = jest.fn(); public requestCapabilities = jest.fn(); public requestCapabilityForRoomTimeline = jest.fn(); + public requestCapabilityToSendEvent = jest.fn(); + public requestCapabilityToReceiveEvent = jest.fn(); + public requestCapabilityToSendMessage = jest.fn(); + public requestCapabilityToReceiveMessage = jest.fn(); public requestCapabilityToSendState = jest.fn(); public requestCapabilityToReceiveState = jest.fn(); public requestCapabilityToSendToDevice = jest.fn(); public requestCapabilityToReceiveToDevice = jest.fn(); + public sendRoomEvent = jest.fn(() => ({ event_id: `$${Math.random()}` })); public sendStateEvent = jest.fn(); public sendToDevice = jest.fn(); public readStateEvents = jest.fn(() => []); @@ -75,6 +80,66 @@ describe("RoomWidgetClient", () => { await client.startClient(); }; + describe("events", () => { + it("sends", async () => { + await makeClient({ sendEvent: ["org.matrix.rageshake_request"] }); + expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org"); + expect(widgetApi.requestCapabilityToSendEvent).toHaveBeenCalledWith("org.matrix.rageshake_request"); + await client.sendEvent("!1:example.org", "org.matrix.rageshake_request", { request_id: 123 }); + expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith( + "org.matrix.rageshake_request", { request_id: 123 }, "!1:example.org", + ); + }); + + it("receives", async () => { + const event = new MatrixEvent({ + type: "org.matrix.rageshake_request", + event_id: "$pduhfiidph", + room_id: "!1:example.org", + sender: "@alice:example.org", + content: { request_id: 123 }, + }).getEffectiveEvent(); + + await makeClient({ receiveEvent: ["org.matrix.rageshake_request"] }); + expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org"); + expect(widgetApi.requestCapabilityToReceiveEvent).toHaveBeenCalledWith("org.matrix.rageshake_request"); + + const emittedEvent = new Promise(resolve => client.once(ClientEvent.Event, resolve)); + const emittedSync = new Promise(resolve => client.once(ClientEvent.Sync, resolve)); + widgetApi.emit( + `action:${WidgetApiToWidgetAction.SendEvent}`, + new CustomEvent(`action:${WidgetApiToWidgetAction.SendEvent}`, { detail: { data: event } }), + ); + + // The client should've emitted about the received event + expect((await emittedEvent).getEffectiveEvent()).toEqual(event); + expect(await emittedSync).toEqual(SyncState.Syncing); + // It should've also inserted the event into the room object + const room = client.getRoom("!1:example.org"); + expect(room).not.toBeNull(); + expect(room!.getLiveTimeline().getEvents().map(e => e.getEffectiveEvent())).toEqual([event]); + }); + }); + + describe("messages", () => { + it("requests permissions for specific message types", async () => { + await makeClient({ sendMessage: [MsgType.Text], receiveMessage: [MsgType.Text] }); + expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org"); + expect(widgetApi.requestCapabilityToSendMessage).toHaveBeenCalledWith(MsgType.Text); + expect(widgetApi.requestCapabilityToReceiveMessage).toHaveBeenCalledWith(MsgType.Text); + }); + + it("requests permissions for all message types", async () => { + await makeClient({ sendMessage: true, receiveMessage: true }); + expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org"); + expect(widgetApi.requestCapabilityToSendMessage).toHaveBeenCalledWith(); + expect(widgetApi.requestCapabilityToReceiveMessage).toHaveBeenCalledWith(); + }); + + // No point in testing sending and receiving since it's done exactly the + // same way as non-message events + }); + describe("state events", () => { const event = new MatrixEvent({ type: "org.example.foo", diff --git a/src/client.ts b/src/client.ts index 210075c42..e3ab3a7dd 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3980,9 +3980,8 @@ export class MatrixClient extends TypedEventEmitter { + protected encryptAndSendEvent(room: Room, event: MatrixEvent): Promise { let cancelled = false; // Add an extra Promise.resolve() to turn synchronous exceptions into promise rejections, // so that we can handle synchronous and asynchronous exceptions with the @@ -4107,7 +4106,7 @@ export class MatrixClient extends TypedEventEmitter(resolve => this.widgetApi.once("ready", resolve)); @@ -70,9 +110,38 @@ export class RoomWidgetClient extends MatrixClient { super(opts); // Request capabilities for the functionality this client needs to support - if (capabilities.sendState?.length || capabilities.receiveState?.length) { + if ( + capabilities.sendEvent?.length + || capabilities.receiveEvent?.length + || capabilities.sendMessage === true + || (Array.isArray(capabilities.sendMessage) && capabilities.sendMessage.length) + || capabilities.receiveMessage === true + || (Array.isArray(capabilities.receiveMessage) && capabilities.receiveMessage.length) + || capabilities.sendState?.length + || capabilities.receiveState?.length + ) { widgetApi.requestCapabilityForRoomTimeline(roomId); } + capabilities.sendEvent?.forEach(eventType => + widgetApi.requestCapabilityToSendEvent(eventType), + ); + capabilities.receiveEvent?.forEach(eventType => + widgetApi.requestCapabilityToReceiveEvent(eventType), + ); + if (capabilities.sendMessage === true) { + widgetApi.requestCapabilityToSendMessage(); + } else if (Array.isArray(capabilities.sendMessage)) { + capabilities.sendMessage.forEach(msgType => + widgetApi.requestCapabilityToSendMessage(msgType), + ); + } + if (capabilities.receiveMessage === true) { + widgetApi.requestCapabilityToReceiveMessage(); + } else if (Array.isArray(capabilities.receiveMessage)) { + capabilities.receiveMessage.forEach(msgType => + widgetApi.requestCapabilityToReceiveMessage(msgType), + ); + } capabilities.sendState?.forEach(({ eventType, stateKey }) => widgetApi.requestCapabilityToSendState(eventType, stateKey), ); @@ -155,6 +224,19 @@ export class RoomWidgetClient extends MatrixClient { throw new Error(`Unknown room: ${roomIdOrAlias}`); } + protected async encryptAndSendEvent(room: Room, event: MatrixEvent): Promise { + let response: ISendEventFromWidgetResponseData; + try { + response = await this.widgetApi.sendRoomEvent(event.getType(), event.getContent(), room.roomId); + } catch (e) { + this.updatePendingEventStatus(room, event, EventStatus.NOT_SENT); + throw e; + } + + room.updatePendingEvent(event, EventStatus.SENT, response.event_id); + return { event_id: response.event_id }; + } + public async sendStateEvent( roomId: string, eventType: string,