From 9162a0ff012bd01e131b4ce47650f747e7307e20 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 23 Aug 2016 12:00:11 +0100 Subject: [PATCH 1/5] Add postMessage API required for integration provisioning Supports querying member state and creating invites only. --- src/ScalarMessaging.js | 171 +++++++++++++++++++++ src/components/views/rooms/RoomSettings.js | 4 + 2 files changed, 175 insertions(+) create mode 100644 src/ScalarMessaging.js diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js new file mode 100644 index 0000000000..7033e6b6a5 --- /dev/null +++ b/src/ScalarMessaging.js @@ -0,0 +1,171 @@ +/* +Copyright 2016 OpenMarket 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. +*/ + +/* +Listens for incoming postMessage requests from the integrations UI URL. The following API is exposed: +{ + action: "invite" | "membership_state", + room_id: $ROOM_ID, + user_id: $USER_ID +} + +The complete request object is returned to the caller with an additional "response" key like so: +{ + action: "invite" | "membership_state", + room_id: $ROOM_ID, + user_id: $USER_ID, + response: { ... } +} + +"response" objects can consist of a sole "error" key to indicate an error. These look like: +{ + error: { + message: "Unable to invite user into room.", + _error: + } +} +The "message" key should be a human-friendly string. + +The response object for "membership_state" looks like: +{ + membership_state: "join" | "leave" | "invite" | "ban" +} + +The response object for "invite" looks like: +{ + invite: true +} + +*/ + +const SdkConfig = require('./SdkConfig'); +const MatrixClientPeg = require("./MatrixClientPeg"); + +const onMessage = function(event) { + if (!event.origin) { // stupid chrome + event.origin = event.originalEvent.origin; + } + + // check it is from the integrations UI URL (remove trailing spaces) + let url = SdkConfig.get().integrations_ui_url; + if (url.endsWith("/")) { + url = url.substr(0, url.length - 1); + } + if (url !== event.origin) { + console.warn("Unauthorised postMessage received. Source URL: " + event.origin); + return; + } + + switch (event.data.action) { + case "membership_state": { + const roomId = event.data.room_id; + const userId = event.data.user_id; + if (!userId) { + return sendError(event, "Missing user_id in request"); + } + if (!roomId) { + return sendError(event, "Missing room_id in request"); + } + console.log(`membership_state of ${userId} in room ${roomId} requested.`); + const client = MatrixClientPeg.get(); + if (!client) { + return sendError(event, "You need to be logged in."); + } + const room = client.getRoom(roomId); + if (!room) { + return sendError(event, "This room is not recognised."); + } + let membershipState = "leave"; + const member = room.getMember(userId); + if (member) { + membershipState = member.membership; + } + sendResponse(event, { + membership_state: membershipState + }); + } + break; + case "invite": { + const roomId = event.data.room_id; + const userId = event.data.user_id; + if (!userId) { + return sendError(event, "Missing user_id in request"); + } + if (!roomId) { + return sendError(event, "Missing room_id in request"); + } + console.log(`Received request to invite ${userId} into room ${roomId}`); + const client = MatrixClientPeg.get(); + if (!client) { + return sendError(event, "You need to be logged in."); + } + const room = client.getRoom(roomId); + if (room) { + // if they are already invited we can resolve immediately. + const member = room.getMember(userId); + if (member && member.membership === "invite") { + sendResponse(event, { + invite: true + }); + return; + } + } + + client.invite(roomId, userId).then(function() { + sendResponse(event, { + invite: true + }); + }, function(err) { + sendError(event, "You need to be able to invite users to do that.", err); + }); + } + break; + default: + console.warn("Unhandled postMessage event with action '" + event.data.action +"'"); + break; + } +}; + +function sendResponse(event, res) { + console.log("Action:" + event.data.action + " succeeded with response: " + JSON.stringify(res)); + const data = event.data; + data.response = res; + event.source.postMessage(data, event.origin) +} + +function sendError(event, msg, nestedError) { + console.error("Action:" + event.data.action + " failed with message: " + msg); + const data = event.data; + data.response = { + error: { + message: msg, + } + }; + if (nestedError) { + data.response.error._error = nestedError; + } + event.source.postMessage(data, event.origin); +} + +module.exports = { + startListening: function() { + window.addEventListener("message", onMessage, false); + }, + + stopListening: function() { + window.removeEventListener("message", onMessage); + } +}; diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index abc90ae486..39d67f5b48 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -23,6 +23,7 @@ var Modal = require('../../../Modal'); var ObjectUtils = require("../../../ObjectUtils"); var dis = require("../../../dispatcher"); var ScalarAuthClient = require("../../../ScalarAuthClient"); +var ScalarMessaging = require('../../../ScalarMessaging'); var UserSettingsStore = require('../../../UserSettingsStore'); // parse a string as an integer; if the input is undefined, or cannot be parsed @@ -81,6 +82,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { + ScalarMessaging.startListening(); MatrixClientPeg.get().getRoomDirectoryVisibility( this.props.room.roomId ).done((result) => { @@ -104,6 +106,8 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { + ScalarMessaging.stopListening(); + dis.dispatch({ action: 'ui_opacity', sideOpacity: 1.0, From f6b008350dcbcf8576e1172f855459735b5ac00c Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 23 Aug 2016 13:31:55 +0100 Subject: [PATCH 2/5] Spaces not tabs --- src/ScalarMessaging.js | 226 ++++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 7033e6b6a5..f2983acaeb 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -17,36 +17,36 @@ limitations under the License. /* Listens for incoming postMessage requests from the integrations UI URL. The following API is exposed: { - action: "invite" | "membership_state", - room_id: $ROOM_ID, - user_id: $USER_ID + action: "invite" | "membership_state", + room_id: $ROOM_ID, + user_id: $USER_ID } The complete request object is returned to the caller with an additional "response" key like so: { - action: "invite" | "membership_state", - room_id: $ROOM_ID, - user_id: $USER_ID, - response: { ... } + action: "invite" | "membership_state", + room_id: $ROOM_ID, + user_id: $USER_ID, + response: { ... } } "response" objects can consist of a sole "error" key to indicate an error. These look like: { - error: { - message: "Unable to invite user into room.", - _error: - } + error: { + message: "Unable to invite user into room.", + _error: + } } The "message" key should be a human-friendly string. The response object for "membership_state" looks like: { - membership_state: "join" | "leave" | "invite" | "ban" + membership_state: "join" | "leave" | "invite" | "ban" } The response object for "invite" looks like: { - invite: true + invite: true } */ @@ -55,117 +55,117 @@ const SdkConfig = require('./SdkConfig'); const MatrixClientPeg = require("./MatrixClientPeg"); const onMessage = function(event) { - if (!event.origin) { // stupid chrome - event.origin = event.originalEvent.origin; - } + if (!event.origin) { // stupid chrome + event.origin = event.originalEvent.origin; + } - // check it is from the integrations UI URL (remove trailing spaces) - let url = SdkConfig.get().integrations_ui_url; - if (url.endsWith("/")) { - url = url.substr(0, url.length - 1); - } - if (url !== event.origin) { - console.warn("Unauthorised postMessage received. Source URL: " + event.origin); - return; - } + // check it is from the integrations UI URL (remove trailing spaces) + let url = SdkConfig.get().integrations_ui_url; + if (url.endsWith("/")) { + url = url.substr(0, url.length - 1); + } + if (url !== event.origin) { + console.warn("Unauthorised postMessage received. Source URL: " + event.origin); + return; + } - switch (event.data.action) { - case "membership_state": { - const roomId = event.data.room_id; - const userId = event.data.user_id; - if (!userId) { - return sendError(event, "Missing user_id in request"); - } - if (!roomId) { - return sendError(event, "Missing room_id in request"); - } - console.log(`membership_state of ${userId} in room ${roomId} requested.`); - const client = MatrixClientPeg.get(); - if (!client) { - return sendError(event, "You need to be logged in."); - } - const room = client.getRoom(roomId); - if (!room) { - return sendError(event, "This room is not recognised."); - } - let membershipState = "leave"; - const member = room.getMember(userId); - if (member) { - membershipState = member.membership; - } - sendResponse(event, { - membership_state: membershipState - }); - } - break; - case "invite": { - const roomId = event.data.room_id; - const userId = event.data.user_id; - if (!userId) { - return sendError(event, "Missing user_id in request"); - } - if (!roomId) { - return sendError(event, "Missing room_id in request"); - } - console.log(`Received request to invite ${userId} into room ${roomId}`); - const client = MatrixClientPeg.get(); - if (!client) { - return sendError(event, "You need to be logged in."); - } - const room = client.getRoom(roomId); - if (room) { - // if they are already invited we can resolve immediately. - const member = room.getMember(userId); - if (member && member.membership === "invite") { - sendResponse(event, { - invite: true - }); - return; - } - } + switch (event.data.action) { + case "membership_state": { + const roomId = event.data.room_id; + const userId = event.data.user_id; + if (!userId) { + return sendError(event, "Missing user_id in request"); + } + if (!roomId) { + return sendError(event, "Missing room_id in request"); + } + console.log(`membership_state of ${userId} in room ${roomId} requested.`); + const client = MatrixClientPeg.get(); + if (!client) { + return sendError(event, "You need to be logged in."); + } + const room = client.getRoom(roomId); + if (!room) { + return sendError(event, "This room is not recognised."); + } + let membershipState = "leave"; + const member = room.getMember(userId); + if (member) { + membershipState = member.membership; + } + sendResponse(event, { + membership_state: membershipState + }); + } + break; + case "invite": { + const roomId = event.data.room_id; + const userId = event.data.user_id; + if (!userId) { + return sendError(event, "Missing user_id in request"); + } + if (!roomId) { + return sendError(event, "Missing room_id in request"); + } + console.log(`Received request to invite ${userId} into room ${roomId}`); + const client = MatrixClientPeg.get(); + if (!client) { + return sendError(event, "You need to be logged in."); + } + const room = client.getRoom(roomId); + if (room) { + // if they are already invited we can resolve immediately. + const member = room.getMember(userId); + if (member && member.membership === "invite") { + sendResponse(event, { + invite: true + }); + return; + } + } - client.invite(roomId, userId).then(function() { - sendResponse(event, { - invite: true - }); - }, function(err) { - sendError(event, "You need to be able to invite users to do that.", err); - }); - } - break; - default: - console.warn("Unhandled postMessage event with action '" + event.data.action +"'"); - break; - } + client.invite(roomId, userId).then(function() { + sendResponse(event, { + invite: true + }); + }, function(err) { + sendError(event, "You need to be able to invite users to do that.", err); + }); + } + break; + default: + console.warn("Unhandled postMessage event with action '" + event.data.action +"'"); + break; + } }; function sendResponse(event, res) { - console.log("Action:" + event.data.action + " succeeded with response: " + JSON.stringify(res)); - const data = event.data; - data.response = res; - event.source.postMessage(data, event.origin) + console.log("Action:" + event.data.action + " succeeded with response: " + JSON.stringify(res)); + const data = event.data; + data.response = res; + event.source.postMessage(data, event.origin) } function sendError(event, msg, nestedError) { - console.error("Action:" + event.data.action + " failed with message: " + msg); - const data = event.data; - data.response = { - error: { - message: msg, - } - }; - if (nestedError) { - data.response.error._error = nestedError; - } - event.source.postMessage(data, event.origin); + console.error("Action:" + event.data.action + " failed with message: " + msg); + const data = event.data; + data.response = { + error: { + message: msg, + } + }; + if (nestedError) { + data.response.error._error = nestedError; + } + event.source.postMessage(data, event.origin); } module.exports = { - startListening: function() { - window.addEventListener("message", onMessage, false); - }, + startListening: function() { + window.addEventListener("message", onMessage, false); + }, - stopListening: function() { - window.removeEventListener("message", onMessage); - } + stopListening: function() { + window.removeEventListener("message", onMessage); + } }; From e96a40004b7634003d43a57c7e797baee4e46ed6 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 23 Aug 2016 14:41:47 +0100 Subject: [PATCH 3/5] Review comments --- src/ScalarMessaging.js | 178 ++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 83 deletions(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index f2983acaeb..3f31cad19e 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -54,6 +54,96 @@ The response object for "invite" looks like: const SdkConfig = require('./SdkConfig'); const MatrixClientPeg = require("./MatrixClientPeg"); +function sendResponse(event, res) { + const data = JSON.parse(JSON.stringify(event.data)); + data.response = res; + event.source.postMessage(data, event.origin); +} + +function sendError(event, msg, nestedError) { + console.error("Action:" + event.data.action + " failed with message: " + msg); + const data = event.data; + data.response = { + error: { + message: msg, + }, + }; + if (nestedError) { + data.response.error._error = nestedError; + } + event.source.postMessage(data, event.origin); +} + +function inviteUser(event) { + const roomId = event.data.room_id; + const userId = event.data.user_id; + if (!userId) { + sendError(event, "Missing user_id in request"); + return; + } + if (!roomId) { + sendError(event, "Missing room_id in request"); + return; + } + console.log(`Received request to invite ${userId} into room ${roomId}`); + const client = MatrixClientPeg.get(); + if (!client) { + sendError(event, "You need to be logged in."); + return; + } + const room = client.getRoom(roomId); + if (room) { + // if they are already invited we can resolve immediately. + const member = room.getMember(userId); + if (member && member.membership === "invite") { + sendResponse(event, { + invite: true, + }); + return; + } + } + + client.invite(roomId, userId).then(function() { + sendResponse(event, { + invite: true, + }); + }, function(err) { + sendError(event, "You need to be able to invite users to do that.", err); + }); +} + +function getMembershipState(event) { + const roomId = event.data.room_id; + const userId = event.data.user_id; + if (!userId) { + sendError(event, "Missing user_id in request"); + return; + } + if (!roomId) { + sendError(event, "Missing room_id in request"); + return; + } + console.log(`membership_state of ${userId} in room ${roomId} requested.`); + const client = MatrixClientPeg.get(); + if (!client) { + sendError(event, "You need to be logged in."); + return; + } + const room = client.getRoom(roomId); + if (!room) { + sendError(event, "This room is not recognised."); + return; + } + let membershipState = "leave"; + const member = room.getMember(userId); + if (member) { + membershipState = member.membership; + } + sendResponse(event, { + membership_state: membershipState, + }); +} + const onMessage = function(event) { if (!event.origin) { // stupid chrome event.origin = event.originalEvent.origin; @@ -70,68 +160,11 @@ const onMessage = function(event) { } switch (event.data.action) { - case "membership_state": { - const roomId = event.data.room_id; - const userId = event.data.user_id; - if (!userId) { - return sendError(event, "Missing user_id in request"); - } - if (!roomId) { - return sendError(event, "Missing room_id in request"); - } - console.log(`membership_state of ${userId} in room ${roomId} requested.`); - const client = MatrixClientPeg.get(); - if (!client) { - return sendError(event, "You need to be logged in."); - } - const room = client.getRoom(roomId); - if (!room) { - return sendError(event, "This room is not recognised."); - } - let membershipState = "leave"; - const member = room.getMember(userId); - if (member) { - membershipState = member.membership; - } - sendResponse(event, { - membership_state: membershipState - }); - } + case "membership_state": + getMembershipState(event); break; - case "invite": { - const roomId = event.data.room_id; - const userId = event.data.user_id; - if (!userId) { - return sendError(event, "Missing user_id in request"); - } - if (!roomId) { - return sendError(event, "Missing room_id in request"); - } - console.log(`Received request to invite ${userId} into room ${roomId}`); - const client = MatrixClientPeg.get(); - if (!client) { - return sendError(event, "You need to be logged in."); - } - const room = client.getRoom(roomId); - if (room) { - // if they are already invited we can resolve immediately. - const member = room.getMember(userId); - if (member && member.membership === "invite") { - sendResponse(event, { - invite: true - }); - return; - } - } - - client.invite(roomId, userId).then(function() { - sendResponse(event, { - invite: true - }); - }, function(err) { - sendError(event, "You need to be able to invite users to do that.", err); - }); - } + case "invite": + inviteUser(event); break; default: console.warn("Unhandled postMessage event with action '" + event.data.action +"'"); @@ -139,27 +172,6 @@ const onMessage = function(event) { } }; -function sendResponse(event, res) { - console.log("Action:" + event.data.action + " succeeded with response: " + JSON.stringify(res)); - const data = event.data; - data.response = res; - event.source.postMessage(data, event.origin) -} - -function sendError(event, msg, nestedError) { - console.error("Action:" + event.data.action + " failed with message: " + msg); - const data = event.data; - data.response = { - error: { - message: msg, - } - }; - if (nestedError) { - data.response.error._error = nestedError; - } - event.source.postMessage(data, event.origin); -} - module.exports = { startListening: function() { window.addEventListener("message", onMessage, false); @@ -167,5 +179,5 @@ module.exports = { stopListening: function() { window.removeEventListener("message", onMessage); - } + }, }; From 1c649303e347ae31c1b6ad720007f2fb288f2d42 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 23 Aug 2016 14:50:29 +0100 Subject: [PATCH 4/5] Consistency with sendResponse --- src/ScalarMessaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 3f31cad19e..22b2b9a6d1 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -62,7 +62,7 @@ function sendResponse(event, res) { function sendError(event, msg, nestedError) { console.error("Action:" + event.data.action + " failed with message: " + msg); - const data = event.data; + const data = JSON.parse(JSON.stringify(event.data); data.response = { error: { message: msg, From ecc7850e1372789d618376701ce946cdbbde9d21 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 23 Aug 2016 14:50:52 +0100 Subject: [PATCH 5/5] Trailing ) --- src/ScalarMessaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 22b2b9a6d1..da1ef7edfc 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -62,7 +62,7 @@ function sendResponse(event, res) { function sendError(event, msg, nestedError) { console.error("Action:" + event.data.action + " failed with message: " + msg); - const data = JSON.parse(JSON.stringify(event.data); + const data = JSON.parse(JSON.stringify(event.data)); data.response = { error: { message: msg,