diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index d5d7c08d50..c5a25468a8 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -24,8 +24,10 @@ import {MatrixClientPeg} from "./MatrixClientPeg"; import RoomViewStore from "./stores/RoomViewStore"; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import SettingsStore from "./settings/SettingsStore"; -import {Capability} from "./widgets/WidgetApi"; +import {Capability, KnownWidgetActions} from "./widgets/WidgetApi"; import {objectClone} from "./utils/objects"; +import {Action} from "./dispatcher/actions"; +import {TempWidgetStore} from "./stores/TempWidgetStore"; const WIDGET_API_VERSION = '0.0.2'; // Current API version const SUPPORTED_WIDGET_API_VERSIONS = [ @@ -218,8 +220,12 @@ export default class FromWidgetPostMessageApi { if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.AlwaysOnScreen)) { ActiveWidgetStore.setWidgetPersistence(widgetId, val); } - } else if (action === 'get_openid') { + } else if (action === 'get_openid' + || action === KnownWidgetActions.CloseWidget) { // Handled by caller + } else if (action === KnownWidgetActions.OpenTempWidget) { + TempWidgetStore.instance.openTempWidget(event.data.data, widgetId); + this.sendResponse(event, {}); // ack } else { console.warn('Widget postMessage event unhandled'); this.sendError(event, {message: 'The postMessage was unhandled'}); diff --git a/src/Modal.tsx b/src/Modal.tsx index 0a36813961..3d95bc1a2b 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -28,7 +28,7 @@ import AsyncWrapper from './AsyncWrapper'; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; -interface IModal { +export interface IModal { elem: React.ReactNode; className?: string; beforeClosePromise?: Promise; diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index c68e926ac1..6a2eeb852c 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -147,6 +147,36 @@ export default class WidgetMessaging { }); } + sendThemeInfo(themeInfo: any) { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.UpdateThemeInfo, + data: themeInfo, + }).catch((error) => { + console.error("Failed to send theme info: ", error); + }); + } + + sendWidgetConfig(widgetConfig: any) { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.SendWidgetConfig, + data: widgetConfig, + }).catch((error) => { + console.error("Failed to send widget info: ", error); + }); + } + + sendTempCloseInfo(info: any) { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.ClosedWidgetResponse, + data: info, + }).catch((error) => { + console.error("Failed to send temp widget close info: ", error); + }); + } + start() { this.fromWidget.addEndpoint(this.widgetId, this.renderedUrl); this.fromWidget.addListener("get_openid", this._onOpenIdRequest); diff --git a/src/components/views/dialogs/TempWidgetDialog.tsx b/src/components/views/dialogs/TempWidgetDialog.tsx new file mode 100644 index 0000000000..1fd3b26b5c --- /dev/null +++ b/src/components/views/dialogs/TempWidgetDialog.tsx @@ -0,0 +1,155 @@ +/* +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 * as React from 'react'; +import BaseDialog from './BaseDialog'; +import { _t } from '../../../languageHandler'; +import { IDialogProps } from "./IDialogProps"; +import WidgetMessaging from "../../../WidgetMessaging"; +import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; +import Field from "../elements/Field"; +import { KnownWidgetActions } from "../../../widgets/WidgetApi"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; + +interface IState { + messaging?: WidgetMessaging; + + androidMode: boolean; + darkTheme: boolean; + accentColor: string; +} + +interface IProps extends IDialogProps { + widgetDefinition: {url: string, data: any}; + sourceWidgetId: string; +} + +// TODO: Make a better dialog + +export default class TempWidgetDialog extends React.PureComponent { + private appFrame: React.RefObject = React.createRef(); + + constructor(props) { + super(props); + this.state = { + androidMode: false, + darkTheme: false, + accentColor: "#03b381", + }; + } + + public componentDidMount() { + // TODO: Don't violate every principle of widget creation + const messaging = new WidgetMessaging( + "TEMP_ID", + this.props.widgetDefinition.url, + this.props.widgetDefinition.url, + false, + this.appFrame.current.contentWindow, + ); + this.setState({messaging}); + } + + public componentWillUnmount() { + this.state.messaging.fromWidget.removeListener(KnownWidgetActions.CloseWidget, this.onWidgetClose); + this.state.messaging.stop(); + } + + private onLoad = () => { + this.state.messaging.getCapabilities().then(caps => { + console.log("Requested capabilities: ", caps); + this.sendTheme(); + this.state.messaging.sendWidgetConfig(this.props.widgetDefinition.data); + }); + this.state.messaging.fromWidget.addListener(KnownWidgetActions.CloseWidget, this.onWidgetClose); + }; + + private sendTheme() { + if (!this.state.messaging) return; + this.state.messaging.sendThemeInfo({ + clientName: this.state.androidMode ? "element-android" : "element-web", + isDark: this.state.darkTheme, + accentColor: this.state.accentColor, + }); + } + + public static sendExitData(sourceWidgetId: string, success: boolean, data?: any) { + const sourceMessaging = ActiveWidgetStore.getWidgetMessaging(sourceWidgetId); + if (!sourceMessaging) { + console.error("No source widget messaging for temp widget"); + return; + } + sourceMessaging.sendTempCloseInfo({success, ...data}); + } + + private onWidgetClose = (req) => { + this.props.onFinished(true); + TempWidgetDialog.sendExitData(this.props.sourceWidgetId, true, req.data); + } + + private onClientToggleChanged = (androidMode) => { + this.setState({androidMode}, () => this.sendTheme()); + }; + + private onDarkThemeChanged = (darkTheme) => { + this.setState({darkTheme}, () => this.sendTheme()); + }; + + private onAccentColorChanged = (ev) => { + this.setState({accentColor: ev.target.value}, () => this.sendTheme()); + }; + + public render() { + // TODO: Don't violate every single security principle + + const widgetUrl = this.props.widgetDefinition.url + + "?widgetId=TEMP_ID&parentUrl=" + encodeURIComponent(window.location.href); + + return +
+ + + +
+
+