From 634ffb0140d2e13e6b9af32400e024ead6b5c577 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 25 Sep 2020 09:39:21 -0600 Subject: [PATCH] Add structure for widget messaging layer --- src/stores/widgets/SdkWidgetDriver.ts | 34 ++++++ src/stores/widgets/WidgetMessagingStore.ts | 117 +++++++++++++++++++++ src/stores/widgets/WidgetSurrogate.ts | 25 +++++ src/utils/iterables.ts | 21 ++++ src/utils/maps.ts | 17 +++ 5 files changed, 214 insertions(+) create mode 100644 src/stores/widgets/SdkWidgetDriver.ts create mode 100644 src/stores/widgets/WidgetMessagingStore.ts create mode 100644 src/stores/widgets/WidgetSurrogate.ts create mode 100644 src/utils/iterables.ts diff --git a/src/stores/widgets/SdkWidgetDriver.ts b/src/stores/widgets/SdkWidgetDriver.ts new file mode 100644 index 0000000000..1462303fa3 --- /dev/null +++ b/src/stores/widgets/SdkWidgetDriver.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Capability, Widget, WidgetDriver, WidgetKind } from "matrix-widget-api"; +import { iterableUnion } from "../../utils/iterables"; + +export class SdkWidgetDriver extends WidgetDriver { + public constructor( + private widget: Widget, + private widgetKind: WidgetKind, + private locationEntityId: string, + private preapprovedCapabilities: Set = new Set(), + ) { + super(); + } + + public async validateCapabilities(requested: Set): Promise> { + // TODO: Prompt the user to accept capabilities + return iterableUnion(requested, this.preapprovedCapabilities); + } +} diff --git a/src/stores/widgets/WidgetMessagingStore.ts b/src/stores/widgets/WidgetMessagingStore.ts new file mode 100644 index 0000000000..6d05cae8c6 --- /dev/null +++ b/src/stores/widgets/WidgetMessagingStore.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ClientWidgetApi, Widget, WidgetDriver, WidgetKind } from "matrix-widget-api"; +import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; +import defaultDispatcher from "../../dispatcher/dispatcher"; +import { ActionPayload } from "../../dispatcher/payloads"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { WidgetSurrogate } from "./WidgetSurrogate"; +import { SdkWidgetDriver } from "./SdkWidgetDriver"; +import { EnhancedMap } from "../../utils/maps"; + +/** + * Temporary holding store for widget messaging instances. This is eventually + * going to be merged with a more complete WidgetStore, but for now it's + * easiest to split this into a single place. + */ +export class WidgetMessagingStore extends AsyncStoreWithClient { + private static internalInstance = new WidgetMessagingStore(); + + // > + private widgetMap = new EnhancedMap>(); + + public constructor() { + super(defaultDispatcher); + } + + public static get instance(): WidgetMessagingStore { + return WidgetMessagingStore.internalInstance; + } + + protected async onAction(payload: ActionPayload): Promise { + // nothing to do + } + + protected async onReady(): Promise { + // just in case + this.widgetMap.clear(); + } + + /** + * Gets the messaging instance for the widget. Returns a falsey value if none + * is present. + * @param {Room} room The room for which the widget lives within. + * @param {Widget} widget The widget to get messaging for. + * @returns {ClientWidgetApi} The messaging, or a falsey value. + */ + public messagingForRoomWidget(room: Room, widget: Widget): ClientWidgetApi { + return this.widgetMap.get(room.roomId)?.get(widget.id)?.messaging; + } + + /** + * Gets the messaging instance for the widget. Returns a falsey value if none + * is present. + * @param {Widget} widget The widget to get messaging for. + * @returns {ClientWidgetApi} The messaging, or a falsey value. + */ + public messagingForAccountWidget(widget: Widget): ClientWidgetApi { + return this.widgetMap.get(this.matrixClient?.getUserId())?.get(widget.id)?.messaging; + } + + private generateMessaging(locationId: string, widget: Widget, iframe: HTMLIFrameElement, driver: WidgetDriver) { + const messaging = new ClientWidgetApi(widget, iframe, driver); + this.widgetMap.getOrCreate(locationId, new EnhancedMap()) + .getOrCreate(widget.id, new WidgetSurrogate(widget, messaging)); + return messaging; + } + + /** + * Generates a messaging instance for the widget. If an instance already exists, it + * will be returned instead. + * @param {Room} room The room in which the widget lives. + * @param {Widget} widget The widget to generate/get messaging for. + * @param {HTMLIFrameElement} iframe The widget's iframe. + * @returns {ClientWidgetApi} The generated/cached messaging. + */ + public generateMessagingForRoomWidget(room: Room, widget: Widget, iframe: HTMLIFrameElement): ClientWidgetApi { + const existing = this.messagingForRoomWidget(room, widget); + if (existing) return existing; + + const driver = new SdkWidgetDriver(widget, WidgetKind.Room, room.roomId); + return this.generateMessaging(room.roomId, widget, iframe, driver); + } + + /** + * Generates a messaging instance for the widget. If an instance already exists, it + * will be returned instead. + * @param {Widget} widget The widget to generate/get messaging for. + * @param {HTMLIFrameElement} iframe The widget's iframe. + * @returns {ClientWidgetApi} The generated/cached messaging. + */ + public generateMessagingForAccountWidget(widget: Widget, iframe: HTMLIFrameElement): ClientWidgetApi { + if (!this.matrixClient) { + throw new Error("No matrix client to create account widgets with"); + } + + const existing = this.messagingForAccountWidget(widget); + if (existing) return existing; + + const userId = this.matrixClient.getUserId(); + const driver = new SdkWidgetDriver(widget, WidgetKind.Account, userId); + return this.generateMessaging(userId, widget, iframe, driver); + } +} diff --git a/src/stores/widgets/WidgetSurrogate.ts b/src/stores/widgets/WidgetSurrogate.ts new file mode 100644 index 0000000000..4d482124a6 --- /dev/null +++ b/src/stores/widgets/WidgetSurrogate.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ClientWidgetApi, Widget } from "matrix-widget-api"; + +export class WidgetSurrogate { + public constructor( + public readonly definition: Widget, + public readonly messaging: ClientWidgetApi, + ) { + } +} diff --git a/src/utils/iterables.ts b/src/utils/iterables.ts new file mode 100644 index 0000000000..3d2585906d --- /dev/null +++ b/src/utils/iterables.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { arrayUnion } from "./arrays"; + +export function iterableUnion, T>(a: C, b: C): Set { + return new Set(arrayUnion(Array.from(a), Array.from(b))); +} diff --git a/src/utils/maps.ts b/src/utils/maps.ts index 96832094f0..630e0af286 100644 --- a/src/utils/maps.ts +++ b/src/utils/maps.ts @@ -44,3 +44,20 @@ export function mapKeyChanges(a: Map, b: Map): K[] { const diff = mapDiff(a, b); return arrayMerge(diff.removed, diff.added, diff.changed); } + +/** + * A Map with added utility. + */ +export class EnhancedMap extends Map { + public constructor(entries?: Iterable<[K, V]>) { + super(entries); + } + + public getOrCreate(key: K, def: V): V { + if (this.has(key)) { + return this.get(key); + } + this.set(key, def); + return def; + } +}