Merge pull request #3908 from matrix-org/zip/11935-room-list-decoration

Room list reflects encryption state
This commit is contained in:
Zoe 2020-01-24 11:14:53 +00:00 committed by GitHub
commit 1d686fe49e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 2 deletions

View file

@ -21,8 +21,10 @@ limitations under the License.
.mx_E2EIcon { .mx_E2EIcon {
margin: 0; margin: 0;
position: absolute; position: absolute;
bottom: 0; bottom: -1px;
right: -5px; right: -2px;
height: 10px;
width: 10px
} }
} }

View file

@ -98,6 +98,19 @@ limitations under the License.
z-index: 2; z-index: 2;
} }
// Note we match .mx_E2EIcon to make sure this matches more tightly than just
// .mx_E2EIcon on its own
.mx_RoomTile_e2eIcon.mx_E2EIcon {
height: 10px;
width: 10px;
display: block;
position: absolute;
bottom: -1px;
right: -2px;
z-index: 1;
margin: 0;
}
.mx_RoomTile_name { .mx_RoomTile_name {
font-size: 14px; font-size: 14px;
padding: 0 6px; padding: 0 6px;

View file

@ -796,6 +796,7 @@ export default createReactClass({
return; return;
} }
// Duplication between here and _updateE2eStatus in RoomTile
/* At this point, the user has encryption on and cross-signing on */ /* At this point, the user has encryption on and cross-signing on */
const e2eMembers = await room.getEncryptionTargetMembers(); const e2eMembers = await room.getEncryptionTargetMembers();
const verified = []; const verified = [];

View file

@ -33,6 +33,9 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
import E2EIcon from './E2EIcon';
// eslint-disable-next-line camelcase
import rate_limited_func from '../../../ratelimitedfunc';
export default createReactClass({ export default createReactClass({
displayName: 'RoomTile', displayName: 'RoomTile',
@ -70,6 +73,7 @@ export default createReactClass({
notificationCount: this.props.room.getUnreadNotificationCount(), notificationCount: this.props.room.getUnreadNotificationCount(),
selected: this.props.room.roomId === RoomViewStore.getRoomId(), selected: this.props.room.roomId === RoomViewStore.getRoomId(),
statusMessage: this._getStatusMessage(), statusMessage: this._getStatusMessage(),
e2eStatus: null,
}); });
}, },
@ -102,6 +106,83 @@ export default createReactClass({
return statusUser._unstable_statusMessage; return statusUser._unstable_statusMessage;
}, },
onRoomStateMember: function(ev, state, member) {
// we only care about leaving users
// because trust state will change if someone joins a megolm session anyway
if (member.membership !== "leave") {
return;
}
// ignore members in other rooms
if (member.roomId !== this.props.room.roomId) {
return;
}
this._updateE2eStatus();
},
onUserVerificationChanged: function(userId, _trustStatus) {
if (!this.props.room.getMember(userId)) {
// Not in this room
return;
}
this._updateE2eStatus();
},
onRoomTimeline: function(ev, room) {
if (!room) return;
if (room.roomId != this.props.room.roomId) return;
if (ev.getType() !== "m.room.encryption") return;
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
this.onFindingRoomToBeEncrypted();
},
onFindingRoomToBeEncrypted: function() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
cli.on("userTrustStatusChanged", this.onUserVerificationChanged);
this._updateE2eStatus();
},
_updateE2eStatus: async function() {
const cli = MatrixClientPeg.get();
if (!cli.isRoomEncrypted(this.props.room.roomId)) {
return;
}
if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) {
return;
}
// Duplication between here and _updateE2eStatus in RoomView
const e2eMembers = await this.props.room.getEncryptionTargetMembers();
const verified = [];
const unverified = [];
e2eMembers.map(({userId}) => userId)
.filter((userId) => userId !== cli.getUserId())
.forEach((userId) => {
(cli.checkUserTrust(userId).isCrossSigningVerified() ?
verified : unverified).push(userId);
});
/* Check all verified user devices. */
for (const userId of verified) {
const devices = await cli.getStoredDevicesForUser(userId);
const allDevicesVerified = devices.every(({deviceId}) => {
return cli.checkDeviceTrust(userId, deviceId).isVerified();
});
if (!allDevicesVerified) {
this.setState({
e2eStatus: "warning",
});
return;
}
}
this.setState({
e2eStatus: unverified.length === 0 ? "verified" : "normal",
});
},
onRoomName: function(room) { onRoomName: function(room) {
if (room !== this.props.room) return; if (room !== this.props.room) return;
this.setState({ this.setState({
@ -151,10 +232,19 @@ export default createReactClass({
}, },
componentDidMount: function() { componentDidMount: function() {
/* We bind here rather than in the definition because otherwise we wind up with the
method only being callable once every 500ms across all instances, which would be wrong */
this._updateE2eStatus = rate_limited_func(this._updateE2eStatus, 500);
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("accountData", this.onAccountData); cli.on("accountData", this.onAccountData);
cli.on("Room.name", this.onRoomName); cli.on("Room.name", this.onRoomName);
cli.on("RoomState.events", this.onJoinRule); cli.on("RoomState.events", this.onJoinRule);
if (cli.isRoomEncrypted(this.props.room.roomId)) {
this.onFindingRoomToBeEncrypted();
} else {
cli.on("Room.timeline", this.onRoomTimeline);
}
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
@ -172,6 +262,9 @@ export default createReactClass({
MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
cli.removeListener("RoomState.events", this.onJoinRule); cli.removeListener("RoomState.events", this.onJoinRule);
cli.removeListener("RoomState.members", this.onRoomStateMember);
cli.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
cli.removeListener("Room.timeline", this.onRoomTimeline);
} }
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
@ -433,6 +526,12 @@ export default createReactClass({
privateIcon = <div className="mx_RoomTile_PrivateIcon" />; privateIcon = <div className="mx_RoomTile_PrivateIcon" />;
} }
let e2eIcon = null;
// For now, skip the icon for DMs. Possibly we want to move the DM icon elsewhere?
if (!dmUserId && this.state.e2eStatus) {
e2eIcon = <E2EIcon status={this.state.e2eStatus} className="mx_RoomTile_e2eIcon" />;
}
return <React.Fragment> return <React.Fragment>
<RovingTabIndexWrapper> <RovingTabIndexWrapper>
{({onFocus, isActive, ref}) => {({onFocus, isActive, ref}) =>
@ -453,6 +552,7 @@ export default createReactClass({
<div className="mx_RoomTile_avatar_container"> <div className="mx_RoomTile_avatar_container">
<RoomAvatar room={this.props.room} width={24} height={24} /> <RoomAvatar room={this.props.room} width={24} height={24} />
{ dmIndicator } { dmIndicator }
{ e2eIcon }
</div> </div>
</div> </div>
{ privateIcon } { privateIcon }