From 9706114bb571a903fd607838229717fe767fe465 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 22 Jan 2020 16:54:31 +0000
Subject: [PATCH 1/6] move E2E_STATE to E2EIcon to simplify imports
---
src/components/views/rooms/E2EIcon.js | 18 +++++++++++++-----
src/components/views/rooms/EventTile.js | 8 +-------
2 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js
index 545d1fd7ed..6ee20023ff 100644
--- a/src/components/views/rooms/E2EIcon.js
+++ b/src/components/views/rooms/E2EIcon.js
@@ -14,22 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React from "react";
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore from '../../../settings/SettingsStore';
+export const E2E_STATE = {
+ VERIFIED: "verified",
+ WARNING: "warning",
+ UNKNOWN: "unknown",
+ NORMAL: "normal",
+};
+
export default function(props) {
- const { isUser } = props;
- const isNormal = props.status === "normal";
- const isWarning = props.status === "warning";
- const isVerified = props.status === "verified";
+ const { isUser, status, className } = props;
+ const isNormal = status === E2E_STATE.NORMAL;
+ const isWarning = status === E2E_STATE.WARNING;
+ const isVerified = status === E2E_STATE.VERIFIED;
const e2eIconClasses = classNames({
mx_E2EIcon: true,
mx_E2EIcon_warning: isWarning,
mx_E2EIcon_normal: isNormal,
mx_E2EIcon_verified: isVerified,
- }, props.className);
+ }, className);
let e2eTitle;
const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing");
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index 634b77c9e1..940515f02e 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -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';
From b7d1c17ad1453c237e99e3bd87d1fdb722bb01d7 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 22 Jan 2020 16:56:27 +0000
Subject: [PATCH 2/6] simple optimization to bail out of check on first failure
---
src/components/structures/RoomView.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 9b02f6d503..23d3002faa 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -812,10 +812,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",
});
From 78e1d1674f15d99a182158fcb73974284512e8c3 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 23 Jan 2020 13:00:17 +0000
Subject: [PATCH 3/6] reactor E2EIcon for reusability
---
src/components/views/rooms/E2EIcon.js | 100 +++++++++++++-------------
1 file changed, 48 insertions(+), 52 deletions(-)
diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js
index 6ee20023ff..36f230f472 100644
--- a/src/components/views/rooms/E2EIcon.js
+++ b/src/components/views/rooms/E2EIcon.js
@@ -15,8 +15,10 @@ limitations under the License.
*/
import React from "react";
+import PropTypes from "prop-types";
import classNames from 'classnames';
-import { _t } from '../../../languageHandler';
+
+import {_t, _td} from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore from '../../../settings/SettingsStore';
@@ -27,71 +29,65 @@ export const E2E_STATE = {
NORMAL: "normal",
};
-export default function(props) {
- const { isUser, status, className } = props;
- const isNormal = status === E2E_STATE.NORMAL;
- const isWarning = status === E2E_STATE.WARNING;
- const isVerified = status === E2E_STATE.VERIFIED;
+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("Some users in this encrypted room are not verified by you or they have not verified " +
+ "their own devices."),
+ [E2E_STATE.VERIFIED]: _td("All users in this encrypted room are verified by you and they have verified their " +
+ "own devices."),
+};
+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 e2eIconClasses = classNames({
mx_E2EIcon: true,
- mx_E2EIcon_warning: isWarning,
- mx_E2EIcon_normal: isNormal,
- mx_E2EIcon_verified: isVerified,
+ mx_E2EIcon_warning: status === E2E_STATE.WARNING,
+ mx_E2EIcon_normal: status === E2E_STATE.NORMAL,
+ mx_E2EIcon_verified: status === E2E_STATE.VERIFIED,
}, className);
- let e2eTitle;
+ let e2eTitle;
const crossSigning = SettingsStore.isFeatureEnabled("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`};
+ if (size) {
+ style = {width: `${size}px`, height: `${size}px`};
}
- const icon = (
);
- if (props.onClick) {
- return ({ icon });
+ const icon = ();
+ if (onClick) {
+ return ({ icon });
} else {
return icon;
}
-}
+};
+
+E2EIcon.propTypes = {
+ isUser: PropTypes.bool,
+ status: PropTypes.oneOf(Object.values(E2E_STATE)),
+ className: PropTypes.string,
+ size: PropTypes.number,
+ onClick: PropTypes.func,
+};
+
+export default E2EIcon;
From 74b08ea4895a581ebb16a83a470e170213f7b24f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 23 Jan 2020 14:38:17 +0000
Subject: [PATCH 4/6] Clean up E2EIcon for better maintainability
---
src/components/views/rooms/E2EIcon.js | 13 +++----
src/hooks/useSettings.js | 52 +++++++++++++++++++++++++++
src/i18n/strings/en_EN.json | 5 +--
3 files changed, 62 insertions(+), 8 deletions(-)
create mode 100644 src/hooks/useSettings.js
diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js
index 36f230f472..7ac3b5af2d 100644
--- a/src/components/views/rooms/E2EIcon.js
+++ b/src/components/views/rooms/E2EIcon.js
@@ -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.
@@ -20,7 +21,7 @@ import classNames from 'classnames';
import {_t, _td} from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
-import SettingsStore from '../../../settings/SettingsStore';
+import {useFeatureEnabled} from "../../../hooks/useSettings";
export const E2E_STATE = {
VERIFIED: "verified",
@@ -35,11 +36,11 @@ const crossSigningUserTitles = {
[E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."),
};
const crossSigningRoomTitles = {
- [E2E_STATE.WARNING]: _td("Some users in this encrypted room are not verified by you or they have not verified " +
- "their own devices."),
- [E2E_STATE.VERIFIED]: _td("All users in this encrypted room are verified by you and they have verified their " +
- "own devices."),
+ [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"),
@@ -58,7 +59,7 @@ const E2EIcon = ({isUser, status, className, size, onClick}) => {
}, className);
let e2eTitle;
- const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing");
+ const crossSigning = useFeatureEnabled("feature_cross_signing");
if (crossSigning && isUser) {
e2eTitle = crossSigningUserTitles[status];
} else if (crossSigning && !isUser) {
diff --git a/src/hooks/useSettings.js b/src/hooks/useSettings.js
new file mode 100644
index 0000000000..151a6369de
--- /dev/null
+++ b/src/hooks/useSettings.js
@@ -0,0 +1,52 @@
+/*
+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.
+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.
+*/
+
+import {useEffect, useState} from "react";
+import SettingsStore from '../settings/SettingsStore';
+
+// Hook to fetch the value of a setting and dynamically update when it changes
+export const useSettingValue = (settingName, roomId = null, excludeDefault = false) => {
+ const [value, setValue] = useState(SettingsStore.getValue(settingName, roomId, excludeDefault));
+
+ useEffect(() => {
+ const ref = SettingsStore.watchSetting(settingName, roomId, () => {
+ setValue(SettingsStore.getValue(settingName, roomId, excludeDefault));
+ });
+ // clean-up
+ return () => {
+ SettingsStore.unwatchSetting(ref);
+ };
+ }, [settingName, roomId, excludeDefault]);
+
+ return value;
+};
+
+// Hook to fetch whether a feature is enabled and dynamically update when that changes
+export const useFeatureEnabled = (featureName, roomId = null) => {
+ const [enabled, setEnabled] = useState(SettingsStore.isFeatureEnabled(featureName, roomId));
+
+ useEffect(() => {
+ const ref = SettingsStore.watchSetting(featureName, roomId, () => {
+ setEnabled(SettingsStore.isFeatureEnabled(featureName, roomId));
+ });
+ // clean-up
+ return () => {
+ SettingsStore.unwatchSetting(ref);
+ };
+ }, [featureName, roomId]);
+
+ return enabled;
+};
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index d19cbb9bfd..fb2898ae3d 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -884,8 +884,9 @@
"This user has not verified all of their devices.": "This user has not verified all of their devices.",
"You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.",
"You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.",
- "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.",
- "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.",
+ "Someone is using an unknown device": "Someone is using an unknown device",
+ "This room is end-to-end encrypted": "This room is end-to-end encrypted",
+ "Everyone in this room is verified": "Everyone in this room is verified",
"Some devices for this user are not trusted": "Some devices for this user are not trusted",
"All devices for this user are trusted": "All devices for this user are trusted",
"Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted",
From 662b34c8dbc2196b4061f08c8555c2a1e02cf5a2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 23 Jan 2020 14:38:39 +0000
Subject: [PATCH 5/6] Update MessageComposer placeholder and e2e icon size
---
res/css/views/rooms/_MessageComposer.scss | 2 +
src/components/views/rooms/MessageComposer.js | 46 ++++++++++---------
src/i18n/strings/en_EN.json | 4 +-
3 files changed, 29 insertions(+), 23 deletions(-)
diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss
index 5efca51844..fae9d0dfe3 100644
--- a/res/css/views/rooms/_MessageComposer.scss
+++ b/res/css/views/rooms/_MessageComposer.scss
@@ -76,6 +76,8 @@ limitations under the License.
left: 60px;
margin-right: 0; // Counteract the E2EIcon class
margin-left: 3px; // Counteract the E2EIcon class
+ width: 12px;
+ height: 12px;
}
.mx_MessageComposer_noperm_error {
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index 8d36f02d02..53e10fa750 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -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)…');
+ }
}
}
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index fb2898ae3d..20707a4f29 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -962,8 +962,10 @@
"Hangup": "Hangup",
"Upload file": "Upload file",
"Send an encrypted reply…": "Send an encrypted reply…",
- "Send a reply (unencrypted)…": "Send a reply (unencrypted)…",
+ "Send a reply…": "Send a reply…",
"Send an encrypted message…": "Send an encrypted message…",
+ "Send a message…": "Send a message…",
+ "Send a reply (unencrypted)…": "Send a reply (unencrypted)…",
"Send a message (unencrypted)…": "Send a message (unencrypted)…",
"The conversation continues here.": "The conversation continues here.",
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
From b72ab57e1b33d443b40a56e73645044233a736f5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 24 Jan 2020 10:13:03 +0000
Subject: [PATCH 6/6] add to
---
src/components/views/rooms/E2EIcon.js | 40 +++++++++++++++++++++------
1 file changed, 31 insertions(+), 9 deletions(-)
diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js
index 7ac3b5af2d..df5fe204d4 100644
--- a/src/components/views/rooms/E2EIcon.js
+++ b/src/components/views/rooms/E2EIcon.js
@@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, {useState} from "react";
import PropTypes from "prop-types";
import classNames from 'classnames';
import {_t, _td} from '../../../languageHandler';
-import AccessibleButton from '../elements/AccessibleButton';
import {useFeatureEnabled} from "../../../hooks/useSettings";
+import AccessibleButton from "../elements/AccessibleButton";
+import Tooltip from "../elements/Tooltip";
export const E2E_STATE = {
VERIFIED: "verified",
@@ -51,7 +52,9 @@ const legacyRoomTitles = {
};
const E2EIcon = ({isUser, status, className, size, onClick}) => {
- const e2eIconClasses = classNames({
+ const [hover, setHover] = useState(false);
+
+ const classes = classNames({
mx_E2EIcon: true,
mx_E2EIcon_warning: status === E2E_STATE.WARNING,
mx_E2EIcon_normal: status === E2E_STATE.NORMAL,
@@ -70,17 +73,36 @@ const E2EIcon = ({isUser, status, className, size, onClick}) => {
e2eTitle = legacyRoomTitles[status];
}
- let style = null;
+ let style;
if (size) {
style = {width: `${size}px`, height: `${size}px`};
}
- const icon = ();
- if (onClick) {
- return ({ icon });
- } else {
- return icon;
+ const onMouseOver = () => setHover(true);
+ const onMouseOut = () => setHover(false);
+
+ let tip;
+ if (hover) {
+ tip = ;
}
+
+ if (onClick) {
+ return (
+
+ { tip }
+
+ );
+ }
+
+ return
+ { tip }
+
;
};
E2EIcon.propTypes = {