From 724cff6a2e889aa62b45790a839408476ea436c7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 30 Jan 2020 16:55:43 +0000 Subject: [PATCH 01/14] Update QR code rendering to support VerificationRequests Makes for building the QR code easier and more common. --- .../elements/crypto/VerificationQRCode.js | 73 ++++++++++++++++++- .../views/right_panel/VerificationPanel.js | 38 +++++----- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index 630a06a07c..f9fea2dc78 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -19,6 +19,9 @@ import PropTypes from "prop-types"; import {replaceableComponent} from "../../../../utils/replaceableComponent"; import * as qs from "qs"; import QRCode from "qrcode-react"; +import {MatrixClientPeg} from "../../../../MatrixClientPeg"; +import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import {ToDeviceChannel} from "matrix-js-sdk/src/crypto/verification/request/ToDeviceChannel"; @replaceableComponent("views.elements.crypto.VerificationQRCode") export default class VerificationQRCode extends React.PureComponent { @@ -31,13 +34,81 @@ export default class VerificationQRCode extends React.PureComponent { // User verification use case only secret: PropTypes.string, otherUserKey: PropTypes.string, // Base64 key being verified - requestEventId: PropTypes.string, + otherUserDeviceKey: PropTypes.string, // Base64 key of the other user's device (optional) + requestEventId: PropTypes.string, // for DM verification only }; static defaultProps = { action: "verify", }; + static async getPropsForRequest(verificationRequest: VerificationRequest) { + const cli = MatrixClientPeg.get(); + const myUserId = cli.getUserId(); + const otherUserId = verificationRequest.otherUserId; + const myDeviceId = cli.getDeviceId(); + const otherDevice = verificationRequest.estimatedTargetDevice; + const otherDeviceId = otherDevice ? otherDevice.deviceId : null; + + const qrProps = { + secret: verificationRequest.encodedSharedSecret, + keyholderUserId: myUserId, + action: "verify", + keys: [], // array of pairs: keyId, base64Key + otherUserKey: "", // base64key + otherUserDeviceKey: "", // base64key + requestEventId: "", // we figure this out in a moment + }; + + const requestEvent = verificationRequest.requestEvent; + qrProps.requestEventId = requestEvent.getId() + ? requestEvent.getId() + : ToDeviceChannel.getTransactionId(requestEvent); + + // Populate the keys we need depending on which direction and users are involved in the verification. + if (myUserId === otherUserId) { + if (!otherDeviceId) { + // New -> Existing session QR code + qrProps.otherUserDeviceKey = null; + } else { + // Existing -> New session QR code + const myDevices = (await cli.getStoredDevicesForUser(myUserId)) || []; + const device = myDevices.find(d => d.deviceId === otherDeviceId); + if (device) qrProps.otherUserDeviceKey = device.getFingerprint(); + } + + // Either direction shares these next few props + + const xsignInfo = cli.getStoredCrossSigningForUser(myUserId); + qrProps.otherUserKey = xsignInfo.getId("master"); + + qrProps.keys = [ + [myDeviceId, cli.getDeviceEd25519Key()], + [xsignInfo.getId("master"), xsignInfo.getId("master")], + ]; + } else { + // Doesn't matter which direction the verification is, we always show the same QR code + // for not-ourself verification. + const myXsignInfo = cli.getStoredCrossSigningForUser(myUserId); + const otherXsignInfo = cli.getStoredCrossSigningForUser(otherUserId); + const otherDevices = (await cli.getStoredDevicesForUser(otherUserId)) || []; + const otherDevice = otherDevices.find(d => d.deviceId === otherDeviceId); + + qrProps.keys = [ + [myDeviceId, cli.getDeviceEd25519Key()], + [myXsignInfo.getId("master"), myXsignInfo.getId("master")], + ]; + qrProps.otherUserKey = otherXsignInfo.getId("master"); + if (otherDevice) qrProps.otherUserDeviceKey = otherDevice.getFingerprint(); + } + + return qrProps; + } + + constructor(props) { + super(props); + } + render() { const query = { request: this.props.requestEventId, diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index 3527747a66..ad1aaf598c 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -20,7 +20,6 @@ import PropTypes from "prop-types"; import * as sdk from '../../../index'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import VerificationQRCode from "../elements/crypto/VerificationQRCode"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {_t} from "../../../languageHandler"; import E2EIcon from "../rooms/E2EIcon"; import { @@ -29,7 +28,7 @@ import { PHASE_READY, PHASE_DONE, PHASE_STARTED, - PHASE_CANCELLED, + PHASE_CANCELLED, VerificationRequest, } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import Spinner from "../elements/Spinner"; @@ -50,12 +49,24 @@ export default class VerificationPanel extends React.PureComponent { constructor(props) { super(props); - this.state = {}; + this.state = { + qrCodeProps: null, // generated by the VerificationQRCode component itself + }; this._hasVerifier = false; + this._generateQRCodeProps(props.request); + } + + async _generateQRCodeProps(verificationRequest: VerificationRequest) { + try { + this.setState({qrCodeProps: await VerificationQRCode.getPropsForRequest(verificationRequest)}); + } catch (e) { + console.error(e); + // Do nothing - we won't render a QR code. + } } renderQRPhase(pending) { - const {member, request} = this.props; + const {member} = this.props; const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let button; @@ -69,10 +80,7 @@ export default class VerificationPanel extends React.PureComponent { ); } - const cli = MatrixClientPeg.get(); - const crossSigningInfo = cli.getStoredCrossSigningForUser(request.otherUserId); - if (!crossSigningInfo || !request.requestEvent || !request.requestEvent.getId()) { - // for whatever reason we can't generate a QR code, offer only SAS Verification + if (!this.state.qrCodeProps) { return

Verify by emoji

{_t("Verify by comparing unique emoji.")}

@@ -81,12 +89,6 @@ export default class VerificationPanel extends React.PureComponent {
; } - const myKeyId = cli.getCrossSigningId(); - const qrCodeKeys = [ - [cli.getDeviceId(), cli.getDeviceEd25519Key()], - [myKeyId, myKeyId], - ]; - // TODO: add way to open camera to scan a QR code return
@@ -96,13 +98,7 @@ export default class VerificationPanel extends React.PureComponent { })}

