mirror of
https://github.com/element-hq/element-web
synced 2024-11-25 02:35:48 +03:00
Add widget postmessage API stub.
This commit is contained in:
parent
b571ab02dc
commit
4f5f44ff38
2 changed files with 126 additions and 21 deletions
|
@ -16,26 +16,48 @@ limitations under the License.
|
|||
|
||||
export default class WidgetMessaging {
|
||||
constructor() {
|
||||
this.listenerCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners for the widget instance
|
||||
* @param {string} widgetId Unique widget identifier
|
||||
* Register widget message event listeners
|
||||
* @return {undefined}
|
||||
*/
|
||||
registerListeners(widgetId) {
|
||||
if (widgetId) {
|
||||
console.log("Register widget instance postmessage listeners");
|
||||
} else {
|
||||
console.error("Register widget event listeners - No widget ID specified!");
|
||||
registerListeners() {
|
||||
if (this.listenerCount === 0) {
|
||||
window.addEventListener("message", this.onMessage, false);
|
||||
}
|
||||
this.listenerCount += 1;
|
||||
}
|
||||
|
||||
derigisterListeners() {
|
||||
this.listenerCount -= 1;
|
||||
if (this.listenerCount === 0) {
|
||||
window.removeEventListener("message", this.onMessage);
|
||||
}
|
||||
if (this.listenerCount < 0) {
|
||||
// Make an error so we get a stack trace
|
||||
const e = new Error(
|
||||
"WidgetMessaging: mismatched startListening / stopListening detected." +
|
||||
" Negative count",
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
derigisterListeners(widgetId) {
|
||||
if (widgetId) {
|
||||
console.log("Register widget instance postmessage listeners");
|
||||
} else {
|
||||
console.error("Deregister widget event listerns - No widget ID specified!");
|
||||
onMessage(event) {
|
||||
console.warn("Checking for widget event", event);
|
||||
if (!event.origin) { // Handle chrome
|
||||
event.origin = event.originalEvent.origin;
|
||||
}
|
||||
|
||||
// Event origin is empty string if undefined
|
||||
if (event.origin.length === 0 || !event.data.widgetData) {
|
||||
// TODO / FIXME -- check for valid origin URLs!!
|
||||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
||||
}
|
||||
|
||||
// TODO -- handle widget actions
|
||||
alert(event.data.widgetData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import React from 'react';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import PlatformPeg from '../../../PlatformPeg';
|
||||
import ScalarAuthClient from '../../../ScalarAuthClient';
|
||||
import WidgetMessaging from '../../../WidgetMessaging';
|
||||
import TintableSvgButton from './TintableSvgButton';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import Modal from '../../../Modal';
|
||||
|
@ -71,16 +72,46 @@ export default React.createClass({
|
|||
return {
|
||||
initialising: true, // True while we are mangling the widget URL
|
||||
loading: true, // True while the iframe content is loading
|
||||
widgetUrl: newProps.url,
|
||||
widgetUrl: this._addWurlParams(newProps.url),
|
||||
widgetPermissionId: widgetPermissionId,
|
||||
// Assume that widget has permission to load if we are the user who
|
||||
// added it to the room, or if explicitly granted by the user
|
||||
hasPermissionToLoad: hasPermissionToLoad === 'true' || newProps.userId === newProps.creatorUserId,
|
||||
error: null,
|
||||
deleting: false,
|
||||
widgetPageTitle: null,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Add widget instance specific parameters to pass in wUrl
|
||||
* Properties passed to widget instance:
|
||||
* - widgetId
|
||||
* - origin / parent URL
|
||||
* @param {string} urlString Url string to modify
|
||||
* @return {string}
|
||||
* Url string with parameters appended.
|
||||
* If url can not be parsed, it is returned unmodified.
|
||||
*/
|
||||
_addWurlParams(urlString) {
|
||||
const u = url.parse(urlString);
|
||||
if (!u) {
|
||||
console.error("_addWurlParams", "Invalid URL", urlString);
|
||||
return url;
|
||||
}
|
||||
|
||||
const params = qs.parse(u.query);
|
||||
// Append widget ID to query parameters
|
||||
params.widgetId = this.props.id;
|
||||
// Append current / parent URL
|
||||
params.parentUrl = window.location.href;
|
||||
u.search = undefined;
|
||||
u.query = params;
|
||||
|
||||
console.log("_addWurlParams", "Modified URL", u.format(), params);
|
||||
return u.format();
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return this._getNewState(this.props);
|
||||
},
|
||||
|
@ -122,6 +153,8 @@ export default React.createClass({
|
|||
},
|
||||
|
||||
componentWillMount() {
|
||||
this.widgetMessagingClient = new WidgetMessaging();
|
||||
this.widgetMessagingClient.registerListeners();
|
||||
window.addEventListener('message', this._onMessage, false);
|
||||
this.setScalarToken();
|
||||
},
|
||||
|
@ -137,7 +170,7 @@ export default React.createClass({
|
|||
console.warn('Non-scalar widget, not setting scalar token!', url);
|
||||
this.setState({
|
||||
error: null,
|
||||
widgetUrl: this.props.url,
|
||||
widgetUrl: this._addWurlParams(this.props.url),
|
||||
initialising: false,
|
||||
});
|
||||
return;
|
||||
|
@ -150,7 +183,7 @@ export default React.createClass({
|
|||
this._scalarClient.getScalarToken().done((token) => {
|
||||
// Append scalar_token as a query param if not already present
|
||||
this._scalarClient.scalarToken = token;
|
||||
const u = url.parse(this.props.url);
|
||||
const u = url.parse(this._addWurlParams(this.props.url));
|
||||
const params = qs.parse(u.query);
|
||||
if (!params.scalar_token) {
|
||||
params.scalar_token = encodeURIComponent(token);
|
||||
|
@ -256,8 +289,36 @@ export default React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when widget iframe has finished loading
|
||||
*/
|
||||
_onLoaded() {
|
||||
this.setState({loading: false});
|
||||
// Get page title and update widget panel
|
||||
// this._updateWidgetTitle();
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch remote content title and update app tile
|
||||
*/
|
||||
_updateWidgetTitle() {
|
||||
const safeUrl = this._getSafeUrl();
|
||||
console.warn("widget title safeurl:", safeUrl);
|
||||
if (safeUrl) {
|
||||
let title = null;
|
||||
try {
|
||||
// title = yield this.getUrlTitle(safeUrl);
|
||||
// console.log("Foo");
|
||||
} catch (e) {
|
||||
console.error("Failed to get title for:", safeUrl);
|
||||
}
|
||||
|
||||
console.warn("widget title:", title);
|
||||
this.setState({widgetPageTitle: title});
|
||||
return;
|
||||
}
|
||||
console.warn("widget title: no url");
|
||||
this.setState({widgetPageTitle: null});
|
||||
},
|
||||
|
||||
// Widget labels to render, depending upon user permissions
|
||||
|
@ -290,6 +351,21 @@ export default React.createClass({
|
|||
return appTileName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the HTML title for a given URL
|
||||
* @param {string} url URL to process
|
||||
* @return {string} Title of the HTML page, or null
|
||||
*/
|
||||
getUrlTitle(url) {
|
||||
return fetch(url)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
const title = doc.querySelectorAll('title')[0];
|
||||
return title.innerText;
|
||||
});
|
||||
},
|
||||
|
||||
onClickMenuBar(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
|
@ -305,6 +381,15 @@ export default React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_getSafeUrl() {
|
||||
const parsedWidgetUrl = url.parse(this.state.widgetUrl);
|
||||
let safeWidgetUrl = '';
|
||||
if (ALLOWED_APP_URL_SCHEMES.indexOf(parsedWidgetUrl.protocol) !== -1) {
|
||||
safeWidgetUrl = url.format(parsedWidgetUrl);
|
||||
}
|
||||
return safeWidgetUrl;
|
||||
},
|
||||
|
||||
render() {
|
||||
let appTileBody;
|
||||
|
||||
|
@ -320,11 +405,6 @@ export default React.createClass({
|
|||
// a link to it.
|
||||
const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox "+
|
||||
"allow-same-origin allow-scripts allow-presentation";
|
||||
const parsedWidgetUrl = url.parse(this.state.widgetUrl);
|
||||
let safeWidgetUrl = '';
|
||||
if (ALLOWED_APP_URL_SCHEMES.indexOf(parsedWidgetUrl.protocol) !== -1) {
|
||||
safeWidgetUrl = url.format(parsedWidgetUrl);
|
||||
}
|
||||
|
||||
if (this.props.show) {
|
||||
const loadingElement = (
|
||||
|
@ -347,7 +427,7 @@ export default React.createClass({
|
|||
{ this.state.loading && loadingElement }
|
||||
<iframe
|
||||
ref="appFrame"
|
||||
src={safeWidgetUrl}
|
||||
src={this._getSafeUrl()}
|
||||
allowFullScreen="true"
|
||||
sandbox={sandboxFlags}
|
||||
onLoad={this._onLoaded}
|
||||
|
@ -383,6 +463,9 @@ export default React.createClass({
|
|||
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
|
||||
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
|
||||
<b>{ this.formatAppTileName() }</b>
|
||||
{ this.state.widgetPageTitle && (
|
||||
<span> - { this.state.widgetPageTitle }</span>
|
||||
) }
|
||||
<span className="mx_AppTileMenuBarWidgets">
|
||||
{ /* Edit widget */ }
|
||||
{ showEditButton && <TintableSvgButton
|
||||
|
|
Loading…
Reference in a new issue