diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js
index ea7eeba756..577eabf5ec 100644
--- a/src/FromWidgetPostMessageApi.js
+++ b/src/FromWidgetPostMessageApi.js
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
+Copyright 2019 Travis Ralston
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
@@ -20,17 +21,19 @@ import IntegrationManager from './IntegrationManager';
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
-const WIDGET_API_VERSION = '0.0.1'; // Current API version
+const WIDGET_API_VERSION = '0.0.2'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [
'0.0.1',
+ '0.0.2',
];
const INBOUND_API_NAME = 'fromWidget';
-// Listen for and handle incomming requests using the 'fromWidget' postMessage
+// Listen for and handle incoming requests using the 'fromWidget' postMessage
// API and initiate responses
export default class FromWidgetPostMessageApi {
constructor() {
this.widgetMessagingEndpoints = [];
+ this.widgetListeners = {}; // {action: func[]}
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
@@ -45,6 +48,32 @@ export default class FromWidgetPostMessageApi {
window.removeEventListener('message', this.onPostMessage);
}
+ /**
+ * Adds a listener for a given action
+ * @param {string} action The action to listen for.
+ * @param {Function} callbackFn A callback function to be called when the action is
+ * encountered. Called with two parameters: the interesting request information and
+ * the raw event received from the postMessage API. The raw event is meant to be used
+ * for sendResponse and similar functions.
+ */
+ addListener(action, callbackFn) {
+ if (!this.widgetListeners[action]) this.widgetListeners[action] = [];
+ this.widgetListeners[action].push(callbackFn);
+ }
+
+ /**
+ * Removes a listener for a given action.
+ * @param {string} action The action that was subscribed to.
+ * @param {Function} callbackFn The original callback function that was used to subscribe
+ * to updates.
+ */
+ removeListener(action, callbackFn) {
+ if (!this.widgetListeners[action]) return;
+
+ const idx = this.widgetListeners.indexOf(callbackFn);
+ if (idx !== -1) this.widgetListeners.splice(idx, 1);
+ }
+
/**
* Register a widget endpoint for trusted postMessage communication
* @param {string} widgetId Unique widget identifier
@@ -117,6 +146,13 @@ export default class FromWidgetPostMessageApi {
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
}
+ // Call any listeners we have registered
+ if (this.widgetListeners[event.data.action]) {
+ for (const fn of this.widgetListeners[event.data.action]) {
+ fn(event.data, event);
+ }
+ }
+
// Although the requestId is required, we don't use it. We'll be nice and process the message
// if the property is missing, but with a warning for widget developers.
if (!event.data.requestId) {
@@ -164,6 +200,8 @@ export default class FromWidgetPostMessageApi {
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
}
+ } else if (action === 'get_openid') {
+ // Handled by caller
} else {
console.warn('Widget postMessage event unhandled');
this.sendError(event, {message: 'The postMessage was unhandled'});
diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js
index 5b722df65f..17ce9360b7 100644
--- a/src/WidgetMessaging.js
+++ b/src/WidgetMessaging.js
@@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd
+Copyright 2019 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,6 +22,10 @@ limitations under the License.
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
+import Modal from "./Modal";
+import QuestionDialog from "./components/views/dialogs/QuestionDialog";
+import {_t} from "./languageHandler";
+import MatrixClientPeg from "./MatrixClientPeg";
if (!global.mxFromWidgetMessaging) {
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
@@ -40,6 +45,7 @@ export default class WidgetMessaging {
this.target = target;
this.fromWidget = global.mxFromWidgetMessaging;
this.toWidget = global.mxToWidgetMessaging;
+ this._openIdHandlerRef = this._onOpenIdRequest.bind(this);
this.start();
}
@@ -109,9 +115,48 @@ export default class WidgetMessaging {
start() {
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);
+ this.fromWidget.addListener("get_openid", this._openIdHandlerRef);
}
stop() {
this.fromWidget.removeEndpoint(this.widgetId, this.widgetUrl);
+ this.fromWidget.removeListener("get_openid", this._openIdHandlerRef);
+ }
+
+ _onOpenIdRequest(ev, rawEv) {
+ if (ev.widgetId !== this.widgetId) return; // not interesting
+
+ // Confirm that we received the request
+ this.fromWidget.sendResponse(rawEv, {state: "request"});
+
+ // TODO: Support blacklisting widgets
+ // TODO: Support whitelisting widgets
+
+ // Actually ask for permission to send the user's data
+ Modal.createTrackedDialog("OpenID widget permissions", '', QuestionDialog, {
+ title: _t("A widget would like to verify your identity"),
+ description: _t(
+ "A widget located at %(widgetUrl)s would like to verify your identity. " +
+ "By allowing this, the widget will be able to verify your user ID, but not " +
+ "perform actions as you.", {
+ widgetUrl: this.widgetUrl,
+ },
+ ),
+ button: _t("Allow"),
+ onFinished: async (confirm) => {
+ const responseBody = {success: confirm};
+ if (confirm) {
+ const credentials = await MatrixClientPeg.get().getOpenIdToken();
+ Object.assign(responseBody, credentials);
+ }
+ this.messageToWidget({
+ api: OUTBOUND_API_NAME,
+ action: "openid_credentials",
+ data: responseBody,
+ }).catch((error) => {
+ console.error("Failed to send OpenID credentials: ", error);
+ });
+ },
+ });
}
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8cc85b6036..e13390e226 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -230,6 +230,9 @@
"%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …",
"%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …",
"%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …",
+ "A widget would like to verify your identity": "A widget would like to verify your identity",
+ "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.",
+ "Allow": "Allow",
"This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.",
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
"Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.",
@@ -924,7 +927,6 @@
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
"Warning: This widget might use cookies.": "Warning: This widget might use cookies.",
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
- "Allow": "Allow",
"Delete Widget": "Delete Widget",
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
"Delete widget": "Delete widget",