This repository has been archived by the owner on Sep 11, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 827
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1672 from matrix-org/rxl881/snapshot
Bi-directional widget postMessaging API (stickerpacks) [WIP]
- Loading branch information
Showing
27 changed files
with
1,641 additions
and
547 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
/* | ||
Copyright 2018 New Vector Ltd | ||
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 URL from 'url'; | ||
import dis from './dispatcher'; | ||
import IntegrationManager from './IntegrationManager'; | ||
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint'; | ||
|
||
const WIDGET_API_VERSION = '0.0.1'; // Current API version | ||
const SUPPORTED_WIDGET_API_VERSIONS = [ | ||
'0.0.1', | ||
]; | ||
const INBOUND_API_NAME = 'fromWidget'; | ||
|
||
// Listen for and handle incomming requests using the 'fromWidget' postMessage | ||
// API and initiate responses | ||
export default class FromWidgetPostMessageApi { | ||
constructor() { | ||
this.widgetMessagingEndpoints = []; | ||
|
||
this.start = this.start.bind(this); | ||
this.stop = this.stop.bind(this); | ||
this.onPostMessage = this.onPostMessage.bind(this); | ||
} | ||
|
||
start() { | ||
window.addEventListener('message', this.onPostMessage); | ||
} | ||
|
||
stop() { | ||
window.removeEventListener('message', this.onPostMessage); | ||
} | ||
|
||
/** | ||
* Register a widget endpoint for trusted postMessage communication | ||
* @param {string} widgetId Unique widget identifier | ||
* @param {string} endpointUrl Widget wurl origin (protocol + (optional port) + host) | ||
*/ | ||
addEndpoint(widgetId, endpointUrl) { | ||
const u = URL.parse(endpointUrl); | ||
if (!u || !u.protocol || !u.host) { | ||
console.warn('Add FromWidgetPostMessageApi endpoint - Invalid origin:', endpointUrl); | ||
return; | ||
} | ||
|
||
const origin = u.protocol + '//' + u.host; | ||
const endpoint = new WidgetMessagingEndpoint(widgetId, origin); | ||
if (this.widgetMessagingEndpoints.some(function(ep) { | ||
return (ep.widgetId === widgetId && ep.endpointUrl === endpointUrl); | ||
})) { | ||
// Message endpoint already registered | ||
console.warn('Add FromWidgetPostMessageApi - Endpoint already registered'); | ||
return; | ||
} else { | ||
console.warn(`Adding fromWidget messaging endpoint for ${widgetId}`, endpoint); | ||
this.widgetMessagingEndpoints.push(endpoint); | ||
} | ||
} | ||
|
||
/** | ||
* De-register a widget endpoint from trusted communication sources | ||
* @param {string} widgetId Unique widget identifier | ||
* @param {string} endpointUrl Widget wurl origin (protocol + (optional port) + host) | ||
* @return {boolean} True if endpoint was successfully removed | ||
*/ | ||
removeEndpoint(widgetId, endpointUrl) { | ||
const u = URL.parse(endpointUrl); | ||
if (!u || !u.protocol || !u.host) { | ||
console.warn('Remove widget messaging endpoint - Invalid origin'); | ||
return; | ||
} | ||
|
||
const origin = u.protocol + '//' + u.host; | ||
if (this.widgetMessagingEndpoints && this.widgetMessagingEndpoints.length > 0) { | ||
const length = this.widgetMessagingEndpoints.length; | ||
this.widgetMessagingEndpoints = this.widgetMessagingEndpoints. | ||
filter(function(endpoint) { | ||
return (endpoint.widgetId != widgetId || endpoint.endpointUrl != origin); | ||
}); | ||
return (length > this.widgetMessagingEndpoints.length); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Handle widget postMessage events | ||
* Messages are only handled where a valid, registered messaging endpoints | ||
* @param {Event} event Event to handle | ||
* @return {undefined} | ||
*/ | ||
onPostMessage(event) { | ||
if (!event.origin) { // Handle chrome | ||
event.origin = event.originalEvent.origin; | ||
} | ||
|
||
// Event origin is empty string if undefined | ||
if ( | ||
event.origin.length === 0 || | ||
!this.trustedEndpoint(event.origin) || | ||
event.data.api !== INBOUND_API_NAME || | ||
!event.data.widgetId | ||
) { | ||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise | ||
} | ||
|
||
const action = event.data.action; | ||
const widgetId = event.data.widgetId; | ||
if (action === 'content_loaded') { | ||
console.warn('Widget reported content loaded for', widgetId); | ||
dis.dispatch({ | ||
action: 'widget_content_loaded', | ||
widgetId: widgetId, | ||
}); | ||
this.sendResponse(event, {success: true}); | ||
} else if (action === 'supported_api_versions') { | ||
this.sendResponse(event, { | ||
api: INBOUND_API_NAME, | ||
supported_versions: SUPPORTED_WIDGET_API_VERSIONS, | ||
}); | ||
} else if (action === 'api_version') { | ||
this.sendResponse(event, { | ||
api: INBOUND_API_NAME, | ||
version: WIDGET_API_VERSION, | ||
}); | ||
} else if (action === 'm.sticker') { | ||
// console.warn('Got sticker message from widget', widgetId); | ||
dis.dispatch({action: 'm.sticker', data: event.data.widgetData, widgetId: event.data.widgetId}); | ||
} else if (action === 'integration_manager_open') { | ||
// Close the stickerpicker | ||
dis.dispatch({action: 'stickerpicker_close'}); | ||
// Open the integration manager | ||
const data = event.data.widgetData; | ||
const integType = (data && data.integType) ? data.integType : null; | ||
const integId = (data && data.integId) ? data.integId : null; | ||
IntegrationManager.open(integType, integId); | ||
} else { | ||
console.warn('Widget postMessage event unhandled'); | ||
this.sendError(event, {message: 'The postMessage was unhandled'}); | ||
} | ||
} | ||
|
||
/** | ||
* Check if message origin is registered as trusted | ||
* @param {string} origin PostMessage origin to check | ||
* @return {boolean} True if trusted | ||
*/ | ||
trustedEndpoint(origin) { | ||
if (!origin) { | ||
return false; | ||
} | ||
|
||
return this.widgetMessagingEndpoints.some((endpoint) => { | ||
// TODO / FIXME -- Should this also check the widgetId? | ||
return endpoint.endpointUrl === origin; | ||
}); | ||
} | ||
|
||
/** | ||
* Send a postmessage response to a postMessage request | ||
* @param {Event} event The original postMessage request event | ||
* @param {Object} res Response data | ||
*/ | ||
sendResponse(event, res) { | ||
const data = JSON.parse(JSON.stringify(event.data)); | ||
data.response = res; | ||
event.source.postMessage(data, event.origin); | ||
} | ||
|
||
/** | ||
* Send an error response to a postMessage request | ||
* @param {Event} event The original postMessage request event | ||
* @param {string} msg Error message | ||
* @param {Error} nestedError Nested error event (optional) | ||
*/ | ||
sendError(event, msg, nestedError) { | ||
console.error('Action:' + event.data.action + ' failed with message: ' + msg); | ||
const data = JSON.parse(JSON.stringify(event.data)); | ||
data.response = { | ||
error: { | ||
message: msg, | ||
}, | ||
}; | ||
if (nestedError) { | ||
data.response.error._error = nestedError; | ||
} | ||
event.source.postMessage(data, event.origin); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
Copyright 2017 New Vector Ltd | ||
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 Modal from './Modal'; | ||
import sdk from './index'; | ||
import SdkConfig from './SdkConfig'; | ||
import ScalarMessaging from './ScalarMessaging'; | ||
import ScalarAuthClient from './ScalarAuthClient'; | ||
import RoomViewStore from './stores/RoomViewStore'; | ||
|
||
if (!global.mxIntegrationManager) { | ||
global.mxIntegrationManager = {}; | ||
} | ||
|
||
export default class IntegrationManager { | ||
static _init() { | ||
if (!global.mxIntegrationManager.client || !global.mxIntegrationManager.connected) { | ||
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { | ||
ScalarMessaging.startListening(); | ||
global.mxIntegrationManager.client = new ScalarAuthClient(); | ||
|
||
return global.mxIntegrationManager.client.connect().then(() => { | ||
global.mxIntegrationManager.connected = true; | ||
}).catch((e) => { | ||
console.error("Failed to connect to integrations server", e); | ||
global.mxIntegrationManager.error = e; | ||
}); | ||
} else { | ||
console.error('Invalid integration manager config', SdkConfig.get()); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Launch the integrations manager on the stickers integration page | ||
* @param {string} integName integration / widget type | ||
* @param {string} integId integration / widget ID | ||
* @param {function} onFinished Callback to invoke on integration manager close | ||
*/ | ||
static async open(integName, integId, onFinished) { | ||
await IntegrationManager._init(); | ||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); | ||
if (global.mxIntegrationManager.error || | ||
!(global.mxIntegrationManager.client && global.mxIntegrationManager.client.hasCredentials())) { | ||
console.error("Scalar error", global.mxIntegrationManager); | ||
return; | ||
} | ||
const integType = 'type_' + integName; | ||
const src = (global.mxIntegrationManager.client && global.mxIntegrationManager.client.hasCredentials()) ? | ||
global.mxIntegrationManager.client.getScalarInterfaceUrlForRoom( | ||
{roomId: RoomViewStore.getRoomId()}, | ||
integType, | ||
integId, | ||
) : | ||
null; | ||
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { | ||
src: src, | ||
onFinished: onFinished, | ||
}, "mx_IntegrationsManager"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.