From 6da4b9d671987a0f777f28efb9970152f43eec4f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 May 2016 14:19:56 +0100 Subject: [PATCH 1/8] Add suport for showing the scalar UI --- src/ScalarAuthClient.js | 45 ++++++++++++++++++++++ src/SdkConfig.js | 45 ++++++++++++++++++++++ src/components/structures/MatrixChat.js | 2 + src/components/views/rooms/RoomSettings.js | 38 ++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 src/ScalarAuthClient.js create mode 100644 src/SdkConfig.js diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js new file mode 100644 index 0000000000..0607e072ac --- /dev/null +++ b/src/ScalarAuthClient.js @@ -0,0 +1,45 @@ +/* +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. +*/ + +var q = require("q"); +var request = require('browser-request'); + +var SdkConfig = require('./SdkConfig'); + +class ScalarAuthClient { + getScalarToken(openid_token_object) { + var defer = q.defer(); + + var scalar_rest_url = SdkConfig.get().integrations_rest_url; + request({ + method: 'POST', + uri: scalar_rest_url+'/register', + body: openid_token_object, + json: true, + }, (err, response, body) => { + if (err) { + defer.reject(err); + } else { + defer.resolve(body); + } + }); + + return defer.promise; + } +} + +module.exports = ScalarAuthClient; + diff --git a/src/SdkConfig.js b/src/SdkConfig.js new file mode 100644 index 0000000000..1452aaa64b --- /dev/null +++ b/src/SdkConfig.js @@ -0,0 +1,45 @@ +/* +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. +*/ + +var DEFAULTS = { + // URL to a page we show in an iframe to configure integrations + integrations_ui_url: "https://scalar.vector.im/", + // Base URL to the REST interface of the integrations server + integrations_rest_url: "https://scalar.vector.im/api", +}; + +class SdkConfig { + + static get() { + return global.mxReactSdkConfig; + } + + static put(cfg) { + var defaultKeys = Object.keys(DEFAULTS); + for (var i = 0; i < defaultKeys.length; ++i) { + if (cfg[defaultKeys[i]] === undefined) { + cfg[defaultKeys[i]] = DEFAULTS[defaultKeys[i]]; + } + } + global.mxReactSdkConfig = cfg; + } + + static unset() { + global.mxReactSdkConfig = undefined; + } +} + +module.exports = SdkConfig; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 08b7866b1c..4f5cbb0050 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -19,6 +19,7 @@ var url = require('url'); var Favico = require('favico.js'); var MatrixClientPeg = require("../../MatrixClientPeg"); +var SdkConfig = require("../../SdkConfig"); var Notifier = require("../../Notifier"); var ContextualMenu = require("../../ContextualMenu"); var RoomListSorter = require("../../RoomListSorter"); @@ -119,6 +120,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { + SdkConfig.put(this.props.config); this.favicon = new Favico({animation: 'none'}); }, diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index fd8bcbfe96..86dfbcf886 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -17,10 +17,12 @@ limitations under the License. var q = require("q"); var React = require('react'); var MatrixClientPeg = require('../../../MatrixClientPeg'); +var SdkConfig = require('../../../SdkConfig'); var sdk = require('../../../index'); var Modal = require('../../../Modal'); var ObjectUtils = require("../../../ObjectUtils"); var dis = require("../../../dispatcher"); +var ScalarAuthClient = require("../../../ScalarAuthClient"); module.exports = React.createClass({ displayName: 'RoomSettings', @@ -71,6 +73,10 @@ module.exports = React.createClass({ console.error("Failed to get room visibility: " + err); }); + this.getScalarToken().done((token) => { + this.setState({scalar_token: token}); + }); + dis.dispatch({ action: 'ui_opacity', sideOpacity: 0.3, @@ -359,6 +365,28 @@ module.exports = React.createClass({ roomState.mayClientSendStateEvent("m.room.guest_access", cli)) }, + getScalarInterfaceUrl: function() { + var url = SdkConfig.get().integrations_ui_url; + url += "?token=" + this.state.scalar_token; + return url; + }, + + getScalarToken() { + var tok = window.localStorage.getItem("mx_scalar_token"); + if (tok) return q(tok); + + // No saved token, so do the dance to get one. First, we + // need an openid bearer token from the HS. + return MatrixClientPeg.get().getOpenIdToken().then((token_object) => { + // Now we can send that to scalar and exchange it for a scalar token + var scalar_auth_client = new ScalarAuthClient(); + return scalar_auth_client.getScalarToken(token_object); + }).then((token_object) => { + window.localStorage.setItem("mx_scalar_token", token_object); + return token_object; + }); + }, + render: function() { // TODO: go through greying out things you don't have permission to change // (or turning them into informative stuff) @@ -367,6 +395,7 @@ module.exports = React.createClass({ var ColorSettings = sdk.getComponent("room_settings.ColorSettings"); var EditableText = sdk.getComponent('elements.EditableText'); var PowerSelector = sdk.getComponent('elements.PowerSelector'); + var Loader = sdk.getComponent("elements.Spinner") var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); var events_levels = (power_levels ? power_levels.getContent().events : {}) || {}; @@ -533,6 +562,13 @@ module.exports = React.createClass({ } + var integrations_section; + if (this.state.scalar_token) { + integrations_section = ; + } else { + integrations_section = ; + } + return (
@@ -682,6 +718,8 @@ module.exports = React.createClass({ This room's internal ID is { this.props.room.roomId }
+

Integrations

+ { integrations_section } ); } From bb9316edfa7186215ec900360319fa579456ff7a Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 May 2016 14:42:00 +0100 Subject: [PATCH 2/8] Pass the room id to scalar --- src/ScalarAuthClient.js | 2 +- src/components/views/rooms/RoomSettings.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 0607e072ac..8216f20314 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -33,7 +33,7 @@ class ScalarAuthClient { if (err) { defer.reject(err); } else { - defer.resolve(body); + defer.resolve(body.access_token); } }); diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 86dfbcf886..87985ae262 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -367,7 +367,8 @@ module.exports = React.createClass({ getScalarInterfaceUrl: function() { var url = SdkConfig.get().integrations_ui_url; - url += "?token=" + this.state.scalar_token; + url += "?token=" + encodeURIComponent(this.state.scalar_token); + url += "&room_id=" + encodeURIComponent(this.props.room.roomId); return url; }, From 75979786fc141959ee9d3962b4c2c4f6de9cda0c Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 May 2016 15:06:23 +0100 Subject: [PATCH 3/8] Call it scalar_token for consistency --- src/components/views/rooms/RoomSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 87985ae262..a7d60df3cd 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -367,7 +367,7 @@ module.exports = React.createClass({ getScalarInterfaceUrl: function() { var url = SdkConfig.get().integrations_ui_url; - url += "?token=" + encodeURIComponent(this.state.scalar_token); + url += "?scalar_token=" + encodeURIComponent(this.state.scalar_token); url += "&room_id=" + encodeURIComponent(this.props.room.roomId); return url; }, From 0bb4f9dd5c7ff9380a628dd855d257fc506ed84c Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 16 May 2016 15:35:39 +0100 Subject: [PATCH 4/8] add browser request as a dep since we need to do requests directly for scalar integ --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3f4a862f6f..156de085eb 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test-multi": "karma start --single-run=false" }, "dependencies": { + "browser-request": "^0.3.3", "classnames": "^2.1.2", "favico.js": "^0.3.10", "filesize": "^3.1.2", From c38c57df1e24ab3aff1d56a36ba09612c018a663 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 19 May 2016 14:12:03 +0100 Subject: [PATCH 5/8] put integrations manager into a modal --- src/components/views/rooms/RoomSettings.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index a7d60df3cd..7d331c7eea 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -388,6 +388,14 @@ module.exports = React.createClass({ }); }, + onManageIntegrations(ev) { + ev.preventDefault(); + var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createDialog(IntegrationsManager, { + src: this.state.scalar_token ? this.getScalarInterfaceUrl() : null + }, ""); + }, + render: function() { // TODO: go through greying out things you don't have permission to change // (or turning them into informative stuff) @@ -565,7 +573,11 @@ module.exports = React.createClass({ var integrations_section; if (this.state.scalar_token) { - integrations_section = ; + integrations_section = ( + + ); } else { integrations_section = ; } @@ -658,6 +670,9 @@ module.exports = React.createClass({ +

Integrations

+ { integrations_section } + This room's internal ID is { this.props.room.roomId } - -

Integrations

- { integrations_section } ); } From 5835948a3daf15c44f55be23689200013aac7ef6 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 19 May 2016 23:00:28 +0100 Subject: [PATCH 6/8] ignore @ prefixes when sorting memberlist --- src/components/views/rooms/MemberList.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f029c519bc..a1769ad5f3 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -387,7 +387,9 @@ module.exports = React.createClass({ // console.log(memberA + " and " + memberB + " have same power level"); if (memberA.name && memberB.name) { // console.log("comparing names: " + memberA.name + " and " + memberB.name); - return memberA.name.localeCompare(memberB.name); + var nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name; + var nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name; + return nameA.localeCompare(nameB); } else { return 0; From 15dccd987177755848fe07229d8ff1e25079c7fc Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 6 Jun 2016 17:55:45 +0100 Subject: [PATCH 7/8] Handle errors with scalar-oauthing Handle error getting the scalar token and check for non-success status codes in the response handler (because apparently browser-request doesn't consider that an error). --- src/ScalarAuthClient.js | 2 ++ src/components/views/rooms/RoomSettings.js | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 8216f20314..67607b4996 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -32,6 +32,8 @@ class ScalarAuthClient { }, (err, response, body) => { if (err) { defer.reject(err); + } else if (response.statusCode / 100 !== 2) { + defer.reject({statusCode: response.statusCode}); } else { defer.resolve(body.access_token); } diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index dc1c89d11d..5a6453aaa0 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -60,6 +60,8 @@ module.exports = React.createClass({ tags: tags, areNotifsMuted: areNotifsMuted, isRoomPublished: this._originalIsRoomPublished, // loaded async in componentWillMount + scalar_token: null, + scalar_error: null, }; }, @@ -75,6 +77,8 @@ module.exports = React.createClass({ this.getScalarToken().done((token) => { this.setState({scalar_token: token}); + }, (err) => { + this.setState({scalar_error: err}); }); dis.dispatch({ @@ -578,6 +582,10 @@ module.exports = React.createClass({
Manage integrations ); + } else if (this.state.scalar_error) { + integrations_section =
+ Unable to contact integrations server +
; } else { integrations_section = ; } From cf70f1731eee00a322a69987d77563f6429f5402 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 4 Aug 2016 23:26:27 +0100 Subject: [PATCH 8/8] hide integration management behind labs checkbox --- src/components/structures/UserSettings.js | 6 +++- src/components/views/rooms/RoomSettings.js | 34 ++++++++++------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 6555668ff4..efbf943687 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -34,7 +34,11 @@ const LABS_FEATURES = [ { name: 'End-to-End Encryption', id: 'e2e_encryption' - } + }, + { + name: 'Integration Management', + id: 'integration_management' + }, ]; // if this looks like a release, use the 'version' from package.json; else use diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 9c13d27f68..f9c91bcc5b 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -67,18 +67,14 @@ module.exports = React.createClass({ tags_changed: false, tags: tags, areNotifsMuted: areNotifsMuted, -<<<<<<< HEAD - isRoomPublished: this._originalIsRoomPublished, // loaded async in componentWillMount - scalar_token: null, - scalar_error: null, -======= // isRoomPublished is loaded async in componentWillMount so when the component // inits, the saved value will always be undefined, however getInitialState() // is also called from the saving code so we must return the correct value here // if we have it (although this could race if the user saves before we load whether // the room is published or not). isRoomPublished: this._originalIsRoomPublished, ->>>>>>> develop + scalar_token: null, + scalar_error: null, }; }, @@ -621,18 +617,20 @@ module.exports = React.createClass({ } var integrations_section; - if (this.state.scalar_token) { - integrations_section = ( - - ); - } else if (this.state.scalar_error) { - integrations_section =
- Unable to contact integrations server -
; - } else { - integrations_section = ; + if (UserSettingsStore.isFeatureEnabled("integration_management")) { + if (this.state.scalar_token) { + integrations_section = ( + + ); + } else if (this.state.scalar_error) { + integrations_section =
+ Unable to contact integrations server +
; + } else { + integrations_section = ; + } } return (