From f5cd079a16abf3099de37c0521bffd655c567617 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 2 Nov 2020 21:32:49 -0700 Subject: [PATCH] Add support for sending/receiving events from widgets Part of MSC2762: https://github.com/matrix-org/matrix-doc/pull/2762 Requires: https://github.com/matrix-org/matrix-widget-api/pull/9 This is the bare minimum required to send an event to a widget and receive events from widgets. Like the view_room action, this is controlled by a well-known permission key. **Danger**: This allows widgets to potentially modify room state. Use the permissions with care. --- src/stores/widgets/StopGapWidget.ts | 32 +++++++++++++++++++++++ src/stores/widgets/StopGapWidgetDriver.ts | 26 +++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 441e037ddf..1c26b67faf 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -55,6 +55,8 @@ import ThemeWatcher from "../../settings/watchers/ThemeWatcher"; import {getCustomTheme} from "../../theme"; import CountlyAnalytics from "../../CountlyAnalytics"; import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import ActiveRoomObserver from "../../ActiveRoomObserver"; // TODO: Destroy all of this code @@ -329,6 +331,10 @@ export class StopGapWidget extends EventEmitter { this.messaging.transport.reply(ev.detail, {}); }); + // Attach listeners for feeding events - the underlying widget classes handle permissions for us + MatrixClientPeg.get().on('event', this.onEvent); + MatrixClientPeg.get().on('Event.decrypted', this.onEventDecrypted); + if (WidgetType.JITSI.matches(this.mockWidget.type)) { this.messaging.on("action:set_always_on_screen", (ev: CustomEvent) => { @@ -422,5 +428,31 @@ export class StopGapWidget extends EventEmitter { if (!this.started) return; WidgetMessagingStore.instance.stopMessaging(this.mockWidget); ActiveWidgetStore.delRoomId(this.mockWidget.id); + + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().off('event', this.onEvent); + MatrixClientPeg.get().off('Event.decrypted', this.onEventDecrypted); + } + } + + private onEvent = (ev: MatrixEvent) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + if (ev.getRoomId() !== ActiveRoomObserver.activeRoomId) return; + this.feedEvent(ev); + }; + + private onEventDecrypted = (ev: MatrixEvent) => { + if (ev.isDecryptionFailure()) return; + if (ev.getRoomId() !== ActiveRoomObserver.activeRoomId) return; + this.feedEvent(ev); + }; + + private feedEvent(ev: MatrixEvent) { + if (!this.messaging) return; + + const raw = ev.event; + this.messaging.feedEvent(raw).catch(e => { + console.error("Error sending event to widget: ", e); + }); } } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 9b455ac481..5c2d1868aa 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { Capability, WidgetDriver, WidgetType } from "matrix-widget-api"; +import { Capability, ISendEventDetails, WidgetDriver, WidgetEventCapability, WidgetType } from "matrix-widget-api"; import { iterableUnion } from "../../utils/iterables"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import { arrayFastClone } from "../../utils/arrays"; import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; +import ActiveRoomObserver from "../../ActiveRoomObserver"; // TODO: Purge this from the universe @@ -47,7 +48,30 @@ export class StopGapWidgetDriver extends WidgetDriver { allowedCaps.push(ElementWidgetCapabilities.CanChangeViewedRoom); } } + if (Array.isArray(wkPerms["event_actions"])) { + if (wkPerms["event_actions"].includes(this.forType)) { + allowedCaps.push(...WidgetEventCapability.findEventCapabilities(requested).map(c => c.raw)); + } + } } return new Set(iterableUnion(requested, allowedCaps)); } + + public async sendEvent(eventType: string, content: any, stateKey: string = null): Promise { + const client = MatrixClientPeg.get(); + const roomId = ActiveRoomObserver.activeRoomId; + + if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); + + let r: {event_id: string} = null; + if (stateKey !== null) { + // state event + r = await client.sendStateEvent(roomId, eventType, content, stateKey); + } else { + // message event + r = await client.sendEvent(roomId, eventType, content); + } + + return {roomId, eventId: r.event_id}; + } }