Merge pull request #3910 from matrix-org/t3chguy/cross-signing-composer

Cross Signing redesign for composer
This commit is contained in:
Michael Telatynski 2020-01-24 13:18:09 +00:00 committed by GitHub
commit 2c40b73ff6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 95 deletions

View file

@ -813,10 +813,10 @@ export default createReactClass({
/* 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();
const anyDeviceNotVerified = devices.some(({deviceId}) => {
return !cli.checkDeviceTrust(userId, deviceId).isVerified();
});
if (!allDevicesVerified) {
if (anyDeviceNotVerified) {
this.setState({
e2eStatus: "warning",
});

View file

@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,76 +15,102 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useState} from "react";
import PropTypes from "prop-types";
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore from '../../../settings/SettingsStore';
export default function(props) {
const { isUser } = props;
const isNormal = props.status === "normal";
const isWarning = props.status === "warning";
const isVerified = props.status === "verified";
const e2eIconClasses = classNames({
import {_t, _td} from '../../../languageHandler';
import {useFeatureEnabled} from "../../../hooks/useSettings";
import AccessibleButton from "../elements/AccessibleButton";
import Tooltip from "../elements/Tooltip";
export const E2E_STATE = {
VERIFIED: "verified",
WARNING: "warning",
UNKNOWN: "unknown",
NORMAL: "normal",
};
const crossSigningUserTitles = {
[E2E_STATE.WARNING]: _td("This user has not verified all of their devices."),
[E2E_STATE.NORMAL]: _td("You have not verified this user. This user has verified all of their devices."),
[E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."),
};
const crossSigningRoomTitles = {
[E2E_STATE.WARNING]: _td("Someone is using an unknown device"),
[E2E_STATE.NORMAL]: _td("This room is end-to-end encrypted"),
[E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"),
};
const legacyUserTitles = {
[E2E_STATE.WARNING]: _td("Some devices for this user are not trusted"),
[E2E_STATE.VERIFIED]: _td("All devices for this user are trusted"),
};
const legacyRoomTitles = {
[E2E_STATE.WARNING]: _td("Some devices in this encrypted room are not trusted"),
[E2E_STATE.VERIFIED]: _td("All devices in this encrypted room are trusted"),
};
const E2EIcon = ({isUser, status, className, size, onClick}) => {
const [hover, setHover] = useState(false);
const classes = classNames({
mx_E2EIcon: true,
mx_E2EIcon_warning: isWarning,
mx_E2EIcon_normal: isNormal,
mx_E2EIcon_verified: isVerified,
}, props.className);
mx_E2EIcon_warning: status === E2E_STATE.WARNING,
mx_E2EIcon_normal: status === E2E_STATE.NORMAL,
mx_E2EIcon_verified: status === E2E_STATE.VERIFIED,
}, className);
let e2eTitle;
const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing");
const crossSigning = useFeatureEnabled("feature_cross_signing");
if (crossSigning && isUser) {
if (isWarning) {
e2eTitle = _t(
"This user has not verified all of their devices.",
);
} else if (isNormal) {
e2eTitle = _t(
"You have not verified this user. " +
"This user has verified all of their devices.",
);
} else if (isVerified) {
e2eTitle = _t(
"You have verified this user. " +
"This user has verified all of their devices.",
);
}
e2eTitle = crossSigningUserTitles[status];
} else if (crossSigning && !isUser) {
if (isWarning) {
e2eTitle = _t(
"Some users in this encrypted room are not verified by you or " +
"they have not verified their own devices.",
);
} else if (isVerified) {
e2eTitle = _t(
"All users in this encrypted room are verified by you and " +
"they have verified their own devices.",
);
}
e2eTitle = crossSigningRoomTitles[status];
} else if (!crossSigning && isUser) {
if (isWarning) {
e2eTitle = _t("Some devices for this user are not trusted");
} else if (isVerified) {
e2eTitle = _t("All devices for this user are trusted");
}
e2eTitle = legacyUserTitles[status];
} else if (!crossSigning && !isUser) {
if (isWarning) {
e2eTitle = _t("Some devices in this encrypted room are not trusted");
} else if (isVerified) {
e2eTitle = _t("All devices in this encrypted room are trusted");
}
e2eTitle = legacyRoomTitles[status];
}
let style = null;
if (props.size) {
style = {width: `${props.size}px`, height: `${props.size}px`};
let style;
if (size) {
style = {width: `${size}px`, height: `${size}px`};
}
const icon = (<div className={e2eIconClasses} style={style} title={e2eTitle} />);
if (props.onClick) {
return (<AccessibleButton onClick={props.onClick}>{ icon }</AccessibleButton>);
} else {
return icon;
const onMouseOver = () => setHover(true);
const onMouseOut = () => setHover(false);
let tip;
if (hover) {
tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} />;
}
}
if (onClick) {
return (
<AccessibleButton
onClick={onClick}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
className={classes}
style={style}
>
{ tip }
</AccessibleButton>
);
}
return <div onMouseOver={onMouseOver} onMouseOut={onMouseOut} className={classes} style={style}>
{ tip }
</div>;
};
E2EIcon.propTypes = {
isUser: PropTypes.bool,
status: PropTypes.oneOf(Object.values(E2E_STATE)),
className: PropTypes.string,
size: PropTypes.number,
onClick: PropTypes.func,
};
export default E2EIcon;

View file

@ -33,6 +33,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
import * as ObjectUtils from "../../../ObjectUtils";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {E2E_STATE} from "./E2EIcon";
const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
@ -66,13 +67,6 @@ const stateEventTileTypes = {
'm.room.related_groups': 'messages.TextualEvent',
};
const E2E_STATE = {
VERIFIED: "verified",
WARNING: "warning",
UNKNOWN: "unknown",
NORMAL: "normal",
};
// Add all the Mjolnir stuff to the renderer
for (const evType of ALL_RULE_TYPES) {
stateEventTileTypes[evType] = 'messages.TextualEvent';

View file

@ -26,6 +26,7 @@ import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore";
function ComposerAvatar(props) {
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
@ -168,7 +169,6 @@ export default class MessageComposer extends React.Component {
constructor(props) {
super(props);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
this._onTombstoneClick = this._onTombstoneClick.bind(this);
@ -182,11 +182,6 @@ export default class MessageComposer extends React.Component {
}
componentDidMount() {
// N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
// for 'event' fires *after* 'RoomEvent', and our room won't have yet been
// marked as encrypted.
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
MatrixClientPeg.get().on("event", this.onEvent);
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._waitForOwnMember();
@ -210,7 +205,6 @@ export default class MessageComposer extends React.Component {
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("event", this.onEvent);
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
}
if (this._roomStoreToken) {
@ -218,13 +212,6 @@ export default class MessageComposer extends React.Component {
}
}
onEvent(event) {
if (event.getType() !== 'm.room.encryption') return;
if (event.getRoomId() !== this.props.room.roomId) return;
// TODO: put (encryption state??) in state
this.forceUpdate();
}
_onRoomStateEvents(ev, state) {
if (ev.getRoomId() !== this.props.room.roomId) return;
@ -282,18 +269,33 @@ export default class MessageComposer extends React.Component {
}
renderPlaceholderText() {
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
if (this.state.isQuoting) {
if (roomIsEncrypted) {
return _t('Send an encrypted reply…');
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
if (this.state.isQuoting) {
if (this.props.e2eStatus) {
return _t('Send an encrypted reply…');
} else {
return _t('Send a reply…');
}
} else {
return _t('Send a reply (unencrypted)…');
if (this.props.e2eStatus) {
return _t('Send an encrypted message…');
} else {
return _t('Send a message…');
}
}
} else {
if (roomIsEncrypted) {
return _t('Send an encrypted message…');
if (this.state.isQuoting) {
if (this.props.e2eStatus) {
return _t('Send an encrypted reply…');
} else {
return _t('Send a reply (unencrypted)…');
}
} else {
return _t('Send a message (unencrypted)…');
if (this.props.e2eStatus) {
return _t('Send an encrypted message…');
} else {
return _t('Send a message (unencrypted)…');
}
}
}
}