Implement RequiresClient capability for widgets (#7005)

pull/21833/head
Timo 2021-10-28 14:17:04 +02:00 committed by GitHub
parent 68b64564c0
commit 5736fea5f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 23 additions and 7 deletions

View File

@ -84,6 +84,7 @@ interface IState {
error: Error; error: Error;
menuDisplayed: boolean; menuDisplayed: boolean;
widgetPageTitle: string; widgetPageTitle: string;
requiresClient: boolean;
} }
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -114,8 +115,10 @@ export default class AppTile extends React.Component<IProps, IState> {
this.persistKey = getPersistKey(this.props.app.id); this.persistKey = getPersistKey(this.props.app.id);
try { try {
this.sgWidget = new StopGapWidget(this.props); this.sgWidget = new StopGapWidget(this.props);
this.sgWidget.on("preparing", this.onWidgetPrepared); this.sgWidget.on("preparing", this.onWidgetPreparing);
this.sgWidget.on("ready", this.onWidgetReady); this.sgWidget.on("ready", this.onWidgetReady);
// emits when the capabilites have been setup or changed
this.sgWidget.on("capabilitiesNotified", this.onWidgetCapabilitiesNotified);
} catch (e) { } catch (e) {
logger.log("Failed to construct widget", e); logger.log("Failed to construct widget", e);
this.sgWidget = null; this.sgWidget = null;
@ -155,6 +158,10 @@ export default class AppTile extends React.Component<IProps, IState> {
error: null, error: null,
menuDisplayed: false, menuDisplayed: false,
widgetPageTitle: this.props.widgetPageTitle, widgetPageTitle: this.props.widgetPageTitle,
// requiresClient is initially set to true. This avoids the broken state of the popout
// button being visible (for an instance) and then disappearing when the widget is loaded.
// requiresClient <-> hide the popout button
requiresClient: true,
}; };
} }
@ -216,7 +223,7 @@ export default class AppTile extends React.Component<IProps, IState> {
} }
try { try {
this.sgWidget = new StopGapWidget(newProps); this.sgWidget = new StopGapWidget(newProps);
this.sgWidget.on("preparing", this.onWidgetPrepared); this.sgWidget.on("preparing", this.onWidgetPreparing);
this.sgWidget.on("ready", this.onWidgetReady); this.sgWidget.on("ready", this.onWidgetReady);
this.startWidget(); this.startWidget();
} catch (e) { } catch (e) {
@ -287,7 +294,7 @@ export default class AppTile extends React.Component<IProps, IState> {
if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true }); if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true });
} }
private onWidgetPrepared = (): void => { private onWidgetPreparing = (): void => {
this.setState({ loading: false }); this.setState({ loading: false });
}; };
@ -297,6 +304,12 @@ export default class AppTile extends React.Component<IProps, IState> {
} }
}; };
private onWidgetCapabilitiesNotified = (): void => {
this.setState({
requiresClient: this.sgWidget.widgetApi.hasCapability(MatrixCapabilities.RequiresClient),
});
};
private onAction = (payload): void => { private onAction = (payload): void => {
if (payload.widgetId === this.props.app.id) { if (payload.widgetId === this.props.app.id) {
switch (payload.action) { switch (payload.action) {
@ -512,7 +525,7 @@ export default class AppTile extends React.Component<IProps, IState> {
{ this.props.showTitle && this.getTileTitle() } { this.props.showTitle && this.getTileTitle() }
</span> </span>
<span className="mx_AppTileMenuBarWidgets"> <span className="mx_AppTileMenuBarWidgets">
{ this.props.showPopout && <AccessibleButton { (this.props.showPopout && !this.state.requiresClient) && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout" className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
title={_t('Popout widget')} title={_t('Popout widget')}
onClick={this.onPopoutWidgetClick} onClick={this.onPopoutWidgetClick}

View File

@ -135,7 +135,7 @@ export class ElementWidget extends Widget {
}; };
} }
public getCompleteUrl(params: ITemplateParams, asPopout=false): string { public getCompleteUrl(params: ITemplateParams, asPopout = false): string {
return runTemplate(asPopout ? this.popoutTemplateUrl : this.templateUrl, { return runTemplate(asPopout ? this.popoutTemplateUrl : this.templateUrl, {
...this.rawDefinition, ...this.rawDefinition,
data: this.rawData, data: this.rawData,
@ -149,7 +149,7 @@ export class StopGapWidget extends EventEmitter {
private scalarToken: string; private scalarToken: string;
private roomId?: string; private roomId?: string;
private kind: WidgetKind; private kind: WidgetKind;
private readUpToMap: {[roomId: string]: string} = {}; // room ID to event ID private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
constructor(private appTileProps: IAppTileProps) { constructor(private appTileProps: IAppTileProps) {
super(); super();
@ -262,6 +262,7 @@ export class StopGapWidget extends EventEmitter {
this.messaging = new ClientWidgetApi(this.mockWidget, iframe, driver); this.messaging = new ClientWidgetApi(this.mockWidget, iframe, driver);
this.messaging.on("preparing", () => this.emit("preparing")); this.messaging.on("preparing", () => this.emit("preparing"));
this.messaging.on("ready", () => this.emit("ready")); this.messaging.on("ready", () => this.emit("ready"));
this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified"));
this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal); this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal);
WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.messaging); WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.messaging);

View File

@ -73,7 +73,9 @@ export class StopGapWidgetDriver extends WidgetDriver {
// Always allow screenshots to be taken because it's a client-induced flow. The widget can't // Always allow screenshots to be taken because it's a client-induced flow. The widget can't
// spew screenshots at us and can't request screenshots of us, so it's up to us to provide the // spew screenshots at us and can't request screenshots of us, so it's up to us to provide the
// button if the widget says it supports screenshots. // button if the widget says it supports screenshots.
this.allowedCapabilities = new Set([...allowedCapabilities, MatrixCapabilities.Screenshots]); this.allowedCapabilities = new Set([...allowedCapabilities,
MatrixCapabilities.Screenshots,
MatrixCapabilities.RequiresClient]);
// Grant the permissions that are specific to given widget types // Grant the permissions that are specific to given widget types
if (WidgetType.JITSI.matches(this.forWidget.type) && forWidgetKind === WidgetKind.Room) { if (WidgetType.JITSI.matches(this.forWidget.type) && forWidgetKind === WidgetKind.Room) {