From 2b432b0d82346915de6d5928399ff0b13c701337 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 27 May 2020 10:28:25 +0100 Subject: [PATCH 1/7] Remove feature_cross_signing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/DeviceListener.ts | 6 +- src/KeyRequestHandler.js | 158 --- .../keybackup/CreateKeyBackupDialog.js | 6 +- src/components/structures/MatrixChat.tsx | 35 +- src/components/structures/RightPanel.js | 107 +- src/components/structures/RoomView.js | 9 - .../views/dialogs/CreateRoomDialog.js | 5 +- .../views/dialogs/DeviceVerifyDialog.js | 4 +- src/components/views/dialogs/InviteDialog.js | 18 +- .../keybackup/RestoreKeyBackupDialog.js | 16 +- .../views/groups/GroupMemberInfo.js | 208 --- src/components/views/right_panel/UserInfo.js | 35 +- src/components/views/rooms/E2EIcon.js | 19 +- src/components/views/rooms/EventTile.js | 9 - src/components/views/rooms/MemberInfo.js | 1165 ----------------- src/components/views/rooms/MemberTile.js | 28 +- src/components/views/rooms/MessageComposer.js | 30 +- src/components/views/rooms/RoomHeader.js | 6 +- src/components/views/rooms/RoomTile.js | 9 +- .../views/settings/CrossSigningPanel.js | 2 + .../views/settings/KeyBackupPanel.js | 9 +- .../tabs/user/SecurityUserSettingsTab.js | 5 +- src/createRoom.js | 6 +- src/settings/Settings.js | 7 - src/verification.js | 3 +- 25 files changed, 106 insertions(+), 1799 deletions(-) delete mode 100644 src/KeyRequestHandler.js delete mode 100644 src/components/views/groups/GroupMemberInfo.js delete mode 100644 src/components/views/rooms/MemberInfo.js diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index ca51b5ac1c..a5175b3220 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -15,7 +15,6 @@ limitations under the License. */ import {MatrixClientPeg} from './MatrixClientPeg'; -import SettingsStore from './settings/SettingsStore'; import { hideToast as hideBulkUnverifiedSessionsToast, showToast as showBulkUnverifiedSessionsToast @@ -173,10 +172,7 @@ export default class DeviceListener { async _recheck() { const cli = MatrixClientPeg.get(); - if ( - !SettingsStore.getValue("feature_cross_signing") || - !await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ) return; + if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return; if (!cli.isCryptoEnabled()) return; // don't recheck until the initial sync is complete: lots of account data events will fire diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js deleted file mode 100644 index ceaff0c54d..0000000000 --- a/src/KeyRequestHandler.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -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 * as sdk from './index'; -import Modal from './Modal'; -import SettingsStore from './settings/SettingsStore'; - -// TODO: We can remove this once cross-signing is the only way. -// https://github.com/vector-im/riot-web/issues/11908 -export default class KeyRequestHandler { - constructor(matrixClient) { - this._matrixClient = matrixClient; - - // the user/device for which we currently have a dialog open - this._currentUser = null; - this._currentDevice = null; - - // userId -> deviceId -> [keyRequest] - this._pendingKeyRequests = Object.create(null); - } - - handleKeyRequest(keyRequest) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.getValue("feature_cross_signing")) { - return; - } - - const userId = keyRequest.userId; - const deviceId = keyRequest.deviceId; - const requestId = keyRequest.requestId; - - if (!this._pendingKeyRequests[userId]) { - this._pendingKeyRequests[userId] = Object.create(null); - } - if (!this._pendingKeyRequests[userId][deviceId]) { - this._pendingKeyRequests[userId][deviceId] = []; - } - - // check if we already have this request - const requests = this._pendingKeyRequests[userId][deviceId]; - if (requests.find((r) => r.requestId === requestId)) { - console.log("Already have this key request, ignoring"); - return; - } - - requests.push(keyRequest); - - if (this._currentUser) { - // ignore for now - console.log("Key request, but we already have a dialog open"); - return; - } - - this._processNextRequest(); - } - - handleKeyRequestCancellation(cancellation) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.getValue("feature_cross_signing")) { - return; - } - - // see if we can find the request in the queue - const userId = cancellation.userId; - const deviceId = cancellation.deviceId; - const requestId = cancellation.requestId; - - if (userId === this._currentUser && deviceId === this._currentDevice) { - console.log( - "room key request cancellation for the user we currently have a" - + " dialog open for", - ); - // TODO: update the dialog. For now, we just ignore the - // cancellation. - return; - } - - if (!this._pendingKeyRequests[userId]) { - return; - } - const requests = this._pendingKeyRequests[userId][deviceId]; - if (!requests) { - return; - } - const idx = requests.findIndex((r) => r.requestId === requestId); - if (idx < 0) { - return; - } - console.log("Forgetting room key request"); - requests.splice(idx, 1); - if (requests.length === 0) { - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - } - } - - _processNextRequest() { - const userId = Object.keys(this._pendingKeyRequests)[0]; - if (!userId) { - return; - } - const deviceId = Object.keys(this._pendingKeyRequests[userId])[0]; - if (!deviceId) { - return; - } - console.log(`Starting KeyShareDialog for ${userId}:${deviceId}`); - - const finished = (r) => { - this._currentUser = null; - this._currentDevice = null; - - if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) { - // request was removed in the time the dialog was displayed - this._processNextRequest(); - return; - } - - if (r) { - for (const req of this._pendingKeyRequests[userId][deviceId]) { - req.share(); - } - } - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - - this._processNextRequest(); - }; - - const KeyShareDialog = sdk.getComponent("dialogs.KeyShareDialog"); - Modal.appendTrackedDialog('Key Share', 'Process Next Request', KeyShareDialog, { - matrixClient: this._matrixClient, - userId: userId, - deviceId: deviceId, - onFinished: finished, - }); - this._currentUser = userId; - this._currentDevice = deviceId; - } -} - diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 7c5170fab6..79fbb98c7b 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -22,7 +22,6 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import PropTypes from 'prop-types'; import {_t, _td} from '../../../../languageHandler'; import { accessSecretStorage } from '../../../../CrossSigningManager'; -import SettingsStore from '../../../../settings/SettingsStore'; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import {copyNode} from "../../../../utils/strings"; import PassphraseField from "../../../../components/views/auth/PassphraseField"; @@ -67,10 +66,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { async componentDidMount() { const cli = MatrixClientPeg.get(); - const secureSecretStorage = ( - SettingsStore.getValue("feature_cross_signing") && - await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ); + const secureSecretStorage = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); this.setState({ secureSecretStorage }); // If we're using secret storage, skip ahead to the backing up step, as diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index f1992bbfcf..2bebdd1e79 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -49,7 +49,6 @@ import PageTypes from '../../PageTypes'; import { getHomePageUrl } from '../../utils/pages'; import createRoom from "../../createRoom"; -import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; @@ -1471,16 +1470,6 @@ export default class MatrixChat extends React.PureComponent { cli.on("Session.logged_out", () => dft.stop()); cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err)); - // TODO: We can remove this once cross-signing is the only way. - // https://github.com/vector-im/riot-web/issues/11908 - const krh = new KeyRequestHandler(cli); - cli.on("crypto.roomKeyRequest", (req) => { - krh.handleKeyRequest(req); - }); - cli.on("crypto.roomKeyRequestCancellation", (req) => { - krh.handleKeyRequestCancellation(req); - }); - cli.on("Room", (room) => { if (MatrixClientPeg.get().isCryptoEnabled()) { const blacklistEnabled = SettingsStore.getValueAt( @@ -1551,13 +1540,6 @@ export default class MatrixChat extends React.PureComponent { }); cli.on("crypto.verification.request", request => { - const isFlagOn = SettingsStore.getValue("feature_cross_signing"); - - if (!isFlagOn && !request.channel.deviceId) { - request.cancel({code: "m.invalid_message", reason: "This client has cross-signing disabled"}); - return; - } - if (request.verifier) { const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { @@ -1600,9 +1582,7 @@ export default class MatrixChat extends React.PureComponent { // be aware of will be signalled through the room shield // changing colour. More advanced behaviour will come once // we implement more settings. - cli.setGlobalErrorOnUnknownDevices( - !SettingsStore.getValue("feature_cross_signing"), - ); + cli.setGlobalErrorOnUnknownDevices(false); } } @@ -1956,18 +1936,7 @@ export default class MatrixChat extends React.PureComponent { return setLoggedInPromise; } - // Test for the master cross-signing key in SSSS as a quick proxy for - // whether cross-signing has been set up on the account. - const masterKeyInStorage = !!cli.getAccountData("m.cross_signing.master"); - if (masterKeyInStorage) { - // Auto-enable cross-signing for the new session when key found in - // secret storage. - SettingsStore.setValue("feature_cross_signing", null, SettingLevel.DEVICE, true); - this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); - } else if ( - SettingsStore.getValue("feature_cross_signing") && - await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ) { + if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { // This will only work if the feature is set to 'enable' in the config, // since it's too early in the lifecycle for users to have turned the // labs flag on. diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 56cc92a8f8..811feb8614 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -189,16 +189,45 @@ export default class RightPanel extends React.Component { } } + onCloseRoomMemberInfo = () => { + // XXX: There are three different ways of 'closing' this panel depending on what state + // things are in... this knows far more than it should do about the state of the rest + // of the app and is generally a bit silly. + if (this.props.user) { + // If we have a user prop then we're displaying a user from the 'user' page type + // in LoggedInView, so need to change the page type to close the panel (we switch + // to the home page which is not obviously the correct thing to do, but I'm not sure + // anything else is - we could hide the close button altogether?) + dis.dispatch({ + action: "view_home_page", + }); + } else { + // Otherwise we have got our user from RoomViewStore which means we're being shown + // within a room, so go back to the member panel if we were in the encryption panel, + // or the member list if we were in the member panel... phew. + dis.dispatch({ + action: Action.ViewUser, + member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? + this.state.member : null, + }); + } + }; + + onCloseGroupMemberInfo = () => { + dis.dispatch({ + action: Action.ViewUser, + member: null, + }); + }; + render() { const MemberList = sdk.getComponent('rooms.MemberList'); - const MemberInfo = sdk.getComponent('rooms.MemberInfo'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo'); const NotificationPanel = sdk.getComponent('structures.NotificationPanel'); const FilePanel = sdk.getComponent('structures.FilePanel'); const GroupMemberList = sdk.getComponent('groups.GroupMemberList'); - const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo'); const GroupRoomList = sdk.getComponent('groups.GroupRoomList'); const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); @@ -220,71 +249,25 @@ export default class RightPanel extends React.Component { break; case RIGHT_PANEL_PHASES.RoomMemberInfo: case RIGHT_PANEL_PHASES.EncryptionPanel: - if (SettingsStore.getValue("feature_cross_signing")) { - const onClose = () => { - // XXX: There are three different ways of 'closing' this panel depending on what state - // things are in... this knows far more than it should do about the state of the rest - // of the app and is generally a bit silly. - if (this.props.user) { - // If we have a user prop then we're displaying a user from the 'user' page type - // in LoggedInView, so need to change the page type to close the panel (we switch - // to the home page which is not obviously the correct thing to do, but I'm not sure - // anything else is - we could hide the close button altogether?) - dis.dispatch({ - action: "view_home_page", - }); - } else { - // Otherwise we have got our user from RoomViewStore which means we're being shown - // within a room, so go back to the member panel if we were in the encryption panel, - // or the member list if we were in the member panel... phew. - dis.dispatch({ - action: Action.ViewUser, - member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? - this.state.member : null, - }); - } - }; - panel = ; - } else { - panel = ; - } + panel = ; break; case RIGHT_PANEL_PHASES.Room3pidMemberInfo: panel = ; break; case RIGHT_PANEL_PHASES.GroupMemberInfo: - if (SettingsStore.getValue("feature_cross_signing")) { - const onClose = () => { - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }; - panel = ; - } else { - panel = ( - - ); - } + panel = ; break; case RIGHT_PANEL_PHASES.GroupRoomInfo: panel = { - this.setState({ - e2eStatus: hasUnverifiedDevices ? "warning" : "verified", - }); - }); - debuglog("e2e check is warning/verified only as cross-signing is off"); - return; - } /* At this point, the user has encryption on and cross-signing on */ this.setState({ diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index cc308a834b..87fbf3de02 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -24,7 +24,6 @@ import withValidation from '../elements/Validation'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {Key} from "../../../Keyboard"; -import SettingsStore from "../../../settings/SettingsStore"; export default createReactClass({ displayName: 'CreateRoomDialog', @@ -66,7 +65,7 @@ export default createReactClass({ createOpts.creation_content = {'m.federate': false}; } - if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { + if (!this.state.isPublic) { opts.encryption = this.state.isEncrypted; } @@ -193,7 +192,7 @@ export default createReactClass({ } let e2eeSection; - if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { + if (!this.state.isPublic) { e2eeSection = t instanceof ThreepidMember); - if (!has3PidMembers) { - const client = MatrixClientPeg.get(); - const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); - if (allHaveDeviceKeys) { - createRoomOptions.encryption = true; - } + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const has3PidMembers = targets.some(t => t instanceof ThreepidMember); + if (!has3PidMembers) { + const client = MatrixClientPeg.get(); + const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; } } diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index a16202ed93..4944c4b5ee 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -90,21 +90,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { _onResetRecoveryClick = () => { this.props.onFinished(false); - - if (SettingsStore.getValue("feature_cross_signing")) { - // If cross-signing is enabled, we reset the SSSS recovery passphrase (and cross-signing keys) - this.props.onFinished(false); - accessSecretStorage(() => {}, /* forceReset = */ true); - } else { - Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', - import('../../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), - { - onFinished: () => { - this._loadBackupStatus(); - }, - }, null, /* priority = */ false, /* static = */ true, - ); - } + accessSecretStorage(() => {}, /* forceReset = */ true); } _onRecoveryKeyChange = (e) => { diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js deleted file mode 100644 index 2582cab573..0000000000 --- a/src/components/views/groups/GroupMemberInfo.js +++ /dev/null @@ -1,208 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd -Copyright 2019 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 React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import dis from '../../../dispatcher/dispatcher'; -import Modal from '../../../Modal'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import { GroupMemberType } from '../../../groups'; -import GroupStore from '../../../stores/GroupStore'; -import AccessibleButton from '../elements/AccessibleButton'; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import {Action} from "../../../dispatcher/actions"; - -export default createReactClass({ - displayName: 'GroupMemberInfo', - - statics: { - contextType: MatrixClientContext, - }, - - propTypes: { - groupId: PropTypes.string, - groupMember: GroupMemberType, - isInvited: PropTypes.bool, - }, - - getInitialState: function() { - return { - removingUser: false, - isUserPrivilegedInGroup: null, - }; - }, - - componentDidMount: function() { - this._unmounted = false; - this._initGroupStore(this.props.groupId); - }, - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps(newProps) { - if (newProps.groupId !== this.props.groupId) { - this._unregisterGroupStore(this.props.groupId); - this._initGroupStore(newProps.groupId); - } - }, - - componentWillUnmount() { - this._unmounted = true; - this._unregisterGroupStore(this.props.groupId); - }, - - _initGroupStore(groupId) { - GroupStore.registerListener(groupId, this.onGroupStoreUpdated); - }, - - _unregisterGroupStore(groupId) { - GroupStore.unregisterListener(this.onGroupStoreUpdated); - }, - - onGroupStoreUpdated: function() { - if (this._unmounted) return; - this.setState({ - isUserInvited: GroupStore.getGroupInvitedMembers(this.props.groupId).some( - (m) => m.userId === this.props.groupMember.userId, - ), - isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId), - }); - }, - - _onKick: function() { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createDialog(ConfirmUserActionDialog, { - matrixClient: this.context, - groupMember: this.props.groupMember, - action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'), - title: this.state.isUserInvited ? _t('Disinvite this user from community?') - : _t('Remove this user from community?'), - danger: true, - onFinished: (proceed) => { - if (!proceed) return; - - this.setState({removingUser: true}); - this.context.removeUserFromGroup( - this.props.groupId, this.props.groupMember.userId, - ).then(() => { - // return to the user list - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }).catch((e) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, { - title: _t('Error'), - description: this.state.isUserInvited ? - _t('Failed to withdraw invitation') : - _t('Failed to remove user from community'), - }); - }).finally(() => { - this.setState({removingUser: false}); - }); - }, - }); - }, - - _onCancel: function(e) { - // Go back to the user list - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }, - - onRoomTileClick(roomId) { - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); - }, - - render: function() { - if (this.state.removingUser) { - const Spinner = sdk.getComponent("elements.Spinner"); - return
- -
; - } - - let adminTools; - if (this.state.isUserPrivilegedInGroup) { - const kickButton = ( - - { this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community') } - - ); - - // No make/revoke admin API yet - /*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator"); - giveModButton = - {giveOpLabel} - ;*/ - - if (kickButton) { - adminTools = -
-

{ _t("Admin Tools") }

-
- { kickButton } -
-
; - } - } - - - const avatarUrl = this.props.groupMember.avatarUrl; - let avatarElement; - if (avatarUrl) { - const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800); - avatarElement = (
- -
); - } - - const groupMemberName = ( - this.props.groupMember.displayname || this.props.groupMember.userId - ); - - return ( -
- - - - - { avatarElement } -

{ groupMemberName }

- -
-
- { this.props.groupMember.userId } -
-
- - { adminTools } -
-
- ); - }, -}); diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 0392746c94..836e35ba22 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -64,10 +64,6 @@ const _disambiguateDevices = (devices) => { }; export const getE2EStatus = (cli, userId, devices) => { - if (!SettingsStore.getValue("feature_cross_signing")) { - const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); - return hasUnverifiedDevice ? "warning" : "verified"; - } const isMe = userId === cli.getUserId(); const userTrust = cli.checkUserTrust(userId); if (!userTrust.isCrossSigningVerified()) { @@ -112,17 +108,15 @@ async function openDMForUser(matrixClient, userId) { dmUserId: userId, }; - if (SettingsStore.getValue("feature_cross_signing")) { - // Check whether all users have uploaded device keys before. - // If so, enable encryption in the new room. - const usersToDevicesMap = await matrixClient.downloadKeys([userId]); - const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => { - // `devices` is an object of the form { deviceId: deviceInfo, ... }. - return Object.keys(devices).length > 0; - }); - if (allHaveDeviceKeys) { - createRoomOptions.encryption = true; - } + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const usersToDevicesMap = await matrixClient.downloadKeys([userId]); + const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => { + // `devices` is an object of the form { deviceId: deviceInfo, ... }. + return Object.keys(devices).length > 0; + }); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; } createRoom(createRoomOptions); @@ -167,9 +161,7 @@ function DeviceItem({userId, device}) { // cross-signing so that other users can then safely trust you. // For other people's devices, the more general verified check that // includes locally verified devices can be used. - const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? - deviceTrust.isCrossSigningVerified() : - deviceTrust.isVerified(); + const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified(); const classes = classNames("mx_UserInfo_device", { mx_UserInfo_device_verified: isVerified, @@ -248,9 +240,7 @@ function DevicesSection({devices, userId, loading}) { // cross-signing so that other users can then safely trust you. // For other people's devices, the more general verified check that // includes locally verified devices can be used. - const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? - deviceTrust.isCrossSigningVerified() : - deviceTrust.isVerified(); + const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified(); if (isVerified) { expandSectionDevices.push(device); @@ -1309,8 +1299,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const userTrust = cli.checkUserTrust(member.userId); const userVerified = userTrust.isCrossSigningVerified(); const isMe = member.userId === cli.getUserId(); - const canVerify = SettingsStore.getValue("feature_cross_signing") && - homeserverSupportsCrossSigning && !userVerified && !isMe; + const canVerify = homeserverSupportsCrossSigning && !userVerified && !isMe; const setUpdating = (updating) => { setPendingUpdateCount(count => count + (updating ? 1 : -1)); diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 5e74656920..bf65c7fb7c 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -20,7 +20,6 @@ import PropTypes from "prop-types"; import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; -import {useSettingValue} from "../../../hooks/useSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Tooltip from "../elements/Tooltip"; @@ -42,15 +41,6 @@ const crossSigningRoomTitles = { [E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"), }; -const legacyUserTitles = { - [E2E_STATE.WARNING]: _td("Some sessions for this user are not trusted"), - [E2E_STATE.VERIFIED]: _td("All sessions for this user are trusted"), -}; -const legacyRoomTitles = { - [E2E_STATE.WARNING]: _td("Some sessions in this encrypted room are not trusted"), - [E2E_STATE.VERIFIED]: _td("All sessions in this encrypted room are trusted"), -}; - const E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => { const [hover, setHover] = useState(false); @@ -62,15 +52,10 @@ const E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => { }, className); let e2eTitle; - const crossSigning = useSettingValue("feature_cross_signing"); - if (crossSigning && isUser) { + if (isUser) { e2eTitle = crossSigningUserTitles[status]; - } else if (crossSigning && !isUser) { + } else { e2eTitle = crossSigningRoomTitles[status]; - } else if (!crossSigning && isUser) { - e2eTitle = legacyUserTitles[status]; - } else if (!crossSigning && !isUser) { - e2eTitle = legacyRoomTitles[status]; } let style; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ee0b40c0de..3d8a2a7c35 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -325,15 +325,6 @@ export default createReactClass({ return; } - // If cross-signing is off, the old behaviour is to scream at the user - // as if they've done something wrong, which they haven't - if (!SettingsStore.getValue("feature_cross_signing")) { - this.setState({ - verified: E2E_STATE.WARNING, - }, this.props.onHeightChanged); - return; - } - if (!this.context.checkUserTrust(mxEvent.getSender()).isCrossSigningVerified()) { this.setState({ verified: E2E_STATE.NORMAL, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js deleted file mode 100644 index ed6c4ad748..0000000000 --- a/src/components/views/rooms/MemberInfo.js +++ /dev/null @@ -1,1165 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 Vector Creations Ltd -Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> - -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. -*/ - -/* - * State vars: - * 'can': { - * kick: boolean, - * ban: boolean, - * mute: boolean, - * modifyLevel: boolean - * }, - * 'muted': boolean, - * 'isTargetMod': boolean - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import classNames from 'classnames'; -import dis from '../../../dispatcher/dispatcher'; -import Modal from '../../../Modal'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import createRoom from '../../../createRoom'; -import DMRoomMap from '../../../utils/DMRoomMap'; -import * as Unread from '../../../Unread'; -import { findReadReceiptFromUserId } from '../../../utils/Receipt'; -import AccessibleButton from '../elements/AccessibleButton'; -import RoomViewStore from '../../../stores/RoomViewStore'; -import SdkConfig from '../../../SdkConfig'; -import MultiInviter from "../../../utils/MultiInviter"; -import SettingsStore from "../../../settings/SettingsStore"; -import E2EIcon from "./E2EIcon"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import {Action} from "../../../dispatcher/actions"; - -export default createReactClass({ - displayName: 'MemberInfo', - - propTypes: { - member: PropTypes.object.isRequired, - }, - - getInitialState: function() { - return { - can: { - kick: false, - ban: false, - mute: false, - modifyLevel: false, - synapseDeactivate: false, - redactMessages: false, - }, - muted: false, - isTargetMod: false, - updating: 0, - devicesLoading: true, - devices: null, - isIgnoring: false, - }; - }, - - statics: { - contextType: MatrixClientContext, - }, - - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount: function() { - this._cancelDeviceList = null; - const cli = this.context; - - // only display the devices list if our client supports E2E - this._enableDevices = cli.isCryptoEnabled(); - - cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - cli.on("Room", this.onRoom); - cli.on("deleteRoom", this.onDeleteRoom); - cli.on("Room.timeline", this.onRoomTimeline); - cli.on("Room.name", this.onRoomName); - cli.on("Room.receipt", this.onRoomReceipt); - cli.on("RoomState.events", this.onRoomStateEvents); - cli.on("RoomMember.name", this.onRoomMemberName); - cli.on("RoomMember.membership", this.onRoomMemberMembership); - cli.on("accountData", this.onAccountData); - - this._checkIgnoreState(); - - this._updateStateForNewMember(this.props.member); - }, - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(newProps) { - if (this.props.member.userId !== newProps.member.userId) { - this._updateStateForNewMember(newProps.member); - } - }, - - componentWillUnmount: function() { - const client = this.context; - if (client) { - client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); - client.removeListener("Room", this.onRoom); - client.removeListener("deleteRoom", this.onDeleteRoom); - client.removeListener("Room.timeline", this.onRoomTimeline); - client.removeListener("Room.name", this.onRoomName); - client.removeListener("Room.receipt", this.onRoomReceipt); - client.removeListener("RoomState.events", this.onRoomStateEvents); - client.removeListener("RoomMember.name", this.onRoomMemberName); - client.removeListener("RoomMember.membership", this.onRoomMemberMembership); - client.removeListener("accountData", this.onAccountData); - } - if (this._cancelDeviceList) { - this._cancelDeviceList(); - } - }, - - _checkIgnoreState: function() { - const isIgnoring = this.context.isUserIgnored(this.props.member.userId); - this.setState({isIgnoring: isIgnoring}); - }, - - _disambiguateDevices: function(devices) { - const names = Object.create(null); - for (let i = 0; i < devices.length; i++) { - const name = devices[i].getDisplayName(); - const indexList = names[name] || []; - indexList.push(i); - names[name] = indexList; - } - for (const name in names) { - if (names[name].length > 1) { - names[name].forEach((j)=>{ - devices[j].ambiguous = true; - }); - } - } - }, - - onDeviceVerificationChanged: function(userId, device) { - if (!this._enableDevices) { - return; - } - - if (userId === this.props.member.userId) { - // no need to re-download the whole thing; just update our copy of - // the list. - - const devices = this.context.getStoredDevicesForUser(userId); - this.setState({ - devices: devices, - e2eStatus: this._getE2EStatus(devices), - }); - } - }, - - _getE2EStatus: function(devices) { - const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); - return hasUnverifiedDevice ? "warning" : "verified"; - }, - - onRoom: function(room) { - this.forceUpdate(); - }, - - onDeleteRoom: function(roomId) { - this.forceUpdate(); - }, - - onRoomTimeline: function(ev, room, toStartOfTimeline) { - if (toStartOfTimeline) return; - this.forceUpdate(); - }, - - onRoomName: function(room) { - this.forceUpdate(); - }, - - onRoomReceipt: function(receiptEvent, room) { - // because if we read a notification, it will affect notification count - // only bother updating if there's a receipt from us - if (findReadReceiptFromUserId(receiptEvent, this.context.credentials.userId)) { - this.forceUpdate(); - } - }, - - onRoomStateEvents: function(ev, state) { - this.forceUpdate(); - }, - - onRoomMemberName: function(ev, member) { - this.forceUpdate(); - }, - - onRoomMemberMembership: function(ev, member) { - if (this.props.member.userId === member.userId) this.forceUpdate(); - }, - - onAccountData: function(ev) { - if (ev.getType() === 'm.direct') { - this.forceUpdate(); - } - }, - - _updateStateForNewMember: async function(member) { - const newState = await this._calculateOpsPermissions(member); - newState.devicesLoading = true; - newState.devices = null; - this.setState(newState); - - if (this._cancelDeviceList) { - this._cancelDeviceList(); - this._cancelDeviceList = null; - } - - this._downloadDeviceList(member); - }, - - _downloadDeviceList: function(member) { - if (!this._enableDevices) { - return; - } - - let cancelled = false; - this._cancelDeviceList = function() { cancelled = true; }; - - const client = this.context; - const self = this; - client.downloadKeys([member.userId], true).then(() => { - return client.getStoredDevicesForUser(member.userId); - }).finally(function() { - self._cancelDeviceList = null; - }).then(function(devices) { - if (cancelled) { - // we got cancelled - presumably a different user now - return; - } - - self._disambiguateDevices(devices); - self.setState({ - devicesLoading: false, - devices: devices, - e2eStatus: self._getE2EStatus(devices), - }); - }, function(err) { - console.log("Error downloading sessions", err); - self.setState({devicesLoading: false}); - }); - }, - - onIgnoreToggle: function() { - const ignoredUsers = this.context.getIgnoredUsers(); - if (this.state.isIgnoring) { - const index = ignoredUsers.indexOf(this.props.member.userId); - if (index !== -1) ignoredUsers.splice(index, 1); - } else { - ignoredUsers.push(this.props.member.userId); - } - - this.context.setIgnoredUsers(ignoredUsers).then(() => { - return this.setState({isIgnoring: !this.state.isIgnoring}); - }); - }, - - onKick: function() { - const membership = this.props.member.membership; - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, { - member: this.props.member, - action: membership === "invite" ? _t("Disinvite") : _t("Kick"), - title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), - askReason: membership === "join", - danger: true, - onFinished: (proceed, reason) => { - if (!proceed) return; - - this.setState({ updating: this.state.updating + 1 }); - this.context.kick( - this.props.member.roomId, this.props.member.userId, - reason || undefined, - ).then(function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Kick success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Kick error: " + err); - Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { - title: _t("Failed to kick"), - description: ((err && err.message) ? err.message : "Operation failed"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - }); - }, - - onBanOrUnban: function() { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, { - member: this.props.member, - action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"), - title: this.props.member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"), - askReason: this.props.member.membership !== 'ban', - danger: this.props.member.membership !== 'ban', - onFinished: (proceed, reason) => { - if (!proceed) return; - - this.setState({ updating: this.state.updating + 1 }); - let promise; - if (this.props.member.membership === 'ban') { - promise = this.context.unban( - this.props.member.roomId, this.props.member.userId, - ); - } else { - promise = this.context.ban( - this.props.member.roomId, this.props.member.userId, - reason || undefined, - ); - } - promise.then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Ban success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Ban error: " + err); - Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to ban user"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - }); - }, - - onRedactAllMessages: async function() { - const {roomId, userId} = this.props.member; - const room = this.context.getRoom(roomId); - if (!room) { - return; - } - const timelineSet = room.getUnfilteredTimelineSet(); - let eventsToRedact = []; - for (const timeline of timelineSet.getTimelines()) { - eventsToRedact = timeline.getEvents().reduce((events, event) => { - if (event.getSender() === userId && !event.isRedacted() && !event.isRedaction()) { - return events.concat(event); - } else { - return events; - } - }, eventsToRedact); - } - - const count = eventsToRedact.length; - const user = this.props.member.name; - - if (count === 0) { - const InfoDialog = sdk.getComponent("dialogs.InfoDialog"); - Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, { - title: _t("No recent messages by %(user)s found", {user}), - description: -
-

{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }

-
, - }); - } else { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const confirmed = await new Promise((resolve) => { - Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, { - title: _t("Remove recent messages by %(user)s", {user}), - description: -
-

{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }

-

{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }

-
, - button: _t("Remove %(count)s messages", {count}), - onFinished: resolve, - }); - }); - - if (!confirmed) { - return; - } - - // Submitting a large number of redactions freezes the UI, - // so first yield to allow to rerender after closing the dialog. - await Promise.resolve(); - - console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`); - await Promise.all(eventsToRedact.map(async event => { - try { - await this.context.redactEvent(roomId, event.getId()); - } catch (err) { - // log and swallow errors - console.error("Could not redact", event.getId()); - console.error(err); - } - })); - console.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`); - } - }, - - _warnSelfDemote: function() { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - return new Promise((resolve) => { - Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, { - title: _t("Demote yourself?"), - description: -
- { _t("You will not be able to undo this change as you are demoting yourself, " + - "if you are the last privileged user in the room it will be impossible " + - "to regain privileges.") } -
, - button: _t("Demote"), - onFinished: resolve, - }); - }); - }, - - onMuteToggle: async function() { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const roomId = this.props.member.roomId; - const target = this.props.member.userId; - const room = this.context.getRoom(roomId); - if (!room) return; - - // if muting self, warn as it may be irreversible - if (target === this.context.getUserId()) { - try { - if (!(await this._warnSelfDemote())) return; - } catch (e) { - console.error("Failed to warn about self demotion: ", e); - return; - } - } - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - const isMuted = this.state.muted; - const powerLevels = powerLevelEvent.getContent(); - const levelToSend = ( - (powerLevels.events ? powerLevels.events["m.room.message"] : null) || - powerLevels.events_default - ); - let level; - if (isMuted) { // unmute - level = levelToSend; - } else { // mute - level = levelToSend - 1; - } - level = parseInt(level); - - if (!isNaN(level)) { - this.setState({ updating: this.state.updating + 1 }); - this.context.setPowerLevel(roomId, target, level, powerLevelEvent).then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mute toggle success"); - }, function(err) { - console.error("Mute error: " + err); - Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to mute user"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - } - }, - - onModToggle: function() { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const roomId = this.props.member.roomId; - const target = this.props.member.userId; - const room = this.context.getRoom(roomId); - if (!room) return; - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - const me = room.getMember(this.context.credentials.userId); - if (!me) return; - - const defaultLevel = powerLevelEvent.getContent().users_default; - let modLevel = me.powerLevel - 1; - if (modLevel > 50 && defaultLevel < 50) modLevel = 50; // try to stick with the vector level defaults - // toggle the level - const newLevel = this.state.isTargetMod ? defaultLevel : modLevel; - this.setState({ updating: this.state.updating + 1 }); - this.context.setPowerLevel(roomId, target, parseInt(newLevel), powerLevelEvent).then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mod toggle success"); - }, function(err) { - if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') { - dis.dispatch({action: 'require_registration'}); - } else { - console.error("Toggle moderator error:" + err); - Modal.createTrackedDialog('Failed to toggle moderator status', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to toggle moderator status"), - }); - } - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - - onSynapseDeactivate: function() { - const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); - Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, { - title: _t("Deactivate user?"), - description: -
{ _t( - "Deactivating this user will log them out and prevent them from logging back in. Additionally, " + - "they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to " + - "deactivate this user?" - ) }
, - button: _t("Deactivate user"), - danger: true, - onFinished: (accepted) => { - if (!accepted) return; - this.context.deactivateSynapseUser(this.props.member.userId).catch(e => { - console.error("Failed to deactivate user"); - console.error(e); - - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, { - title: _t('Failed to deactivate user'), - description: ((e && e.message) ? e.message : _t("Operation failed")), - }); - }); - }, - }); - }, - - _applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) { - this.setState({ updating: this.state.updating + 1 }); - this.context.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Power change success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to change power level " + err); - Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to change power level"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - - onPowerChange: async function(powerLevel) { - const roomId = this.props.member.roomId; - const target = this.props.member.userId; - const room = this.context.getRoom(roomId); - if (!room) return; - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - if (!powerLevelEvent.getContent().users) { - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - return; - } - - const myUserId = this.context.getUserId(); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - // If we are changing our own PL it can only ever be decreasing, which we cannot reverse. - if (myUserId === target) { - try { - if (!(await this._warnSelfDemote())) return; - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - } catch (e) { - console.error("Failed to warn about self demotion: ", e); - } - return; - } - - const myPower = powerLevelEvent.getContent().users[myUserId]; - if (parseInt(myPower) === parseInt(powerLevel)) { - Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, { - title: _t("Warning!"), - description: -
- { _t("You will not be able to undo this change as you are promoting the user " + - "to have the same power level as yourself.") }
- { _t("Are you sure?") } -
, - button: _t("Continue"), - onFinished: (confirmed) => { - if (confirmed) { - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - } - }, - }); - return; - } - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - }, - - onNewDMClick: function() { - this.setState({ updating: this.state.updating + 1 }); - createRoom({dmUserId: this.props.member.userId}).finally(() => { - this.setState({ updating: this.state.updating - 1 }); - }); - }, - - onLeaveClick: function() { - dis.dispatch({ - action: 'leave_room', - room_id: this.props.member.roomId, - }); - }, - - _calculateOpsPermissions: async function(member) { - let canDeactivate = false; - if (this.context) { - try { - canDeactivate = await this.context.isSynapseAdministrator(); - } catch (e) { - console.error(e); - } - } - - const defaultPerms = { - can: { - // Calculate permissions for Synapse before doing the PL checks - synapseDeactivate: canDeactivate, - }, - muted: false, - }; - const room = this.context.getRoom(member.roomId); - if (!room) return defaultPerms; - - const powerLevels = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevels) return defaultPerms; - - const me = room.getMember(this.context.credentials.userId); - if (!me) return defaultPerms; - - const them = member; - return { - can: { - ...defaultPerms.can, - ...await this._calculateCanPermissions(me, them, powerLevels.getContent()), - }, - muted: this._isMuted(them, powerLevels.getContent()), - isTargetMod: them.powerLevel > powerLevels.getContent().users_default, - }; - }, - - _calculateCanPermissions: function(me, them, powerLevels) { - const isMe = me.userId === them.userId; - const can = { - kick: false, - ban: false, - mute: false, - modifyLevel: false, - modifyLevelMax: 0, - redactMessages: me.powerLevel >= powerLevels.redact, - }; - - const canAffectUser = them.powerLevel < me.powerLevel || isMe; - if (!canAffectUser) { - //console.info("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel); - return can; - } - const editPowerLevel = ( - (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || - powerLevels.state_default - ); - - can.kick = me.powerLevel >= powerLevels.kick; - can.ban = me.powerLevel >= powerLevels.ban; - can.invite = me.powerLevel >= powerLevels.invite; - can.mute = me.powerLevel >= editPowerLevel; - can.modifyLevel = me.powerLevel >= editPowerLevel && (isMe || me.powerLevel > them.powerLevel); - can.modifyLevelMax = me.powerLevel; - - return can; - }, - - _isMuted: function(member, powerLevelContent) { - if (!powerLevelContent || !member) return false; - - const levelToSend = ( - (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) || - powerLevelContent.events_default - ); - return member.powerLevel < levelToSend; - }, - - onCancel: function(e) { - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }, - - onMemberAvatarClick: function() { - const member = this.props.member; - const avatarUrl = member.getMxcAvatarUrl(); - if (!avatarUrl) return; - - const httpUrl = this.context.mxcUrlToHttp(avatarUrl); - const ImageView = sdk.getComponent("elements.ImageView"); - const params = { - src: httpUrl, - name: member.name, - }; - - Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); - }, - - onRoomTileClick(roomId) { - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); - }, - - _renderDevices: function() { - if (!this._enableDevices) return null; - - const devices = this.state.devices; - const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); - const Spinner = sdk.getComponent("elements.Spinner"); - - let devComponents; - if (this.state.devicesLoading) { - // still loading - devComponents = ; - } else if (devices === null) { - devComponents = _t("Unable to load session list"); - } else if (devices.length === 0) { - devComponents = _t("No sessions with registered encryption keys"); - } else { - devComponents = []; - for (let i = 0; i < devices.length; i++) { - devComponents.push(); - } - } - - return ( -
-

{ _t("Sessions") }

-
- { devComponents } -
-
- ); - }, - - onShareUserClick: function() { - const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); - Modal.createTrackedDialog('share room member dialog', '', ShareDialog, { - target: this.props.member, - }); - }, - - _renderUserOptions: function() { - const cli = this.context; - const member = this.props.member; - - let ignoreButton = null; - let insertPillButton = null; - let inviteUserButton = null; - let readReceiptButton = null; - - // Only allow the user to ignore the user if its not ourselves - // same goes for jumping to read receipt - if (member.userId !== cli.getUserId()) { - ignoreButton = ( - - { this.state.isIgnoring ? _t("Unignore") : _t("Ignore") } - - ); - - if (member.roomId) { - const room = cli.getRoom(member.roomId); - const eventId = room.getEventReadUpTo(member.userId); - - const onReadReceiptButton = function() { - dis.dispatch({ - action: 'view_room', - highlighted: true, - event_id: eventId, - room_id: member.roomId, - }); - }; - - const onInsertPillButton = function() { - dis.dispatch({ - action: 'insert_mention', - user_id: member.userId, - }); - }; - - readReceiptButton = ( - - { _t('Jump to read receipt') } - - ); - - insertPillButton = ( - - { _t('Mention') } - - ); - } - - if (this.state.can.invite && (!member || !member.membership || member.membership === 'leave')) { - const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId(); - const onInviteUserButton = async () => { - try { - // We use a MultiInviter to re-use the invite logic, even though - // we're only inviting one user. - const inviter = new MultiInviter(roomId); - await inviter.invite([member.userId]).then(() => { - if (inviter.getCompletionState(member.userId) !== "invited") - throw new Error(inviter.getErrorText(member.userId)); - }); - } catch (err) { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { - title: _t('Failed to invite'), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - } - }; - - inviteUserButton = ( - - { _t('Invite') } - - ); - } - } - - const shareUserButton = ( - - { _t('Share Link to User') } - - ); - - return ( -
-

{ _t("User Options") }

-
- { readReceiptButton } - { shareUserButton } - { insertPillButton } - { ignoreButton } - { inviteUserButton } -
-
- ); - }, - - render: function() { - let startChat; - let kickButton; - let banButton; - let muteButton; - let giveModButton; - let redactButton; - let synapseDeactivateButton; - let spinner; - - if (this.props.member.userId !== this.context.credentials.userId) { - // TODO: Immutable DMs replaces a lot of this - const dmRoomMap = new DMRoomMap(this.context); - // dmRooms will not include dmRooms that we have been invited into but did not join. - // Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room. - // XXX: we potentially want DMs we have been invited to, to also show up here :L - // especially as logic below concerns specially if we haven't joined but have been invited - const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId); - - const RoomTile = sdk.getComponent("rooms.RoomTile"); - - const tiles = []; - for (const roomId of dmRooms) { - const room = this.context.getRoom(roomId); - if (room) { - const myMembership = room.getMyMembership(); - // not a DM room if we have are not joined - if (myMembership !== 'join') continue; - - const them = this.props.member; - // not a DM room if they are not joined - if (!them.membership || them.membership !== 'join') continue; - - const highlight = room.getUnreadNotificationCount('highlight') > 0; - - tiles.push( - , - ); - } - } - - const labelClasses = classNames({ - mx_MemberInfo_createRoom_label: true, - mx_RoomTile_name: true, - }); - let startNewChat = -
- -
-
{ _t("Start a chat") }
-
; - - if (tiles.length > 0) startNewChat = null; // Don't offer a button for a new chat if we have one. - - startChat =
-

{ _t("Direct chats") }

- { tiles } - { startNewChat } -
; - } - - if (this.state.updating) { - const Loader = sdk.getComponent("elements.Spinner"); - spinner = ; - } - - if (this.state.can.kick) { - const membership = this.props.member.membership; - const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); - kickButton = ( - - { kickLabel } - - ); - } - - if (this.state.can.redactMessages) { - redactButton = ( - - { _t("Remove recent messages") } - - ); - } - - if (this.state.can.ban) { - let label = _t("Ban"); - if (this.props.member.membership === 'ban') { - label = _t("Unban"); - } - banButton = ( - - { label } - - ); - } - if (this.state.can.mute) { - const muteLabel = this.state.muted ? _t("Unmute") : _t("Mute"); - muteButton = ( - - { muteLabel } - - ); - } - if (this.state.can.toggleMod) { - const giveOpLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator"); - giveModButton = - { giveOpLabel } - ; - } - - // We don't need a perfect check here, just something to pass as "probably not our homeserver". If - // someone does figure out how to bypass this check the worst that happens is an error. - const sameHomeserver = this.props.member.userId.endsWith(`:${MatrixClientPeg.getHomeserverName()}`); - if (this.state.can.synapseDeactivate && sameHomeserver) { - synapseDeactivateButton = ( - - {_t("Deactivate user")} - - ); - } - - let adminTools; - if (kickButton || banButton || muteButton || giveModButton || synapseDeactivateButton || redactButton) { - adminTools = -
-

{ _t("Admin Tools") }

- -
- { muteButton } - { kickButton } - { banButton } - { redactButton } - { giveModButton } - { synapseDeactivateButton } -
-
; - } - - const memberName = this.props.member.name; - - let presenceState; - let presenceLastActiveAgo; - let presenceCurrentlyActive; - let statusMessage; - - if (this.props.member.user) { - presenceState = this.props.member.user.presence; - presenceLastActiveAgo = this.props.member.user.lastActiveAgo; - presenceCurrentlyActive = this.props.member.user.currentlyActive; - - if (SettingsStore.isFeatureEnabled("feature_custom_status")) { - statusMessage = this.props.member.user._unstable_statusMessage; - } - } - - const room = this.context.getRoom(this.props.member.roomId); - const powerLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null; - const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0; - - const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"]; - const hsUrl = this.context.baseUrl; - let showPresence = true; - if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) { - showPresence = enablePresenceByHsUrl[hsUrl]; - } - - let presenceLabel = null; - if (showPresence) { - const PresenceLabel = sdk.getComponent('rooms.PresenceLabel'); - presenceLabel = ; - } - - let statusLabel = null; - if (statusMessage) { - statusLabel = { statusMessage }; - } - - let roomMemberDetails = null; - let e2eIconElement; - - if (this.props.member.roomId) { // is in room - const PowerSelector = sdk.getComponent('elements.PowerSelector'); - roomMemberDetails =
-
- -
-
- {presenceLabel} - {statusLabel} -
-
; - - const isEncrypted = this.context.isRoomEncrypted(this.props.member.roomId); - if (this.state.e2eStatus && isEncrypted) { - e2eIconElement = (); - } - } - - const {member} = this.props; - const avatarUrl = member.avatarUrl || (member.getMxcAvatarUrl && member.getMxcAvatarUrl()); - let avatarElement; - if (avatarUrl) { - const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800); - avatarElement =
- -
; - } - - let backButton; - if (this.props.member.roomId) { - backButton = (); - } - - return ( -
-
- { backButton } - { e2eIconElement } -

{ memberName }

-
- { avatarElement } -
- -
-
- { this.props.member.userId } -
- { roomMemberDetails } -
-
- -
- { this._renderUserOptions() } - - { adminTools } - - { startChat } - - { this._renderDevices() } - - { spinner } -
-
-
- ); - }, -}); diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 2d290564c3..3be378b341 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -57,21 +57,19 @@ export default createReactClass({ } } - if (SettingsStore.getValue("feature_cross_signing")) { - const { roomId } = this.props.member; - if (roomId) { - const isRoomEncrypted = cli.isRoomEncrypted(roomId); - this.setState({ - isRoomEncrypted, - }); - if (isRoomEncrypted) { - cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged); - cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - this.updateE2EStatus(); - } else { - // Listen for room to become encrypted - cli.on("RoomState.events", this.onRoomStateEvents); - } + const { roomId } = this.props.member; + if (roomId) { + const isRoomEncrypted = cli.isRoomEncrypted(roomId); + this.setState({ + isRoomEncrypted, + }); + if (isRoomEncrypted) { + cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged); + cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); + this.updateE2EStatus(); + } else { + // Listen for room to become encrypted + cli.on("RoomState.events", this.onRoomStateEvents); } } }, diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 732df3dbbf..14caed0183 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -281,33 +281,17 @@ export default class MessageComposer extends React.Component { } renderPlaceholderText() { - if (SettingsStore.getValue("feature_cross_signing")) { - if (this.state.isQuoting) { - if (this.props.e2eStatus) { - return _t('Send an encrypted reply…'); - } else { - return _t('Send a reply…'); - } + if (this.state.isQuoting) { + if (this.props.e2eStatus) { + return _t('Send an encrypted reply…'); } else { - if (this.props.e2eStatus) { - return _t('Send an encrypted message…'); - } else { - return _t('Send a message…'); - } + return _t('Send a reply…'); } } else { - if (this.state.isQuoting) { - if (this.props.e2eStatus) { - return _t('Send an encrypted reply…'); - } else { - return _t('Send a reply (unencrypted)…'); - } + if (this.props.e2eStatus) { + return _t('Send an encrypted message…'); } else { - if (this.props.e2eStatus) { - return _t('Send an encrypted message…'); - } else { - return _t('Send a message (unencrypted)…'); - } + return _t('Send a message…'); } } } diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 17495e6299..4820d0c8ff 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -168,10 +168,8 @@ export default createReactClass({ const joinRule = joinRules && joinRules.getContent().join_rule; let privateIcon; // Don't show an invite-only icon for DMs. Users know they're invite-only. - if (!dmUserId && SettingsStore.getValue("feature_cross_signing")) { - if (joinRule == "invite") { - privateIcon = ; - } + if (!dmUserId && joinRule === "invite") { + privateIcon = ; } if (this.props.onCancelClick) { diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 44e5ae7643..5917f2ae77 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -155,9 +155,6 @@ export default createReactClass({ if (!cli.isRoomEncrypted(this.props.room.roomId)) { return; } - if (!SettingsStore.getValue("feature_cross_signing")) { - return; - } /* At this point, the user has encryption on and cross-signing on */ this.setState({ @@ -515,10 +512,8 @@ export default createReactClass({ } let privateIcon = null; - if (SettingsStore.getValue("feature_cross_signing")) { - if (this.state.joinRule == "invite" && !dmUserId) { - privateIcon = ; - } + if (this.state.joinRule === "invite" && !dmUserId) { + privateIcon = ; } let e2eIcon = null; diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index b1642e260d..7eb239cbca 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -194,6 +194,8 @@ export default class CrossSigningPanel extends React.PureComponent { ); } + + // TODO: determine how better to expose this to users in addition to prompts at login/toast let bootstrapButton; if ( (!enabledForAccount || !crossSigningPublicKeysOnDevice) && diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index fa3fa03c74..d09870d5e3 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -316,7 +316,7 @@ export default class KeyBackupPanel extends React.PureComponent { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } - let buttonRow = ( + const buttonRow = (
{restoreButtonCaption} @@ -326,13 +326,6 @@ export default class KeyBackupPanel extends React.PureComponent {
); - if (this.state.backupKeyStored && !SettingsStore.getValue("feature_cross_signing")) { - buttonRow =

⚠️ {_t( - "Backup key stored in secret storage, but this feature is not " + - "enabled on this session. Please enable cross-signing in Labs to " + - "modify key backup state.", - )}

; - } return
{clientBackupStatus}
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index bed057f03d..a410617631 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -306,9 +306,7 @@ export default class SecurityUserSettingsTab extends React.Component { // in having advanced details here once all flows are implemented, we // can remove this. const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); - let crossSigning; - if (SettingsStore.getValue("feature_cross_signing")) { - crossSigning = ( + const crossSigning = (
{_t("Cross-signing")}
@@ -316,7 +314,6 @@ export default class SecurityUserSettingsTab extends React.Component {
); - } const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); diff --git a/src/createRoom.js b/src/createRoom.js index 18fc787e1c..b5761e91c5 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -23,7 +23,6 @@ import dis from "./dispatcher/dispatcher"; import * as Rooms from "./Rooms"; import DMRoomMap from "./utils/DMRoomMap"; import {getAddressType} from "./UserAddress"; -import SettingsStore from "./settings/SettingsStore"; /** * Create a new room, and switch to it. @@ -226,10 +225,7 @@ export async function ensureDMExists(client, userId) { if (existingDMRoom) { roomId = existingDMRoom.roomId; } else { - let encryption; - if (SettingsStore.getValue("feature_cross_signing")) { - encryption = canEncryptToAllUsers(client, [userId]); - } + const encryption = canEncryptToAllUsers(client, [userId]); roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false}); await _waitForMember(client, roomId, userId); } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index e6aa112c5f..ea10a027cf 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -164,13 +164,6 @@ export const SETTINGS = { supportedLevels: ['account'], default: null, }, - "feature_cross_signing": { - // XXX: We shouldn't be using the feature prefix for non-feature settings. There is an exception - // for this case though as we're converting a feature to a setting for a temporary safety net. - displayName: _td("Enable cross-signing to verify per-user instead of per-session"), - supportedLevels: ['device', 'config'], // we shouldn't use LEVELS_FEATURE for non-features, so copy it here. - default: true, - }, "feature_bridge_state": { isFeature: true, supportedLevels: LEVELS_FEATURE, diff --git a/src/verification.js b/src/verification.js index 289ac9544b..1dccb7dc28 100644 --- a/src/verification.js +++ b/src/verification.js @@ -22,12 +22,11 @@ import { _t } from './languageHandler'; import {RIGHT_PANEL_PHASES} from "./stores/RightPanelStorePhases"; import {findDMForUser} from './createRoom'; import {accessSecretStorage} from './CrossSigningManager'; -import SettingsStore from './settings/SettingsStore'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; async function enable4SIfNeeded() { const cli = MatrixClientPeg.get(); - if (!cli.isCryptoEnabled() || !SettingsStore.getValue("feature_cross_signing")) { + if (!cli.isCryptoEnabled()) { return false; } const usk = cli.getCrossSigningId("user_signing"); From cf50e1a40830d3c712c542d2402acb854098314e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 27 May 2020 10:31:30 +0100 Subject: [PATCH 2/7] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 88 +++++++++++++++---------------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7474af2ee7..351ebd3be0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -419,7 +419,6 @@ "Use the improved room list (in development - refresh to apply changes)": "Use the improved room list (in development - refresh to apply changes)", "Support adding custom themes": "Support adding custom themes", "Use IRC layout": "Use IRC layout", - "Enable cross-signing to verify per-user instead of per-session": "Enable cross-signing to verify per-user instead of per-session", "Show info about bridges in room settings": "Show info about bridges in room settings", "Font size": "Font size", "Custom font size": "Custom font size", @@ -680,7 +679,6 @@ "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", - "Backup key stored in secret storage, but this feature is not enabled on this session. Please enable cross-signing in Labs to modify key backup state.": "Backup key stored in secret storage, but this feature is not enabled on this session. Please enable cross-signing in Labs to modify key backup state.", "Backup version: ": "Backup version: ", "Algorithm: ": "Algorithm: ", "Backup key stored: ": "Backup key stored: ", @@ -992,10 +990,6 @@ "Someone is using an unknown session": "Someone is using an unknown session", "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 sessions for this user are not trusted": "Some sessions for this user are not trusted", - "All sessions for this user are trusted": "All sessions for this user are trusted", - "Some sessions in this encrypted room are not trusted": "Some sessions in this encrypted room are not trusted", - "All sessions in this encrypted room are trusted": "All sessions in this encrypted room are trusted", "Edit message": "Edit message", "Mod": "Mod", "This event could not be displayed": "This event could not be displayed", @@ -1016,50 +1010,6 @@ "Scroll to most recent messages": "Scroll to most recent messages", "Close preview": "Close preview", "device id: ": "device id: ", - "Disinvite": "Disinvite", - "Kick": "Kick", - "Disinvite this user?": "Disinvite this user?", - "Kick this user?": "Kick this user?", - "Failed to kick": "Failed to kick", - "Ban": "Ban", - "Unban this user?": "Unban this user?", - "Ban this user?": "Ban this user?", - "Failed to ban user": "Failed to ban user", - "No recent messages by %(user)s found": "No recent messages by %(user)s found", - "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", - "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", - "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", - "Remove %(count)s messages|other": "Remove %(count)s messages", - "Remove %(count)s messages|one": "Remove 1 message", - "Demote yourself?": "Demote yourself?", - "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.", - "Demote": "Demote", - "Failed to mute user": "Failed to mute user", - "Failed to toggle moderator status": "Failed to toggle moderator status", - "Deactivate user?": "Deactivate user?", - "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?", - "Deactivate user": "Deactivate user", - "Failed to deactivate user": "Failed to deactivate user", - "Failed to change power level": "Failed to change power level", - "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.", - "Are you sure?": "Are you sure?", - "No sessions with registered encryption keys": "No sessions with registered encryption keys", - "Sessions": "Sessions", - "Jump to read receipt": "Jump to read receipt", - "Mention": "Mention", - "Invite": "Invite", - "Share Link to User": "Share Link to User", - "User Options": "User Options", - "Start a chat": "Start a chat", - "Direct chats": "Direct chats", - "Remove recent messages": "Remove recent messages", - "Unmute": "Unmute", - "Mute": "Mute", - "Revoke Moderator": "Revoke Moderator", - "Make Moderator": "Make Moderator", - "Admin Tools": "Admin Tools", "and %(count)s others...|other": "and %(count)s others...", "and %(count)s others...|one": "and one other...", "Invite to this room": "Invite to this room", @@ -1074,8 +1024,6 @@ "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.", "You do not have permission to post to this room": "You do not have permission to post to this room", @@ -1204,6 +1152,7 @@ "Show Stickers": "Show Stickers", "Failed to revoke invite": "Failed to revoke invite", "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.", + "Admin Tools": "Admin Tools", "Revoke invite": "Revoke invite", "Invited by %(sender)s": "Invited by %(sender)s", "Jump to first unread message.": "Jump to first unread message.", @@ -1270,13 +1219,48 @@ "%(count)s sessions|other": "%(count)s sessions", "%(count)s sessions|one": "%(count)s session", "Hide sessions": "Hide sessions", + "Jump to read receipt": "Jump to read receipt", + "Mention": "Mention", + "Invite": "Invite", + "Share Link to User": "Share Link to User", "Direct message": "Direct message", + "Demote yourself?": "Demote yourself?", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.", + "Demote": "Demote", + "Disinvite": "Disinvite", + "Kick": "Kick", + "Disinvite this user?": "Disinvite this user?", + "Kick this user?": "Kick this user?", + "Failed to kick": "Failed to kick", + "No recent messages by %(user)s found": "No recent messages by %(user)s found", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", + "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", + "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", + "Remove %(count)s messages|other": "Remove %(count)s messages", + "Remove %(count)s messages|one": "Remove 1 message", + "Remove recent messages": "Remove recent messages", + "Ban": "Ban", + "Unban this user?": "Unban this user?", + "Ban this user?": "Ban this user?", + "Failed to ban user": "Failed to ban user", + "Failed to mute user": "Failed to mute user", + "Unmute": "Unmute", + "Mute": "Mute", "Remove from community": "Remove from community", "Disinvite this user from community?": "Disinvite this user from community?", "Remove this user from community?": "Remove this user from community?", "Failed to withdraw invitation": "Failed to withdraw invitation", "Failed to remove user from community": "Failed to remove user from community", "%(role)s in %(roomName)s": "%(role)s in %(roomName)s", + "Failed to change power level": "Failed to change power level", + "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.", + "Are you sure?": "Are you sure?", + "Deactivate user?": "Deactivate user?", + "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?", + "Deactivate user": "Deactivate user", + "Failed to deactivate user": "Failed to deactivate user", "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", "Security": "Security", "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.", From cc68f53fc2854bd52df22bdc6bef91668c2c9828 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 27 May 2020 10:47:01 +0100 Subject: [PATCH 3/7] tidy up and delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 3 --- src/components/structures/RightPanel.js | 19 +++++-------------- .../views/dialogs/DeviceVerifyDialog.js | 8 ++------ src/components/views/dialogs/InviteDialog.js | 1 - .../keybackup/RestoreKeyBackupDialog.js | 2 -- .../views/settings/KeyBackupPanel.js | 1 - .../tabs/user/SecurityUserSettingsTab.js | 2 +- 7 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 2bebdd1e79..125828689d 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1937,9 +1937,6 @@ export default class MatrixChat extends React.PureComponent { } if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { - // This will only work if the feature is set to 'enable' in the config, - // since it's too early in the lifecycle for users to have turned the - // labs flag on. this.setStateForNewView({ view: Views.E2E_SETUP }); } else { this.onLoggedIn(); diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 811feb8614..776130e709 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -26,7 +26,6 @@ import dis from '../../dispatcher/dispatcher'; import RateLimitedFunc from '../../ratelimitedfunc'; import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker'; import GroupStore from '../../stores/GroupStore'; -import SettingsStore from "../../settings/SettingsStore"; import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases"; import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; @@ -189,7 +188,7 @@ export default class RightPanel extends React.Component { } } - onCloseRoomMemberInfo = () => { + onCloseUserInfo = () => { // XXX: There are three different ways of 'closing' this panel depending on what state // things are in... this knows far more than it should do about the state of the rest // of the app and is generally a bit silly. @@ -203,23 +202,15 @@ export default class RightPanel extends React.Component { }); } else { // Otherwise we have got our user from RoomViewStore which means we're being shown - // within a room, so go back to the member panel if we were in the encryption panel, + // within a room/group, so go back to the member panel if we were in the encryption panel, // or the member list if we were in the member panel... phew. dis.dispatch({ action: Action.ViewUser, - member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? - this.state.member : null, + member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? this.state.member : null, }); } }; - onCloseGroupMemberInfo = () => { - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }; - render() { const MemberList = sdk.getComponent('rooms.MemberList'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); @@ -253,7 +244,7 @@ export default class RightPanel extends React.Component { user={this.state.member} roomId={this.props.roomId} key={this.props.roomId || this.state.member.userId} - onClose={this.onCloseRoomMemberInfo} + onClose={this.onCloseUserInfo} phase={this.state.phase} verificationRequest={this.state.verificationRequest} verificationRequestPromise={this.state.verificationRequestPromise} @@ -267,7 +258,7 @@ export default class RightPanel extends React.Component { user={this.state.member} groupId={this.props.groupId} key={this.state.member.userId} - onClose={this.onCloseGroupMemberInfo} />; + onClose={this.onCloseUserInfo} />; break; case RIGHT_PANEL_PHASES.GroupRoomInfo: panel = r.ready || r.started); this.setState({phase: PHASE_PICK_VERIFICATION_OPTION}); - } else { - this._verifier = client.beginKeyVerification( - verificationMethods.SAS, this.props.userId, this.props.device.deviceId, - ); } + if (!this._verifier) return; this._verifier.on('show_sas', this._onVerifierShowSas); // throws upon cancellation diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 79eb1b5b9d..c245ba35aa 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -33,7 +33,6 @@ import Modal from "../../../Modal"; import {humanizeTime} from "../../../utils/humanize"; import createRoom, {canEncryptToAllUsers} from "../../../createRoom"; import {inviteMultipleToRoom} from "../../../RoomInvite"; -import SettingsStore from '../../../settings/SettingsStore'; import {Key} from "../../../Keyboard"; import {Action} from "../../../dispatcher/actions"; import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy"; diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 4944c4b5ee..a27853db56 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -20,10 +20,8 @@ import PropTypes from 'prop-types'; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import { MatrixClient } from 'matrix-js-sdk'; -import Modal from '../../../../Modal'; import { _t } from '../../../../languageHandler'; import { accessSecretStorage } from '../../../../CrossSigningManager'; -import SettingsStore from "../../../../settings/SettingsStore"; const RESTORE_TYPE_PASSPHRASE = 0; const RESTORE_TYPE_RECOVERYKEY = 1; diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index d09870d5e3..a7a2c768db 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -21,7 +21,6 @@ import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; -import SettingsStore from '../../../settings/SettingsStore'; export default class KeyBackupPanel extends React.PureComponent { constructor(props) { diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index a410617631..b3c3f63d72 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import {_t} from "../../../../../languageHandler"; -import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import * as FormattingUtils from "../../../../../utils/FormattingUtils"; import AccessibleButton from "../../../elements/AccessibleButton"; From f0cabd55c090fab6010f63b9e2064012e78bd13c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 28 May 2020 16:59:27 +0100 Subject: [PATCH 4/7] Remove legacy codepaths for Unknown Device Error (UDE/UDD) handling as we now always `setGlobalErrorOnUnknownDevices(false)` Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .eslintignore.errorfiles | 1 - res/css/_components.scss | 1 - .../views/dialogs/_UnknownDeviceDialog.scss | 48 ----- src/CallHandler.js | 53 ++--- src/components/structures/RoomStatusBar.js | 131 +++++------- .../views/dialogs/UnknownDeviceDialog.js | 187 ------------------ src/cryptodevices.js | 123 ------------ 7 files changed, 63 insertions(+), 481 deletions(-) delete mode 100644 res/css/views/dialogs/_UnknownDeviceDialog.scss delete mode 100644 src/components/views/dialogs/UnknownDeviceDialog.js delete mode 100644 src/cryptodevices.js diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index ffc3b21181..381c7cfd70 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -9,7 +9,6 @@ src/components/structures/UploadBar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js src/components/views/dialogs/SetPasswordDialog.js -src/components/views/dialogs/UnknownDeviceDialog.js src/components/views/elements/AddressSelector.js src/components/views/elements/DirectorySearchBox.js src/components/views/elements/MemberEventListSummary.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 44c63b9df7..37a97d27e4 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -81,7 +81,6 @@ @import "./views/dialogs/_SlashCommandHelpDialog.scss"; @import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TermsDialog.scss"; -@import "./views/dialogs/_UnknownDeviceDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; diff --git a/res/css/views/dialogs/_UnknownDeviceDialog.scss b/res/css/views/dialogs/_UnknownDeviceDialog.scss deleted file mode 100644 index daa6bd2352..0000000000 --- a/res/css/views/dialogs/_UnknownDeviceDialog.scss +++ /dev/null @@ -1,48 +0,0 @@ -/* -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. -*/ - -.mx_UnknownDeviceDialog { - height: 100%; - display: flex; - flex-direction: column; -} - -.mx_UnknownDeviceDialog ul { - list-style: none; - padding: 0; -} -// userid -.mx_UnknownDeviceDialog p { - font-weight: bold; - font-size: $font-16px; -} - -.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons { - flex-direction: row !important; -} - -.mx_UnknownDeviceDialog .mx_Dialog_content { - margin-bottom: 24px; - overflow-y: scroll; -} - -.mx_UnknownDeviceDialog_deviceList > li { - padding: 4px; -} - -.mx_UnknownDeviceDialog_deviceList > li > * { - padding-bottom: 0; -} diff --git a/src/CallHandler.js b/src/CallHandler.js index c95ed16eb3..e3916c25d6 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -60,7 +60,6 @@ import * as sdk from './index'; import { _t } from './languageHandler'; import Matrix from 'matrix-js-sdk'; import dis from './dispatcher/dispatcher'; -import { showUnknownDeviceDialogForCalls } from './cryptodevices'; import WidgetUtils from './utils/WidgetUtils'; import WidgetEchoStore from './stores/WidgetEchoStore'; import SettingsStore, { SettingLevel } from './settings/SettingsStore'; @@ -134,47 +133,19 @@ function _reAttemptCall(call) { function _setCallListeners(call) { call.on("error", function(err) { console.error("Call error:", err); - if (err.code === 'unknown_devices') { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - Modal.createTrackedDialog('Call Failed', '', QuestionDialog, { - title: _t('Call Failed'), - description: _t( - "There are unknown sessions in this room: "+ - "if you proceed without verifying them, it will be "+ - "possible for someone to eavesdrop on your call.", - ), - button: _t('Review Sessions'), - onFinished: function(confirmed) { - if (confirmed) { - const room = MatrixClientPeg.get().getRoom(call.roomId); - showUnknownDeviceDialogForCalls( - MatrixClientPeg.get(), - room, - () => { - _reAttemptCall(call); - }, - call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"), - call.direction === 'outbound' ? _t("Call") : _t("Answer"), - ); - } - }, - }); - } else { - if ( - MatrixClientPeg.get().getTurnServers().length === 0 && - SettingsStore.getValue("fallbackICEServerAllowed") === null - ) { - _showICEFallbackPrompt(); - return; - } - - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { - title: _t('Call Failed'), - description: err.message, - }); + if ( + MatrixClientPeg.get().getTurnServers().length === 0 && + SettingsStore.getValue("fallbackICEServerAllowed") === null + ) { + _showICEFallbackPrompt(); + return; } + + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { + title: _t('Call Failed'), + description: err.message, + }); }); call.on("hangup", function() { _setCallState(undefined, call.roomId, "ended"); diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index ae628fd06a..dd4b9759d6 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -24,7 +24,6 @@ import { _t, _td } from '../../languageHandler'; import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import Resend from '../../Resend'; -import * as cryptodevices from '../../cryptodevices'; import dis from '../../dispatcher/dispatcher'; import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils'; @@ -126,13 +125,6 @@ export default createReactClass({ }); }, - _onSendWithoutVerifyingClick: function() { - cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => { - cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices); - Resend.resendUnsentEvents(this.props.room); - }); - }, - _onResendAllClick: function() { Resend.resendUnsentEvents(this.props.room); dis.dispatch({action: 'focus_composer'}); @@ -143,10 +135,6 @@ export default createReactClass({ dis.dispatch({action: 'focus_composer'}); }, - _onShowDevicesClick: function() { - cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room); - }, - _onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { if (room.roomId !== this.props.room.roomId) return; @@ -213,82 +201,65 @@ export default createReactClass({ if (!unsentMessages.length) return null; let title; - let content; - const hasUDE = unsentMessages.some((m) => { - return m.error && m.error.name === "UnknownDeviceError"; - }); - - if (hasUDE) { - title = _t("Message not sent due to unknown sessions being present"); - content = _t( - "Show sessions, send anyway or cancel.", + let consentError = null; + let resourceLimitError = null; + for (const m of unsentMessages) { + if (m.error && m.error.errcode === 'M_CONSENT_NOT_GIVEN') { + consentError = m.error; + break; + } else if (m.error && m.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { + resourceLimitError = m.error; + break; + } + } + if (consentError) { + title = _t( + "You can't send any messages until you review and agree to " + + "our terms and conditions.", {}, { - 'showSessionsText': (sub) => { sub }, - 'sendAnywayText': (sub) => { sub }, - 'cancelText': (sub) => { sub }, + 'consentLink': (sub) => + + { sub } + , }, ); + } else if (resourceLimitError) { + title = messageForResourceLimitError( + resourceLimitError.data.limit_type, + resourceLimitError.data.admin_contact, { + 'monthly_active_user': _td( + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " + + "Please contact your service administrator to continue using the service.", + ), + '': _td( + "Your message wasn't sent because this homeserver has exceeded a resource limit. " + + "Please contact your service administrator to continue using the service.", + ), + }); + } else if ( + unsentMessages.length === 1 && + unsentMessages[0].error && + unsentMessages[0].error.data && + unsentMessages[0].error.data.error + ) { + title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error; } else { - let consentError = null; - let resourceLimitError = null; - for (const m of unsentMessages) { - if (m.error && m.error.errcode === 'M_CONSENT_NOT_GIVEN') { - consentError = m.error; - break; - } else if (m.error && m.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { - resourceLimitError = m.error; - break; - } - } - if (consentError) { - title = _t( - "You can't send any messages until you review and agree to " + - "our terms and conditions.", - {}, - { - 'consentLink': (sub) => - - { sub } - , - }, - ); - } else if (resourceLimitError) { - title = messageForResourceLimitError( - resourceLimitError.data.limit_type, - resourceLimitError.data.admin_contact, { - 'monthly_active_user': _td( - "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " + - "Please contact your service administrator to continue using the service.", - ), - '': _td( - "Your message wasn't sent because this homeserver has exceeded a resource limit. " + - "Please contact your service administrator to continue using the service.", - ), - }); - } else if ( - unsentMessages.length === 1 && - unsentMessages[0].error && - unsentMessages[0].error.data && - unsentMessages[0].error.data.error - ) { - title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error; - } else { - title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length }); - } - content = _t("%(count)s Resend all or cancel all now. " + - "You can also select individual messages to resend or cancel.", - { count: unsentMessages.length }, - { - 'resendText': (sub) => - { sub }, - 'cancelText': (sub) => - { sub }, - }, - ); + title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length }); } + const content = _t("%(count)s Resend all or cancel all " + + "now. You can also select individual messages to resend or cancel.", + { count: unsentMessages.length }, + { + 'resendText': (sub) => + { sub }, + 'cancelText': (sub) => + { sub }, + }, + ); + return
diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js deleted file mode 100644 index 4cad13b047..0000000000 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ /dev/null @@ -1,187 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector 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. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import { _t } from '../../../languageHandler'; -import SettingsStore from "../../../settings/SettingsStore"; -import { markAllDevicesKnown } from '../../../cryptodevices'; - -function UserUnknownDeviceList(props) { - const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); - const {userId, userDevices} = props; - - const deviceListEntries = Object.keys(userDevices).map((deviceId) => -
  • , - ); - - return ( -
      - { deviceListEntries } -
    - ); -} - -UserUnknownDeviceList.propTypes = { - userId: PropTypes.string.isRequired, - - // map from deviceid -> deviceinfo - userDevices: PropTypes.object.isRequired, -}; - - -function UnknownDeviceList(props) { - const {devices} = props; - - const userListEntries = Object.keys(devices).map((userId) => -
  • -

    { userId }:

    - -
  • , - ); - - return
      { userListEntries }
    ; -} - -UnknownDeviceList.propTypes = { - // map from userid -> deviceid -> deviceinfo - devices: PropTypes.object.isRequired, -}; - - -export default createReactClass({ - displayName: 'UnknownDeviceDialog', - - propTypes: { - room: PropTypes.object.isRequired, - - // map from userid -> deviceid -> deviceinfo or null if devices are not yet loaded - devices: PropTypes.object, - - onFinished: PropTypes.func.isRequired, - - // Label for the button that marks all devices known and tries the send again - sendAnywayLabel: PropTypes.string.isRequired, - - // Label for the button that to send the event if you've verified all devices - sendLabel: PropTypes.string.isRequired, - - // function to retry the request once all devices are verified / known - onSend: PropTypes.func.isRequired, - }, - - componentDidMount: function() { - MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged); - }, - - componentWillUnmount: function() { - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener("deviceVerificationChanged", this._onDeviceVerificationChanged); - } - }, - - _onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) { - if (this.props.devices[userId] && this.props.devices[userId][deviceId]) { - // XXX: Mutating props :/ - this.props.devices[userId][deviceId] = deviceInfo; - this.forceUpdate(); - } - }, - - _onDismissClicked: function() { - this.props.onFinished(); - }, - - _onSendAnywayClicked: function() { - markAllDevicesKnown(MatrixClientPeg.get(), this.props.devices); - - this.props.onFinished(); - this.props.onSend(); - }, - - _onSendClicked: function() { - this.props.onFinished(); - this.props.onSend(); - }, - - render: function() { - if (this.props.devices === null) { - const Spinner = sdk.getComponent("elements.Spinner"); - return ; - } - - let warning; - if (SettingsStore.getValue("blacklistUnverifiedDevices", this.props.room.roomId)) { - warning = ( -

    - { _t("You are currently blacklisting unverified sessions; to send " + - "messages to these sessions you must verify them.") } -

    - ); - } else { - warning = ( -
    -

    - { _t("We recommend you go through the verification process " + - "for each session to confirm they belong to their legitimate owner, " + - "but you can resend the message without verifying if you prefer.") } -

    -
    - ); - } - - let haveUnknownDevices = false; - Object.keys(this.props.devices).forEach((userId) => { - Object.keys(this.props.devices[userId]).map((deviceId) => { - const device = this.props.devices[userId][deviceId]; - if (device.isUnverified() && !device.isKnown()) { - haveUnknownDevices = true; - } - }); - }); - const sendButtonOnClick = haveUnknownDevices ? this._onSendAnywayClicked : this._onSendClicked; - const sendButtonLabel = haveUnknownDevices ? this.props.sendAnywayLabel : this.props.sendAnywayLabel; - - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return ( - -
    -

    - { _t('"%(RoomName)s" contains sessions that you haven\'t seen before.', {RoomName: this.props.room.name}) } -

    - { warning } - { _t("Unknown sessions") }: - - -
    - -
    - ); - // XXX: do we want to give the user the option to enable blacklistUnverifiedDevices for this room (or globally) at this point? - // It feels like confused users will likely turn it on and then disappear in a cloud of UISIs... - }, -}); diff --git a/src/cryptodevices.js b/src/cryptodevices.js deleted file mode 100644 index 86b97364f9..0000000000 --- a/src/cryptodevices.js +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2017 New Vector 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. -*/ - -import Resend from './Resend'; -import * as sdk from './index'; -import dis from './dispatcher/dispatcher'; -import Modal from './Modal'; -import { _t } from './languageHandler'; - -/** - * Mark all given devices as 'known' - * - * @param {MatrixClient} matrixClient A MatrixClient - * @param {Object} devices Map from userid -> deviceid -> deviceinfo - */ -export function markAllDevicesKnown(matrixClient, devices) { - Object.keys(devices).forEach((userId) => { - Object.keys(devices[userId]).map((deviceId) => { - matrixClient.setDeviceKnown(userId, deviceId, true); - }); - }); -} - -/** - * Gets all crypto devices in a room that are marked neither known - * nor verified. - * - * @param {MatrixClient} matrixClient A MatrixClient - * @param {Room} room js-sdk room object representing the room - * @return {Promise} A promise which resolves to a map userId->deviceId->{@link - * module:crypto~DeviceInfo|DeviceInfo}. - */ -export async function getUnknownDevicesForRoom(matrixClient, room) { - const roomMembers = (await room.getEncryptionTargetMembers()).map((m) => { - return m.userId; - }); - const devices = await matrixClient.downloadKeys(roomMembers, false); - const unknownDevices = {}; - // This is all devices in this room, so find the unknown ones. - Object.keys(devices).forEach((userId) => { - Object.keys(devices[userId]).map((deviceId) => { - const device = devices[userId][deviceId]; - - if (device.isUnverified() && !device.isKnown()) { - if (unknownDevices[userId] === undefined) { - unknownDevices[userId] = {}; - } - unknownDevices[userId][deviceId] = device; - } - }); - }); - return unknownDevices; -} - -function focusComposer() { - dis.dispatch({action: 'focus_composer'}); -} - -/** - * Show the UnknownDeviceDialog for a given room. The dialog will inform the user - * that messages they sent to this room have not been sent due to unknown devices - * being present. - * - * @param {MatrixClient} matrixClient A MatrixClient - * @param {Room} room js-sdk room object representing the room - */ -export function showUnknownDeviceDialogForMessages(matrixClient, room) { - getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => { - const onSendClicked = () => { - Resend.resendUnsentEvents(room); - }; - - const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); - Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { - room: room, - devices: unknownDevices, - sendAnywayLabel: _t("Send anyway"), - sendLabel: _t("Send"), - onSend: onSendClicked, - onFinished: focusComposer, - }, 'mx_Dialog_unknownDevice'); - }); -} - -/** - * Show the UnknownDeviceDialog for a given room. The dialog will inform the user - * that a call they tried to place or answer in the room couldn't be placed or - * answered due to unknown devices being present. - * - * @param {MatrixClient} matrixClient A MatrixClient - * @param {Room} room js-sdk room object representing the room - * @param {func} sendAnyway Function called when the 'call anyway' or 'call' - * button is pressed. This should attempt to place or answer the call again. - * @param {string} sendAnywayLabel Label for the button displayed to retry the call - * when unknown devices are still present (eg. "Call Anyway") - * @param {string} sendLabel Label for the button displayed to retry the call - * after all devices have been verified (eg. "Call") - */ -export function showUnknownDeviceDialogForCalls(matrixClient, room, sendAnyway, sendAnywayLabel, sendLabel) { - getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => { - const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); - Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { - room: room, - devices: unknownDevices, - sendAnywayLabel: sendAnywayLabel, - sendLabel: sendLabel, - onSend: sendAnyway, - }, 'mx_Dialog_unknownDevice'); - }); -} From 8aaa7825efa4c8ec7b7b0a4dbfda19261aae2133 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 28 May 2020 17:12:19 +0100 Subject: [PATCH 5/7] Also kill off things that these kept behind Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .eslintignore.errorfiles | 1 - res/css/_components.scss | 2 - .../views/dialogs/_DeviceVerifyDialog.scss | 29 -- res/css/views/rooms/_MemberDeviceInfo.scss | 95 ----- src/CallHandler.js | 12 - .../views/dialogs/DeviceVerifyDialog.js | 373 ------------------ .../views/dialogs/KeyShareDialog.js | 178 --------- .../views/elements/DeviceVerifyButtons.js | 127 ------ .../views/rooms/MemberDeviceInfo.js | 59 --- src/i18n/strings/en_EN.json | 47 +-- 10 files changed, 5 insertions(+), 918 deletions(-) delete mode 100644 res/css/views/dialogs/_DeviceVerifyDialog.scss delete mode 100644 res/css/views/rooms/_MemberDeviceInfo.scss delete mode 100644 src/components/views/dialogs/DeviceVerifyDialog.js delete mode 100644 src/components/views/dialogs/KeyShareDialog.js delete mode 100644 src/components/views/elements/DeviceVerifyButtons.js delete mode 100644 src/components/views/rooms/MemberDeviceInfo.js diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 381c7cfd70..8362fcd2b4 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -22,7 +22,6 @@ src/components/views/room_settings/ColorSettings.js src/components/views/rooms/Autocomplete.js src/components/views/rooms/AuxPanel.js src/components/views/rooms/LinkPreviewWidget.js -src/components/views/rooms/MemberDeviceInfo.js src/components/views/rooms/MemberInfo.js src/components/views/rooms/MemberList.js src/components/views/rooms/RoomList.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 37a97d27e4..7bb159f00f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -61,7 +61,6 @@ @import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss"; -@import "./views/dialogs/_DeviceVerifyDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; @@ -166,7 +165,6 @@ @import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_LinkPreviewWidget.scss"; -@import "./views/rooms/_MemberDeviceInfo.scss"; @import "./views/rooms/_MemberInfo.scss"; @import "./views/rooms/_MemberList.scss"; @import "./views/rooms/_MessageComposer.scss"; diff --git a/res/css/views/dialogs/_DeviceVerifyDialog.scss b/res/css/views/dialogs/_DeviceVerifyDialog.scss deleted file mode 100644 index 1997e0c21d..0000000000 --- a/res/css/views/dialogs/_DeviceVerifyDialog.scss +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2019 New Vector 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. -*/ - -.mx_DeviceVerifyDialog_cryptoSection ul { - display: table; -} - -.mx_DeviceVerifyDialog_cryptoSection li { - display: table-row; -} - -.mx_DeviceVerifyDialog_cryptoSection label, -.mx_DeviceVerifyDialog_cryptoSection span { - display: table-cell; - padding-right: 1em; -} diff --git a/res/css/views/rooms/_MemberDeviceInfo.scss b/res/css/views/rooms/_MemberDeviceInfo.scss deleted file mode 100644 index 71b05a93fc..0000000000 --- a/res/css/views/rooms/_MemberDeviceInfo.scss +++ /dev/null @@ -1,95 +0,0 @@ -/* -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. -*/ - -.mx_MemberDeviceInfo { - display: flex; - padding-bottom: 10px; - align-items: flex-start; -} - -.mx_MemberDeviceInfo_icon { - margin-top: 4px; - width: 12px; - height: 12px; - mask-repeat: no-repeat; - mask-size: 100%; -} -.mx_MemberDeviceInfo_icon_blacklisted { - mask-image: url('$(res)/img/e2e/blacklisted.svg'); - background-color: $warning-color; -} -.mx_MemberDeviceInfo_icon_verified { - mask-image: url('$(res)/img/e2e/verified.svg'); - background-color: $accent-color; -} -.mx_MemberDeviceInfo_icon_unverified { - mask-image: url('$(res)/img/e2e/warning.svg'); - background-color: $warning-color; -} - -.mx_MemberDeviceInfo > .mx_DeviceVerifyButtons { - display: flex; - flex-direction: column; - flex: 0 1 auto; - align-items: stretch; -} - -.mx_MemberDeviceInfo_textButton { - @mixin mx_DialogButton_small; - margin: 2px; - flex: 1; -} - -.mx_MemberDeviceInfo_textButton:hover { - @mixin mx_DialogButton_hover; -} - -.mx_MemberDeviceInfo_deviceId { - word-break: break-word; - font-size: $font-13px; -} - -.mx_MemberDeviceInfo_deviceInfo { - margin: 0 5px 5px 8px; - flex: 1; -} - -/* "Unblacklist" is too long for a regular button: make it wider and - reduce the padding. */ -.mx_EncryptedEventDialog .mx_MemberDeviceInfo_blacklist, -.mx_EncryptedEventDialog .mx_MemberDeviceInfo_unblacklist { - padding-left: 1em; - padding-right: 1em; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified, -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified, -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted { - float: right; - padding-left: 1em; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified { - color: $e2e-verified-color; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified { - color: $e2e-unverified-color; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted { - color: $e2e-warning-color; -} diff --git a/src/CallHandler.js b/src/CallHandler.js index e3916c25d6..4414bce457 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -118,18 +118,6 @@ function pause(audioId) { } } -function _reAttemptCall(call) { - if (call.direction === 'outbound') { - dis.dispatch({ - action: 'place_call', - room_id: call.roomId, - type: call.type, - }); - } else { - call.answer(); - } -} - function _setCallListeners(call) { call.on("error", function(err) { console.error("Call error:", err); diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js deleted file mode 100644 index 5e02a085d8..0000000000 --- a/src/components/views/dialogs/DeviceVerifyDialog.js +++ /dev/null @@ -1,373 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2019 New Vector Ltd -Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> - -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 React from 'react'; -import PropTypes from 'prop-types'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import * as sdk from '../../../index'; -import * as FormattingUtils from '../../../utils/FormattingUtils'; -import { _t } from '../../../languageHandler'; -import {verificationMethods} from 'matrix-js-sdk/src/crypto'; -import {ensureDMExists} from "../../../createRoom"; -import dis from "../../../dispatcher/dispatcher"; -import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode"; -import VerificationQREmojiOptions from "../verification/VerificationQREmojiOptions"; - -const MODE_LEGACY = 'legacy'; -const MODE_SAS = 'sas'; - -const PHASE_START = 0; -const PHASE_WAIT_FOR_PARTNER_TO_ACCEPT = 1; -const PHASE_PICK_VERIFICATION_OPTION = 2; -const PHASE_SHOW_SAS = 3; -const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 4; -const PHASE_VERIFIED = 5; -const PHASE_CANCELLED = 6; - -export default class DeviceVerifyDialog extends React.Component { - static propTypes = { - userId: PropTypes.string.isRequired, - device: PropTypes.object.isRequired, - onFinished: PropTypes.func.isRequired, - }; - - constructor() { - super(); - this._verifier = null; - this._showSasEvent = null; - this._request = null; - this.state = { - phase: PHASE_START, - mode: MODE_SAS, - sasVerified: false, - }; - } - - componentWillUnmount() { - if (this._verifier) { - this._verifier.removeListener('show_sas', this._onVerifierShowSas); - this._verifier.cancel('User cancel'); - } - } - - _onSwitchToLegacyClick = () => { - if (this._verifier) { - this._verifier.removeListener('show_sas', this._onVerifierShowSas); - this._verifier.cancel('User cancel'); - this._verifier = null; - } - this.setState({mode: MODE_LEGACY}); - } - - _onSwitchToSasClick = () => { - this.setState({mode: MODE_SAS}); - } - - _onCancelClick = () => { - this.props.onFinished(false); - } - - _onUseSasClick = async () => { - try { - this._verifier = this._request.beginKeyVerification(verificationMethods.SAS); - this._verifier.on('show_sas', this._onVerifierShowSas); - // throws upon cancellation - await this._verifier.verify(); - this.setState({phase: PHASE_VERIFIED}); - this._verifier.removeListener('show_sas', this._onVerifierShowSas); - this._verifier = null; - } catch (e) { - console.log("Verification failed", e); - this.setState({ - phase: PHASE_CANCELLED, - }); - this._verifier = null; - this._request = null; - } - }; - - _onLegacyFinished = (confirm) => { - if (confirm) { - MatrixClientPeg.get().setDeviceVerified( - this.props.userId, this.props.device.deviceId, true, - ); - } - this.props.onFinished(confirm); - } - - _onSasRequestClick = async () => { - this.setState({ - phase: PHASE_WAIT_FOR_PARTNER_TO_ACCEPT, - }); - const client = MatrixClientPeg.get(); - const verifyingOwnDevice = this.props.userId === client.getUserId(); - try { - if (!verifyingOwnDevice) { - const roomId = await ensureDMExistsAndOpen(this.props.userId); - // throws upon cancellation before having started - const request = await client.requestVerificationDM( - this.props.userId, roomId, - ); - await request.waitFor(r => r.ready || r.started); - if (request.ready) { - this._verifier = request.beginKeyVerification(verificationMethods.SAS); - } else { - this._verifier = request.verifier; - } - } else { - this._request = await client.requestVerification(this.props.userId, [ - verificationMethods.SAS, - SHOW_QR_CODE_METHOD, - verificationMethods.RECIPROCATE_QR_CODE, - ]); - - await this._request.waitFor(r => r.ready || r.started); - this.setState({phase: PHASE_PICK_VERIFICATION_OPTION}); - } - - if (!this._verifier) return; - this._verifier.on('show_sas', this._onVerifierShowSas); - // throws upon cancellation - await this._verifier.verify(); - this.setState({phase: PHASE_VERIFIED}); - this._verifier.removeListener('show_sas', this._onVerifierShowSas); - this._verifier = null; - } catch (e) { - console.log("Verification failed", e); - this.setState({ - phase: PHASE_CANCELLED, - }); - this._verifier = null; - } - } - - _onSasMatchesClick = () => { - this._showSasEvent.confirm(); - this.setState({ - phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM, - }); - } - - _onVerifiedDoneClick = () => { - this.props.onFinished(true); - } - - _onVerifierShowSas = (e) => { - this._showSasEvent = e; - this.setState({ - phase: PHASE_SHOW_SAS, - }); - } - - _renderSasVerification() { - let body; - switch (this.state.phase) { - case PHASE_START: - body = this._renderVerificationPhaseStart(); - break; - case PHASE_WAIT_FOR_PARTNER_TO_ACCEPT: - body = this._renderVerificationPhaseWaitAccept(); - break; - case PHASE_PICK_VERIFICATION_OPTION: - body = this._renderVerificationPhasePick(); - break; - case PHASE_SHOW_SAS: - body = this._renderSasVerificationPhaseShowSas(); - break; - case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM: - body = this._renderSasVerificationPhaseWaitForPartnerToConfirm(); - break; - case PHASE_VERIFIED: - body = this._renderVerificationPhaseVerified(); - break; - case PHASE_CANCELLED: - body = this._renderVerificationPhaseCancelled(); - break; - } - - const BaseDialog = sdk.getComponent("dialogs.BaseDialog"); - return ( - - {body} - - ); - } - - _renderVerificationPhaseStart() { - const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return ( -
    - - {_t("Use Legacy Verification (for older clients)")} - -

    - { _t("Verify by comparing a short text string.") } -

    -

    - {_t("To be secure, do this in person or use a trusted way to communicate.")} -

    - -
    - ); - } - - _renderVerificationPhaseWaitAccept() { - const Spinner = sdk.getComponent("views.elements.Spinner"); - const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); - - return ( -
    - -

    {_t("Waiting for partner to accept...")}

    -

    {_t( - "Nothing appearing? Not all clients support interactive verification yet. " + - ".", - {}, {button: sub => - {sub} - }, - )}

    -
    - ); - } - - _renderVerificationPhasePick() { - return ; - } - - _renderSasVerificationPhaseShowSas() { - const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); - return ; - } - - _renderSasVerificationPhaseWaitForPartnerToConfirm() { - const Spinner = sdk.getComponent('views.elements.Spinner'); - return
    - -

    {_t( - "Waiting for %(userId)s to confirm...", {userId: this.props.userId}, - )}

    -
    ; - } - - _renderVerificationPhaseVerified() { - const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete'); - return ; - } - - _renderVerificationPhaseCancelled() { - const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled'); - return ; - } - - _renderLegacyVerification() { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); - - let text; - if (MatrixClientPeg.get().getUserId() === this.props.userId) { - text = _t("To verify that this session can be trusted, please check that the key you see " + - "in User Settings on that device matches the key below:"); - } else { - text = _t("To verify that this session can be trusted, please contact its owner using some other " + - "means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings " + - "for this session matches the key below:"); - } - - const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint()); - const body = ( -
    - - {_t("Use two-way text verification")} - -

    - { text } -

    -
    -
      -
    • { this.props.device.getDisplayName() }
    • -
    • { this.props.device.deviceId }
    • -
    • { key }
    • -
    -
    -

    - { _t("If it matches, press the verify button below. " + - "If it doesn't, then someone else is intercepting this session " + - "and you probably want to press the blacklist button instead.") } -

    -
    - ); - - return ( - - ); - } - - render() { - if (this.state.mode === MODE_LEGACY) { - return this._renderLegacyVerification(); - } else { - return
    - {this._renderSasVerification()} -
    ; - } - } -} - -async function ensureDMExistsAndOpen(userId) { - const roomId = await ensureDMExists(MatrixClientPeg.get(), userId); - // don't use andView and spinner in createRoom, together, they cause this dialog to close and reopen, - // we causes us to loose the verifier and restart, and we end up having two verification requests - dis.dispatch({ - action: 'view_room', - room_id: roomId, - should_peek: false, - }); - return roomId; -} diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js deleted file mode 100644 index 8ef36bb59f..0000000000 --- a/src/components/views/dialogs/KeyShareDialog.js +++ /dev/null @@ -1,178 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import Modal from '../../../Modal'; -import React from 'react'; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; - -import { _t, _td } from '../../../languageHandler'; - -// TODO: We can remove this once cross-signing is the only way. -// https://github.com/vector-im/riot-web/issues/11908 - -/** - * Dialog which asks the user whether they want to share their keys with - * an unverified device. - * - * onFinished is called with `true` if the key should be shared, `false` if it - * should not, and `undefined` if the dialog is cancelled. (In other words: - * truthy: do the key share. falsy: don't share the keys). - */ -export default createReactClass({ - propTypes: { - matrixClient: PropTypes.object.isRequired, - userId: PropTypes.string.isRequired, - deviceId: PropTypes.string.isRequired, - onFinished: PropTypes.func.isRequired, - }, - - getInitialState: function() { - return { - deviceInfo: null, - wasNewDevice: false, - }; - }, - - componentDidMount: function() { - this._unmounted = false; - const userId = this.props.userId; - const deviceId = this.props.deviceId; - - // give the client a chance to refresh the device list - this.props.matrixClient.downloadKeys([userId], false).then((r) => { - if (this._unmounted) { return; } - - const deviceInfo = r[userId][deviceId]; - - if (!deviceInfo) { - console.warn(`No details found for session ${userId}:${deviceId}`); - - this.props.onFinished(false); - return; - } - - const wasNewDevice = !deviceInfo.isKnown(); - - this.setState({ - deviceInfo: deviceInfo, - wasNewDevice: wasNewDevice, - }); - - // if the device was new before, it's not any more. - if (wasNewDevice) { - this.props.matrixClient.setDeviceKnown( - userId, - deviceId, - true, - ); - } - }); - }, - - componentWillUnmount: function() { - this._unmounted = true; - }, - - - _onVerifyClicked: function() { - const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); - - console.log("KeyShareDialog: Starting verify dialog"); - Modal.createTrackedDialog('Key Share', 'Starting dialog', DeviceVerifyDialog, { - userId: this.props.userId, - device: this.state.deviceInfo, - onFinished: (verified) => { - if (verified) { - // can automatically share the keys now. - this.props.onFinished(true); - } - }, - }, null, /* priority = */ false, /* static = */ true); - }, - - _onShareClicked: function() { - console.log("KeyShareDialog: User clicked 'share'"); - this.props.onFinished(true); - }, - - _onIgnoreClicked: function() { - console.log("KeyShareDialog: User clicked 'ignore'"); - this.props.onFinished(false); - }, - - _renderContent: function() { - const displayName = this.state.deviceInfo.getDisplayName() || - this.state.deviceInfo.deviceId; - - let text; - if (this.state.wasNewDevice) { - text = _td("You added a new session '%(displayName)s', which is" - + " requesting encryption keys."); - } else { - text = _td("Your unverified session '%(displayName)s' is requesting" - + " encryption keys."); - } - text = _t(text, {displayName: displayName}); - - return ( -
    -

    { text }

    - -
    - - - -
    -
    - ); - }, - - render: function() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const Spinner = sdk.getComponent('views.elements.Spinner'); - - let content; - - if (this.state.deviceInfo) { - content = this._renderContent(); - } else { - content = ( -
    -

    { _t('Loading session info...') }

    - -
    - ); - } - - return ( - - { content } - - ); - }, -}); diff --git a/src/components/views/elements/DeviceVerifyButtons.js b/src/components/views/elements/DeviceVerifyButtons.js deleted file mode 100644 index 7328d50328..0000000000 --- a/src/components/views/elements/DeviceVerifyButtons.js +++ /dev/null @@ -1,127 +0,0 @@ -/* -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. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import * as sdk from '../../../index'; -import Modal from '../../../Modal'; -import { _t } from '../../../languageHandler'; - -// XXX: This component is *not* cross-signing aware. Once everything is -// cross-signing, this component should just go away. -export default createReactClass({ - displayName: 'DeviceVerifyButtons', - - propTypes: { - userId: PropTypes.string.isRequired, - device: PropTypes.object.isRequired, - }, - - getInitialState: function() { - return { - device: this.props.device, - }; - }, - - componentDidMount: function() { - const cli = MatrixClientPeg.get(); - cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - }, - - componentWillUnmount: function() { - const cli = MatrixClientPeg.get(); - if (cli) { - cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); - } - }, - - onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) { - if (userId === this.props.userId && deviceId === this.props.device.deviceId) { - this.setState({ device: deviceInfo }); - } - }, - - onVerifyClick: function() { - const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); - Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { - userId: this.props.userId, - device: this.state.device, - }, null, /* priority = */ false, /* static = */ true); - }, - - onUnverifyClick: function() { - MatrixClientPeg.get().setDeviceVerified( - this.props.userId, this.state.device.deviceId, false, - ); - }, - - onBlacklistClick: function() { - MatrixClientPeg.get().setDeviceBlocked( - this.props.userId, this.state.device.deviceId, true, - ); - }, - - onUnblacklistClick: function() { - MatrixClientPeg.get().setDeviceBlocked( - this.props.userId, this.state.device.deviceId, false, - ); - }, - - render: function() { - let blacklistButton = null; let verifyButton = null; - - if (this.state.device.isBlocked()) { - blacklistButton = ( - - ); - } else { - blacklistButton = ( - - ); - } - - if (this.state.device.isVerified()) { - verifyButton = ( - - ); - } else { - verifyButton = ( - - ); - } - - return ( -
    - { verifyButton } - { blacklistButton } -
    - ); - }, -}); diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js deleted file mode 100644 index dbf0ae0c62..0000000000 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ /dev/null @@ -1,59 +0,0 @@ -/* -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. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import classNames from 'classnames'; - -export default class MemberDeviceInfo extends React.Component { - render() { - const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons'); - // XXX: These checks are not cross-signing aware but this component is only used - // from the old, pre-cross-signing memberinfopanel - const iconClasses = classNames({ - mx_MemberDeviceInfo_icon: true, - mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(), - mx_MemberDeviceInfo_icon_verified: this.props.device.isVerified(), - mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(), - }); - const indicator = (
    ); - const deviceName = (this.props.device.ambiguous || this.props.showDeviceId) ? - (this.props.device.getDisplayName() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" : - this.props.device.getDisplayName(); - - // add the deviceId as a titletext to help with debugging - return ( -
    - { indicator } -
    -
    - { deviceName } -
    -
    - -
    - ); - } -} - -MemberDeviceInfo.displayName = 'MemberDeviceInfo'; -MemberDeviceInfo.propTypes = { - userId: PropTypes.string.isRequired, - device: PropTypes.object.isRequired, -}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 351ebd3be0..d0091935c8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -35,12 +35,6 @@ "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", "Dismiss": "Dismiss", "Call Failed": "Call Failed", - "There are unknown sessions in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown sessions in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", - "Review Sessions": "Review Sessions", - "Call Anyway": "Call Anyway", - "Answer Anyway": "Answer Anyway", - "Call": "Call", - "Answer": "Answer", "Call Timeout": "Call Timeout", "The remote side failed to pick up": "The remote side failed to pick up", "Call failed due to misconfigured server": "Call failed due to misconfigured server", @@ -75,8 +69,6 @@ "Enter passphrase": "Enter passphrase", "Cancel": "Cancel", "Setting up keys": "Setting up keys", - "Send anyway": "Send anyway", - "Send": "Send", "Sun": "Sun", "Mon": "Mon", "Tue": "Tue", @@ -1009,7 +1001,6 @@ "Invite only": "Invite only", "Scroll to most recent messages": "Scroll to most recent messages", "Close preview": "Close preview", - "device id: ": "device id: ", "and %(count)s others...|other": "and %(count)s others...", "and %(count)s others...|one": "and one other...", "Invite to this room": "Invite to this room", @@ -1419,10 +1410,6 @@ "Popout widget": "Popout widget", "More options": "More options", "Create new room": "Create new room", - "Unblacklist": "Unblacklist", - "Blacklist": "Blacklist", - "Unverify": "Unverify", - "Verify...": "Verify...", "Join": "Join", "No results": "No results", "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.", @@ -1596,22 +1583,8 @@ "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", - "Verify session": "Verify session", - "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", - "Verify by comparing a short text string.": "Verify by comparing a short text string.", - "Begin Verifying": "Begin Verifying", - "Waiting for partner to accept...": "Waiting for partner to accept...", - "Nothing appearing? Not all clients support interactive verification yet. .": "Nothing appearing? Not all clients support interactive verification yet. .", - "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...", - "To verify that this session can be trusted, please check that the key you see in User Settings on that device matches the key below:": "To verify that this session can be trusted, please check that the key you see in User Settings on that device matches the key below:", - "To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:": "To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:", - "Use two-way text verification": "Use two-way text verification", - "Session name": "Session name", - "Session ID": "Session ID", - "Session key": "Session key", - "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this session and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this session and you probably want to press the blacklist button instead.", - "I verify that the keys match": "I verify that the keys match", "Back": "Back", + "Send": "Send", "Send Custom Event": "Send Custom Event", "You must specify an event type!": "You must specify an event type!", "Event sent!": "Event sent!", @@ -1653,13 +1626,6 @@ "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", - "You added a new session '%(displayName)s', which is requesting encryption keys.": "You added a new session '%(displayName)s', which is requesting encryption keys.", - "Your unverified session '%(displayName)s' is requesting encryption keys.": "Your unverified session '%(displayName)s' is requesting encryption keys.", - "Start verification": "Start verification", - "Share without verifying": "Share without verifying", - "Ignore request": "Ignore request", - "Loading session info...": "Loading session info...", - "Encryption key request": "Encryption key request", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", @@ -1682,7 +1648,11 @@ "Are you sure you want to sign out?": "Are you sure you want to sign out?", "Confirm by comparing the following with the User Settings in your other session:": "Confirm by comparing the following with the User Settings in your other session:", "Confirm this user's session by comparing the following with their User Settings:": "Confirm this user's session by comparing the following with their User Settings:", + "Session name": "Session name", + "Session ID": "Session ID", + "Session key": "Session key", "If they don't match, the security of your communication may be compromised.": "If they don't match, the security of your communication may be compromised.", + "Verify session": "Verify session", "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", "Message edits": "Message edits", "Your account is not secure": "Your account is not secure", @@ -1768,11 +1738,6 @@ "Summary": "Summary", "Document": "Document", "Next": "Next", - "You are currently blacklisting unverified sessions; to send messages to these sessions you must verify them.": "You are currently blacklisting unverified sessions; to send messages to these sessions you must verify them.", - "We recommend you go through the verification process for each session to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each session to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", - "Room contains unknown sessions": "Room contains unknown sessions", - "\"%(RoomName)s\" contains sessions that you haven't seen before.": "\"%(RoomName)s\" contains sessions that you haven't seen before.", - "Unknown sessions": "Unknown sessions", "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", "Upload files": "Upload files", "Upload all": "Upload all", @@ -2035,8 +2000,6 @@ "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", "Explore rooms": "Explore rooms", - "Message not sent due to unknown sessions being present": "Message not sent due to unknown sessions being present", - "Show sessions, send anyway or cancel.": "Show sessions, send anyway or cancel.", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", From 8087b521e69bd5773ab201f53815d942ab0bb249 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 29 May 2020 21:42:33 +0100 Subject: [PATCH 6/7] Autocomplete: use scrollIntoView for auto-scroll instead of broken manual scrollTop calculation Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/Components.tsx | 22 +++++++++++++----- src/components/views/rooms/Autocomplete.tsx | 25 ++++++++++----------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx index 19a7a969d6..3b4469321d 100644 --- a/src/autocomplete/Components.tsx +++ b/src/autocomplete/Components.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {createRef} from 'react'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted @@ -30,7 +30,11 @@ interface ITextualCompletionProps { className?: string; } -export class TextualCompletion extends React.PureComponent { +export abstract class Completion extends React.PureComponent { + nodeRef = createRef(); +} + +export class TextualCompletion extends Completion { render() { const { title, @@ -40,7 +44,11 @@ export class TextualCompletion extends React.PureComponent +
    { title } { subtitle } { description } @@ -57,7 +65,7 @@ interface IPillCompletionProps { className?: string; } -export class PillCompletion extends React.PureComponent { +export class PillCompletion extends Completion { render() { const { title, @@ -68,7 +76,11 @@ export class PillCompletion extends React.PureComponent { ...restProps } = this.props; return ( -
    +
    { initialComponent } { title } { subtitle } diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 975c8e84a5..40f585a5b8 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -15,8 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import ReactDOM from 'react-dom'; +import React, {createRef} from 'react'; import classNames from 'classnames'; import flatMap from 'lodash/flatMap'; import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter'; @@ -24,6 +23,7 @@ import {Room} from 'matrix-js-sdk/src/models/room'; import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; +import { Completion } from '../../../autocomplete/Components'; const COMPOSER_SELECTED = 0; @@ -54,7 +54,7 @@ export default class Autocomplete extends React.PureComponent { autocompleter: Autocompleter; queryRequested: string; debounceCompletionsRequest: NodeJS.Timeout; - containerRef: React.RefObject; + private containerRef = createRef(); constructor(props) { super(props); @@ -78,8 +78,6 @@ export default class Autocomplete extends React.PureComponent { forceComplete: false, }; - - this.containerRef = React.createRef(); } componentDidMount() { @@ -256,14 +254,15 @@ export default class Autocomplete extends React.PureComponent { componentDidUpdate(prevProps: IProps) { this.applyNewProps(prevProps.query, prevProps.room); // this is the selected completion, so scroll it into view if needed - const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`]; - if (selectedCompletion && this.containerRef.current) { - const domNode = ReactDOM.findDOMNode(selectedCompletion); - const offsetTop = domNode && (domNode as HTMLElement).offsetTop; - if (offsetTop > this.containerRef.current.scrollTop + this.containerRef.current.offsetHeight || - offsetTop < this.containerRef.current.scrollTop) { - this.containerRef.current.scrollTop = offsetTop - this.containerRef.current.offsetTop; - } + const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as Completion; + + if (selectedCompletion && selectedCompletion.nodeRef.current) { + selectedCompletion.nodeRef.current.scrollIntoView({ + behavior: "auto", + block: "nearest", + }); + } else { + this.containerRef.current.scrollTo({ top: 0 }); } } From bc83984a626413443b326cf079bf135a62993d06 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 30 May 2020 13:30:59 +0100 Subject: [PATCH 7/7] tidy up the ref to ref with a forwardRef and initialComponent signature Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/CommunityProvider.tsx | 7 +- src/autocomplete/Components.tsx | 91 ++++++++------------- src/autocomplete/EmojiProvider.tsx | 4 +- src/autocomplete/NotifProvider.tsx | 4 +- src/autocomplete/RoomProvider.tsx | 4 +- src/autocomplete/UserProvider.tsx | 7 +- src/components/views/rooms/Autocomplete.tsx | 7 +- 7 files changed, 51 insertions(+), 73 deletions(-) diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx index 3edb1ff81d..d7eac59f91 100644 --- a/src/autocomplete/CommunityProvider.tsx +++ b/src/autocomplete/CommunityProvider.tsx @@ -90,11 +90,12 @@ export default class CommunityProvider extends AutocompleteProvider { type: "community", href: makeGroupPermalink(groupId), component: ( - - } title={name} description={groupId} /> + ), range, })) diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx index 3b4469321d..0ee0088f02 100644 --- a/src/autocomplete/Components.tsx +++ b/src/autocomplete/Components.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {createRef} from 'react'; +import React, {forwardRef} from 'react'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted @@ -30,62 +30,37 @@ interface ITextualCompletionProps { className?: string; } -export abstract class Completion extends React.PureComponent { - nodeRef = createRef(); +export const TextualCompletion = forwardRef((props, ref) => { + const {title, subtitle, description, className, ...restProps} = props; + return ( +
    + { title } + { subtitle } + { description } +
    + ); +}); + +interface IPillCompletionProps extends ITextualCompletionProps { + children?: React.ReactNode, } -export class TextualCompletion extends Completion { - render() { - const { - title, - subtitle, - description, - className, - ...restProps - } = this.props; - return ( -
    - { title } - { subtitle } - { description } -
    - ); - } -} - -interface IPillCompletionProps { - title?: string; - subtitle?: string; - description?: string; - initialComponent?: React.ReactNode, - className?: string; -} - -export class PillCompletion extends Completion { - render() { - const { - title, - subtitle, - description, - initialComponent, - className, - ...restProps - } = this.props; - return ( -
    - { initialComponent } - { title } - { subtitle } - { description } -
    - ); - } -} +export const PillCompletion = forwardRef((props, ref) => { + const {title, subtitle, description, className, children, ...restProps} = props; + return ( +
    + { children } + { title } + { subtitle } + { description } +
    + ); +}); diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 3a3cec779e..e1e02fcf87 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -121,9 +121,9 @@ export default class EmojiProvider extends AutocompleteProvider { return { completion: unicode, component: ( - { unicode } - } /> + ), range, }; diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx index b217612b0e..ef1823c0ca 100644 --- a/src/autocomplete/NotifProvider.tsx +++ b/src/autocomplete/NotifProvider.tsx @@ -48,7 +48,9 @@ export default class NotifProvider extends AutocompleteProvider { type: "at-room", suffix: ' ', component: ( - } title="@room" description={_t("Notify the whole room")} /> + + + ), range, }]; diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 01e770407c..0d8aac4218 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -103,7 +103,9 @@ export default class RoomProvider extends AutocompleteProvider { suffix: ' ', href: makeRoomPermalink(room.displayedAlias), component: ( - } title={room.room.name} description={room.displayedAlias} /> + + + ), range, }; diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 1680eb5d54..eeb6c7a522 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -125,10 +125,9 @@ export default class UserProvider extends AutocompleteProvider { suffix: (selection.beginning && range.start === 0) ? ': ' : ' ', href: makeUserPermalink(user.userId), component: ( - } - title={displayName} - description={user.userId} /> + + + ), range, }; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 40f585a5b8..63f31c07b9 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -23,7 +23,6 @@ import {Room} from 'matrix-js-sdk/src/models/room'; import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; -import { Completion } from '../../../autocomplete/Components'; const COMPOSER_SELECTED = 0; @@ -254,10 +253,10 @@ export default class Autocomplete extends React.PureComponent { componentDidUpdate(prevProps: IProps) { this.applyNewProps(prevProps.query, prevProps.room); // this is the selected completion, so scroll it into view if needed - const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as Completion; + const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as HTMLElement; - if (selectedCompletion && selectedCompletion.nodeRef.current) { - selectedCompletion.nodeRef.current.scrollIntoView({ + if (selectedCompletion) { + selectedCompletion.scrollIntoView({ behavior: "auto", block: "nearest", });