From 086698cd113e6988b539f57a914c7e65a97791f6 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 13:09:07 +0100 Subject: [PATCH 1/5] Add device info to user settings Requires e81ce23 from matrix-js-sdk --- src/components/structures/UserSettings.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index e56e5d9d87..9bdde0dde4 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -244,6 +244,23 @@ module.exports = React.createClass({ }); }, + _renderDeviceInfo: function() { + var client = MatrixClientPeg.get(); + var deviceId = client.deviceId; + var olmKey = client.getDeviceEd25519Key() || ""; + return ( +
+

Cryptography

+
+
    +
  • Device ID: {deviceId}
  • +
  • Device key: {olmKey}
  • +
+
+
+ ); + }, + render: function() { var self = this; var Loader = sdk.getComponent("elements.Spinner"); @@ -390,6 +407,8 @@ module.exports = React.createClass({ {notification_area} + {this._renderDeviceInfo()} +

Advanced

From f3e66e6fd2cfed08be795f02ad8894437feb6d1f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 13:13:41 +0100 Subject: [PATCH 2/5] Add device info to member info Requires e81ce23 in matrix-js-sdk --- src/component-index.js | 1 + .../views/rooms/MemberDeviceInfo.js | 58 +++++++++ src/components/views/rooms/MemberInfo.js | 115 ++++++++++++++---- 3 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 src/components/views/rooms/MemberDeviceInfo.js diff --git a/src/component-index.js b/src/component-index.js index 967cc5d685..3570523bde 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -79,6 +79,7 @@ module.exports.components['views.rooms.EntityTile'] = require('./components/view module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile'); module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList'); module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget'); +module.exports.components['views.rooms.MemberDeviceInfo'] = require('./components/views/rooms/MemberDeviceInfo'); module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo'); module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList'); module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile'); diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js new file mode 100644 index 0000000000..6af7b848c0 --- /dev/null +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -0,0 +1,58 @@ +/* +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 React = require('react'); +var MatrixClientPeg = require("../../../MatrixClientPeg"); + +module.exports = React.createClass({ + displayName: 'MemberDeviceInfo', + propTypes: { + userId: React.PropTypes.string.isRequired, + device: React.PropTypes.object.isRequired, + }, + + onVerifyClick: function() { + MatrixClientPeg.get().setDeviceVerified(this.props.userId, + this.props.device.id); + // TODO: wire this up properly + this.props.device.verified = true; + this.forceUpdate(); + }, + + render: function() { + var indicator = null, button = null; + if (this.props.device.verified) { + indicator = ( +
+ ); + } else { + button = ( +
+ Verify +
+ ); + } + return ( +
+
{this.props.device.id}
+
{this.props.device.key}
+ {indicator} + {button} +
+ ); + }, +}); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index ba4a3734f5..385f9dee19 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -34,23 +34,81 @@ var sdk = require('../../../index'); module.exports = React.createClass({ displayName: 'MemberInfo', + propTypes: { + member: React.PropTypes.object.isRequired, + onFinished: React.PropTypes.func, + }, + getDefaultProps: function() { return { onFinished: function() {} }; }, - componentDidMount: function() { - // work out the current state - if (this.props.member) { - var memberState = this._calculateOpsPermissions(this.props.member); - this.setState(memberState); + getInitialState: function() { + return { + can: { + kick: false, + ban: false, + mute: false, + modifyLevel: false + }, + muted: false, + isTargetMod: false, + updating: 0, + devices: null, // null means device list is loading } }, + + componentWillMount: function() { + this._cancelDeviceList = null; + }, + + componentDidMount: function() { + this._updateStateForNewMember(this.props.member); + }, + componentWillReceiveProps: function(newProps) { - var memberState = this._calculateOpsPermissions(newProps.member); - this.setState(memberState); + if (this.props.member.userId != newProps.member.userId) { + this._updateStateForNewMember(newProps.member); + } + }, + + componentWillUnmount: function() { + if (this._cancelDeviceList) { + this._cancelDeviceList(); + } + }, + + _updateStateForNewMember: function(member) { + var newState = this._calculateOpsPermissions(member); + newState.devices = null; + this.setState(newState); + + if (this._cancelDeviceList) { + this._cancelDeviceList(); + this._cancelDeviceList = null; + } + + this._downloadDeviceList(member); + }, + + _downloadDeviceList: function(member) { + var cancelled = false; + this._cancelDeviceList = function() { cancelled = true; } + + var client = MatrixClientPeg.get(); + var self = this; + client.downloadKeys([member.userId], true).done(function() { + if (cancelled) { + // we got cancelled - presumably a different user now + return; + } + self._cancelDeviceList = null; + var devices = client.listDeviceKeys(member.userId); + self.setState({devices: devices}); + }); }, onKick: function() { @@ -371,20 +429,6 @@ module.exports = React.createClass({ this.props.onFinished(); }, - getInitialState: function() { - return { - can: { - kick: false, - ban: false, - mute: false, - modifyLevel: false - }, - muted: false, - isTargetMod: false, - updating: 0, - } - }, - _calculateOpsPermissions: function(member) { var defaultPerms = { can: {}, @@ -476,6 +520,32 @@ module.exports = React.createClass({ Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); }, + _renderDevices: function() { + var devices = this.state.devices; + var MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); + var Spinner = sdk.getComponent("elements.Spinner"); + + var devComponents; + if (devices === null) { + // still loading + devComponents = ; + } else { + devComponents = []; + for (var i = 0; i < devices.length; i++) { + devComponents.push(); + } + } + + return ( +
+

Devices

+ {devComponents} +
+ ); + }, + render: function() { var startChat, kickButton, banButton, muteButton, giveModButton, spinner; if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) { @@ -552,6 +622,8 @@ module.exports = React.createClass({ { startChat } + { this._renderDevices() } + { adminTools } { spinner } @@ -559,4 +631,3 @@ module.exports = React.createClass({ ); } }); - From 1616431d27a6dbc6878c2f710a2d8f597c1446b5 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 17:01:13 +0100 Subject: [PATCH 3/5] EventTile: add classes to indicate verifiedness Add a couple of CSS classes to event tiles to reflect whether encrypted events have been verified or not. --- src/components/views/rooms/EventTile.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 7db8af9312..13534866c5 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -128,18 +128,25 @@ module.exports = React.createClass({ }, getInitialState: function() { - return {menu: false, allReadAvatars: false}; + return {menu: false, allReadAvatars: false, verified: null}; }, componentWillMount: function() { // don't do RR animations until we are mounted this._suppressReadReceiptAnimation = true; + this._verifyEvent(this.props.mxEvent); }, componentDidMount: function() { this._suppressReadReceiptAnimation = false; }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.mxEvent !== this.props.mxEvent) { + this._verifyEvent(nextProps.mxEvent); + } + }, + shouldComponentUpdate: function (nextProps, nextState) { if (!ObjectUtils.shallowEqual(this.state, nextState)) { return true; @@ -152,6 +159,18 @@ module.exports = React.createClass({ return false; }, + _verifyEvent: function(mxEvent) { + var verified = null; + + if (mxEvent.isEncrypted()) { + verified = MatrixClientPeg.get().isEventSenderVerified(mxEvent); + } + + this.setState({ + verified: verified + }); + }, + _propsEqual: function(objA, objB) { var keysA = Object.keys(objA); var keysB = Object.keys(objB); @@ -346,6 +365,8 @@ module.exports = React.createClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, menu: this.state.menu, + mx_EventTile_verified: this.state.verified == true, + mx_EventTile_unverified: this.state.verified == false, }); var timestamp = From 7ce49c752f68c582f0d7d19ed19b9fec64486346 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 18:35:43 +0100 Subject: [PATCH 4/5] Wire up events to update UI on device verification Use the dispatcher to update event tiles and memberdeviceinfo when a device is marked as verified. --- src/components/views/rooms/EventTile.js | 15 ++++++++++++++ .../views/rooms/MemberDeviceInfo.js | 13 +++++++++--- src/components/views/rooms/MemberInfo.js | 20 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 13534866c5..e1390ced6a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -139,6 +139,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._suppressReadReceiptAnimation = false; + this.dispatcherRef = dispatcher.register(this.onAction); }, componentWillReceiveProps: function (nextProps) { @@ -159,6 +160,20 @@ module.exports = React.createClass({ return false; }, + componentWillUnmount: function() { + dispatcher.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + switch (payload.action) { + case 'device_verified': + if (payload.params.userId == this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); + } + break; + } + }, + _verifyEvent: function(mxEvent) { var verified = null; diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index 6af7b848c0..296e0b4720 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -17,6 +17,8 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../MatrixClientPeg"); +var dis = require("../../../dispatcher"); + module.exports = React.createClass({ displayName: 'MemberDeviceInfo', propTypes: { @@ -27,9 +29,14 @@ module.exports = React.createClass({ onVerifyClick: function() { MatrixClientPeg.get().setDeviceVerified(this.props.userId, this.props.device.id); - // TODO: wire this up properly - this.props.device.verified = true; - this.forceUpdate(); + + dis.dispatch({ + action: 'device_verified', + params: { + userId: this.props.userId, + deviceId: this.props.device.id, + }, + }); }, render: function() { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 385f9dee19..2f68ebfb7d 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,6 +67,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._updateStateForNewMember(this.props.member); + this.dispatcherRef = dis.register(this.onAction); }, componentWillReceiveProps: function(newProps) { @@ -76,11 +77,30 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); if (this._cancelDeviceList) { this._cancelDeviceList(); } }, + onAction: function(payload) { + switch (payload.action) { + case 'device_verified': + if (payload.params.userId == this.props.member.userId) { + this._onDeviceVerified(); + } + break; + } + }, + + _onDeviceVerified: function() { + // no need to re-download the whole thing; just update our copy of the + // list. + var devices = MatrixClientPeg.get().listDeviceKeys( + this.props.member.userId); + this.setState({devices: devices}); + }, + _updateStateForNewMember: function(member) { var newState = this._calculateOpsPermissions(member); newState.devices = null; From 85770feb31437419200cd89bde2b3f204570e2bb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 21:25:42 +0100 Subject: [PATCH 5/5] device verification: use a js-sdk event We'll probably want to be able to bubble up device verifications from the js-sdk at some point, so let's use a js-sdk event for this. --- src/components/views/rooms/EventTile.js | 17 ++++++----- .../views/rooms/MemberDeviceInfo.js | 10 ------- src/components/views/rooms/MemberInfo.js | 28 ++++++++----------- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index e1390ced6a..ff02139d87 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -139,7 +139,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._suppressReadReceiptAnimation = false; - this.dispatcherRef = dispatcher.register(this.onAction); + MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified); }, componentWillReceiveProps: function (nextProps) { @@ -161,16 +161,15 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - dispatcher.unregister(this.dispatcherRef); + var client = MatrixClientPeg.get(); + if (client) { + client.removeListener("deviceVerified", this.onDeviceVerified); + } }, - onAction: function(payload) { - switch (payload.action) { - case 'device_verified': - if (payload.params.userId == this.props.mxEvent.getSender()) { - this._verifyEvent(this.props.mxEvent); - } - break; + onDeviceVerified: function(userId, device) { + if (userId == this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); } }, diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index 296e0b4720..da53c744b2 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -17,8 +17,6 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../MatrixClientPeg"); -var dis = require("../../../dispatcher"); - module.exports = React.createClass({ displayName: 'MemberDeviceInfo', propTypes: { @@ -29,14 +27,6 @@ module.exports = React.createClass({ onVerifyClick: function() { MatrixClientPeg.get().setDeviceVerified(this.props.userId, this.props.device.id); - - dis.dispatch({ - action: 'device_verified', - params: { - userId: this.props.userId, - deviceId: this.props.device.id, - }, - }); }, render: function() { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 2f68ebfb7d..c50b6e919e 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,7 +67,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._updateStateForNewMember(this.props.member); - this.dispatcherRef = dis.register(this.onAction); + MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified); }, componentWillReceiveProps: function(newProps) { @@ -77,30 +77,24 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); + var client = MatrixClientPeg.get(); + if (client) { + client.removeListener("deviceVerified", this.onDeviceVerified); + } if (this._cancelDeviceList) { this._cancelDeviceList(); } }, - onAction: function(payload) { - switch (payload.action) { - case 'device_verified': - if (payload.params.userId == this.props.member.userId) { - this._onDeviceVerified(); - } - break; + onDeviceVerified: function(userId, device) { + if (userId == this.props.member.userId) { + // no need to re-download the whole thing; just update our copy of + // the list. + var devices = MatrixClientPeg.get().listDeviceKeys(userId); + this.setState({devices: devices}); } }, - _onDeviceVerified: function() { - // no need to re-download the whole thing; just update our copy of the - // list. - var devices = MatrixClientPeg.get().listDeviceKeys( - this.props.member.userId); - this.setState({devices: devices}); - }, - _updateStateForNewMember: function(member) { var newState = this._calculateOpsPermissions(member); newState.devices = null;