Merge pull request #3908 from matrix-org/zip/11935-room-list-decoration
Room list reflects encryption state
This commit is contained in:
commit
1d686fe49e
4 changed files with 118 additions and 2 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
Loading…
Reference in a new issue