From 107fa98180fa0b605ade8acca390c9bde6655edf Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 20 Nov 2020 16:53:15 -0700 Subject: [PATCH 1/2] Support arbitrary widgets sticking to the screen + sending stickers Following https://github.com/matrix-org/matrix-react-sdk/pull/5385, it is now possible for a widget to request these capabilities without being a video conference or sticker picker. This commit actually enables this support for those kinds of widgets. This commit also fixes an issue in the URL templating where some variables might get set to 'undefined' - this appears to be a scoping issue, so StopGapWidget now stores the definition alongside the superclass. Fixes https://github.com/vector-im/element-web/issues/15001 --- src/components/views/elements/AppTile.js | 31 ++++----- .../views/elements/PersistentApp.js | 2 - .../views/right_panel/WidgetCard.tsx | 1 - src/components/views/rooms/AppsDrawer.js | 1 - src/components/views/rooms/Stickerpicker.js | 1 - src/stores/widgets/StopGapWidget.ts | 69 +++++++++---------- 6 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index f3ebe24c15..b862a1e912 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -23,7 +23,6 @@ import PropTypes from 'prop-types'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import AccessibleButton from './AccessibleButton'; import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; import AppPermission from './AppPermission'; import AppWarning from './AppWarning'; import Spinner from './Spinner'; @@ -375,19 +374,18 @@ export default class AppTile extends React.Component { /> ); - // if the widget would be allowed to remain on screen, we must put it in - // a PersistedElement from the get-go, otherwise the iframe will be - // re-mounted later when we do. - if (this.props.whitelistCapabilities.includes('m.always_on_screen')) { - const PersistedElement = sdk.getComponent("elements.PersistedElement"); - // Also wrap the PersistedElement in a div to fix the height, otherwise - // AppTile's border is in the wrong place - appTileBody =
- - {appTileBody} - -
; - } + + // all widgets can theoretically be allowed to remain on screen, so we wrap + // them all in a PersistedElement from the get-go. If we wait, the iframe will + // be re-mounted later, which means the widget has to start over, which is bad. + + // Also wrap the PersistedElement in a div to fix the height, otherwise + // AppTile's border is in the wrong place + appTileBody =
+ + {appTileBody} + +
; } } @@ -474,10 +472,6 @@ AppTile.propTypes = { handleMinimisePointerEvents: PropTypes.bool, // Optionally hide the popout widget icon showPopout: PropTypes.bool, - // Widget capabilities to allow by default (without user confirmation) - // NOTE -- Use with caution. This is intended to aid better integration / UX - // basic widget capabilities, e.g. injecting sticker message events. - whitelistCapabilities: PropTypes.array, // Is this an instance of a user widget userWidget: PropTypes.bool, }; @@ -488,7 +482,6 @@ AppTile.defaultProps = { showTitle: true, showPopout: true, handleMinimisePointerEvents: false, - whitelistCapabilities: [], userWidget: false, miniMode: false, }; diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index 64825dfc96..a1e805c085 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -71,7 +71,6 @@ export default class PersistentApp extends React.Component { appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(), persistentWidgetInRoomId, appEvent.getId(), ); - const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, persistentWidgetInRoomId); const AppTile = sdk.getComponent('elements.AppTile'); return ; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index 7dbb77df18..c1753e90e3 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -103,7 +103,6 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { creatorUserId={app.creatorUserId} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} - whitelistCapabilities={WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, room.roomId)} /> ; }; diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 9cf213b44e..5e5823e630 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -221,7 +221,6 @@ export default class AppsDrawer extends React.Component { creatorUserId={app.creatorUserId} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} - whitelistCapabilities={capWhitelist} />); }); diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index ae7ed48898..0b81f82721 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -280,7 +280,6 @@ export default class Stickerpicker extends React.Component { showPopout={false} onMinimiseClick={this._onHideStickersClick} handleMinimisePointerEvents={true} - whitelistCapabilities={['m.sticker', 'visibility']} userWidget={true} /> diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 04e27dd5fc..e8c0ea141e 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -75,8 +75,8 @@ interface IAppTileProps { // TODO: Don't use this because it's wrong class ElementWidget extends Widget { - constructor(w) { - super(w); + constructor(private rawDefinition: IWidget) { + super(rawDefinition); } public get templateUrl(): string { @@ -137,12 +137,7 @@ class ElementWidget extends Widget { public getCompleteUrl(params: ITemplateParams, asPopout=false): string { return runTemplate(asPopout ? this.popoutTemplateUrl : this.templateUrl, { - // we need to supply a whole widget to the template, but don't have - // easy access to the definition the superclass is using, so be sad - // and gutwrench it. - // This isn't a problem when the widget architecture is fixed and this - // subclass gets deleted. - ...super['definition'], // XXX: Private member access + ...this.rawDefinition, data: this.rawData, }, params); } @@ -351,18 +346,39 @@ export class StopGapWidget extends EventEmitter { 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) => { - if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { + this.messaging.on(`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`, + (ev: CustomEvent) => { + if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { + if (WidgetType.JITSI.matches(this.mockWidget.type)) { CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true); - ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value); - ev.preventDefault(); - this.messaging.transport.reply(ev.detail, {}); // ack } - }, - ); - } else if (WidgetType.STICKERPICKER.matches(this.mockWidget.type)) { + ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value); + ev.preventDefault(); + this.messaging.transport.reply(ev.detail, {}); // ack + } + }, + ); + + // TODO: Replace this event listener with appropriate driver functionality once the API + // establishes a sane way to send events back and forth. + this.messaging.on(`action:${WidgetApiFromWidgetAction.SendSticker}`, + (ev: CustomEvent) => { + if (this.messaging.hasCapability(MatrixCapabilities.StickerSending)) { + // Acknowledge first + ev.preventDefault(); + this.messaging.transport.reply(ev.detail, {}); + + // Send the sticker + defaultDispatcher.dispatch({ + action: 'm.sticker', + data: ev.detail.data, + widgetId: this.mockWidget.id, + }); + } + }, + ); + + if (WidgetType.STICKERPICKER.matches(this.mockWidget.type)) { this.messaging.on(`action:${ElementWidgetActions.OpenIntegrationManager}`, (ev: CustomEvent) => { // Acknowledge first @@ -394,23 +410,6 @@ export class StopGapWidget extends EventEmitter { } }, ); - - // TODO: Replace this event listener with appropriate driver functionality once the API - // establishes a sane way to send events back and forth. - this.messaging.on(`action:${WidgetApiFromWidgetAction.SendSticker}`, - (ev: CustomEvent) => { - // Acknowledge first - ev.preventDefault(); - this.messaging.transport.reply(ev.detail, {}); - - // Send the sticker - defaultDispatcher.dispatch({ - action: 'm.sticker', - data: ev.detail.data, - widgetId: this.mockWidget.id, - }); - }, - ); } } From 43be434aecd44e902dc41c0b4e55ea8616a183d3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 20 Nov 2020 19:36:45 -0700 Subject: [PATCH 2/2] Appease the linter --- src/components/views/rooms/AppsDrawer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 5e5823e630..3208844bc5 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -210,8 +210,6 @@ export default class AppsDrawer extends React.Component { if (!this.props.showApps) return
; const apps = this.state.apps.map((app, index, arr) => { - const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); - return (