diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 6aaeab060f..83dd9d7b1e 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -108,7 +108,6 @@ export default class AppTile extends React.Component { return !!currentlyAllowedWidgets[newProps.app.eventId]; }; - const PersistedElement = sdk.getComponent("elements.PersistedElement"); return { initialising: true, // True while we are mangling the widget URL // True while the iframe content is loading @@ -190,7 +189,6 @@ export default class AppTile extends React.Component { // if it's not remaining on screen, get rid of the PersistedElement container if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) { ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); - const PersistedElement = sdk.getComponent("elements.PersistedElement"); PersistedElement.destroyElement(this._persistKey); } } diff --git a/src/components/views/elements/AppTile2.tsx b/src/components/views/elements/AppTile2.tsx new file mode 100644 index 0000000000..78bb6f7754 --- /dev/null +++ b/src/components/views/elements/AppTile2.tsx @@ -0,0 +1,77 @@ +/* + * 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, WidgetKind } from "matrix-widget-api"; +import * as React from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; + +interface IProps { + widget: Widget; + kind: WidgetKind; + room?: Room; + + // TODO: All the showUIElement props +} + +interface IState { + loading: boolean; +} + +export class AppTile2 extends React.PureComponent { + private messaging: ClientWidgetApi; + private iframeRef = React.createRef(); + + public constructor(props: IProps) { + super(props); + + if (props.kind === WidgetKind.Room && !props.room) { + throw new Error("Expected room when supplied with a room widget"); + } + + this.state = { + loading: true, + }; + } + + private get isMixedContent(): boolean { + const myProtocol = window.location.protocol; + const widgetProtocol = new URL(this.props.widget.templateUrl).protocol; + return myProtocol === 'https:' && widgetProtocol !== 'https:'; + } + + public componentDidMount() { + if (!this.iframeRef.current) { + throw new Error("iframe has not yet been associated - fix the render code"); + } + + // TODO: Provide capabilities to widget messaging + + if (this.props.kind === WidgetKind.Room) { + this.messaging = WidgetMessagingStore.instance + .generateMessagingForRoomWidget(this.props.room, this.props.widget, this.iframeRef.current); + } else if (this.props.kind === WidgetKind.Account) { + this.messaging = WidgetMessagingStore.instance + .generateMessagingForAccountWidget(this.props.widget, this.iframeRef.current); + } else { + throw new Error("Unexpected widget kind: " + this.props.kind); + } + + this.messaging.once("ready", () => { + this.setState({loading: false}); + }); + } +} diff --git a/src/stores/ActiveWidgetStore.js b/src/stores/ActiveWidgetStore.js index bf9ae3586c..d6aaf83196 100644 --- a/src/stores/ActiveWidgetStore.js +++ b/src/stores/ActiveWidgetStore.js @@ -17,6 +17,7 @@ limitations under the License. import EventEmitter from 'events'; import {MatrixClientPeg} from '../MatrixClientPeg'; +import {WidgetMessagingStore} from "./widgets/WidgetMessagingStore"; /** * Stores information about the widgets active in the app right now: @@ -29,15 +30,6 @@ class ActiveWidgetStore extends EventEmitter { super(); this._persistentWidgetId = null; - // A list of negotiated capabilities for each widget, by ID - // { - // widgetId: [caps...], - // } - this._capsByWidgetId = {}; - - // A WidgetMessaging instance for each widget ID - this._widgetMessagingByWidgetId = {}; - // What room ID each widget is associated with (if it's a room widget) this._roomIdByWidgetId = {}; @@ -54,8 +46,6 @@ class ActiveWidgetStore extends EventEmitter { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); } - this._capsByWidgetId = {}; - this._widgetMessagingByWidgetId = {}; this._roomIdByWidgetId = {}; } @@ -76,9 +66,16 @@ class ActiveWidgetStore extends EventEmitter { if (id !== this._persistentWidgetId) return; const toDeleteId = this._persistentWidgetId; + const result = WidgetMessagingStore.instance.findWidgetById(id); + if (result) { + if (result.room) { + WidgetMessagingStore.instance.stopMessagingForRoomWidget(result.room, result.widget); + } else { + WidgetMessagingStore.instance.stopMessagingForAccountWidget(result.widget); + } + } + this.setWidgetPersistence(toDeleteId, false); - this.delWidgetMessaging(toDeleteId); - this.delWidgetCapabilities(toDeleteId); this.delRoomId(toDeleteId); } @@ -99,43 +96,6 @@ class ActiveWidgetStore extends EventEmitter { return this._persistentWidgetId; } - setWidgetCapabilities(widgetId, caps) { - this._capsByWidgetId[widgetId] = caps; - this.emit('update'); - } - - widgetHasCapability(widgetId, cap) { - return this._capsByWidgetId[widgetId] && this._capsByWidgetId[widgetId].includes(cap); - } - - delWidgetCapabilities(widgetId) { - delete this._capsByWidgetId[widgetId]; - this.emit('update'); - } - - setWidgetMessaging(widgetId, wm) { - // Stop any existing widget messaging first - this.delWidgetMessaging(widgetId); - this._widgetMessagingByWidgetId[widgetId] = wm; - this.emit('update'); - } - - getWidgetMessaging(widgetId) { - return this._widgetMessagingByWidgetId[widgetId]; - } - - delWidgetMessaging(widgetId) { - if (this._widgetMessagingByWidgetId[widgetId]) { - try { - this._widgetMessagingByWidgetId[widgetId].stop(); - } catch (e) { - console.error('Failed to stop listening for widgetMessaging events', e.message); - } - delete this._widgetMessagingByWidgetId[widgetId]; - this.emit('update'); - } - } - getRoomId(widgetId) { return this._roomIdByWidgetId[widgetId]; } diff --git a/src/stores/widgets/WidgetMessagingStore.ts b/src/stores/widgets/WidgetMessagingStore.ts index dfa8eed943..fedc9c6c87 100644 --- a/src/stores/widgets/WidgetMessagingStore.ts +++ b/src/stores/widgets/WidgetMessagingStore.ts @@ -51,6 +51,25 @@ export class WidgetMessagingStore extends AsyncStoreWithClient { this.widgetMap.clear(); } + /** + * Finds a widget by ID. Not guaranteed to return an accurate result. + * @param {string} id The widget ID. + * @returns {{widget, room}} The widget and possible room ID, or a falsey value + * if not found. + * @deprecated Do not use. + */ + public findWidgetById(id: string): { widget: Widget, room?: Room } { + for (const key of this.widgetMap.keys()) { + for (const [entityId, surrogate] of this.widgetMap.get(key).entries()) { + if (surrogate.definition.id === id) { + const room: Room = this.matrixClient?.getRoom(entityId); // will be null for non-rooms + return {room, widget: surrogate.definition}; + } + } + } + return null; + } + /** * Gets the messaging instance for the widget. Returns a falsey value if none * is present.