- +
From b92fe594369c3dffad51d2a20af188d222897f95 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 4 Feb 2020 00:01:58 +0000 Subject: [PATCH 02/14] Improve event indexing status strings for translation The strings used for the count of rooms was hard to translate, so this adds a bit more context. --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 6 ++++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index b98fecf22f..5ae90b694e 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -144,8 +144,10 @@ export default class ManageEventIndexDialog extends React.Component {
{_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
{_t("Indexed messages:")} {formatCountLong(this.state.eventCount)}
- {_t("Number of rooms:")} {formatCountLong(this.state.crawlingRoomsCount)} {_t("of ")} - {formatCountLong(this.state.roomCount)}
+ {_t("Indexed rooms:")} {_t("%(crawlingRooms)s out of %(totalRooms)s", { + crawlingRooms: formatCountLong(this.state.crawlingRoomsCount), + totalRooms: formatCountLong(this.state.roomCount), + })}
{crawlerState}
Date: Mon, 3 Feb 2020 17:59:13 +0100 Subject: [PATCH 03/14] make a static dialog close again if background is clicked --- src/Modal.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Modal.js b/src/Modal.js index b6215b2b5a..54e56edd72 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -217,9 +217,13 @@ class ModalManager { } closeAll() { - const modalsToClose = [...this._modals, this._priorityModal]; + const modalsToClose = this._modals.slice(); this._modals = []; - this._priorityModal = null; + + if (this._priorityModal) { + modalsToClose.push(this._priorityModal); + this._priorityModal = null; + } if (this._staticModal && modalsToClose.length === 0) { modalsToClose.push(this._staticModal); From b522d785330550404fc6e12f4f9292eacc68f0e4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 5 Feb 2020 12:43:39 +0000 Subject: [PATCH 04/14] Let pointer events fall through to scroll button This makes it easier to click the entire visible area of the scroll button, including the green circle at the top. --- res/css/views/rooms/_TopUnreadMessagesBar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.scss b/res/css/views/rooms/_TopUnreadMessagesBar.scss index 505af9691d..a3916f321a 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.scss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.scss @@ -32,9 +32,9 @@ limitations under the License. width: 4px; height: 4px; border-radius: 16px; - overflow: hidden; background-color: $secondary-accent-color; border: 6px solid $accent-color; + pointer-events: none; } .mx_TopUnreadMessagesBar_scrollUp { From 7e07a42dc14594e640598ae4613e87e61b9e3155 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 13:07:13 +0100 Subject: [PATCH 05/14] resolve finished promise when closing dialog by clicking background ... by calling the same close method as otherwise and not have a special path that just calls the onFinished callback. This will also not close all the dialogs anymore, but that sort of seems like the intented behaviour? --- src/Modal.js | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/Modal.js b/src/Modal.js index 54e56edd72..33c3140ff1 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -47,7 +47,7 @@ class ModalManager { } */ ]; - this.closeAll = this.closeAll.bind(this); + this.onBackgroundClick = this.onBackgroundClick.bind(this); } hasDialogs() { @@ -124,6 +124,7 @@ class ModalManager { ); modal.onFinished = props ? props.onFinished : null; modal.className = className; + modal.close = closeDialog; return {modal, closeDialog, onFinishedProm}; } @@ -216,28 +217,16 @@ class ModalManager { }; } - closeAll() { - const modalsToClose = this._modals.slice(); - this._modals = []; - - if (this._priorityModal) { - modalsToClose.push(this._priorityModal); - this._priorityModal = null; + onBackgroundClick() { + const modal = this._getCurrentModal(); + if (!modal) { + return; } + modal.close(); + } - if (this._staticModal && modalsToClose.length === 0) { - modalsToClose.push(this._staticModal); - this._staticModal = null; - } - - for (let i = 0; i < modalsToClose.length; i++) { - const m = modalsToClose[i]; - if (m && m.onFinished) { - m.onFinished(false); - } - } - - this._reRender(); + _getCurrentModal() { + return this._priorityModal ? this._priorityModal : (this._modals[0] || this._staticModal); } _reRender() { @@ -268,7 +257,7 @@ class ModalManager {
{ this._staticModal.elem }
-
+
); @@ -278,8 +267,8 @@ class ModalManager { ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer()); } - const modal = this._priorityModal ? this._priorityModal : this._modals[0]; - if (modal) { + const modal = this._getCurrentModal(); + if (modal !== this._staticModal) { const classes = "mx_Dialog_wrapper " + (this._staticModal ? "mx_Dialog_wrapperWithStaticUnder " : '') + (modal.className ? modal.className : ''); @@ -289,7 +278,7 @@ class ModalManager {
{modal.elem}
-
+
); From c44ebef06f3298e5abc34cb453a27ddf7ae26287 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 13:10:06 +0100 Subject: [PATCH 06/14] add onBeforeClose option to Modal so we can throw up another "are you sure" dialog in the cases we want to do so. This also passes a reason so we can only do so for ways of dismissing (like backgroundClick) that are easy to do by accident. --- src/Modal.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Modal.js b/src/Modal.js index 33c3140ff1..2e5624aa67 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -106,7 +106,7 @@ class ModalManager { return this.appendDialogAsync(...rest); } - _buildModal(prom, props, className) { + _buildModal(prom, props, className, options) { const modal = {}; // never call this from onFinished() otherwise it will loop @@ -124,14 +124,27 @@ class ModalManager { ); modal.onFinished = props ? props.onFinished : null; modal.className = className; + modal.onBeforeClose = options.onBeforeClose; + modal.beforeClosePromise = null; modal.close = closeDialog; + modal.closeReason = null; return {modal, closeDialog, onFinishedProm}; } _getCloseFn(modal, props) { const deferred = defer(); - return [(...args) => { + return [async (...args) => { + if (modal.beforeClosePromise) { + await modal.beforeClosePromise; + } else if (modal.onBeforeClose) { + modal.beforeClosePromise = modal.onBeforeClose(modal.closeReason); + const shouldClose = await modal.beforeClosePromise; + modal.beforeClosePromise = null; + if (!shouldClose) { + return; + } + } deferred.resolve(args); if (props && props.onFinished) props.onFinished.apply(null, args); const i = this._modals.indexOf(modal); @@ -186,9 +199,9 @@ class ModalManager { * static at a time. * @returns {object} Object with 'close' parameter being a function that will close the dialog */ - createDialogAsync(prom, props, className, isPriorityModal, isStaticModal) { - const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className); - + createDialogAsync(prom, props, className, isPriorityModal, isStaticModal, options = {}) { + const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, options); + modal.close = closeDialog; if (isPriorityModal) { // XXX: This is destructive this._priorityModal = modal; @@ -207,7 +220,7 @@ class ModalManager { } appendDialogAsync(prom, props, className) { - const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className); + const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, {}); this._modals.push(modal); this._reRender(); @@ -222,7 +235,13 @@ class ModalManager { if (!modal) { return; } + // we want to pass a reason to the onBeforeClose + // callback, but close is currently defined to + // pass all number of arguments to the onFinished callback + // so, pass the reason to close through a member variable + modal.closeReason = "backgroundClick"; modal.close(); + modal.closeReason = null; } _getCurrentModal() { From 70a4d3415eae1356fc3d3228a5a5d9e054264b4e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 13:11:24 +0100 Subject: [PATCH 07/14] confirm to close the passphrase dialog if it was done by backgroundClick as it is easy to do by accident --- src/CrossSigningManager.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index a560c956f1..8feb651ea5 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -70,6 +70,7 @@ async function getSecretStorageKey({ keys: keyInfos }) { sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", AccessSecretStorageDialog, + /* props= */ { keyInfo: info, checkPrivateKey: async (input) => { @@ -77,6 +78,22 @@ async function getSecretStorageKey({ keys: keyInfos }) { return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey); }, }, + /* className= */ null, + /* isPriorityModal= */ false, + /* isStaticModal= */ false, + /* options= */ { + onBeforeClose: async (reason) => { + if (reason !== "backgroundClick") { + return true; + } + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + const [sure] = await Modal.createDialog(QuestionDialog, { + title: _t("Cancel entering passphrase?"), + description: _t("If you cancel now, you won't complete your SSSS operation!"), + }).finished; + return sure; + }, + }, ); const [input] = await finished; if (!input) { From 4cd4110a52d867c4e22bf1ef87f7b43a1f97edd4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 13:13:37 +0100 Subject: [PATCH 08/14] fixup: this is already done in _buildModal --- src/Modal.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Modal.js b/src/Modal.js index 2e5624aa67..f55e497e6f 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -201,7 +201,6 @@ class ModalManager { */ createDialogAsync(prom, props, className, isPriorityModal, isStaticModal, options = {}) { const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, options); - modal.close = closeDialog; if (isPriorityModal) { // XXX: This is destructive this._priorityModal = modal; From cf7ad725a60765f8d98cbe1234e172e895cdaf39 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 15:06:36 +0100 Subject: [PATCH 09/14] copy and i18n --- src/CrossSigningManager.js | 2 +- src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 8feb651ea5..15daede92b 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -89,7 +89,7 @@ async function getSecretStorageKey({ keys: keyInfos }) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const [sure] = await Modal.createDialog(QuestionDialog, { title: _t("Cancel entering passphrase?"), - description: _t("If you cancel now, you won't complete your SSSS operation!"), + description: _t("If you cancel now, you won't complete your secret storage operation!"), }).finished; return sure; }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d125d10cfb..6fb4b86aac 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -60,6 +60,8 @@ "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", "The server does not support the room version specified.": "The server does not support the room version specified.", "Failure to create room": "Failure to create room", + "Cancel entering passphrase?": "Cancel entering passphrase?", + "If you cancel now, you won't complete your secret storage operation!": "If you cancel now, you won't complete your secret storage operation!", "Setting up keys": "Setting up keys", "Send anyway": "Send anyway", "Send": "Send", From a8958458aae19f546c185b67cc6b6ba153a48fb4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 15:29:35 +0100 Subject: [PATCH 10/14] fix lint, add jsdoc --- src/Modal.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Modal.js b/src/Modal.js index f55e497e6f..de441740f1 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -170,6 +170,12 @@ class ModalManager { }, deferred.promise]; } + /** + * @callback onBeforeClose + * @param {string?} reason either "backgroundClick" or null + * @return {Promise} whether the dialog should close + */ + /** * Open a modal view. * @@ -197,6 +203,8 @@ class ModalManager { * also be removed from the stack. This is not compatible * with being a priority modal. Only one modal can be * static at a time. + * @param {Object} options? extra options for the dialog + * @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog * @returns {object} Object with 'close' parameter being a function that will close the dialog */ createDialogAsync(prom, props, className, isPriorityModal, isStaticModal, options = {}) { From 02d169060daa466df4b3db45b0e373f0c90c057b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 6 Feb 2020 16:51:02 +0100 Subject: [PATCH 11/14] differentiate dismiss dialog based on name passed from js-sdk also make dialog a bit nicer with more descriptive button --- src/CrossSigningManager.js | 34 +++++++++++++++++++++++++--------- src/i18n/strings/en_EN.json | 8 +++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 15daede92b..694b2b0a25 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -43,7 +43,28 @@ export class AccessCancelledError extends Error { } } -async function getSecretStorageKey({ keys: keyInfos }) { +async function confirmToDismiss(name) { + let description; + if (name === "m.cross_signing.user_signing") { + description = _t("If you cancel now, you won't complete verifying the other user."); + } else if (name === "m.cross_signing.self_signing") { + description = _t("If you cancel now, you won't complete verifying your other session."); + } else { + description = _t("If you cancel now, you won't complete your secret storage operation."); + } + + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + const [sure] = await Modal.createDialog(QuestionDialog, { + title: _t("Cancel entering passphrase?"), + description, + danger: true, + cancelButton: _t("Enter passphrase"), + button: _t("Cancel"), + }).finished; + return sure; +} + +async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { const keyInfoEntries = Object.entries(keyInfos); if (keyInfoEntries.length > 1) { throw new Error("Multiple storage key requests not implemented"); @@ -83,15 +104,10 @@ async function getSecretStorageKey({ keys: keyInfos }) { /* isStaticModal= */ false, /* options= */ { onBeforeClose: async (reason) => { - if (reason !== "backgroundClick") { - return true; + if (reason === "backgroundClick") { + return confirmToDismiss(ssssItemName); } - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const [sure] = await Modal.createDialog(QuestionDialog, { - title: _t("Cancel entering passphrase?"), - description: _t("If you cancel now, you won't complete your secret storage operation!"), - }).finished; - return sure; + return true; }, }, ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6fb4b86aac..2e21f08447 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -60,8 +60,12 @@ "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", "The server does not support the room version specified.": "The server does not support the room version specified.", "Failure to create room": "Failure to create room", + "If you cancel now, you won't complete verifying the other user.": "If you cancel now, you won't complete verifying the other user.", + "If you cancel now, you won't complete verifying your other session.": "If you cancel now, you won't complete verifying your other session.", + "If you cancel now, you won't complete your secret storage operation.": "If you cancel now, you won't complete your secret storage operation.", "Cancel entering passphrase?": "Cancel entering passphrase?", - "If you cancel now, you won't complete your secret storage operation!": "If you cancel now, you won't complete your secret storage operation!", + "Enter passphrase": "Enter passphrase", + "Cancel": "Cancel", "Setting up keys": "Setting up keys", "Send anyway": "Send anyway", "Send": "Send", @@ -452,7 +456,6 @@ "Verify this device by confirming the following number appears on its screen.": "Verify this device by confirming the following number appears on its screen.", "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.", "Unable to find a supported verification method.": "Unable to find a supported verification method.", - "Cancel": "Cancel", "Waiting for %(displayName)s to verify…": "Waiting for %(displayName)s to verify…", "They match": "They match", "They don't match": "They don't match", @@ -2022,7 +2025,6 @@ "Export room keys": "Export room keys", "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", - "Enter passphrase": "Enter passphrase", "Confirm passphrase": "Confirm passphrase", "Export": "Export", "Import room keys": "Import room keys", From c916ef453490b718854989ca2a74ac476b61934a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 6 Feb 2020 17:57:17 +0000 Subject: [PATCH 12/14] Only emit in RoomViewStore when state actually changes This adds a shallow state check to attempt to only emit a store update when something actually changes. Fixes https://github.com/vector-im/riot-web/issues/12256 --- src/stores/RoomViewStore.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 9bcc2815e6..64dfd56b2f 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -66,6 +66,20 @@ class RoomViewStore extends Store { } _setState(newState) { + // If values haven't changed, there's nothing to do. + // This only tries a shallow comparison, so unchanged objects will slip + // through, but that's probably okay for now. + let stateChanged = false; + for (const key of Object.keys(newState)) { + if (this._state[key] !== newState[key]) { + stateChanged = true; + break; + } + } + if (!stateChanged) { + return; + } + this._state = Object.assign(this._state, newState); this.__emitChange(); } From bdeb9cccc42a23ae8143a092a62dfffa4654d6ee Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 7 Feb 2020 15:34:45 +0000 Subject: [PATCH 13/14] Rename estimatedTargetDevice to targetDevice --- src/components/views/elements/crypto/VerificationQRCode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index f9fea2dc78..61177cb833 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -47,7 +47,7 @@ export default class VerificationQRCode extends React.PureComponent { const myUserId = cli.getUserId(); const otherUserId = verificationRequest.otherUserId; const myDeviceId = cli.getDeviceId(); - const otherDevice = verificationRequest.estimatedTargetDevice; + const otherDevice = verificationRequest.targetDevice; const otherDeviceId = otherDevice ? otherDevice.deviceId : null; const qrProps = { From f6abd369cafc49a1b9d8f3d33a4407bf2ec45cd3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 7 Feb 2020 15:36:57 +0000 Subject: [PATCH 14/14] Fix comments --- src/components/views/elements/crypto/VerificationQRCode.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/crypto/VerificationQRCode.js b/src/components/views/elements/crypto/VerificationQRCode.js index 61177cb833..1c0fdcbf44 100644 --- a/src/components/views/elements/crypto/VerificationQRCode.js +++ b/src/components/views/elements/crypto/VerificationQRCode.js @@ -34,7 +34,7 @@ export default class VerificationQRCode extends React.PureComponent { // User verification use case only secret: PropTypes.string, otherUserKey: PropTypes.string, // Base64 key being verified - otherUserDeviceKey: PropTypes.string, // Base64 key of the other user's device (optional) + otherUserDeviceKey: PropTypes.string, // Base64 key of the other user's device (or what we think it is; optional) requestEventId: PropTypes.string, // for DM verification only }; @@ -68,10 +68,10 @@ export default class VerificationQRCode extends React.PureComponent { // Populate the keys we need depending on which direction and users are involved in the verification. if (myUserId === otherUserId) { if (!otherDeviceId) { - // New -> Existing session QR code + // Existing scanning New session's QR code qrProps.otherUserDeviceKey = null; } else { - // Existing -> New session QR code + // New scanning Existing session's QR code const myDevices = (await cli.getStoredDevicesForUser(myUserId)) || []; const device = myDevices.find(d => d.deviceId === otherDeviceId); if (device) qrProps.otherUserDeviceKey = device.getFingerprint();