From 3302469a2b4368500d20840a1f101781b5ef3711 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 24 Feb 2020 18:04:11 +0000 Subject: [PATCH 01/83] Catch errors sooner so users can recover more easily --- res/css/views/rooms/_EventTile.scss | 14 ++++ src/components/structures/MessagePanel.js | 76 ++++++++++--------- .../views/messages/TileErrorBoundary.js | 70 +++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 4 files changed, 126 insertions(+), 35 deletions(-) create mode 100644 src/components/views/messages/TileErrorBoundary.js diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index d292c729dd..68aca63459 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -653,3 +653,17 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } } } + +.mx_EventTile_tileError { + color: red; + + .mx_EventTile_line span { + padding: 4px 8px; + border-radius: 11px; + box-shadow: 0px 0px 3px red inset; + } + + a { + margin-left: 1em; + } +} \ No newline at end of file diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index b8b11fbb31..e6f8de61a9 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -502,6 +502,7 @@ export default class MessagePanel extends React.Component { } _getTilesForEvent(prevEvent, mxEv, last) { + const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary'); const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const ret = []; @@ -575,25 +576,27 @@ export default class MessagePanel extends React.Component { ref={this._collectEventNode.bind(this, eventId)} data-scroll-tokens={scrollToken} > - + + + , ); @@ -755,6 +758,7 @@ export default class MessagePanel extends React.Component { } render() { + const ErrorBoundary = sdk.getComponent('elements.ErrorBoundary'); const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile"); const Spinner = sdk.getComponent("elements.Spinner"); @@ -787,22 +791,24 @@ export default class MessagePanel extends React.Component { } return ( - - { topSpinner } - { this._getEventTiles() } - { whoIsTyping } - { bottomSpinner } - + + + { topSpinner } + { this._getEventTiles() } + { whoIsTyping } + { bottomSpinner } + + ); } } diff --git a/src/components/views/messages/TileErrorBoundary.js b/src/components/views/messages/TileErrorBoundary.js new file mode 100644 index 0000000000..372d402899 --- /dev/null +++ b/src/components/views/messages/TileErrorBoundary.js @@ -0,0 +1,70 @@ +/* +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 React from 'react'; +import classNames from 'classnames'; +import { _t } from '../../../languageHandler'; +import * as sdk from '../../../index'; +import Modal from '../../../Modal'; + +export default class TileErrorBoundary extends React.Component { + constructor(props) { + super(props); + + this.state = { + error: null, + }; + } + + static getDerivedStateFromError(error) { + // Side effects are not permitted here, so we only update the state so + // that the next render shows an error message. + return { error }; + } + + _onBugReport = () => { + const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); + if (!BugReportDialog) { + return; + } + Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, { + label: 'react-soft-crash-tile', + }); + }; + + render() { + if (this.state.error) { + const classes = { + mx_EventTile: true, + mx_EventTile_info: true, + mx_EventTile_content: true, + mx_EventTile_tileError: true, + }; + return (
+
+ + {_t("An error occurred while rendering this event.")} + + {_t("Submit debug logs")} + + +
+
); + } + + return this.props.children; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1d030f5118..adce7f9a03 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1268,6 +1268,7 @@ "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", "edited": "edited", + "An error occurred while rendering this event.": "An error occurred while rendering this event.", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", "Message removed": "Message removed", From 0d03a8791dc6ba1cdb7ec7dd1e0290f09fcc0b7f Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 25 Feb 2020 10:20:42 +0000 Subject: [PATCH 02/83] style lint --- res/css/views/rooms/_EventTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 68aca63459..9e683c5fe4 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -666,4 +666,4 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { a { margin-left: 1em; } -} \ No newline at end of file +} From adf6dfe4ea831e08730485f85147d9805f711aa8 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 25 Feb 2020 10:30:35 +0000 Subject: [PATCH 03/83] Threaded through the event type to the user --- src/components/structures/MessagePanel.js | 2 +- src/components/views/messages/TileErrorBoundary.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index e6f8de61a9..d35b0fce1f 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -576,7 +576,7 @@ export default class MessagePanel extends React.Component { ref={this._collectEventNode.bind(this, eventId)} data-scroll-tokens={scrollToken} > - + {_t("An error occurred while rendering this event.")} + { mxEvent && ` [${mxEvent.getType()}]` } {_t("Submit debug logs")} From 04849f7f0d91ed9bee30db744d8397cf9e835800 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 26 Mar 2020 13:47:32 +0000 Subject: [PATCH 04/83] incorporated design feedback --- res/css/views/rooms/_EventTile.scss | 3 +-- src/components/views/messages/TileErrorBoundary.js | 6 +++--- src/i18n/strings/en_EN.json | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 2f89c96d57..59f82808dc 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -661,11 +661,10 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_tileError { color: red; + text-align: center; .mx_EventTile_line span { padding: 4px 8px; - border-radius: 11px; - box-shadow: 0px 0px 3px red inset; } a { diff --git a/src/components/views/messages/TileErrorBoundary.js b/src/components/views/messages/TileErrorBoundary.js index a8e7b144f2..e42ddab16a 100644 --- a/src/components/views/messages/TileErrorBoundary.js +++ b/src/components/views/messages/TileErrorBoundary.js @@ -57,10 +57,10 @@ export default class TileErrorBoundary extends React.Component { return (
- {_t("An error occurred while rendering this event.")} - { mxEvent && ` [${mxEvent.getType()}]` } + {_t("Can't load this message")} + { mxEvent && ` (${mxEvent.getType()})` } - {_t("Submit debug logs")} + {_t("Submit logs")}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index cf170fce62..40b1cab99d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1294,7 +1294,8 @@ "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", "edited": "edited", - "An error occurred while rendering this event.": "An error occurred while rendering this event.", + "Can't load this message": "Can't load this message", + "Submit logs": "Submit logs", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", "Message removed": "Message removed", From 36120738525816f8442ab49aded73a7e0f96f766 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Apr 2020 15:12:19 +0100 Subject: [PATCH 05/83] Nuke the icon_person.svg DM Indicator as it causes more confusion than it solves. Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/img/icon_person.svg | 23 ------------------- src/components/views/rooms/RoomBreadcrumbs.js | 12 ---------- src/components/views/rooms/RoomTile.js | 14 ----------- 3 files changed, 49 deletions(-) delete mode 100644 res/img/icon_person.svg diff --git a/res/img/icon_person.svg b/res/img/icon_person.svg deleted file mode 100644 index 4be70df0db..0000000000 --- a/res/img/icon_person.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - 815EF7DE-169A-4322-AE2A-B65CBE91DCED - Created with sketchtool. - - - - - - - - - - - - - - - - - - diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index 1d433c9a40..86c0d7ca96 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -363,17 +363,6 @@ export default class RoomBreadcrumbs extends React.Component { badge =
{r.formattedCount}
; } - let dmIndicator; - if (this._isDmRoom(r.room) && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { - dmIndicator = {_t("Direct; - } - return ( {badge} - {dmIndicator} {tooltip} ); diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 0b06be48af..448f856b50 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -479,20 +479,7 @@ export default createReactClass({ let ariaLabel = name; - let dmIndicator; let dmOnline; - /* Post-cross-signing we don't show DM indicators at all, instead relying on user - context to let them know when that is. */ - if (dmUserId && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { - dmIndicator = dm; - } - const { room } = this.props; const member = room.getMember(dmUserId); if ( @@ -557,7 +544,6 @@ export default createReactClass({
- { dmIndicator } { e2eIcon }
From a28aa4c0b44702ede672f252e161aed37bd7aa03 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Apr 2020 15:13:19 +0100 Subject: [PATCH 06/83] Tweak user online dot in room tile, make it occupy same space and inverse behaviour of the context menu button Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile.scss | 31 +++++++++++++++++-------- res/css/views/rooms/_UserOnlineDot.scss | 4 ++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 31d887cbbb..aa8a77de78 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -24,6 +24,20 @@ limitations under the License. margin: 0; padding: 0 8px 0 10px; position: relative; + + .mx_RoomTile_menuButton { + display: none; + flex: 0 0 16px; + height: 16px; + background-image: url('$(res)/img/icon_context.svg'); + background-repeat: no-repeat; + background-position: center; + } + + .mx_UserOnlineDot { + display: block; + margin-right: 5px; + } } .mx_RoomTile:focus { @@ -31,15 +45,6 @@ limitations under the License. background-color: $roomtile-focused-bg-color; } -.mx_RoomTile_menuButton { - display: none; - flex: 0 0 16px; - height: 16px; - background-image: url('$(res)/img/icon_context.svg'); - background-repeat: no-repeat; - background-position: center; -} - .mx_RoomTile_tooltip { display: inline-block; position: relative; @@ -151,7 +156,10 @@ limitations under the License. } .mx_RoomTile_menuButton { - display: none; //no design for this for now + display: none; // no design for this for now + } + .mx_UserOnlineDot { + display: none; // no design for this for now } } @@ -164,6 +172,9 @@ limitations under the License. .mx_RoomTile_menuButton { display: block; } + .mx_UserOnlineDot { + display: none; + } } .mx_RoomTile_unreadNotify .mx_RoomTile_badge, diff --git a/res/css/views/rooms/_UserOnlineDot.scss b/res/css/views/rooms/_UserOnlineDot.scss index 339e5cc48a..f9da8648ed 100644 --- a/res/css/views/rooms/_UserOnlineDot.scss +++ b/res/css/views/rooms/_UserOnlineDot.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_UserOnlineDot { border-radius: 50%; background-color: $accent-color; - height: 5px; - width: 5px; + height: 6px; + width: 6px; display: inline-block; } From 1ae370b97eca7d9d6ef13b1c05fc6776968528ca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Apr 2020 15:20:35 +0100 Subject: [PATCH 07/83] Pull feature_presence_in_room_list out of labs. Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile.js | 5 +---- src/i18n/strings/en_EN.json | 3 +-- src/settings/Settings.js | 6 ------ 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 448f856b50..87d288561c 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -482,10 +482,7 @@ export default createReactClass({ let dmOnline; const { room } = this.props; const member = room.getMember(dmUserId); - if ( - member && member.membership === "join" && room.getJoinedMemberCount() === 2 && - SettingsStore.isFeatureEnabled("feature_presence_in_room_list") - ) { + if (member && member.membership === "join" && room.getJoinedMemberCount() === 2) { const UserOnlineDot = sdk.getComponent('rooms.UserOnlineDot'); dmOnline = ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 24a6568d82..163b3ad341 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -393,7 +393,6 @@ "Render simple counters in room header": "Render simple counters in room header", "Multiple integration managers": "Multiple integration managers", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", - "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", "Support adding custom themes": "Support adding custom themes", "Enable cross-signing to verify per-user instead of per-session (in development)": "Enable cross-signing to verify per-user instead of per-session (in development)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", @@ -1075,7 +1074,6 @@ "Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", "Replying": "Replying", - "Direct Chat": "Direct Chat", "Room %(name)s": "Room %(name)s", "Recent rooms": "Recent rooms", "No rooms to show": "No rooms to show", @@ -1815,6 +1813,7 @@ "Forget": "Forget", "Favourite": "Favourite", "Low Priority": "Low Priority", + "Direct Chat": "Direct Chat", "Clear status": "Clear status", "Update status": "Update status", "Set status": "Set status", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 0d72017878..df3bf91b17 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -131,12 +131,6 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, - "feature_presence_in_room_list": { - isFeature: true, - displayName: _td("Show a presence dot next to DMs in the room list"), - supportedLevels: LEVELS_FEATURE, - default: false, - }, "feature_custom_themes": { isFeature: true, displayName: _td("Support adding custom themes"), From 1395cb02edaab4f9f589814cc58e79cdc261ab1d Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 6 Apr 2020 11:44:46 +0100 Subject: [PATCH 08/83] Fixup alignment --- res/css/views/rooms/_EventTile.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 0f324cc082..3c91089dc9 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -665,6 +665,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { color: red; text-align: center; + // Remove some of the default tile padding so that the error is centered + margin-right: 0; + .mx_EventTile_line { + padding-left: 0; + margin-right: 0; + } + .mx_EventTile_line span { padding: 4px 8px; } From 0153f39c10bedae25cd04abd74ffa0406ccb07dd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 9 Apr 2020 22:55:28 +0100 Subject: [PATCH 09/83] Rageshake, remind user of unsupported browser and report missing features in report Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/dialogs/BugReportDialog.js | 8 ++++++++ src/rageshake/submit-rageshake.js | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/components/views/dialogs/BugReportDialog.js b/src/components/views/dialogs/BugReportDialog.js index 6e337d53dc..2f7ad4163d 100644 --- a/src/components/views/dialogs/BugReportDialog.js +++ b/src/components/views/dialogs/BugReportDialog.js @@ -137,12 +137,20 @@ export default class BugReportDialog extends React.Component { ); } + let warning; + if (window.Modernizr && Object.values(window.Modernizr).some(support => support === false)) { + warning =

+ { _t("Your browser is unsupported, you accepted that things may not work.") } +

; + } + return (
+ { warning }

{ _t( "Debug logs contain application usage data including your " + diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js index 00ef87f89c..2c529ea0b9 100644 --- a/src/rageshake/submit-rageshake.js +++ b/src/rageshake/submit-rageshake.js @@ -136,6 +136,13 @@ export default async function sendBugReport(bugReportEndpoint, opts) { } catch (e) {} } + if (window.Modernizr) { + const missingFeatures = Object.keys(window.Modernizr).filter(key => window.Modernizr[key] === false); + if (missingFeatures.length > 0) { + body.append("modernizr_missing_features", missingFeatures.join(", ")); + } + } + if (opts.sendLogs) { progressCallback(_t("Collecting logs")); const logs = await rageshake.getLogsForReport(); From f91565c231278bbf510bcd33ac9ffded38983987 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 10 Apr 2020 13:33:57 +0100 Subject: [PATCH 10/83] only append crypto details if crypto is enabled Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/rageshake/submit-rageshake.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js index 2c529ea0b9..55c89427c5 100644 --- a/src/rageshake/submit-rageshake.js +++ b/src/rageshake/submit-rageshake.js @@ -96,12 +96,14 @@ export default async function sendBugReport(bugReportEndpoint, opts) { body.append('device_id', client.deviceId); } - const keys = [`ed25519:${client.getDeviceEd25519Key()}`]; - if (client.getDeviceCurve25519Key) { - keys.push(`curve25519:${client.getDeviceCurve25519Key()}`); + if (client.isCryptoEnabled()) { + const keys = [`ed25519:${client.getDeviceEd25519Key()}`]; + if (client.getDeviceCurve25519Key) { + keys.push(`curve25519:${client.getDeviceCurve25519Key()}`); + } + body.append('device_keys', keys.join(', ')); + body.append('cross_signing_key', client.getCrossSigningId()); } - body.append('device_keys', keys.join(', ')); - body.append('cross_signing_key', client.getCrossSigningId()); if (opts.label) { body.append('label', opts.label); From 8cf6a8c3112fb49c18a440bcadf8a775b72837ec Mon Sep 17 00:00:00 2001 From: thobyv-kismat Date: Sat, 11 Apr 2020 02:59:26 +0100 Subject: [PATCH 11/83] refactor RoomScrollStateStore to accomodate scrollmaps for file/notif panel --- src/components/structures/RoomView.js | 4 ++-- src/stores/RoomScrollStateStore.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 4a3666fc38..291297a815 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -269,7 +269,7 @@ export default createReactClass({ // If an event ID wasn't specified, default to the one saved for this room // in the scroll state store. Assume initialEventPixelOffset should be set. if (!newState.initialEventId) { - const roomScrollState = RoomScrollStateStore.getScrollState(newState.roomId); + const roomScrollState = RoomScrollStateStore.getRoomViewScrollState(newState.roomId); if (roomScrollState) { newState.initialEventId = roomScrollState.focussedEvent; newState.initialEventPixelOffset = roomScrollState.pixelOffset; @@ -470,7 +470,7 @@ export default createReactClass({ // update the scroll map before we get unmounted if (this.state.roomId) { - RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState()); + RoomScrollStateStore.setRoomViewScrollState(this.state.roomId, this._getScrollState()); } if (this.state.shouldPeek) { diff --git a/src/stores/RoomScrollStateStore.js b/src/stores/RoomScrollStateStore.js index 07848283d1..37440c3e81 100644 --- a/src/stores/RoomScrollStateStore.js +++ b/src/stores/RoomScrollStateStore.js @@ -32,15 +32,15 @@ class RoomScrollStateStore { // // pixelOffset: the number of pixels the window is scrolled down // from the focussedEvent. - this._scrollStateMap = {}; + this._RoomViewScrollStateMap = {}; } - getScrollState(roomId) { - return this._scrollStateMap[roomId]; + getRoomViewScrollState(roomId) { + return this._RoomViewScrollStateMap[roomId]; } - setScrollState(roomId, scrollState) { - this._scrollStateMap[roomId] = scrollState; + setRoomViewScrollState(roomId, scrollState) { + this._RoomViewScrollStateMap[roomId] = scrollState; } } From 719165c67fd6771a8fbf428556dc2b04cf566248 Mon Sep 17 00:00:00 2001 From: thobyv-kismat Date: Sat, 11 Apr 2020 04:03:32 +0100 Subject: [PATCH 12/83] fix file panel scroll position lost on room change --- src/components/structures/FilePanel.js | 44 +++++++++++++++++++++++++- src/stores/RoomScrollStateStore.js | 9 ++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index f8c03be864..926355dd88 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -25,6 +25,8 @@ import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; +import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; + /* * Component which shows the filtered file using a TimelinePanel */ @@ -41,6 +43,8 @@ const FilePanel = createReactClass({ getInitialState: function() { return { timelineSet: null, + initialEventId: null, + initialEventPixelOffset: null, }; }, @@ -84,6 +88,16 @@ const FilePanel = createReactClass({ await this.updateTimelineSet(this.props.roomId); + if (this.props.roomId) { + const filePanelScrollState = RoomScrollStateStore.getFilePanelScrollState(this.props.roomId); + if (filePanelScrollState) { + this.setState({ + initialEventId: filePanelScrollState.focussedEvent, + initialEventPixelOffset: filePanelScrollState.pixelOffset, + }); + } + } + if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; // The timelineSets filter makes sure that encrypted events that contain @@ -101,6 +115,10 @@ const FilePanel = createReactClass({ }, componentWillUnmount() { + if (this.props.roomId) { + RoomScrollStateStore.setFilePanelScrollState(this.props.roomId, this._getScrollState()); + } + const client = MatrixClientPeg.get(); if (client === null) return; @@ -190,6 +208,26 @@ const FilePanel = createReactClass({ } }, + _getScrollState: function() { + const messagePanel = this._messagePanel; + const scrollState = messagePanel.getScrollState(); + + if (!messagePanel) return null; + + if (!scrollState || scrollState.stuckAtBottom) { + return null; + } + + return { + focussedEvent: scrollState.trackedScrollToken, + pixelOffset: scrollState.pixelOffset, + }; + }, + + _getTimelinePanelRef: function(ref) { + this._messagePanel = ref; + }, + render: function() { if (MatrixClientPeg.get().isGuest()) { return

@@ -215,11 +253,15 @@ const FilePanel = createReactClass({ // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); return (
- Date: Tue, 14 Apr 2020 17:34:39 +0100 Subject: [PATCH 13/83] Allow network dropdown to be scrollable and fix context menu window padding calc Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/directory/_NetworkDropdown.scss | 2 ++ src/components/structures/ContextMenu.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss index 269b507e3c..dd1892c448 100644 --- a/res/css/views/directory/_NetworkDropdown.scss +++ b/res/css/views/directory/_NetworkDropdown.scss @@ -35,6 +35,8 @@ limitations under the License. border-radius: 4px; border: 1px solid $dialog-close-fg-color; background-color: $primary-bg-color; + max-height: calc(100vh - 20px); // allow 10px padding on both top and bottom + overflow-y: auto; } .mx_NetworkDropdown_menu_network { diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index b4647a6c30..98b0867ccc 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -245,7 +245,6 @@ export class ContextMenu extends React.Component { } const contextMenuRect = this.state.contextMenuElem ? this.state.contextMenuElem.getBoundingClientRect() : null; - const padding = 10; const chevronOffset = {}; if (props.chevronFace) { @@ -264,7 +263,8 @@ export class ContextMenu extends React.Component { // If we know the dimensions of the context menu, adjust its position // such that it does not leave the (padded) window. if (contextMenuRect) { - adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height - padding); + const padding = 10; + adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height + padding); } position.top = adjusted; From 89bc3bdd5bb1fb0668f76981b05f8712de33b89d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 00:16:11 +0100 Subject: [PATCH 14/83] consolidate and extract copyPlaintext, copyNode and selectText Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../keybackup/CreateKeyBackupDialog.js | 14 +--- .../CreateSecretStorageDialog.js | 14 +--- src/components/views/dialogs/ShareDialog.js | 76 ++++++++----------- src/components/views/messages/TextualBody.js | 26 ++----- src/utils/strings.ts | 75 ++++++++++++++++++ 5 files changed, 117 insertions(+), 88 deletions(-) create mode 100644 src/utils/strings.ts diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 3a480a2579..7e5e0afb79 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -25,6 +25,7 @@ import { _t } from '../../../../languageHandler'; import { accessSecretStorage } from '../../../../CrossSigningManager'; import SettingsStore from '../../../../settings/SettingsStore'; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; +import {copyNode} from "../../../../utils/strings"; const PHASE_PASSPHRASE = 0; const PHASE_PASSPHRASE_CONFIRM = 1; @@ -37,16 +38,6 @@ const PHASE_OPTOUT_CONFIRM = 6; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms. -// XXX: copied from ShareDialog: factor out into utils -function selectText(target) { - const range = document.createRange(); - range.selectNodeContents(target); - - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); -} - /* * Walks the user through the process of creating an e2e key backup * on the server. @@ -101,8 +92,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { } _onCopyClick = () => { - selectText(this._recoveryKeyNode); - const successful = document.execCommand('copy'); + const successful = copyNode(this._recoveryKeyNode); if (successful) { this.setState({ copied: true, diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index d63db617d5..cfad49c38d 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -24,6 +24,7 @@ import FileSaver from 'file-saver'; import { _t } from '../../../../languageHandler'; import Modal from '../../../../Modal'; import { promptForBackupPassphrase } from '../../../../CrossSigningManager'; +import {copyNode} from "../../../../utils/strings"; const PHASE_LOADING = 0; const PHASE_MIGRATE = 1; @@ -38,16 +39,6 @@ const PHASE_CONFIRM_SKIP = 8; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms. -// XXX: copied from ShareDialog: factor out into utils -function selectText(target) { - const range = document.createRange(); - range.selectNodeContents(target); - - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); -} - /* * Walks the user through the process of creating a passphrase to guard Secure * Secret Storage in account data. @@ -169,8 +160,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _onCopyClick = () => { - selectText(this._recoveryKeyNode); - const successful = document.execCommand('copy'); + const successful = copyNode(this._recoveryKeyNode); if (successful) { this.setState({ copied: true, diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.js index 1bc9decd39..ebd1f8d1eb 100644 --- a/src/components/views/dialogs/ShareDialog.js +++ b/src/components/views/dialogs/ShareDialog.js @@ -23,6 +23,7 @@ import QRCode from 'qrcode-react'; import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks"; import * as ContextMenu from "../../structures/ContextMenu"; import {toRightOf} from "../../structures/ContextMenu"; +import {copyPlaintext, selectText} from "../../../utils/strings"; const socials = [ { @@ -81,45 +82,26 @@ export default class ShareDialog extends React.Component { linkSpecificEvent: this.props.target instanceof MatrixEvent, permalinkCreator, }; - - this._link = createRef(); - } - - static _selectText(target) { - const range = document.createRange(); - range.selectNodeContents(target); - - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); } static onLinkClick(e) { e.preventDefault(); - const {target} = e; - ShareDialog._selectText(target); + selectText(e.target); } - onCopyClick(e) { + async onCopyClick(e) { e.preventDefault(); + const target = e.target; // copy target before we go async and React throws it away - ShareDialog._selectText(this._link.current); - - let successful; - try { - successful = document.execCommand('copy'); - } catch (err) { - console.error('Failed to copy: ', err); - } - - const buttonRect = e.target.getBoundingClientRect(); + const successful = await copyPlaintext(this.getUrl()); + const buttonRect = target.getBoundingClientRect(); const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const {close} = ContextMenu.createMenu(GenericTextContextMenu, { ...toRightOf(buttonRect, 2), message: successful ? _t('Copied!') : _t('Failed to copy'), }); // Drop a reference to this close handler for componentWillUnmount - this.closeCopiedTooltip = e.target.onmouseleave = close; + this.closeCopiedTooltip = target.onmouseleave = close; } onLinkSpecificEventCheckboxClick() { @@ -134,10 +116,32 @@ export default class ShareDialog extends React.Component { if (this.closeCopiedTooltip) this.closeCopiedTooltip(); } - render() { - let title; + getUrl() { let matrixToUrl; + if (this.props.target instanceof Room) { + if (this.state.linkSpecificEvent) { + const events = this.props.target.getLiveTimeline().getEvents(); + matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId()); + } else { + matrixToUrl = this.state.permalinkCreator.forRoom(); + } + } else if (this.props.target instanceof User || this.props.target instanceof RoomMember) { + matrixToUrl = makeUserPermalink(this.props.target.userId); + } else if (this.props.target instanceof Group) { + matrixToUrl = makeGroupPermalink(this.props.target.groupId); + } else if (this.props.target instanceof MatrixEvent) { + if (this.state.linkSpecificEvent) { + matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId()); + } else { + matrixToUrl = this.props.permalinkCreator.forRoom(); + } + } + return matrixToUrl; + } + + render() { + let title; let checkbox; if (this.props.target instanceof Room) { @@ -155,18 +159,10 @@ export default class ShareDialog extends React.Component {
; } - - if (this.state.linkSpecificEvent) { - matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId()); - } else { - matrixToUrl = this.state.permalinkCreator.forRoom(); - } } else if (this.props.target instanceof User || this.props.target instanceof RoomMember) { title = _t('Share User'); - matrixToUrl = makeUserPermalink(this.props.target.userId); } else if (this.props.target instanceof Group) { title = _t('Share Community'); - matrixToUrl = makeGroupPermalink(this.props.target.groupId); } else if (this.props.target instanceof MatrixEvent) { title = _t('Share Room Message'); checkbox =
@@ -178,14 +174,9 @@ export default class ShareDialog extends React.Component { { _t('Link to selected message') }
; - - if (this.state.linkSpecificEvent) { - matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId()); - } else { - matrixToUrl = this.props.permalinkCreator.forRoom(); - } } + const matrixToUrl = this.getUrl(); const encodedUrl = encodeURIComponent(matrixToUrl); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); @@ -196,8 +187,7 @@ export default class ShareDialog extends React.Component { >
- diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 27514d0e23..882e331675 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -34,6 +34,7 @@ import {pillifyLinks, unmountPills} from '../../../utils/pillify'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import {isPermalinkHost} from "../../../utils/permalinks/Permalinks"; import {toRightOf} from "../../structures/ContextMenu"; +import {copyPlaintext} from "../../../utils/strings"; export default createReactClass({ displayName: 'TextualBody', @@ -69,23 +70,6 @@ export default createReactClass({ }; }, - copyToClipboard: function(text) { - const textArea = document.createElement("textarea"); - textArea.value = text; - document.body.appendChild(textArea); - textArea.select(); - - let successful = false; - try { - successful = document.execCommand('copy'); - } catch (err) { - console.log('Unable to copy'); - } - - document.body.removeChild(textArea); - return successful; - }, - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs UNSAFE_componentWillMount: function() { this._content = createRef(); @@ -277,17 +261,17 @@ export default createReactClass({ Array.from(ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre')).forEach((p) => { const button = document.createElement("span"); button.className = "mx_EventTile_copyButton"; - button.onclick = (e) => { + button.onclick = async () => { const copyCode = button.parentNode.getElementsByTagName("pre")[0]; - const successful = this.copyToClipboard(copyCode.textContent); + const successful = await copyPlaintext(copyCode.textContent); - const buttonRect = e.target.getBoundingClientRect(); + const buttonRect = button.getBoundingClientRect(); const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const {close} = ContextMenu.createMenu(GenericTextContextMenu, { ...toRightOf(buttonRect, 2), message: successful ? _t('Copied!') : _t('Failed to copy'), }); - e.target.onmouseleave = close; + button.onmouseleave = close; }; // Wrap a div around
 so that the copy button can be correctly positioned
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
new file mode 100644
index 0000000000..7d1fa0049d
--- /dev/null
+++ b/src/utils/strings.ts
@@ -0,0 +1,75 @@
+/*
+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.
+*/
+
+/**
+ * Copy plaintext to user's clipboard
+ * It will overwrite user's selection range
+ * In certain browsers it may only work if triggered by a user action or may ask user for permissions
+ * Tries to use new async clipboard API if available
+ * @param text the plaintext to put in the user's clipboard
+ */
+export async function copyPlaintext(text: string): Promise {
+    try {
+        if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
+            await navigator.clipboard.writeText(text);
+            return true;
+        } else {
+            const textArea = document.createElement("textarea");
+            textArea.value = text;
+
+            // Avoid scrolling to bottom
+            textArea.style.top = "0";
+            textArea.style.left = "0";
+            textArea.style.position = "fixed";
+
+            document.body.appendChild(textArea);
+            const selection = document.getSelection();
+            const range = document.createRange();
+            // range.selectNodeContents(textArea);
+            range.selectNode(textArea);
+            selection.removeAllRanges();
+            selection.addRange(range);
+
+            const successful = document.execCommand("copy");
+            selection.removeAllRanges();
+            document.body.removeChild(textArea);
+            return successful;
+        }
+    } catch (e) {
+        console.error(e);
+    }
+    return false;
+}
+
+export function selectText(target: Element) {
+    const range = document.createRange();
+    range.selectNodeContents(target);
+
+    const selection = window.getSelection();
+    selection.removeAllRanges();
+    selection.addRange(range);
+}
+
+/**
+ * Copy rich text to user's clipboard
+ * It will overwrite user's selection range
+ * In certain browsers it may only work if triggered by a user action or may ask user for permissions
+ * @param ref pointer to the node to copy
+ */
+export function copyNode(ref: Element): boolean {
+    selectText(ref);
+    return document.execCommand('copy');
+}

From 276b5b874c1a8e7c4fa826c7fba0507fcc9babbe Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 15 Apr 2020 00:22:19 +0100
Subject: [PATCH 15/83] Convert ShareDialog to Typescript

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 .../{ShareDialog.js => ShareDialog.tsx}       | 45 +++++++++++++------
 1 file changed, 32 insertions(+), 13 deletions(-)
 rename src/components/views/dialogs/{ShareDialog.js => ShareDialog.tsx} (86%)

diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.tsx
similarity index 86%
rename from src/components/views/dialogs/ShareDialog.js
rename to src/components/views/dialogs/ShareDialog.tsx
index ebd1f8d1eb..375cb65b5f 100644
--- a/src/components/views/dialogs/ShareDialog.js
+++ b/src/components/views/dialogs/ShareDialog.tsx
@@ -1,5 +1,6 @@
 /*
 Copyright 2018 New Vector Ltd
+Copyright 2020 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,9 +15,13 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, {createRef} from 'react';
-import PropTypes from 'prop-types';
-import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import {Room} from "matrix-js-sdk/src/models/room";
+import {User} from "matrix-js-sdk/src/models/user";
+import {Group} from "matrix-js-sdk/src/models/group";
+import {RoomMember} from "matrix-js-sdk/src/models/room-member";
+import {MatrixEvent} from "matrix-js-sdk/src/models/event";
 import * as sdk from '../../../index';
 import { _t } from '../../../languageHandler';
 import QRCode from 'qrcode-react';
@@ -53,7 +58,18 @@ const socials = [
     },
 ];
 
-export default class ShareDialog extends React.Component {
+interface IProps {
+    onFinished: () => void;
+    target: Room | User | Group | RoomMember | MatrixEvent;
+    permalinkCreator: RoomPermalinkCreator;
+}
+
+interface IState {
+    linkSpecificEvent: boolean;
+    permalinkCreator: RoomPermalinkCreator;
+}
+
+export default class ShareDialog extends React.PureComponent {
     static propTypes = {
         onFinished: PropTypes.func.isRequired,
         target: PropTypes.oneOfType([
@@ -65,6 +81,8 @@ export default class ShareDialog extends React.Component {
         ]).isRequired,
     };
 
+    protected closeCopiedTooltip: () => void;
+
     constructor(props) {
         super(props);
 
@@ -206,17 +224,18 @@ export default class ShareDialog extends React.Component {
                         
                     
From 95eaf94cd89c12c56eb2e909fb4d9a5c8092f27f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 00:40:38 +0100 Subject: [PATCH 16/83] Fix pills being broken by unescaped characters Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/editor/deserialize.js | 2 +- src/editor/serialize.js | 2 +- test/editor/deserialize-test.js | 16 ++++++++++++++++ test/editor/serialize-test.js | 12 ++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 190963f357..d80a62b981 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -50,7 +50,7 @@ function parseLink(a, partCreator) { if (href === a.textContent) { return partCreator.plain(a.textContent); } else { - return partCreator.plain(`[${a.textContent}](${href})`); + return partCreator.plain(`[${a.textContent.replace(/[\\\]]/, c => "\\" + c)}](${href})`); } } } diff --git a/src/editor/serialize.js b/src/editor/serialize.js index ba380f2809..341d92d3c8 100644 --- a/src/editor/serialize.js +++ b/src/editor/serialize.js @@ -30,7 +30,7 @@ export function mdSerialize(model) { return html + part.text; case "room-pill": case "user-pill": - return html + `[${part.text}](${makeGenericPermalink(part.resourceId)})`; + return html + `[${part.text.replace(/[\\\]]/, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; } }, ""); } diff --git a/test/editor/deserialize-test.js b/test/editor/deserialize-test.js index 1c58a6c40b..be8fe8aeab 100644 --- a/test/editor/deserialize-test.js +++ b/test/editor/deserialize-test.js @@ -148,6 +148,22 @@ describe('editor/deserialize', function() { expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice", resourceId: "@alice:hs.tld"}); expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); }); + it('user pill with displayname containing backslash', function() { + const html = "Hi Alice\!"; + const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); + expect(parts.length).toBe(3); + expect(parts[0]).toStrictEqual({type: "plain", text: "Hi "}); + expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice\\", resourceId: "@alice:hs.tld"}); + expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); + }); + it('user pill with displayname containing closing square bracket', function() { + const html = "Hi Alice]!"; + const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); + expect(parts.length).toBe(3); + expect(parts[0]).toStrictEqual({type: "plain", text: "Hi "}); + expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice]", resourceId: "@alice:hs.tld"}); + expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); + }); it('room pill', function() { const html = "Try #room:hs.tld?"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); diff --git a/test/editor/serialize-test.js b/test/editor/serialize-test.js index 7517e46437..d5fb800600 100644 --- a/test/editor/serialize-test.js +++ b/test/editor/serialize-test.js @@ -43,4 +43,16 @@ describe('editor/serialize', function() { const html = htmlSerializeIfNeeded(model, {}); expect(html).toBe("hello world"); }); + it('displaynames ending in a backslash work', function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.userPill("Displayname\\", "@user:server")]); + const html = htmlSerializeIfNeeded(model, {}); + expect(html).toBe("Displayname\"); + }); + it('displaynames containing a closing square bracket work', function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.userPill("Displayname]", "@user:server")]); + const html = htmlSerializeIfNeeded(model, {}); + expect(html).toBe("Displayname]"); + }); }); From c72139fc3f5de58371d3b4e998e1b0b1d8223c3a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 00:49:08 +0100 Subject: [PATCH 17/83] Convert serialize and deserialize to TypeScript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/editor/{deserialize.js => deserialize.ts} | 36 +++++++++++-------- src/editor/{serialize.js => serialize.ts} | 19 +++++----- 2 files changed, 32 insertions(+), 23 deletions(-) rename src/editor/{deserialize.js => deserialize.ts} (87%) rename src/editor/{serialize.js => serialize.ts} (82%) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.ts similarity index 87% rename from src/editor/deserialize.js rename to src/editor/deserialize.ts index d80a62b981..8878a8877c 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.ts @@ -1,6 +1,6 @@ /* Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -15,11 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + import { walkDOMDepthFirst } from "./dom"; import { checkBlockNode } from "../HtmlUtils"; -import {getPrimaryPermalinkEntity} from "../utils/permalinks/Permalinks"; +import { getPrimaryPermalinkEntity } from "../utils/permalinks/Permalinks"; +import { PartCreator } from "./parts"; -function parseAtRoomMentions(text, partCreator) { +function parseAtRoomMentions(text: string, partCreator: PartCreator) { const ATROOM = "@room"; const parts = []; text.split(ATROOM).forEach((textPart, i, arr) => { @@ -37,7 +40,7 @@ function parseAtRoomMentions(text, partCreator) { return parts; } -function parseLink(a, partCreator) { +function parseLink(a: HTMLAnchorElement, partCreator: PartCreator) { const {href} = a; const resourceId = getPrimaryPermalinkEntity(href); // The room/user ID const prefix = resourceId ? resourceId[0] : undefined; // First character of ID @@ -56,11 +59,11 @@ function parseLink(a, partCreator) { } } -function parseCodeBlock(n, partCreator) { +function parseCodeBlock(n: HTMLElement, partCreator: PartCreator) { const parts = []; let language = ""; if (n.firstChild && n.firstChild.nodeName === "CODE") { - for (const className of n.firstChild.classList) { + for (const className of (n.firstChild).classList) { if (className.startsWith("language-")) { language = className.substr("language-".length); break; @@ -77,12 +80,17 @@ function parseCodeBlock(n, partCreator) { return parts; } -function parseHeader(el, partCreator) { +function parseHeader(el: HTMLElement, partCreator: PartCreator) { const depth = parseInt(el.nodeName.substr(1), 10); return partCreator.plain("#".repeat(depth) + " "); } -function parseElement(n, partCreator, lastNode, state) { +interface IState { + listIndex: number[]; + listDepth?: number; +} + +function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLElement | undefined, state: IState) { switch (n.nodeName) { case "H1": case "H2": @@ -92,7 +100,7 @@ function parseElement(n, partCreator, lastNode, state) { case "H6": return parseHeader(n, partCreator); case "A": - return parseLink(n, partCreator); + return parseLink(n, partCreator); case "BR": return partCreator.newline(); case "EM": @@ -123,7 +131,7 @@ function parseElement(n, partCreator, lastNode, state) { break; } case "OL": - state.listIndex.push(n.start || 1); + state.listIndex.push((n).start || 1); // fallthrough case "UL": state.listDepth = (state.listDepth || 0) + 1; @@ -174,7 +182,7 @@ function prefixQuoteLines(isFirstNode, parts, partCreator) { } } -function parseHtmlMessage(html, partCreator, isQuotedMessage) { +function parseHtmlMessage(html: string, partCreator: PartCreator, isQuotedMessage: boolean) { // no nodes from parsing here should be inserted in the document, // as scripts in event handlers, etc would be executed then. // we're only taking text, so that is fine @@ -182,7 +190,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { const parts = []; let lastNode; let inQuote = isQuotedMessage; - const state = { + const state: IState = { listIndex: [], }; @@ -249,7 +257,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { return parts; } -export function parsePlainTextMessage(body, partCreator, isQuotedMessage) { +export function parsePlainTextMessage(body: string, partCreator: PartCreator, isQuotedMessage: boolean) { const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n, collapses \r\n const parts = lines.reduce((parts, line, i) => { if (isQuotedMessage) { @@ -265,7 +273,7 @@ export function parsePlainTextMessage(body, partCreator, isQuotedMessage) { return parts; } -export function parseEvent(event, partCreator, {isQuotedMessage = false} = {}) { +export function parseEvent(event: MatrixEvent, partCreator: PartCreator, {isQuotedMessage = false} = {}) { const content = event.getContent(); let parts; if (content.format === "org.matrix.custom.html") { diff --git a/src/editor/serialize.js b/src/editor/serialize.ts similarity index 82% rename from src/editor/serialize.js rename to src/editor/serialize.ts index 341d92d3c8..9ff1cfbd80 100644 --- a/src/editor/serialize.js +++ b/src/editor/serialize.ts @@ -1,6 +1,6 @@ /* Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -17,8 +17,9 @@ limitations under the License. import Markdown from '../Markdown'; import {makeGenericPermalink} from "../utils/permalinks/Permalinks"; +import EditorModel from "./model"; -export function mdSerialize(model) { +export function mdSerialize(model: EditorModel) { return model.parts.reduce((html, part) => { switch (part.type) { case "newline": @@ -35,7 +36,7 @@ export function mdSerialize(model) { }, ""); } -export function htmlSerializeIfNeeded(model, {forceHTML = false} = {}) { +export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { const md = mdSerialize(model); const parser = new Markdown(md); if (!parser.isPlainText() || forceHTML) { @@ -43,7 +44,7 @@ export function htmlSerializeIfNeeded(model, {forceHTML = false} = {}) { } } -export function textSerialize(model) { +export function textSerialize(model: EditorModel) { return model.parts.reduce((text, part) => { switch (part.type) { case "newline": @@ -60,11 +61,11 @@ export function textSerialize(model) { }, ""); } -export function containsEmote(model) { +export function containsEmote(model: EditorModel) { return startsWith(model, "/me "); } -export function startsWith(model, prefix) { +export function startsWith(model: EditorModel, prefix: string) { const firstPart = model.parts[0]; // part type will be "plain" while editing, // and "command" while composing a message. @@ -73,18 +74,18 @@ export function startsWith(model, prefix) { firstPart.text.startsWith(prefix); } -export function stripEmoteCommand(model) { +export function stripEmoteCommand(model: EditorModel) { // trim "/me " return stripPrefix(model, "/me "); } -export function stripPrefix(model, prefix) { +export function stripPrefix(model: EditorModel, prefix: string) { model = model.clone(); model.removeText({index: 0, offset: 0}, prefix.length); return model; } -export function unescapeMessage(model) { +export function unescapeMessage(model: EditorModel) { const {parts} = model; if (parts.length) { const firstPart = parts[0]; From f1def8b0de4eadaa6672cb87d3fa3ffc80d6c43b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 00:50:28 +0100 Subject: [PATCH 18/83] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- test/editor/deserialize-test.js | 2 +- test/editor/serialize-test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/editor/deserialize-test.js b/test/editor/deserialize-test.js index be8fe8aeab..4184552559 100644 --- a/test/editor/deserialize-test.js +++ b/test/editor/deserialize-test.js @@ -149,7 +149,7 @@ describe('editor/deserialize', function() { expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); }); it('user pill with displayname containing backslash', function() { - const html = "Hi Alice\!"; + const html = "Hi Alice\\!"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); expect(parts.length).toBe(3); expect(parts[0]).toStrictEqual({type: "plain", text: "Hi "}); diff --git a/test/editor/serialize-test.js b/test/editor/serialize-test.js index d5fb800600..a69e3598e3 100644 --- a/test/editor/serialize-test.js +++ b/test/editor/serialize-test.js @@ -43,13 +43,13 @@ describe('editor/serialize', function() { const html = htmlSerializeIfNeeded(model, {}); expect(html).toBe("hello world"); }); - it('displaynames ending in a backslash work', function () { + it('displaynames ending in a backslash work', function() { const pc = createPartCreator(); const model = new EditorModel([pc.userPill("Displayname\\", "@user:server")]); const html = htmlSerializeIfNeeded(model, {}); - expect(html).toBe("Displayname\"); + expect(html).toBe("Displayname\\"); }); - it('displaynames containing a closing square bracket work', function () { + it('displaynames containing a closing square bracket work', function() { const pc = createPartCreator(); const model = new EditorModel([pc.userPill("Displayname]", "@user:server")]); const html = htmlSerializeIfNeeded(model, {}); From cb10640eafed873a9dce27c9f357b33c22430b2e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 00:53:35 +0100 Subject: [PATCH 19/83] detslint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/editor/deserialize.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index 8878a8877c..6680029130 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -132,10 +132,10 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl } case "OL": state.listIndex.push((n).start || 1); - // fallthrough + /* falls through */ case "UL": state.listDepth = (state.listDepth || 0) + 1; - // fallthrough + /* falls through */ default: // don't textify block nodes we'll descend into if (!checkDescendInto(n)) { @@ -244,7 +244,7 @@ function parseHtmlMessage(html: string, partCreator: PartCreator, isQuotedMessag break; case "OL": state.listIndex.pop(); - // fallthrough + /* falls through */ case "UL": state.listDepth -= 1; break; @@ -259,7 +259,7 @@ function parseHtmlMessage(html: string, partCreator: PartCreator, isQuotedMessag export function parsePlainTextMessage(body: string, partCreator: PartCreator, isQuotedMessage: boolean) { const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n, collapses \r\n - const parts = lines.reduce((parts, line, i) => { + return lines.reduce((parts, line, i) => { if (isQuotedMessage) { parts.push(partCreator.plain(QUOTE_LINE_PREFIX)); } @@ -270,7 +270,6 @@ export function parsePlainTextMessage(body: string, partCreator: PartCreator, is } return parts; }, []); - return parts; } export function parseEvent(event: MatrixEvent, partCreator: PartCreator, {isQuotedMessage = false} = {}) { From 4454db30d67e9ff82848da0a6d242f1452e9f93a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 01:02:08 +0100 Subject: [PATCH 20/83] Escape opening square bracket too Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/editor/deserialize.ts | 2 +- src/editor/serialize.ts | 2 +- test/editor/deserialize-test.js | 8 ++++++++ test/editor/serialize-test.js | 6 ++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index 6680029130..5322f09f11 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -53,7 +53,7 @@ function parseLink(a: HTMLAnchorElement, partCreator: PartCreator) { if (href === a.textContent) { return partCreator.plain(a.textContent); } else { - return partCreator.plain(`[${a.textContent.replace(/[\\\]]/, c => "\\" + c)}](${href})`); + return partCreator.plain(`[${a.textContent.replace(/[[\\\]]/, c => "\\" + c)}](${href})`); } } } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 9ff1cfbd80..d501bdd47e 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -31,7 +31,7 @@ export function mdSerialize(model: EditorModel) { return html + part.text; case "room-pill": case "user-pill": - return html + `[${part.text.replace(/[\\\]]/, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; + return html + `[${part.text.replace(/[[\\\]]/, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; } }, ""); } diff --git a/test/editor/deserialize-test.js b/test/editor/deserialize-test.js index 4184552559..fb97d75752 100644 --- a/test/editor/deserialize-test.js +++ b/test/editor/deserialize-test.js @@ -156,6 +156,14 @@ describe('editor/deserialize', function() { expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice\\", resourceId: "@alice:hs.tld"}); expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); }); + it('user pill with displayname containing opening square bracket', function() { + const html = "Hi Alice[!"; + const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); + expect(parts.length).toBe(3); + expect(parts[0]).toStrictEqual({type: "plain", text: "Hi "}); + expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice[", resourceId: "@alice:hs.tld"}); + expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); + }); it('user pill with displayname containing closing square bracket', function() { const html = "Hi Alice]!"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); diff --git a/test/editor/serialize-test.js b/test/editor/serialize-test.js index a69e3598e3..a114f89de2 100644 --- a/test/editor/serialize-test.js +++ b/test/editor/serialize-test.js @@ -49,6 +49,12 @@ describe('editor/serialize', function() { const html = htmlSerializeIfNeeded(model, {}); expect(html).toBe("Displayname\\"); }); + it('displaynames containing an opening square bracket work', function() { + const pc = createPartCreator(); + const model = new EditorModel([pc.userPill("Displayname[", "@user:server")]); + const html = htmlSerializeIfNeeded(model, {}); + expect(html).toBe("Displayname["); + }); it('displaynames containing a closing square bracket work', function() { const pc = createPartCreator(); const model = new EditorModel([pc.userPill("Displayname]", "@user:server")]); From 9c1939b75679980f14f3ee550af00e65346c1fd0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 02:31:30 +0100 Subject: [PATCH 21/83] match all, not just first instance of tokens to escape Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/editor/deserialize.ts | 2 +- src/editor/serialize.ts | 2 +- test/editor/deserialize-test.js | 4 ++-- test/editor/serialize-test.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index 5322f09f11..48d1d98ae4 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -53,7 +53,7 @@ function parseLink(a: HTMLAnchorElement, partCreator: PartCreator) { if (href === a.textContent) { return partCreator.plain(a.textContent); } else { - return partCreator.plain(`[${a.textContent.replace(/[[\\\]]/, c => "\\" + c)}](${href})`); + return partCreator.plain(`[${a.textContent.replace(/[[\\\]]/g, c => "\\" + c)}](${href})`); } } } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index d501bdd47e..4d0b8cd03a 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -31,7 +31,7 @@ export function mdSerialize(model: EditorModel) { return html + part.text; case "room-pill": case "user-pill": - return html + `[${part.text.replace(/[[\\\]]/, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; + return html + `[${part.text.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; } }, ""); } diff --git a/test/editor/deserialize-test.js b/test/editor/deserialize-test.js index fb97d75752..2bd5d7e4c6 100644 --- a/test/editor/deserialize-test.js +++ b/test/editor/deserialize-test.js @@ -157,11 +157,11 @@ describe('editor/deserialize', function() { expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); }); it('user pill with displayname containing opening square bracket', function() { - const html = "Hi Alice[!"; + const html = "Hi Alice[[!"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); expect(parts.length).toBe(3); expect(parts[0]).toStrictEqual({type: "plain", text: "Hi "}); - expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice[", resourceId: "@alice:hs.tld"}); + expect(parts[1]).toStrictEqual({type: "user-pill", text: "Alice[[", resourceId: "@alice:hs.tld"}); expect(parts[2]).toStrictEqual({type: "plain", text: "!"}); }); it('user pill with displayname containing closing square bracket', function() { diff --git a/test/editor/serialize-test.js b/test/editor/serialize-test.js index a114f89de2..bd26ae91bb 100644 --- a/test/editor/serialize-test.js +++ b/test/editor/serialize-test.js @@ -51,9 +51,9 @@ describe('editor/serialize', function() { }); it('displaynames containing an opening square bracket work', function() { const pc = createPartCreator(); - const model = new EditorModel([pc.userPill("Displayname[", "@user:server")]); + const model = new EditorModel([pc.userPill("Displayname[[", "@user:server")]); const html = htmlSerializeIfNeeded(model, {}); - expect(html).toBe("Displayname["); + expect(html).toBe("Displayname[["); }); it('displaynames containing a closing square bracket work', function() { const pc = createPartCreator(); From e9c755e73fe87015719dc2f872d5526e22ec1f9a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 Apr 2020 10:25:05 +0100 Subject: [PATCH 22/83] Remove end-to-end message info option when cross-signing is used This end-to-end info dialog repeats info available elsewhere and has not been updated to make sense for cross-signing, triggers old verification methods, etc. For now, this hides the option to present a more consistent UI. Relates to https://github.com/vector-im/riot-web/issues/11752 --- src/components/views/messages/MessageActionBar.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index fbc864caf2..5516ff2146 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -26,6 +26,7 @@ import Modal from '../../../Modal'; import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; import RoomContext from "../../../contexts/RoomContext"; +import SettingsStore from '../../../settings/SettingsStore'; const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -48,7 +49,7 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo }; let e2eInfoCallback = null; - if (mxEvent.isEncrypted()) { + if (mxEvent.isEncrypted() && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { e2eInfoCallback = onCryptoClick; } From 346b2e8588752f68f70a8df47fa8000a6cb55e44 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 12:21:49 +0100 Subject: [PATCH 23/83] Login block on initialSync with spinners Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/auth/_AuthBody.scss | 18 ++++++++++++ src/components/structures/MatrixChat.js | 23 +++++---------- src/components/structures/auth/Login.js | 33 +++++++++++++++++++--- src/components/views/auth/PasswordLogin.js | 5 ++-- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 468a4b3d62..4b2d6b1bf1 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -119,6 +119,24 @@ limitations under the License. margin-right: 0; } +.mx_AuthBody_paddedFooter { + height: 80px; // height of the submit button + register link + padding-top: 28px; + text-align: center; + + .mx_AuthBody_paddedFooter_title { + margin-top: 16px; + font-size: $font-15px; + line-height: $font-24px; + } + + .mx_AuthBody_paddedFooter_subtitle { + margin-top: 8px; + font-size: $font-10px; + line-height: $font-14px; + } +} + .mx_AuthBody_changeFlow { display: block; text-align: center; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index da416142f8..61b72f650b 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1906,23 +1906,12 @@ export default createReactClass({ this._onLoggedIn(); } - // Test for the master cross-signing key in SSSS as a quick proxy for - // whether cross-signing has been set up on the account. We can't - // really continue until we know whether it's there or not so retry - // if this fails. - let masterKeyInStorage; - while (masterKeyInStorage === undefined) { - try { - masterKeyInStorage = !!await cli.getAccountDataFromServer("m.cross_signing.master"); - } catch (e) { - if (e.errcode === "M_NOT_FOUND") { - masterKeyInStorage = false; - } else { - console.warn("Secret storage account data check failed: retrying...", e); - } - } - } + this.setState({ pendingInitialSync: true }); + await this.firstSyncPromise.promise; + // 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. @@ -1939,6 +1928,7 @@ export default createReactClass({ } else { this._onLoggedIn(); } + this.setState({ pendingInitialSync: false }); return setLoggedInPromise; }, @@ -2060,6 +2050,7 @@ export default createReactClass({ const Login = sdk.getComponent('structures.auth.Login'); view = ( ); }, @@ -629,9 +635,11 @@ export default createReactClass({ render: function() { const Loader = sdk.getComponent("elements.Spinner"); + const InlineSpinner = sdk.getComponent("elements.InlineSpinner"); const AuthHeader = sdk.getComponent("auth.AuthHeader"); const AuthBody = sdk.getComponent("auth.AuthBody"); - const loader = this.isBusy() ?
: null; + const loader = this.isBusy() && !this.state.busyLoggingIn ? +
: null; const errorText = this.state.errorText; @@ -658,6 +666,25 @@ export default createReactClass({ ); } + let footer; + if (this.props.isSyncing || this.state.busyLoggingIn) { + footer =
+
+ + { this.props.isSyncing ? _t("Syncing...") : _t("Signing In...") } +
+ { this.props.isSyncing &&
+ {_t("If you've joined lots of rooms, this might take a while")} +
} +
; + } else { + footer = ( + + { _t('Create account') } + + ); + } + return ( @@ -670,9 +697,7 @@ export default createReactClass({ { serverDeadSection } { this.renderServerComponent() } { this.renderLoginComponentForStep() } - - { _t('Create account') } - + { footer } ); diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index e64b8360c3..d27e4a8259 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -43,6 +43,7 @@ export default class PasswordLogin extends React.Component { onPasswordChanged: PropTypes.func, loginIncorrect: PropTypes.bool, disableSubmit: PropTypes.bool, + hideSubmit: PropTypes.bool, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, }; @@ -332,11 +333,11 @@ export default class PasswordLogin extends React.Component { disabled={this.props.disableSubmit} /> {forgotPasswordJsx} - + /> }
); From 76ce4081cbc0769b0eb702dab26ea539905bbf48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 12:31:27 +0100 Subject: [PATCH 24/83] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3c5be84f20..84c172ea4d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2102,6 +2102,9 @@ "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", + "Syncing...": "Syncing...", + "Signing In...": "Signing In...", + "If you've joined lots of rooms, this might take a while": "If you've joined lots of rooms, this might take a while", "Create account": "Create account", "Failed to fetch avatar URL": "Failed to fetch avatar URL", "Set a display name:": "Set a display name:", From 8b25223026d6406929f4d6ab7d6b6ab091b1007a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 13:45:12 +0100 Subject: [PATCH 25/83] fix onLoggedIn getting called twice Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 61b72f650b..519b39d436 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1902,13 +1902,19 @@ export default createReactClass({ const cli = MatrixClientPeg.get(); // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` // because the client hasn't been started yet. - if (!isCryptoAvailable()) { + const cryptoAvailable = isCryptoAvailable(); + if (!cryptoAvailable) { this._onLoggedIn(); } this.setState({ pendingInitialSync: true }); await this.firstSyncPromise.promise; + if (!cryptoAvailable) { + this.setState({ pendingInitialSync: false }); + 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"); From ac26719b691493e339f2da7c1cf3367a14d464c5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 14:07:37 +0100 Subject: [PATCH 26/83] disable password reset button when spinning Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/structures/auth/_Login.scss | 10 ++++++++++ src/components/structures/auth/Login.js | 2 +- src/components/views/auth/PasswordLogin.js | 21 +++++++++++++-------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 4ce90cc6bd..02436833a2 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -89,3 +89,13 @@ limitations under the License. .mx_Login_underlinedServerName { border-bottom: 1px dashed $accent-color; } + +div.mx_AccessibleButton_kind_link.mx_Login_forgot { + // style it as a link + font-size: inherit; + padding: 0; + + &.mx_AccessibleButton_disabled { + cursor: not-allowed; + } +} diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 93c93fd5ab..851ea30ba0 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -599,7 +599,7 @@ export default createReactClass({ loginIncorrect={this.state.loginIncorrect} serverConfig={this.props.serverConfig} disableSubmit={this.isBusy()} - hideSubmit={this.props.isSyncing || this.state.busyLoggingIn} + busy={this.props.isSyncing || this.state.busyLoggingIn} /> ); }, diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index d27e4a8259..aeaa91845b 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -23,6 +23,7 @@ import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; +import AccessibleButton from "../elements/AccessibleButton"; /** * A pure UI component which displays a username/password form. @@ -43,8 +44,8 @@ export default class PasswordLogin extends React.Component { onPasswordChanged: PropTypes.func, loginIncorrect: PropTypes.bool, disableSubmit: PropTypes.bool, - hideSubmit: PropTypes.bool, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, + busy: PropTypes.bool, }; static defaultProps = { @@ -266,12 +267,16 @@ export default class PasswordLogin extends React.Component { if (this.props.onForgotPasswordClick) { forgotPasswordJsx = {_t('Not sure of your password? Set a new one', {}, { - a: sub => - {sub} - , + a: sub => ( + + {sub} + + ), })} ; } @@ -333,7 +338,7 @@ export default class PasswordLogin extends React.Component { disabled={this.props.disableSubmit} /> {forgotPasswordJsx} - { !this.props.hideSubmit && Date: Wed, 15 Apr 2020 14:19:47 +0100 Subject: [PATCH 27/83] disable language dropdown too Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/elements/_Dropdown.scss | 4 ++++ src/components/structures/auth/Login.js | 2 +- src/components/views/auth/AuthHeader.js | 7 ++++++- src/components/views/auth/LanguageSelector.js | 6 ++++-- src/components/views/elements/LanguageDropdown.js | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/res/css/views/elements/_Dropdown.scss b/res/css/views/elements/_Dropdown.scss index 0dd9656c9c..32a68d5252 100644 --- a/res/css/views/elements/_Dropdown.scss +++ b/res/css/views/elements/_Dropdown.scss @@ -33,6 +33,10 @@ limitations under the License. user-select: none; } +.mx_Dropdown_input.mx_AccessibleButton_disabled { + cursor: not-allowed; +} + .mx_Dropdown_input:focus { border-color: $input-focused-border-color; } diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 851ea30ba0..5d3cb69417 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -687,7 +687,7 @@ export default createReactClass({ return ( - +

{_t('Sign in')} diff --git a/src/components/views/auth/AuthHeader.js b/src/components/views/auth/AuthHeader.js index 133fd41359..6e787ba77c 100644 --- a/src/components/views/auth/AuthHeader.js +++ b/src/components/views/auth/AuthHeader.js @@ -16,12 +16,17 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; export default createReactClass({ displayName: 'AuthHeader', + propTypes: { + disableLanguageSelector: PropTypes.bool, + }, + render: function() { const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo'); const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector'); @@ -29,7 +34,7 @@ export default createReactClass({ return (
- +
); }, diff --git a/src/components/views/auth/LanguageSelector.js b/src/components/views/auth/LanguageSelector.js index 99578d4504..83db5d225b 100644 --- a/src/components/views/auth/LanguageSelector.js +++ b/src/components/views/auth/LanguageSelector.js @@ -28,12 +28,14 @@ function onChange(newLang) { } } -export default function LanguageSelector() { +export default function LanguageSelector({disabled}) { if (SdkConfig.get()['disable_login_language_selector']) return
; const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown'); - return ; } diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js index 18a7e95e85..e37109caff 100644 --- a/src/components/views/elements/LanguageDropdown.js +++ b/src/components/views/elements/LanguageDropdown.js @@ -114,6 +114,7 @@ export default class LanguageDropdown extends React.Component { searchEnabled={true} value={value} label={_t("Language Dropdown")} + disabled={this.props.disabled} > { options } ; From 8c2c777e2bec06fa01e4b06088f687870fa4b362 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 Apr 2020 17:37:32 +0200 Subject: [PATCH 28/83] disable reemitting on the client for events mapped from the indexer --- src/indexing/EventIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 1226b84b5b..e4e8f26031 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -383,7 +383,7 @@ export default class EventIndex extends EventEmitter { // We have a checkpoint, let us fetch some messages, again, very // conservatively to not bother our homeserver too much. - const eventMapper = client.getEventMapper(); + const eventMapper = client.getEventMapper({preventReEmit: true}); // TODO we need to ensure to use member lazy loading with this // request so we get the correct profiles. let res; From 49e1e6e59fa057f11aeaef08b1300d2b91a30e0b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 17:31:45 +0100 Subject: [PATCH 29/83] null-guard MatrixClientPeg in RoomViewStore Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/stores/RoomViewStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index b32e088a76..841734dfb7 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -159,7 +159,7 @@ class RoomViewStore extends Store { } case 'sync_state': this._setState({ - matrixClientIsReady: MatrixClientPeg.get().isInitialSyncComplete(), + matrixClientIsReady: MatrixClientPeg.get() && MatrixClientPeg.get().isInitialSyncComplete(), }); break; } From a37ecbbb34ef83ef6f38bae2895a95c325401cfe Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 15 Apr 2020 19:24:33 +0100 Subject: [PATCH 30/83] update console.error --- src/utils/strings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/strings.ts b/src/utils/strings.ts index 7d1fa0049d..5856682445 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -49,7 +49,7 @@ export async function copyPlaintext(text: string): Promise { return successful; } } catch (e) { - console.error(e); + console.error("copyPlaintext failed", e); } return false; } From b4e2daaf46edfac87f1587b95dc63285a88e14fc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 15 Apr 2020 13:18:42 -0600 Subject: [PATCH 31/83] Convert cross-signing feature flag to setting This is intended as a temporary measure until we're comfortable with removing the flag entirely. --- src/DeviceListener.js | 2 +- src/KeyRequestHandler.js | 4 ++-- .../views/dialogs/keybackup/CreateKeyBackupDialog.js | 2 +- src/components/structures/MatrixChat.js | 8 ++++---- src/components/structures/RightPanel.js | 4 ++-- src/components/structures/RoomView.js | 2 +- src/components/views/dialogs/CreateRoomDialog.js | 4 ++-- src/components/views/dialogs/DeviceVerifyDialog.js | 2 +- src/components/views/dialogs/InviteDialog.js | 2 +- src/components/views/messages/MessageActionBar.js | 2 +- src/components/views/right_panel/UserInfo.js | 10 +++++----- src/components/views/rooms/E2EIcon.js | 4 ++-- src/components/views/rooms/EventTile.js | 2 +- src/components/views/rooms/MemberTile.js | 2 +- src/components/views/rooms/MessageComposer.js | 2 +- src/components/views/rooms/RoomBreadcrumbs.js | 2 +- src/components/views/rooms/RoomHeader.js | 2 +- src/components/views/rooms/RoomTile.js | 6 +++--- src/components/views/settings/KeyBackupPanel.js | 2 +- .../views/settings/tabs/user/LabsUserSettingsTab.js | 1 + .../settings/tabs/user/SecurityUserSettingsTab.js | 2 +- src/createRoom.js | 2 +- src/settings/Settings.js | 4 ++-- src/verification.js | 2 +- 24 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 21c844e11c..3201e4af45 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -124,7 +124,7 @@ export default class DeviceListener { const cli = MatrixClientPeg.get(); if ( - !SettingsStore.isFeatureEnabled("feature_cross_signing") || + !SettingsStore.getValue("feature_cross_signing") || !await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") ) return; diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js index 30f3b7d50e..ceaff0c54d 100644 --- a/src/KeyRequestHandler.js +++ b/src/KeyRequestHandler.js @@ -35,7 +35,7 @@ export default class KeyRequestHandler { handleKeyRequest(keyRequest) { // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { return; } @@ -70,7 +70,7 @@ export default class KeyRequestHandler { handleKeyRequestCancellation(cancellation) { // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { return; } diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 3a480a2579..55419cecff 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -77,7 +77,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { async componentDidMount() { const cli = MatrixClientPeg.get(); const secureSecretStorage = ( - SettingsStore.isFeatureEnabled("feature_cross_signing") && + SettingsStore.getValue("feature_cross_signing") && await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") ); this.setState({ secureSecretStorage }); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 519b39d436..1293ccc7e9 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1506,7 +1506,7 @@ export default createReactClass({ }); cli.on("crypto.verification.request", request => { - const isFlagOn = SettingsStore.isFeatureEnabled("feature_cross_signing"); + 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"}); @@ -1556,7 +1556,7 @@ export default createReactClass({ // changing colour. More advanced behaviour will come once // we implement more settings. cli.setGlobalErrorOnUnknownDevices( - !SettingsStore.isFeatureEnabled("feature_cross_signing"), + !SettingsStore.getValue("feature_cross_signing"), ); } }, @@ -1921,10 +1921,10 @@ export default createReactClass({ if (masterKeyInStorage) { // Auto-enable cross-signing for the new session when key found in // secret storage. - SettingsStore.setFeatureEnabled("feature_cross_signing", true); + SettingsStore.setValue("feature_cross_signing", null, SettingLevel.DEVICE, true); this.setStateForNewView({ view: VIEWS.COMPLETE_SECURITY }); } else if ( - SettingsStore.isFeatureEnabled("feature_cross_signing") && + SettingsStore.getValue("feature_cross_signing") && await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") ) { // This will only work if the feature is set to 'enable' in the config, diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 3c97d2f4ae..f5bdfdf40d 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -219,7 +219,7 @@ export default class RightPanel extends React.Component { break; case RIGHT_PANEL_PHASES.RoomMemberInfo: case RIGHT_PANEL_PHASES.EncryptionPanel: - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { const onClose = () => { dis.dispatch({ action: "view_user", @@ -246,7 +246,7 @@ export default class RightPanel extends React.Component { panel = ; break; case RIGHT_PANEL_PHASES.GroupMemberInfo: - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { const onClose = () => { dis.dispatch({ action: "view_user", diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 78bd34bf7f..179e0aa2e9 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -822,7 +822,7 @@ export default createReactClass({ }); return; } - if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (!SettingsStore.getValue("feature_cross_signing")) { room.hasUnverifiedDevices().then((hasUnverifiedDevices) => { this.setState({ e2eStatus: hasUnverifiedDevices ? "warning" : "verified", diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index f18e28b85e..fa8c7dd30e 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -65,7 +65,7 @@ export default createReactClass({ createOpts.creation_content = {'m.federate': false}; } - if (!this.state.isPublic && SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { opts.encryption = this.state.isEncrypted; } @@ -192,7 +192,7 @@ export default createReactClass({ } let e2eeSection; - if (!this.state.isPublic && SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { e2eeSection =

{ _t("You can’t disable this later. Bridges & most bots won’t work yet.") }

diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js index 39e391269c..a3f9430476 100644 --- a/src/components/views/dialogs/DeviceVerifyDialog.js +++ b/src/components/views/dialogs/DeviceVerifyDialog.js @@ -131,7 +131,7 @@ export default class DeviceVerifyDialog extends React.Component { } else { this._verifier = request.verifier; } - } else if (verifyingOwnDevice && SettingsStore.isFeatureEnabled("feature_cross_signing")) { + } else if (verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) { this._request = await client.requestVerification(this.props.userId, [ verificationMethods.SAS, SHOW_QR_CODE_METHOD, diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index f0d5443cac..a46fa0df07 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -574,7 +574,7 @@ export default class InviteDialog extends React.PureComponent { const createRoomOptions = {inlineErrors: true}; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { // Check whether all users have uploaded device keys before. // If so, enable encryption in the new room. const client = MatrixClientPeg.get(); diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 5516ff2146..0cde90e417 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -49,7 +49,7 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo }; let e2eInfoCallback = null; - if (mxEvent.isEncrypted() && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (mxEvent.isEncrypted() && !SettingsStore.getValue("feature_cross_signing")) { e2eInfoCallback = onCryptoClick; } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index abe54b355e..862e4f7897 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -63,7 +63,7 @@ const _disambiguateDevices = (devices) => { }; export const getE2EStatus = (cli, userId, devices) => { - if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (!SettingsStore.getValue("feature_cross_signing")) { const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); return hasUnverifiedDevice ? "warning" : "verified"; } @@ -111,7 +111,7 @@ async function openDMForUser(matrixClient, userId) { dmUserId: userId, }; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + 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]); @@ -166,7 +166,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.isFeatureEnabled("feature_cross_signing")) ? + const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified(); @@ -237,7 +237,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.isFeatureEnabled("feature_cross_signing")) ? + const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified(); @@ -1298,7 +1298,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.isFeatureEnabled("feature_cross_signing") && + const canVerify = SettingsStore.getValue("feature_cross_signing") && homeserverSupportsCrossSigning && !userVerified && !isMe; const setUpdating = (updating) => { diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index a2c99fad99..9189cfdf26 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -20,7 +20,7 @@ import PropTypes from "prop-types"; import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; -import {useFeatureEnabled} from "../../../hooks/useSettings"; +import {useFeatureEnabled, useSettingValue} from "../../../hooks/useSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Tooltip from "../elements/Tooltip"; @@ -62,7 +62,7 @@ const E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => { }, className); let e2eTitle; - const crossSigning = useFeatureEnabled("feature_cross_signing"); + const crossSigning = useSettingValue("feature_cross_signing"); if (crossSigning && isUser) { e2eTitle = crossSigningUserTitles[status]; } else if (crossSigning && !isUser) { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 75fbe5caa3..f67877373e 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -323,7 +323,7 @@ export default createReactClass({ // 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.isFeatureEnabled("feature_cross_signing")) { + if (!SettingsStore.getValue("feature_cross_signing")) { this.setState({ verified: E2E_STATE.WARNING, }, this.props.onHeightChanged); diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index bf2a1bee23..d830624f8a 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -56,7 +56,7 @@ export default createReactClass({ } } - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { const { roomId } = this.props.member; if (roomId) { const isRoomEncrypted = cli.isRoomEncrypted(roomId); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index c86bcb2ff0..4749742a7d 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -270,7 +270,7 @@ export default class MessageComposer extends React.Component { } renderPlaceholderText() { - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { if (this.state.isQuoting) { if (this.props.e2eStatus) { return _t('Send an encrypted reply…'); diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index 1d433c9a40..09228c454b 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -364,7 +364,7 @@ export default class RoomBreadcrumbs extends React.Component { } let dmIndicator; - if (this._isDmRoom(r.room) && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (this._isDmRoom(r.room) && !SettingsStore.getValue("feature_cross_signing")) { dmIndicator = ; } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index d264b087a0..a34ade365b 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -155,7 +155,7 @@ export default createReactClass({ if (!cli.isRoomEncrypted(this.props.room.roomId)) { return; } - if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (!SettingsStore.getValue("feature_cross_signing")) { return; } @@ -488,7 +488,7 @@ export default createReactClass({ let dmOnline; /* Post-cross-signing we don't show DM indicators at all, instead relying on user context to let them know when that is. */ - if (dmUserId && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (dmUserId && !SettingsStore.getValue("feature_cross_signing")) { dmIndicator = ; } diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 9d60ed1188..5548768221 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -326,7 +326,7 @@ export default class KeyBackupPanel extends React.PureComponent {
); - if (this.state.backupKeyStored && !SettingsStore.isFeatureEnabled("feature_cross_signing")) { + 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 " + diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index 3e69107159..fe160032ff 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -62,6 +62,7 @@ export default class LabsUserSettingsTab extends React.Component {

{flags} + diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 3dca6e2490..1cde5d6f87 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -270,7 +270,7 @@ export default class SecurityUserSettingsTab extends React.Component { // can remove this. const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); let crossSigning; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { crossSigning = (
{_t("Cross-signing")} diff --git a/src/createRoom.js b/src/createRoom.js index 66d4d1908e..a39d2c2216 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -227,7 +227,7 @@ export async function ensureDMExists(client, userId) { roomId = existingDMRoom.roomId; } else { let encryption; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (SettingsStore.getValue("feature_cross_signing")) { encryption = canEncryptToAllUsers(client, [userId]); } roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false}); diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 0d72017878..b539591efd 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -152,10 +152,10 @@ export const SETTINGS = { default: null, }, "feature_cross_signing": { - isFeature: true, + //isFeature: true, displayName: _td("Enable cross-signing to verify per-user instead of per-session (in development)"), supportedLevels: LEVELS_FEATURE, - default: false, + default: true, }, "feature_event_indexing": { isFeature: true, diff --git a/src/verification.js b/src/verification.js index e00e5e05fa..ca839940e5 100644 --- a/src/verification.js +++ b/src/verification.js @@ -27,7 +27,7 @@ import {verificationMethods} from 'matrix-js-sdk/src/crypto'; async function enable4SIfNeeded() { const cli = MatrixClientPeg.get(); - if (!cli.isCryptoEnabled() || !SettingsStore.isFeatureEnabled("feature_cross_signing")) { + if (!cli.isCryptoEnabled() || !SettingsStore.getValue("feature_cross_signing")) { return false; } const usk = cli.getCrossSigningId("user_signing"); From bedffdb8e2718fa6748fbe87a188b82bac232b2e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 01:46:32 +0100 Subject: [PATCH 32/83] Fix Message Context Menu options not displaying: block Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/context_menus/_MessageContextMenu.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index d15d566bdb..2ecb93e734 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -19,6 +19,7 @@ limitations under the License. } .mx_MessageContextMenu_field { + display: block; padding: 3px 6px 3px 6px; cursor: pointer; white-space: nowrap; From 15075e4c10a08e19d17bdec6420b93c10a108a5a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 Apr 2020 10:36:19 +0200 Subject: [PATCH 33/83] load twemoji in matrixclient rather than loggedinview --- src/components/structures/LoggedInView.tsx | 3 --- src/components/structures/MatrixChat.js | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 9de2aac8e9..f90ae18f00 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -25,7 +25,6 @@ import { DragDropContext } from 'react-beautiful-dnd'; import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard'; import PageTypes from '../../PageTypes'; import CallMediaHandler from '../../CallMediaHandler'; -import { fixupColorFonts } from '../../utils/FontManager'; import * as sdk from '../../index'; import dis from '../../dispatcher'; import sessionStore from '../../stores/SessionStore'; @@ -166,8 +165,6 @@ class LoggedInView extends React.PureComponent { this._matrixClient.on("sync", this.onSync); this._matrixClient.on("RoomState.events", this.onRoomStateEvents); - fixupColorFonts(); - this._roomView = React.createRef(); this._resizeContainer = React.createRef(); } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 519b39d436..4b269ceae4 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -66,6 +66,7 @@ import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { defer } from "../../utils/promise"; import ToastStore from "../../stores/ToastStore"; import * as StorageManager from "../../utils/StorageManager"; +import { fixupColorFonts } from '../../utils/FontManager'; /** constants for MatrixChat.state.view */ export const VIEWS = { @@ -244,6 +245,8 @@ export default createReactClass({ this._pageChanging = false; + // load emoji font + fixupColorFonts(); // check we have the right tint applied for this theme. // N.B. we don't call the whole of setTheme() here as we may be // racing with the theme CSS download finishing from index.js From 33391a31105d601ad76eb0da98ab8afaf810a06b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 Apr 2020 09:25:18 +0000 Subject: [PATCH 34/83] Revert "Fix: load Twemoji before login so complete security gets the right emojis during SAS" --- src/components/structures/LoggedInView.tsx | 3 +++ src/components/structures/MatrixChat.js | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index f90ae18f00..9de2aac8e9 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -25,6 +25,7 @@ import { DragDropContext } from 'react-beautiful-dnd'; import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard'; import PageTypes from '../../PageTypes'; import CallMediaHandler from '../../CallMediaHandler'; +import { fixupColorFonts } from '../../utils/FontManager'; import * as sdk from '../../index'; import dis from '../../dispatcher'; import sessionStore from '../../stores/SessionStore'; @@ -165,6 +166,8 @@ class LoggedInView extends React.PureComponent { this._matrixClient.on("sync", this.onSync); this._matrixClient.on("RoomState.events", this.onRoomStateEvents); + fixupColorFonts(); + this._roomView = React.createRef(); this._resizeContainer = React.createRef(); } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4b269ceae4..519b39d436 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -66,7 +66,6 @@ import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { defer } from "../../utils/promise"; import ToastStore from "../../stores/ToastStore"; import * as StorageManager from "../../utils/StorageManager"; -import { fixupColorFonts } from '../../utils/FontManager'; /** constants for MatrixChat.state.view */ export const VIEWS = { @@ -245,8 +244,6 @@ export default createReactClass({ this._pageChanging = false; - // load emoji font - fixupColorFonts(); // check we have the right tint applied for this theme. // N.B. we don't call the whole of setTheme() here as we may be // racing with the theme CSS download finishing from index.js From 3933c4c31cb19cc1d300a52228ff2a3d84171cb1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 Apr 2020 11:35:54 +0200 Subject: [PATCH 35/83] ensure twemoji font is loaded when showing SAS emojis --- src/components/views/verification/VerificationShowSas.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js index 0a8947f2c2..edf860c4c2 100644 --- a/src/components/views/verification/VerificationShowSas.js +++ b/src/components/views/verification/VerificationShowSas.js @@ -20,6 +20,7 @@ import { _t, _td } from '../../../languageHandler'; import {PendingActionSpinner} from "../right_panel/EncryptionInfo"; import AccessibleButton from "../elements/AccessibleButton"; import DialogButtons from "../elements/DialogButtons"; +import { fixupColorFonts } from '../../../utils/FontManager'; function capFirst(s) { return s.charAt(0).toUpperCase() + s.slice(1); @@ -44,6 +45,13 @@ export default class VerificationShowSas extends React.Component { }; } + componentWillMount() { + // As this component is also used before login (during complete security), + // also make sure we have a working emoji font to display the SAS emojis here. + // This is also done from LoggedInView. + fixupColorFonts(); + } + onMatchClick = () => { this.setState({ pending: true }); this.props.onDone(); From a9a2999ccc433bc19df083e6f8a551feca9b8fc3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 12:26:12 +0100 Subject: [PATCH 36/83] Use `recovery passphrase` and `recovery key` everywhere Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../keybackup/CreateKeyBackupDialog.js | 14 +++--- .../keybackup/NewRecoveryMethodDialog.js | 3 +- .../keybackup/RecoveryMethodRemovedDialog.js | 2 +- .../CreateSecretStorageDialog.js | 16 +++---- .../structures/auth/SetupEncryptionBody.js | 2 +- .../keybackup/RestoreKeyBackupDialog.js | 4 +- .../AccessSecretStorageDialog.js | 14 +++--- src/i18n/strings/en_EN.json | 47 +++++++++---------- 8 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 7e5e0afb79..29e68243ee 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -262,7 +262,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { let helpText; if (this.state.zxcvbnResult) { if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) { - helpText = _t("Great! This passphrase looks strong enough."); + helpText = _t("Great! This recovery passphrase looks strong enough."); } else { const suggestions = []; for (let i = 0; i < this.state.zxcvbnResult.feedback.suggestions.length; ++i) { @@ -287,7 +287,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { )}

{_t( "We'll store an encrypted copy of your keys on our server. " + - "Protect your backup with a passphrase to keep it secure.", + "Protect your backup with a recovery passphrase to keep it secure.", )}

{_t("For maximum security, this should be different from your account password.")}

@@ -297,7 +297,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { onChange={this._onPassPhraseChange} value={this.state.passPhrase} className="mx_CreateKeyBackupDialog_passPhraseInput" - placeholder={_t("Enter a passphrase...")} + placeholder={_t("Enter a recovery passphrase...")} autoFocus={true} />
@@ -354,7 +354,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Please enter your passphrase a second time to confirm.", + "Please enter your recovery passphrase a second time to confirm.", )}

@@ -363,7 +363,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { onChange={this._onPassPhraseConfirmChange} value={this.state.passPhraseConfirm} className="mx_CreateKeyBackupDialog_passPhraseInput" - placeholder={_t("Repeat your passphrase...")} + placeholder={_t("Repeat your recovery passphrase...")} autoFocus={true} />
@@ -383,7 +383,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { return

{_t( "Your recovery key is a safety net - you can use it to restore " + - "access to your encrypted messages if you forget your passphrase.", + "access to your encrypted messages if you forget your recovery passphrase.", )}

{_t( "Keep a copy of it somewhere secure, like a password manager or even a safe.", @@ -479,7 +479,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { case PHASE_PASSPHRASE: return _t('Secure your backup with a passphrase'); case PHASE_PASSPHRASE_CONFIRM: - return _t('Confirm your passphrase'); + return _t('Confirm your recovery passphrase'); case PHASE_OPTOUT_CONFIRM: return _t('Warning!'); case PHASE_SHOWKEY: diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js index 6588ff5191..d41c8c53ef 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js @@ -57,8 +57,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { ; const newMethodDetected =

{_t( - "A new recovery passphrase and key for Secure " + - "Messages have been detected.", + "A new recovery passphrase and recovery key for Secure Messages have been detected.", )}

; const hackWarning =

{_t( diff --git a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js b/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js index c5222dafd5..95fcb96967 100644 --- a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js +++ b/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js @@ -55,7 +55,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent { >

{_t( - "This session has detected that your recovery passphrase and key " + + "This session has detected that your recovery passphrase and recovery key " + "for Secure Messages have been removed.", )}

{_t( diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index cfad49c38d..b876ed57b5 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -462,7 +462,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { let helpText; if (this.state.zxcvbnResult) { if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) { - helpText = _t("Great! This passphrase looks strong enough."); + helpText = _t("Great! This recovery passphrase looks strong enough."); } else { // We take the warning from zxcvbn or failing that, the first // suggestion. In practice The first is generally the most relevant @@ -491,7 +491,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { "granting them access to encrypted messages and marking them as trusted for other users.", )}

{_t( - "Secure your encryption keys with a passphrase. For maximum security " + + "Secure your encryption keys with a recovery passphrase. For maximum security " + "this should be different to your account password:", )}

@@ -501,7 +501,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { className="mx_CreateSecretStorageDialog_passPhraseField" onChange={this._onPassPhraseChange} value={this.state.passPhrase} - label={_t("Enter a passphrase")} + label={_t("Enter a recovery passphrase")} autoFocus={true} autoComplete="new-password" /> @@ -512,7 +512,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
@@ -569,7 +569,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Enter your passphrase a second time to confirm it.", + "Enter your recovery passphrase a second time to confirm it.", )}

@@ -604,7 +604,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return

{_t( "Your recovery key is a safety net - you can use it to restore " + - "access to your encrypted messages if you forget your passphrase.", + "access to your encrypted messages if you forget your recovery passphrase.", )}

{_t( "Keep a copy of it somewhere secure, like a password manager or even a safe.", @@ -703,7 +703,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_PASSPHRASE: return _t('Set up encryption'); case PHASE_PASSPHRASE_CONFIRM: - return _t('Confirm passphrase'); + return _t('Confirm recovery passphrase'); case PHASE_CONFIRM_SKIP: return _t('Are you sure?'); case PHASE_SHOWKEY: diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index a982957ed0..e6302a4685 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -116,7 +116,7 @@ export default class SetupEncryptionBody extends React.Component { "granting it access to encrypted messages.", )}

{_t( - "If you can’t access one, ", + "If you can’t access one, ", {}, { button: sub =>

{_t( - "Backup could not be decrypted with this key: " + + "Backup could not be decrypted with this recovery key: " + "please verify that you entered the correct recovery key.", )}

; @@ -291,7 +291,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { title = _t("Incorrect recovery passphrase"); content =

{_t( - "Backup could not be decrypted with this passphrase: " + + "Backup could not be decrypted with this recovery passphrase: " + "please verify that you entered the correct recovery passphrase.", )}

; diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index e3a7d7f532..4c0c608ad6 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -119,14 +119,13 @@ export default class AccessSecretStorageDialog extends React.PureComponent { if (hasPassphrase && !this.state.forceRecoveryKey) { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - title = _t("Enter secret storage passphrase"); + title = _t("Enter recovery passphrase"); let keyStatus; if (this.state.keyMatches === false) { keyStatus =
{"\uD83D\uDC4E "}{_t( - "Unable to access secret storage. Please verify that you " + - "entered the correct passphrase.", + "Unable to access. Please verify that you entered the correct recovery passphrase.", )}
; } else { @@ -141,7 +140,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { )}

{_t( "Access your secure message history and your cross-signing " + - "identity for verifying other sessions by entering your passphrase.", + "identity for verifying other sessions by entering your recovery passphrase.", )}

@@ -164,7 +163,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { /> {_t( - "If you've forgotten your passphrase you can "+ + "If you've forgotten your recovery passphrase you can "+ "use your recovery key or " + "set up new recovery options." , {}, { @@ -183,7 +182,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { })}
; } else { - title = _t("Enter secret storage recovery key"); + title = _t("Enter recovery key"); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -193,8 +192,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } else if (this.state.keyMatches === false) { keyStatus =
{"\uD83D\uDC4E "}{_t( - "Unable to access secret storage. Please verify that you " + - "entered the correct recovery key.", + "Unable to access. Please verify that you entered the correct recovery key.", )}
; } else if (this.state.recoveryKeyValid) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c172ea4d..dcd24b91de 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1778,32 +1778,30 @@ "Remember my selection for this widget": "Remember my selection for this widget", "Allow": "Allow", "Deny": "Deny", - "Enter secret storage passphrase": "Enter secret storage passphrase", - "Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.", + "Enter recovery passphrase": "Enter recovery passphrase", + "Unable to access. Please verify that you entered the correct recovery passphrase.": "Unable to access. Please verify that you entered the correct recovery passphrase.", "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.", - "Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase.", - "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.", - "Enter secret storage recovery key": "Enter secret storage recovery key", - "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", + "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.", + "Enter recovery key": "Enter recovery key", + "Unable to access. Please verify that you entered the correct recovery key.": "Unable to access. Please verify that you entered the correct recovery key.", "This looks like a valid recovery key!": "This looks like a valid recovery key!", "Not a valid recovery key": "Not a valid recovery key", "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.", "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", "Unable to load backup status": "Unable to load backup status", "Recovery key mismatch": "Recovery key mismatch", - "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", + "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.", "Incorrect recovery passphrase": "Incorrect recovery passphrase", - "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.", "Unable to restore backup": "Unable to restore backup", "No backup found!": "No backup found!", "Backup restored": "Backup restored", "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", - "Enter recovery passphrase": "Enter recovery passphrase", "Warning: you should only set up key backup from a trusted computer.": "Warning: you should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", - "Enter recovery key": "Enter recovery key", "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", @@ -2119,7 +2117,7 @@ "Registration Successful": "Registration Successful", "Create your account": "Create your account", "Use an existing session to verify this one, granting it access to encrypted messages.": "Use an existing session to verify this one, granting it access to encrypted messages.", - "If you can’t access one, ": "If you can’t access one, ", + "If you can’t access one, ": "If you can’t access one, ", "Use your other device to continue…": "Use your other device to continue…", "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", "Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.", @@ -2182,18 +2180,18 @@ "Restore": "Restore", "You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.", "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", - "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", + "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", "Set up encryption on this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Set up encryption on this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", - "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:", - "Enter a passphrase": "Enter a passphrase", - "Back up my encryption keys, securing them with the same passphrase": "Back up my encryption keys, securing them with the same passphrase", + "Secure your encryption keys with a recovery passphrase. For maximum security this should be different to your account password:": "Secure your encryption keys with a recovery passphrase. For maximum security this should be different to your account password:", + "Enter a recovery passphrase": "Enter a recovery passphrase", + "Back up my encryption keys, securing them with the same recovery passphrase": "Back up my encryption keys, securing them with the same recovery passphrase", "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "That doesn't match.": "That doesn't match.", "Go back to set it again.": "Go back to set it again.", - "Enter your passphrase a second time to confirm it.": "Enter your passphrase a second time to confirm it.", - "Confirm your passphrase": "Confirm your passphrase", - "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.", + "Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.", + "Confirm your recovery passphrase": "Confirm your recovery passphrase", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", "Your recovery key": "Your recovery key", "Copy": "Copy", @@ -2205,15 +2203,16 @@ "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", "Upgrade your encryption": "Upgrade your encryption", + "Confirm recovery passphrase": "Confirm recovery passphrase", "Make a copy of your recovery key": "Make a copy of your recovery key", "You're done!": "You're done!", "Unable to set up secret storage": "Unable to set up secret storage", "Retry": "Retry", - "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", + "We'll store an encrypted copy of your keys on our server. Protect your backup with a recovery passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a recovery passphrase to keep it secure.", "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", - "Enter a passphrase...": "Enter a passphrase...", - "Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.", - "Repeat your passphrase...": "Repeat your passphrase...", + "Enter a recovery passphrase...": "Enter a recovery passphrase...", + "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", + "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", "Set up Secure Message Recovery": "Set up Secure Message Recovery", @@ -2227,13 +2226,13 @@ "Set up": "Set up", "Don't ask again": "Don't ask again", "New Recovery Method": "New Recovery Method", - "A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.", + "A new recovery passphrase and recovery key for Secure Messages have been detected.": "A new recovery passphrase and recovery key for Secure Messages have been detected.", "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "This session is encrypting history using the new recovery method.": "This session is encrypting history using the new recovery method.", "Go to Settings": "Go to Settings", "Set up Secure Messages": "Set up Secure Messages", "Recovery Method Removed": "Recovery Method Removed", - "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "This session has detected that your recovery passphrase and key for Secure Messages have been removed.", + "This session has detected that your recovery passphrase and recovery key for Secure Messages have been removed.": "This session has detected that your recovery passphrase and recovery key for Secure Messages have been removed.", "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.", "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "If disabled, messages from encrypted rooms won't appear in search results.": "If disabled, messages from encrypted rooms won't appear in search results.", From f65c31383d0c2c1cd3c70446cab545a40e8e84bb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 12:52:35 +0100 Subject: [PATCH 37/83] Copy tweaks with Nad Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/CrossSigningManager.js | 2 +- .../views/dialogs/keybackup/CreateKeyBackupDialog.js | 2 +- .../dialogs/keybackup/NewRecoveryMethodDialog.js | 2 +- .../dialogs/keybackup/RecoveryMethodRemovedDialog.js | 2 +- .../secretstorage/AccessSecretStorageDialog.js | 6 ++---- src/i18n/strings/en_EN.json | 12 ++++++------ src/settings/Settings.js | 2 +- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 07ec776bd1..c37d0f8bf5 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -51,7 +51,7 @@ async function confirmToDismiss(name) { } 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."); + description = _t("If you cancel now, you won't complete your operation."); } const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 29e68243ee..b010987036 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -287,7 +287,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { )}

{_t( "We'll store an encrypted copy of your keys on our server. " + - "Protect your backup with a recovery passphrase to keep it secure.", + "Secure your backup with a recovery passphrase.", )}

{_t("For maximum security, this should be different from your account password.")}

diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js index d41c8c53ef..9e2264a960 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js @@ -57,7 +57,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { ; const newMethodDetected =

{_t( - "A new recovery passphrase and recovery key for Secure Messages have been detected.", + "A new recovery passphrase and key for Secure Messages have been detected.", )}

; const hackWarning =

{_t( diff --git a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js b/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js index 95fcb96967..c5222dafd5 100644 --- a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js +++ b/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js @@ -55,7 +55,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent { >

{_t( - "This session has detected that your recovery passphrase and recovery key " + + "This session has detected that your recovery passphrase and key " + "for Secure Messages have been removed.", )}

{_t( diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 4c0c608ad6..f2e7b7e704 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -134,8 +134,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

{_t( - "Warning: You should only access secret storage " + - "from a trusted computer.", {}, + "Warning: You should only do this on a trusted computer.", {}, { b: sub => {sub} }, )}

{_t( @@ -207,8 +206,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

{_t( - "Warning: You should only access secret storage " + - "from a trusted computer.", {}, + "Warning: You should only do this on a trusted computer.", {}, { b: sub => {sub} }, )}

{_t( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index dcd24b91de..4ee910a1b6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -70,7 +70,7 @@ "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.", + "If you cancel now, you won't complete your operation.": "If you cancel now, you won't complete your operation.", "Cancel entering passphrase?": "Cancel entering passphrase?", "Enter passphrase": "Enter passphrase", "Cancel": "Cancel", @@ -444,7 +444,7 @@ "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", "Show previews/thumbnails for images": "Show previews/thumbnails for images", "Enable message search in encrypted rooms": "Enable message search in encrypted rooms", - "Keep secret storage passphrase in memory for this session": "Keep secret storage passphrase in memory for this session", + "Keep recovery passphrase in memory for this session": "Keep recovery passphrase in memory for this session", "How fast should messages be downloaded.": "How fast should messages be downloaded.", "Manually verify all remote sessions": "Manually verify all remote sessions", "Collecting app version information": "Collecting app version information", @@ -1780,7 +1780,7 @@ "Deny": "Deny", "Enter recovery passphrase": "Enter recovery passphrase", "Unable to access. Please verify that you entered the correct recovery passphrase.": "Unable to access. Please verify that you entered the correct recovery passphrase.", - "Warning: You should only access secret storage from a trusted computer.": "Warning: You should only access secret storage from a trusted computer.", + "Warning: You should only do this on a trusted computer.": "Warning: You should only do this on a trusted computer.", "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.", "Enter recovery key": "Enter recovery key", @@ -2208,7 +2208,7 @@ "You're done!": "You're done!", "Unable to set up secret storage": "Unable to set up secret storage", "Retry": "Retry", - "We'll store an encrypted copy of your keys on our server. Protect your backup with a recovery passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a recovery passphrase to keep it secure.", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", "Enter a recovery passphrase...": "Enter a recovery passphrase...", "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", @@ -2226,13 +2226,13 @@ "Set up": "Set up", "Don't ask again": "Don't ask again", "New Recovery Method": "New Recovery Method", - "A new recovery passphrase and recovery key for Secure Messages have been detected.": "A new recovery passphrase and recovery key for Secure Messages have been detected.", + "A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.", "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "This session is encrypting history using the new recovery method.": "This session is encrypting history using the new recovery method.", "Go to Settings": "Go to Settings", "Set up Secure Messages": "Set up Secure Messages", "Recovery Method Removed": "Recovery Method Removed", - "This session has detected that your recovery passphrase and recovery key for Secure Messages have been removed.": "This session has detected that your recovery passphrase and recovery key for Secure Messages have been removed.", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "This session has detected that your recovery passphrase and key for Secure Messages have been removed.", "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.", "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "If disabled, messages from encrypted rooms won't appear in search results.": "If disabled, messages from encrypted rooms won't appear in search results.", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 0d72017878..317508ca86 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -516,7 +516,7 @@ export const SETTINGS = { }, "keepSecretStoragePassphraseForSession": { supportedLevels: ['device', 'config'], - displayName: _td("Keep secret storage passphrase in memory for this session"), + displayName: _td("Keep recovery passphrase in memory for this session"), default: false, }, "crawlerSleepTime": { From 1fb0f39c84e3a6340ad841acefe982db00ebfc1a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 13:23:01 +0100 Subject: [PATCH 38/83] Replace `Verify this session` and `Complete security` Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/auth/CompleteSecurity.js | 12 ++++++------ .../views/dialogs/VerificationRequestDialog.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 06cece0af2..3ef3140298 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -59,17 +59,17 @@ export default class CompleteSecurity extends React.Component { let title; if (phase === PHASE_INTRO) { - icon = ; - title = _t("Complete security"); + icon = ; + title = _t("Verify session"); } else if (phase === PHASE_DONE) { - icon = ; + icon = ; title = _t("Session verified"); } else if (phase === PHASE_CONFIRM_SKIP) { - icon = ; + icon = ; title = _t("Are you sure?"); } else if (phase === PHASE_BUSY) { - icon = ; - title = _t("Complete security"); + icon = ; + title = _t("Verify session"); } else { throw new Error(`Unknown phase ${phase}`); } diff --git a/src/components/views/dialogs/VerificationRequestDialog.js b/src/components/views/dialogs/VerificationRequestDialog.js index 7ff2cb8f50..dcb9168bda 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.js +++ b/src/components/views/dialogs/VerificationRequestDialog.js @@ -48,7 +48,7 @@ export default class VerificationRequestDialog extends React.Component { const member = this.props.member || otherUserId && MatrixClientPeg.get().getUser(otherUserId); const title = request && request.isSelfVerification ? - _t("Verify this session") : _t("Verification Request"); + _t("Verify session") : _t("Verification Request"); return Date: Thu, 16 Apr 2020 13:28:12 +0100 Subject: [PATCH 39/83] further tweaks by Matthew Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/auth/CompleteSecurity.js | 4 ++-- src/components/views/dialogs/VerificationRequestDialog.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 3ef3140298..95128c0be9 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -60,7 +60,7 @@ export default class CompleteSecurity extends React.Component { if (phase === PHASE_INTRO) { icon = ; - title = _t("Verify session"); + title = _t("Verify this session"); } else if (phase === PHASE_DONE) { icon = ; title = _t("Session verified"); @@ -69,7 +69,7 @@ export default class CompleteSecurity extends React.Component { title = _t("Are you sure?"); } else if (phase === PHASE_BUSY) { icon = ; - title = _t("Verify session"); + title = _t("Verify this session"); } else { throw new Error(`Unknown phase ${phase}`); } diff --git a/src/components/views/dialogs/VerificationRequestDialog.js b/src/components/views/dialogs/VerificationRequestDialog.js index dcb9168bda..3a6e9a2d10 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.js +++ b/src/components/views/dialogs/VerificationRequestDialog.js @@ -48,7 +48,7 @@ export default class VerificationRequestDialog extends React.Component { const member = this.props.member || otherUserId && MatrixClientPeg.get().getUser(otherUserId); const title = request && request.isSelfVerification ? - _t("Verify session") : _t("Verification Request"); + _t("Verify other session") : _t("Verification Request"); return Date: Thu, 16 Apr 2020 13:34:18 +0100 Subject: [PATCH 40/83] update Create SSSS Dialog copy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index b876ed57b5..b16812a3c0 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -487,12 +487,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return

{_t( - "Set up encryption on this session to allow it to verify other sessions, " + - "granting them access to encrypted messages and marking them as trusted for other users.", - )}

-

{_t( - "Secure your encryption keys with a recovery passphrase. For maximum security " + - "this should be different to your account password:", + "Set a recovery passphrase to secure encrypted information and recover it if you log out. " + + "This should be different to your account password:", )}

@@ -512,7 +508,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
From 8c8509a6035d48e98299e20494043db6f52a20fe Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 13:48:52 +0100 Subject: [PATCH 41/83] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4ee910a1b6..ed1fbf6831 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1772,6 +1772,7 @@ "Upload %(count)s other files|one": "Upload %(count)s other file", "Cancel All": "Cancel All", "Upload Error": "Upload Error", + "Verify other session": "Verify other session", "Verification Request": "Verification Request", "A widget would like to verify your identity": "A widget would like to verify your identity", "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.", @@ -2062,7 +2063,6 @@ "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", "Could not load user profile": "Could not load user profile", - "Complete security": "Complete security", "Session verified": "Session verified", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", @@ -2181,10 +2181,9 @@ "You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.", "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", - "Set up encryption on this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Set up encryption on this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", - "Secure your encryption keys with a recovery passphrase. For maximum security this should be different to your account password:": "Secure your encryption keys with a recovery passphrase. For maximum security this should be different to your account password:", + "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:", "Enter a recovery passphrase": "Enter a recovery passphrase", - "Back up my encryption keys, securing them with the same recovery passphrase": "Back up my encryption keys, securing them with the same recovery passphrase", + "Back up encryption keys": "Back up encryption keys", "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "That doesn't match.": "That doesn't match.", From af59c1b5b316024cea882cc8edb972a3abf45a76 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 14:02:32 +0100 Subject: [PATCH 42/83] iterate copy some more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/dialogs/secretstorage/CreateSecretStorageDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index b16812a3c0..0cc145fd96 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -508,7 +508,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ed1fbf6831..cf02e51608 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2183,7 +2183,7 @@ "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:", "Enter a recovery passphrase": "Enter a recovery passphrase", - "Back up encryption keys": "Back up encryption keys", + "Back up encrypted message keys": "Back up encrypted message keys", "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "That doesn't match.": "That doesn't match.", From e942a3552550cc5031cc4822b0bfcf99cf65a286 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 14:09:41 +0100 Subject: [PATCH 43/83] Fixxy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../dialogs/secretstorage/AccessSecretStorageDialog.js | 6 ++++-- src/i18n/strings/en_EN.json | 4 ++-- .../views/dialogs/AccessSecretStorageDialog-test.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index f2e7b7e704..7d7edffcbf 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -125,7 +125,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent { if (this.state.keyMatches === false) { keyStatus =
{"\uD83D\uDC4E "}{_t( - "Unable to access. Please verify that you entered the correct recovery passphrase.", + "Unable to access secret storage. " + + "Please verify that you entered the correct recovery passphrase.", )}
; } else { @@ -191,7 +192,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } else if (this.state.keyMatches === false) { keyStatus =
{"\uD83D\uDC4E "}{_t( - "Unable to access. Please verify that you entered the correct recovery key.", + "Unable to access secret storage. " + + "Please verify that you entered the correct recovery key.", )}
; } else if (this.state.recoveryKeyValid) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index cf02e51608..54da81e1ff 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1780,12 +1780,12 @@ "Allow": "Allow", "Deny": "Deny", "Enter recovery passphrase": "Enter recovery passphrase", - "Unable to access. Please verify that you entered the correct recovery passphrase.": "Unable to access. Please verify that you entered the correct recovery passphrase.", + "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.", "Warning: You should only do this on a trusted computer.": "Warning: You should only do this on a trusted computer.", "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.", "Enter recovery key": "Enter recovery key", - "Unable to access. Please verify that you entered the correct recovery key.": "Unable to access. Please verify that you entered the correct recovery key.", + "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", "This looks like a valid recovery key!": "This looks like a valid recovery key!", "Not a valid recovery key": "Not a valid recovery key", "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.", diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index 30512ca4dd..c754a4b607 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -100,7 +100,7 @@ describe("AccessSecretStorageDialog", function() { }); expect(notification.props.children).toEqual( ["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " + - "entered the correct passphrase."]); + "entered the correct recovery passphrase."]); done(); }); }); From aea7ede7d049b6f7893d8f6103be64b5439543f7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 14:55:43 +0100 Subject: [PATCH 44/83] update PHASE_PASSPHRASE copy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/dialogs/keybackup/CreateKeyBackupDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index b010987036..a20f5566e9 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -477,7 +477,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { _titleForPhase(phase) { switch (phase) { case PHASE_PASSPHRASE: - return _t('Secure your backup with a passphrase'); + return _t('Secure your backup with a recovery passphrase'); case PHASE_PASSPHRASE_CONFIRM: return _t('Confirm your recovery passphrase'); case PHASE_OPTOUT_CONFIRM: diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 54da81e1ff..a291065145 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2215,7 +2215,7 @@ "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", "Set up Secure Message Recovery": "Set up Secure Message Recovery", - "Secure your backup with a passphrase": "Secure your backup with a passphrase", + "Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase", "Starting backup...": "Starting backup...", "Success!": "Success!", "Create key backup": "Create key backup", From e1e65a0fe19c536ba8f726ab5465ff9e24c5f1c8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Apr 2020 17:13:33 +0100 Subject: [PATCH 45/83] Make RoomPublishSetting import-skinnable Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/room_settings/RoomPublishSetting.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/room_settings/RoomPublishSetting.js b/src/components/views/room_settings/RoomPublishSetting.js index bac2dfc656..6cc3ce26ba 100644 --- a/src/components/views/room_settings/RoomPublishSetting.js +++ b/src/components/views/room_settings/RoomPublishSetting.js @@ -18,7 +18,9 @@ import React from 'react'; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import {_t} from "../../../languageHandler"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; +import {replaceableComponent} from "../../../utils/replaceableComponent"; +@replaceableComponent("views.room_settings.RoomPublishSetting") export default class RoomPublishSetting extends React.PureComponent { constructor(props) { super(props); From b5130ca5dcd9111128fd659792febba35cf987b4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 11:12:47 -0600 Subject: [PATCH 46/83] Clean up setting definition to follow surrounding practices It's not perfect, but we're at least okay with it. --- src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a291065145..be8f1a7a4a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -399,7 +399,7 @@ "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", "Support adding custom themes": "Support adding custom themes", - "Enable cross-signing to verify per-user instead of per-session (in development)": "Enable cross-signing to verify per-user instead of per-session (in development)", + "Enable cross-signing to verify per-user instead of per-session": "Enable cross-signing to verify per-user instead of per-session", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", "Show info about bridges in room settings": "Show info about bridges in room settings", "Show padlocks on invite only rooms": "Show padlocks on invite only rooms", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 232c5d9f66..32ff0a5f94 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -152,9 +152,10 @@ export const SETTINGS = { default: null, }, "feature_cross_signing": { - //isFeature: true, - displayName: _td("Enable cross-signing to verify per-user instead of per-session (in development)"), - supportedLevels: LEVELS_FEATURE, + // 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_event_indexing": { From 54f8ba4d802ddd02286761efb07254793170bad4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 11:15:12 -0600 Subject: [PATCH 47/83] Remove UI to change the cross-signing setting --- src/components/views/settings/tabs/user/LabsUserSettingsTab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index fe160032ff..3e69107159 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -62,7 +62,6 @@ export default class LabsUserSettingsTab extends React.Component {
{flags} - From 0fcaaab1d01830ac73d15b6ffb9b3c316e1b9184 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 11:18:54 -0600 Subject: [PATCH 48/83] Appease the linter --- src/components/views/rooms/E2EIcon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 9189cfdf26..5e74656920 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -20,7 +20,7 @@ import PropTypes from "prop-types"; import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; -import {useFeatureEnabled, useSettingValue} from "../../../hooks/useSettings"; +import {useSettingValue} from "../../../hooks/useSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Tooltip from "../elements/Tooltip"; From 25fa675f88c783a995992fe6ff6b6638f2822ffe Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 11:46:29 -0600 Subject: [PATCH 49/83] Blind attempt at fixing the end to end tests --- .../CreateSecretStorageDialog.js | 6 ++++- test/end-to-end-tests/src/usecases/signup.js | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 0cc145fd96..a2aa4f27e8 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -614,7 +614,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent { {this._recoveryKey.encodedPrivateKey}
- + {_t("Copy")} diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index ef8a259091..ffde22f929 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -79,6 +79,33 @@ module.exports = async function signup(session, username, password, homeserver) const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); + //plow through cross-signing setup by entering arbitrary details + //TODO: It's probably important for the tests to know the passphrase + const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/ + let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); + await session.replaceInputText(passphraseField, xsigningPassphrase); + let xsignContButton = await session.query('.mx_CreateSecretStorageDialog_passPhraseContainer .mx_Dialog_primary'); + await xsignContButton.click(); + + //repeat passphrase entry + passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); + await session.replaceInputText(passphraseField, xsigningPassphrase); + xsignContButton = await session.query('.mx_CreateSecretStorageDialog_passPhraseContainer .mx_Dialog_primary'); + await xsignContButton.click(); + + //ignore the recovery key + //TODO: It's probably important for the tests to know the recovery key + const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn'); + await copyButton.click(); + + //acknowledge that we copied the recovery key to a safe place + const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); + await copyContinueButton.click(); + + //acknowledge that we're done cross-signing setup and our keys are safe + const doneOkButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); + await doneOkButton.click(); + //wait for registration to finish so the hash gets set //onhashchange better? From 5769b07a92e478f820fe0a9a955a4509c28c9554 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 12:04:01 -0600 Subject: [PATCH 50/83] Select the right continue button There's no buttons in the field. --- test/end-to-end-tests/src/usecases/signup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index ffde22f929..e686ebdb7d 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -84,13 +84,13 @@ module.exports = async function signup(session, username, password, homeserver) const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/ let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); await session.replaceInputText(passphraseField, xsigningPassphrase); - let xsignContButton = await session.query('.mx_CreateSecretStorageDialog_passPhraseContainer .mx_Dialog_primary'); + let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); //repeat passphrase entry passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); await session.replaceInputText(passphraseField, xsigningPassphrase); - xsignContButton = await session.query('.mx_CreateSecretStorageDialog_passPhraseContainer .mx_Dialog_primary'); + xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); //ignore the recovery key From f4a5361b14c62dc7aba9ae7d0073210ff06b1bfc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 12:13:05 -0600 Subject: [PATCH 51/83] Wait a bit before continuing with the passphrase The continue button is probably no-oping due to being disabled. --- test/end-to-end-tests/src/usecases/signup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index e686ebdb7d..aa9f6b7efa 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -84,12 +84,14 @@ module.exports = async function signup(session, username, password, homeserver) const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/ let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); await session.replaceInputText(passphraseField, xsigningPassphrase); + await session.delay(1000); // give it a second to analyze our passphrase for security let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); //repeat passphrase entry passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); await session.replaceInputText(passphraseField, xsigningPassphrase); + await session.delay(1000); // give it a second to analyze our passphrase for security xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); From d04af1f3a8e6c4e11723915298494a6449432f86 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 12:23:22 -0600 Subject: [PATCH 52/83] Disable e2e tests for now --- .../src/scenarios/e2e-encryption.js | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index 2f08acf417..a764b85db6 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -15,35 +15,41 @@ See the License for the specific language governing permissions and limitations under the License. */ -const sendMessage = require('../usecases/send-message'); -const acceptInvite = require('../usecases/accept-invite'); -const invite = require('../usecases/invite'); -const {receiveMessage} = require('../usecases/timeline'); -const {createRoom} = require('../usecases/create-room'); -const changeRoomSettings = require('../usecases/room-settings'); -const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); -const assert = require('assert'); +// TODO: Update test for cross signing -module.exports = async function e2eEncryptionScenarios(alice, bob) { - console.log(" creating an e2e encrypted room and join through invite:"); - const room = "secrets"; - await createRoom(bob, room); - await changeRoomSettings(bob, {encryption: true}); - // await cancelKeyBackup(bob); - await invite(bob, "@alice:localhost"); - await acceptInvite(alice, room); - // do sas verifcation - bob.log.step(`starts SAS verification with ${alice.username}`); - const bobSasPromise = startSasVerifcation(bob, alice.username); - const aliceSasPromise = acceptSasVerification(alice, bob.username); - // wait in parallel, so they don't deadlock on each other - const [bobSas, aliceSas] = await Promise.all([bobSasPromise, aliceSasPromise]); - assert.deepEqual(bobSas, aliceSas); - bob.log.done(`done (match for ${bobSas.join(", ")})`); - const aliceMessage = "Guess what I just heard?!"; - await sendMessage(alice, aliceMessage); - await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); - const bobMessage = "You've got to tell me!"; - await sendMessage(bob, bobMessage); - await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); +module.exports = async function() { + console.log(" this is supposed to be an e2e test, but it's broken"); }; + +// const sendMessage = require('../usecases/send-message'); +// const acceptInvite = require('../usecases/accept-invite'); +// const invite = require('../usecases/invite'); +// const {receiveMessage} = require('../usecases/timeline'); +// const {createRoom} = require('../usecases/create-room'); +// const changeRoomSettings = require('../usecases/room-settings'); +// const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); +// const assert = require('assert'); +// +// module.exports = async function e2eEncryptionScenarios(alice, bob) { +// console.log(" creating an e2e encrypted room and join through invite:"); +// const room = "secrets"; +// await createRoom(bob, room); +// await changeRoomSettings(bob, {encryption: true}); +// // await cancelKeyBackup(bob); +// await invite(bob, "@alice:localhost"); +// await acceptInvite(alice, room); +// // do sas verifcation +// bob.log.step(`starts SAS verification with ${alice.username}`); +// const bobSasPromise = startSasVerifcation(bob, alice.username); +// const aliceSasPromise = acceptSasVerification(alice, bob.username); +// // wait in parallel, so they don't deadlock on each other +// const [bobSas, aliceSas] = await Promise.all([bobSasPromise, aliceSasPromise]); +// assert.deepEqual(bobSas, aliceSas); +// bob.log.done(`done (match for ${bobSas.join(", ")})`); +// const aliceMessage = "Guess what I just heard?!"; +// await sendMessage(alice, aliceMessage); +// await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); +// const bobMessage = "You've got to tell me!"; +// await sendMessage(bob, bobMessage); +// await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); +// }; From 4af33d91ebd4d84e5e8796985ab951a5d1dd294d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 12:26:08 -0600 Subject: [PATCH 53/83] Track the issue number too --- test/end-to-end-tests/src/scenarios/e2e-encryption.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index a764b85db6..f30b814644 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -16,6 +16,7 @@ limitations under the License. */ // TODO: Update test for cross signing +// https://github.com/vector-im/riot-web/issues/13226 module.exports = async function() { console.log(" this is supposed to be an e2e test, but it's broken"); From 1deece3fb55e69e12653e8d98b28189b9654d8e4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 12:41:48 -0600 Subject: [PATCH 54/83] Short-circuit all end to end tests --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ba69c4272..4204b87b5a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "lint:types": "tsc --noEmit --jsx react", "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", - "test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" + "test:e2e": "echo 'The tests are broken with cross-signing. Fix them: https://github.com/vector-im/riot-web/issues/13226'", + "test:e2e_real": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" }, "dependencies": { "@babel/runtime": "^7.8.3", From 3614170906b3289774b7e8d023e3e129a94b2d61 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 12:46:34 -0600 Subject: [PATCH 55/83] Disable scripts in CI too --- scripts/ci/end-to-end-tests.sh | 46 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index 2f907dffa2..fa1f2b983f 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -13,25 +13,29 @@ handle_error() { trap 'handle_error' ERR +echo "Tests are disabled, see https://github.com/vector-im/riot-web/issues/13226" +exit 0 -echo "--- Building Riot" -scripts/ci/layered-riot-web.sh -cd ../riot-web -riot_web_dir=`pwd` -CI_PACKAGE=true yarn build -cd ../matrix-react-sdk -# run end to end tests -pushd test/end-to-end-tests -ln -s $riot_web_dir riot/riot-web -# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh -# CHROME_PATH=$(which google-chrome-stable) ./run.sh -echo "--- Install synapse & other dependencies" -./install.sh -# install static webserver to server symlinked local copy of riot -./riot/install-webserver.sh -rm -r logs || true -mkdir logs -echo "+++ Running end-to-end tests" -TESTS_STARTED=1 -./run.sh --no-sandbox --log-directory logs/ -popd +#TODO: Uncomment all of this in https://github.com/vector-im/riot-web/issues/13226 + +#echo "--- Building Riot" +#scripts/ci/layered-riot-web.sh +#cd ../riot-web +#riot_web_dir=`pwd` +#CI_PACKAGE=true yarn build +#cd ../matrix-react-sdk +## run end to end tests +#pushd test/end-to-end-tests +#ln -s $riot_web_dir riot/riot-web +## PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh +## CHROME_PATH=$(which google-chrome-stable) ./run.sh +#echo "--- Install synapse & other dependencies" +#./install.sh +## install static webserver to server symlinked local copy of riot +#./riot/install-webserver.sh +#rm -r logs || true +#mkdir logs +#echo "+++ Running end-to-end tests" +#TESTS_STARTED=1 +#./run.sh --no-sandbox --log-directory logs/ +#popd From d05ceec9d6558cad1a52c54eaea65ce5de9ed1f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2020 18:53:03 +0000 Subject: [PATCH 56/83] Bump https-proxy-agent from 2.2.1 to 2.2.4 in /test/end-to-end-tests Bumps [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) from 2.2.1 to 2.2.4. - [Release notes](https://github.com/TooTallNate/node-https-proxy-agent/releases) - [Commits](https://github.com/TooTallNate/node-https-proxy-agent/compare/2.2.1...2.2.4) Signed-off-by: dependabot[bot] --- test/end-to-end-tests/yarn.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/end-to-end-tests/yarn.lock b/test/end-to-end-tests/yarn.lock index 4379b24946..c26dde0f97 100644 --- a/test/end-to-end-tests/yarn.lock +++ b/test/end-to-end-tests/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488" integrity sha512-sKDlqv6COJrR7ar0+GqqhrXQDzQlMcqMnF2iEU6m9hLo8kxozoAGUazwPyELHlRVmjsbvlnGXjnzyptSXVmceA== -agent-base@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== +agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== dependencies: es6-promisify "^5.0.0" @@ -233,9 +233,9 @@ entities@^1.1.1, entities@~1.1.1: integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== es6-promise@^4.0.3: - version "4.2.6" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" - integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== es6-promisify@^5.0.0: version "5.0.0" @@ -359,11 +359,11 @@ http-signature@~1.2.0: sshpk "^1.7.0" https-proxy-agent@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" - integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== dependencies: - agent-base "^4.1.0" + agent-base "^4.3.0" debug "^3.1.0" inflight@^1.0.4: @@ -471,9 +471,9 @@ ms@2.0.0: integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== nth-check@~1.0.1: version "1.0.2" From 9e18dbc0dc20fddad451b6e4eee48a03a6b3c422 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Apr 2020 21:08:14 +0100 Subject: [PATCH 57/83] Fix key backup debug panel The type changed so it thought it was not stored when it was --- src/components/views/settings/KeyBackupPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 5548768221..fa3fa03c74 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -75,7 +75,7 @@ export default class KeyBackupPanel extends React.PureComponent { async _checkKeyBackupStatus() { try { const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup(); - const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored(); + const backupKeyStored = Boolean(await MatrixClientPeg.get().isKeyBackupKeyStored()); this.setState({ backupInfo, backupSigStatus: trustInfo, From 0489dcc8da2f825bdee276239decc98f38545766 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 15:55:33 -0600 Subject: [PATCH 58/83] Minor updates to e2e test instructions on Windows --- test/end-to-end-tests/Windows.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/Windows.md b/test/end-to-end-tests/Windows.md index dee4fabb3f..39b06a9a62 100644 --- a/test/end-to-end-tests/Windows.md +++ b/test/end-to-end-tests/Windows.md @@ -8,7 +8,7 @@ and start following these steps to get going: 3. Run `dos2unix ./test/end-to-end-tests/*.sh ./test/end-to-end-tests/synapse/*.sh ./test/end-to-end-tests/riot/*.sh` 4. Install NodeJS for ubuntu: ```bash - curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - + curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt-get update sudo apt-get install nodejs ``` @@ -24,6 +24,7 @@ and start following these steps to get going: ```bash cd ./test/end-to-end-tests ./synapse/install.sh + ./install.sh ./run.sh --riot-url http://localhost:8080 --no-sandbox ``` From 6d46ef548e27bda21006e4bd024ef697549e5965 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 16:26:29 -0600 Subject: [PATCH 59/83] Enable tests again to catch failures --- package.json | 3 +-- scripts/ci/end-to-end-tests.sh | 47 +++++++++++++++------------------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 4204b87b5a..7ba69c4272 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,7 @@ "lint:types": "tsc --noEmit --jsx react", "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", - "test:e2e": "echo 'The tests are broken with cross-signing. Fix them: https://github.com/vector-im/riot-web/issues/13226'", - "test:e2e_real": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" + "test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080" }, "dependencies": { "@babel/runtime": "^7.8.3", diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index fa1f2b983f..1233677db4 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -13,29 +13,24 @@ handle_error() { trap 'handle_error' ERR -echo "Tests are disabled, see https://github.com/vector-im/riot-web/issues/13226" -exit 0 - -#TODO: Uncomment all of this in https://github.com/vector-im/riot-web/issues/13226 - -#echo "--- Building Riot" -#scripts/ci/layered-riot-web.sh -#cd ../riot-web -#riot_web_dir=`pwd` -#CI_PACKAGE=true yarn build -#cd ../matrix-react-sdk -## run end to end tests -#pushd test/end-to-end-tests -#ln -s $riot_web_dir riot/riot-web -## PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh -## CHROME_PATH=$(which google-chrome-stable) ./run.sh -#echo "--- Install synapse & other dependencies" -#./install.sh -## install static webserver to server symlinked local copy of riot -#./riot/install-webserver.sh -#rm -r logs || true -#mkdir logs -#echo "+++ Running end-to-end tests" -#TESTS_STARTED=1 -#./run.sh --no-sandbox --log-directory logs/ -#popd +echo "--- Building Riot" +scripts/ci/layered-riot-web.sh +cd ../riot-web +riot_web_dir=`pwd` +CI_PACKAGE=true yarn build +cd ../matrix-react-sdk +# run end to end tests +pushd test/end-to-end-tests +ln -s $riot_web_dir riot/riot-web +# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh +# CHROME_PATH=$(which google-chrome-stable) ./run.sh +echo "--- Install synapse & other dependencies" +./install.sh +# install static webserver to server symlinked local copy of riot +./riot/install-webserver.sh +rm -r logs || true +mkdir logs +echo "+++ Running end-to-end tests" +TESTS_STARTED=1 +./run.sh --no-sandbox --log-directory logs/ +popd From a5f5f759cb83cbd03e53e3a2a826134c34cd52d4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 Apr 2020 16:27:43 -0600 Subject: [PATCH 60/83] Create unencrypted rooms by default in e2e-tests Otherwise the lazy loading test will try to join the room after the encrypted messages have already been sent, making them invisible. See https://github.com/vector-im/riot-web/issues/13226#issuecomment-614928362 --- src/components/views/dialogs/CreateRoomDialog.js | 7 ++++++- src/components/views/elements/LabelledToggleSwitch.js | 6 +++++- test/end-to-end-tests/src/usecases/create-room.js | 8 ++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index fa8c7dd30e..74e006354b 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -194,7 +194,12 @@ export default createReactClass({ let e2eeSection; if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { e2eeSection = - +

{ _t("You can’t disable this later. Bridges & most bots won’t work yet.") }

; } diff --git a/src/components/views/elements/LabelledToggleSwitch.js b/src/components/views/elements/LabelledToggleSwitch.js index ecd4d39bf8..78beb2aa91 100644 --- a/src/components/views/elements/LabelledToggleSwitch.js +++ b/src/components/views/elements/LabelledToggleSwitch.js @@ -35,6 +35,9 @@ export default class LabelledToggleSwitch extends React.Component { // True to put the toggle in front of the label // Default false. toggleInFront: PropTypes.bool, + + // Additional class names to append to the switch. Optional. + className: PropTypes.string, }; render() { @@ -50,8 +53,9 @@ export default class LabelledToggleSwitch extends React.Component { secondPart = temp; } + const classes = `mx_SettingsFlag ${this.props.className || ""}`; return ( -
+
{firstPart} {secondPart}
diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index 140748bca7..ab2d9b69b9 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -20,7 +20,7 @@ async function openRoomDirectory(session) { await roomDirectoryButton.click(); } -async function createRoom(session, roomName) { +async function createRoom(session, roomName, encrypted=false) { session.log.step(`creates room "${roomName}"`); const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); @@ -33,10 +33,14 @@ async function createRoom(session, roomName) { const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); await addRoomButton.click(); - const roomNameInput = await session.query('.mx_CreateRoomDialog_name input'); await session.replaceInputText(roomNameInput, roomName); + if (!encrypted) { + const encryptionToggle = await session.query('.mx_CreateRoomDialog_e2eSwitch .mx_ToggleSwitch'); + await encryptionToggle.click(); + } + const createButton = await session.query('.mx_Dialog_primary'); await createButton.click(); From c3ff75d18f55831488706a022aa68de93ef63e72 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 Apr 2020 11:04:52 +0100 Subject: [PATCH 61/83] Don't break spills over multiple lines, ellipsis them at max-1-line Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/elements/_RichText.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index e3f88cc779..42e14525bf 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -14,8 +14,10 @@ } a.mx_Pill { - word-break: break-all; - display: inline; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 100%; } /* More specific to override `.markdown-body a` text-decoration */ From 81c7660bf572167b63f9f85570b1f4ffa8b82acc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 Apr 2020 12:25:16 +0100 Subject: [PATCH 62/83] Iterate copy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/dialogs/BugReportDialog.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/BugReportDialog.js b/src/components/views/dialogs/BugReportDialog.js index 2f7ad4163d..9bb716fe3f 100644 --- a/src/components/views/dialogs/BugReportDialog.js +++ b/src/components/views/dialogs/BugReportDialog.js @@ -140,7 +140,7 @@ export default class BugReportDialog extends React.Component { let warning; if (window.Modernizr && Object.values(window.Modernizr).some(support => support === false)) { warning =

- { _t("Your browser is unsupported, you accepted that things may not work.") } + { _t("Reminder: Your browser is unsupported, so your experience may be unpredictable.") }

; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 067d5d8eee..aa104e3686 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1526,6 +1526,7 @@ "Logs sent": "Logs sent", "Thank you!": "Thank you!", "Failed to send logs: ": "Failed to send logs: ", + "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Reminder: Your browser is unsupported, so your experience may be unpredictable.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", "Before submitting logs, you must create a GitHub issue to describe your problem.": "Before submitting logs, you must create a GitHub issue to describe your problem.", "GitHub issue": "GitHub issue", From 4d91dc5bba5388897fcf317a7ef193380983ae86 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 Apr 2020 12:30:09 +0100 Subject: [PATCH 63/83] iterate pills in rich text styling Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/elements/_RichText.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index 42e14525bf..e01b1f8938 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -17,7 +17,8 @@ a.mx_Pill { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - max-width: 100%; + vertical-align: text-bottom; + max-width: calc(100% - 1ch); } /* More specific to override `.markdown-body a` text-decoration */ From 518639e8b1621cc4a0b6ad9c46845a37b821a866 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 Apr 2020 16:04:38 +0100 Subject: [PATCH 64/83] Fix typo Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/dialogs/KeySignatureUploadFailedDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/KeySignatureUploadFailedDialog.js b/src/components/views/dialogs/KeySignatureUploadFailedDialog.js index a04c4a389f..e59f77fce9 100644 --- a/src/components/views/dialogs/KeySignatureUploadFailedDialog.js +++ b/src/components/views/dialogs/KeySignatureUploadFailedDialog.js @@ -85,7 +85,7 @@ export default function KeySignatureUploadFailedDialog({ {_t("Upload completed")} : cancelled ? {_t("Cancelled signature upload")} : - {_t("Unabled to upload")}} + {_t("Unable to upload")}} Date: Fri, 17 Apr 2020 14:31:33 -0600 Subject: [PATCH 65/83] Fix end-to-end tests for end-to-end encryption verification Fixes https://github.com/vector-im/riot-web/issues/13226 This isn't the fastest route, but it is a predictable route for the happy path we probably want to test. For example, Alice will already be staring at the DM and could easily accept the verification there, but we probably want to make sure that the toast is present and does the right thing. Similarly, neither of them need to verify that there's green shields everywhere, they should be implied, however an explicit check follows a real user's gaze. --- src/components/views/right_panel/UserInfo.js | 3 +- .../views/right_panel/VerificationPanel.js | 9 +- .../src/scenarios/directory.js | 2 +- .../src/scenarios/e2e-encryption.js | 67 ++++++------- .../src/scenarios/lazy-loading.js | 2 +- test/end-to-end-tests/src/session.js | 4 + .../src/usecases/create-room.js | 39 +++++++- .../src/usecases/room-settings.js | 92 +++++++++++++++++- test/end-to-end-tests/src/usecases/verify.js | 97 ++++++++++++++----- 9 files changed, 244 insertions(+), 71 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 862e4f7897..979bac23e6 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1308,8 +1308,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { useHasCrossSigningKeys(cli, member, canVerify, setUpdating ); if (canVerify) { + // Note: mx_UserInfo_verifyButton is for the end-to-end tests verifyButton = ( - { + { if (hasCrossSigningKeys) { verifyUser(member); } else { diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index b60cc234eb..67efd29d27 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -123,10 +123,17 @@ export default class VerificationPanel extends React.PureComponent { const sasLabel = showQR ? _t("If you can't scan the code above, verify by comparing unique emoji.") : _t("Verify by comparing unique emoji."); + + // Note: mx_VerificationPanel_verifyByEmojiButton is for the end-to-end tests sasBlock =

{_t("Verify by emoji")}

{sasLabel}

- + {_t("Verify by emoji")}
; diff --git a/test/end-to-end-tests/src/scenarios/directory.js b/test/end-to-end-tests/src/scenarios/directory.js index ca2f99f192..b5be9ed4f4 100644 --- a/test/end-to-end-tests/src/scenarios/directory.js +++ b/test/end-to-end-tests/src/scenarios/directory.js @@ -20,7 +20,7 @@ const join = require('../usecases/join'); const sendMessage = require('../usecases/send-message'); const {receiveMessage} = require('../usecases/timeline'); const {createRoom} = require('../usecases/create-room'); -const changeRoomSettings = require('../usecases/room-settings'); +const {changeRoomSettings} = require('../usecases/room-settings'); module.exports = async function roomDirectoryScenarios(alice, bob) { console.log(" creating a public room and join through directory:"); diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index f30b814644..586b3a0404 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -1,6 +1,6 @@ /* Copyright 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -15,42 +15,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Update test for cross signing -// https://github.com/vector-im/riot-web/issues/13226 +const sendMessage = require('../usecases/send-message'); +const acceptInvite = require('../usecases/accept-invite'); +const invite = require('../usecases/invite'); +const {receiveMessage} = require('../usecases/timeline'); +const {createDm} = require('../usecases/create-room'); +const {checkRoomSettings} = require('../usecases/room-settings'); +const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); +const assert = require('assert'); -module.exports = async function() { - console.log(" this is supposed to be an e2e test, but it's broken"); +module.exports = async function e2eEncryptionScenarios(alice, bob) { + console.log(" creating an e2e encrypted DM and join through invite:"); + await createDm(bob, ['@alice:localhost']); + await checkRoomSettings(bob, {encryption: true}); // for sanity, should be e2e-by-default + await acceptInvite(alice, 'bob'); + // do sas verifcation + bob.log.step(`starts SAS verification with ${alice.username}`); + const bobSasPromise = startSasVerifcation(bob, alice.username); + const aliceSasPromise = acceptSasVerification(alice, bob.username); + // wait in parallel, so they don't deadlock on each other + // the logs get a bit messy here, but that's fine enough for debugging (hopefully) + const [bobSas, aliceSas] = await Promise.all([bobSasPromise, aliceSasPromise]); + assert.deepEqual(bobSas, aliceSas); + bob.log.done(`done (match for ${bobSas.join(", ")})`); + const aliceMessage = "Guess what I just heard?!"; + await sendMessage(alice, aliceMessage); + await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); + const bobMessage = "You've got to tell me!"; + await sendMessage(bob, bobMessage); + await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); }; - -// const sendMessage = require('../usecases/send-message'); -// const acceptInvite = require('../usecases/accept-invite'); -// const invite = require('../usecases/invite'); -// const {receiveMessage} = require('../usecases/timeline'); -// const {createRoom} = require('../usecases/create-room'); -// const changeRoomSettings = require('../usecases/room-settings'); -// const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); -// const assert = require('assert'); -// -// module.exports = async function e2eEncryptionScenarios(alice, bob) { -// console.log(" creating an e2e encrypted room and join through invite:"); -// const room = "secrets"; -// await createRoom(bob, room); -// await changeRoomSettings(bob, {encryption: true}); -// // await cancelKeyBackup(bob); -// await invite(bob, "@alice:localhost"); -// await acceptInvite(alice, room); -// // do sas verifcation -// bob.log.step(`starts SAS verification with ${alice.username}`); -// const bobSasPromise = startSasVerifcation(bob, alice.username); -// const aliceSasPromise = acceptSasVerification(alice, bob.username); -// // wait in parallel, so they don't deadlock on each other -// const [bobSas, aliceSas] = await Promise.all([bobSasPromise, aliceSasPromise]); -// assert.deepEqual(bobSas, aliceSas); -// bob.log.done(`done (match for ${bobSas.join(", ")})`); -// const aliceMessage = "Guess what I just heard?!"; -// await sendMessage(alice, aliceMessage); -// await receiveMessage(bob, {sender: "alice", body: aliceMessage, encrypted: true}); -// const bobMessage = "You've got to tell me!"; -// await sendMessage(bob, bobMessage); -// await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); -// }; diff --git a/test/end-to-end-tests/src/scenarios/lazy-loading.js b/test/end-to-end-tests/src/scenarios/lazy-loading.js index 0c45b0d083..6d321dc737 100644 --- a/test/end-to-end-tests/src/scenarios/lazy-loading.js +++ b/test/end-to-end-tests/src/scenarios/lazy-loading.js @@ -25,7 +25,7 @@ const { } = require('../usecases/timeline'); const {createRoom} = require('../usecases/create-room'); const {getMembersInMemberlist} = require('../usecases/memberlist'); -const changeRoomSettings = require('../usecases/room-settings'); +const {changeRoomSettings} = require('../usecases/room-settings'); const assert = require('assert'); module.exports = async function lazyLoadingScenarios(alice, bob, charlies) { diff --git a/test/end-to-end-tests/src/session.js b/test/end-to-end-tests/src/session.js index f25c5056ad..55c2ed440c 100644 --- a/test/end-to-end-tests/src/session.js +++ b/test/end-to-end-tests/src/session.js @@ -75,6 +75,10 @@ module.exports = class RiotSession { return this.getElementProperty(field, 'outerHTML'); } + isChecked(field) { + return this.getElementProperty(field, 'checked'); + } + consoleLogs() { return this.consoleLog.buffer; } diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index ab2d9b69b9..c40cbba096 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -27,7 +27,7 @@ async function createRoom(session, roomName, encrypted=false) { const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms")); if (roomsIndex === -1) { - throw new Error("could not find room list section that contains rooms in header"); + throw new Error("could not find room list section that contains 'rooms' in header"); } const roomsHeader = roomListHeaders[roomsIndex]; const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); @@ -48,4 +48,39 @@ async function createRoom(session, roomName, encrypted=false) { session.log.done(); } -module.exports = {openRoomDirectory, createRoom}; +async function createDm(session, invitees) { + session.log.step(`creates DM with ${JSON.stringify(invitees)}`); + + const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); + const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); + const dmsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes('direct messages')); + if (dmsIndex === -1) { + throw new Error("could not find room list section that contains 'direct messages' in header"); + } + const dmsHeader = roomListHeaders[dmsIndex]; + const addRoomButton = await dmsHeader.$(".mx_RoomSubList_addRoom"); + await addRoomButton.click(); + + const inviteesEditor = await session.query('.mx_InviteDialog_editor textarea'); + for (const target of invitees) { + await session.replaceInputText(inviteesEditor, target); + await session.delay(1000); // give it a moment to figure out a suggestion + // find the suggestion and accept it + const suggestions = await session.queryAll('.mx_InviteDialog_roomTile_userId'); + const suggestionTexts = await Promise.all(suggestions.map(s => session.innerText(s))); + const suggestionIndex = suggestionTexts.indexOf(target); + if (suggestionIndex === -1) { + throw new Error(`failed to find a suggestion in the DM dialog to invite ${target} with`); + } + await suggestions[suggestionIndex].click(); + } + + // press the go button and hope for the best + const goButton = await session.query('.mx_InviteDialog_goButton'); + await goButton.click(); + + await session.query('.mx_MessageComposer'); + session.log.done(); +} + +module.exports = {openRoomDirectory, createRoom, createDm}; diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index ab6d66ea6d..b705463965 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -30,18 +30,102 @@ async function setSettingsToggle(session, toggle, enabled) { } } -module.exports = async function changeRoomSettings(session, settings) { - session.log.startGroup(`changes the room settings`); +async function checkSettingsToggle(session, toggle, shouldBeEnabled) { + const className = await session.getElementProperty(toggle, "className"); + const checked = className.includes("mx_ToggleSwitch_on"); + if (checked === shouldBeEnabled) { + session.log.done('set as expected'); + } else { + // other logs in the area should give more context as to what this means. + throw new Error("settings toggle value didn't match expectation"); + } +} + +async function findTabs(session) { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[title=Settings]"); await settingsButton.click(); + //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); const tabLabels = await Promise.all(tabButtons.map(t => session.innerText(t))); const securityTabButton = tabButtons[tabLabels.findIndex(l => l.toLowerCase().includes("security"))]; + return {securityTabButton}; +} + +async function checkRoomSettings(session, expectedSettings) { + session.log.startGroup(`checks the room settings`); + + const {securityTabButton} = await findTabs(session); + const generalSwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); + const isDirectory = generalSwitches[0]; + + if (typeof expectedSettings.directory === 'boolean') { + session.log.step(`checks directory listing is ${expectedSettings.directory}`); + await checkSettingsToggle(session, isDirectory, expectedSettings.directory); + } + + if (expectedSettings.alias) { + session.log.step(`checks for local alias of ${expectedSettings.alias}`); + const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary"); + await summary.click(); + const localAliases = await session.query('.mx_RoomSettingsDialog .mx_AliasSettings .mx_EditableItem_item'); + const localAliasTexts = await Promise.all(localAliases.map(a => session.innerText(a))); + if (localAliasTexts.find(a => a.includes(expectedSettings.alias))) { + session.log.done("present"); + } else { + throw new Error(`could not find local alias ${expectedSettings.alias}`); + } + } + + securityTabButton.click(); + await session.delay(500); + const securitySwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); + const e2eEncryptionToggle = securitySwitches[0]; + + if (typeof expectedSettings.encryption === "boolean") { + session.log.step(`checks room e2e encryption is ${expectedSettings.encryption}`); + await checkSettingsToggle(session, e2eEncryptionToggle, expectedSettings.encryption); + } + + if (expectedSettings.visibility) { + session.log.step(`checks visibility is ${expectedSettings.visibility}`); + const radios = await session.queryAll(".mx_RoomSettingsDialog input[type=radio]"); + assert.equal(radios.length, 7); + const inviteOnly = radios[0]; + const publicNoGuests = radios[1]; + const publicWithGuests = radios[2]; + + let expectedRadio = null; + if (expectedSettings.visibility === "invite_only") { + expectedRadio = inviteOnly; + } else if (expectedSettings.visibility === "public_no_guests") { + expectedRadio = publicNoGuests; + } else if (expectedSettings.visibility === "public_with_guests") { + expectedRadio = publicWithGuests; + } else { + throw new Error(`unrecognized room visibility setting: ${expectedSettings.visibility}`); + } + if (await session.isChecked(expectedRadio)) { + session.log.done(); + } else { + throw new Error("room visibility is not as expected"); + } + } + + const closeButton = await session.query(".mx_RoomSettingsDialog .mx_Dialog_cancelButton"); + await closeButton.click(); + + session.log.endGroup(); +} + +async function changeRoomSettings(session, settings) { + session.log.startGroup(`changes the room settings`); + + const {securityTabButton} = await findTabs(session); const generalSwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch"); const isDirectory = generalSwitches[0]; @@ -100,4 +184,6 @@ module.exports = async function changeRoomSettings(session, settings) { await closeButton.click(); session.log.endGroup(); -}; +} + +module.exports = {checkRoomSettings, changeRoomSettings}; diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js index 5f507f96e6..7ff3f3d8bb 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.js @@ -25,10 +25,19 @@ async function assertVerified(session) { } async function startVerification(session, name) { + session.log.step("opens their opponent's profile and starts verification"); await openMemberInfo(session, name); // click verify in member info - const firstVerifyButton = await session.query(".mx_MemberDeviceInfo_verify"); + const firstVerifyButton = await session.query(".mx_UserInfo_verifyButton"); await firstVerifyButton.click(); + + // wait for the animation to finish + await session.delay(1000); + + // click 'start verification' + const startVerifyButton = await session.query('.mx_UserInfo_container .mx_AccessibleButton_kind_primary'); + await startVerifyButton.click(); + session.log.done(); } async function getSasCodes(session) { @@ -38,33 +47,73 @@ async function getSasCodes(session) { return sasLabels; } -module.exports.startSasVerifcation = async function(session, name) { - await startVerification(session, name); - // expect "Verify device" dialog and click "Begin Verification" - await assertDialog(session, "Verify device"); - // click "Begin Verification" - await acceptDialog(session); +async function doSasVerification(session) { + session.log.step("hunts for the emoji to yell at their opponent"); const sasCodes = await getSasCodes(session); - // click "Verify" - await acceptDialog(session); - await assertVerified(session); - // click "Got it" when verification is done - await acceptDialog(session); + session.log.done(sasCodes); + + // Assume they match + session.log.step("assumes the emoji match"); + const matchButton = await session.query(".mx_VerificationShowSas .mx_AccessibleButton_kind_primary"); + await matchButton.click(); + session.log.done(); + + // Wait for a big green shield (universal sign that it worked) + session.log.step("waits for a green shield"); + await session.query(".mx_VerificationPanel_verified_section .mx_E2EIcon_verified"); + session.log.done(); + + // Click 'Got It' + session.log.step("confirms the green shield"); + const doneButton = await session.query(".mx_VerificationPanel_verified_section .mx_AccessibleButton_kind_primary"); + await doneButton.click(); + session.log.done(); + + // Wait a bit for the animation + session.log.step("confirms their opponent has a green shield"); + await session.delay(1000); + + // Verify that we now have a green shield in their name (proving it still works) + await session.query('.mx_UserInfo_profile .mx_E2EIcon_verified'); + session.log.done(); + + return sasCodes; +} + +module.exports.startSasVerifcation = async function(session, name) { + session.log.startGroup("starts verification"); + await startVerification(session, name); + + // expect to be waiting (the presence of a spinner is a good thing) + await session.query('.mx_UserInfo_container .mx_EncryptionInfo_spinner'); + + const sasCodes = await doSasVerification(session); + session.log.endGroup(); return sasCodes; }; module.exports.acceptSasVerification = async function(session, name) { - await assertDialog(session, "Incoming Verification Request"); - const opponentLabelElement = await session.query(".mx_IncomingSasDialog_opponentProfile h2"); - const opponentLabel = await session.innerText(opponentLabelElement); - assert(opponentLabel, name); - // click "Continue" button - await acceptDialog(session); - const sasCodes = await getSasCodes(session); - // click "Verify" - await acceptDialog(session); - await assertVerified(session); - // click "Got it" when verification is done - await acceptDialog(session); + session.log.startGroup("accepts verification"); + const requestToast = await session.query('.mx_Toast_icon_verification'); + + // verify the toast is for verification + const toastHeader = await requestToast.$("h2"); + const toastHeaderText = await session.innerText(toastHeader); + assert.equal(toastHeaderText, 'Verification Request'); + const toastDescription = await requestToast.$(".mx_Toast_description"); + const toastDescText = await session.innerText(toastDescription); + assert.equal(toastDescText.startsWith(name), true, + `verification opponent mismatch: expected to start with '${name}', got '${toastDescText}'`); + + // accept the verification + const acceptButton = await requestToast.$(".mx_AccessibleButton_kind_primary"); + await acceptButton.click(); + + // find the emoji button + const startEmojiButton = await session.query(".mx_VerificationPanel_verifyByEmojiButton"); + await startEmojiButton.click(); + + const sasCodes = await doSasVerification(session); + session.log.endGroup(); return sasCodes; }; From 5a9898591d0fb0edbebfefd06f9e5b0f34c19135 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Apr 2020 14:35:02 -0600 Subject: [PATCH 66/83] Appease the linter --- test/end-to-end-tests/src/scenarios/e2e-encryption.js | 1 - test/end-to-end-tests/src/usecases/verify.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index 586b3a0404..d31d2c0d57 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -17,7 +17,6 @@ limitations under the License. const sendMessage = require('../usecases/send-message'); const acceptInvite = require('../usecases/accept-invite'); -const invite = require('../usecases/invite'); const {receiveMessage} = require('../usecases/timeline'); const {createDm} = require('../usecases/create-room'); const {checkRoomSettings} = require('../usecases/room-settings'); diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js index 7ff3f3d8bb..719333c0d8 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.js @@ -1,6 +1,6 @@ /* Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -17,7 +17,6 @@ limitations under the License. const assert = require('assert'); const {openMemberInfo} = require("./memberlist"); -const {assertDialog, acceptDialog} = require("./dialog"); async function assertVerified(session) { const dialogSubTitle = await session.innerText(await session.query(".mx_Dialog h2")); From 77df61065322d3991c087fc5021b44502870afdc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Apr 2020 14:38:05 -0600 Subject: [PATCH 67/83] Appease the linter a bit more --- test/end-to-end-tests/src/usecases/verify.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js index 719333c0d8..98e73ad6b7 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.js @@ -18,11 +18,6 @@ limitations under the License. const assert = require('assert'); const {openMemberInfo} = require("./memberlist"); -async function assertVerified(session) { - const dialogSubTitle = await session.innerText(await session.query(".mx_Dialog h2")); - assert(dialogSubTitle, "Verified!"); -} - async function startVerification(session, name) { session.log.step("opens their opponent's profile and starts verification"); await openMemberInfo(session, name); From 88b5c3ca840799fce2ec92fc6cdbc7e400a85516 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Apr 2020 14:39:45 -0600 Subject: [PATCH 68/83] Don't explode if the e2e test directory exists when crashing This is largely expected in local test environments where the developer probably won't remember to clear out the directory themselves. --- test/end-to-end-tests/start.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/start.js b/test/end-to-end-tests/start.js index 83bc186356..6c80608903 100644 --- a/test/end-to-end-tests/start.js +++ b/test/end-to-end-tests/start.js @@ -93,7 +93,13 @@ async function writeLogs(sessions, dir) { for (let i = 0; i < sessions.length; ++i) { const session = sessions[i]; const userLogDir = `${dir}/${session.username}`; - fs.mkdirSync(userLogDir); + try { + fs.mkdirSync(userLogDir); + } catch (e) { + // typically this will be EEXIST. If it's something worse, the next few + // lines will fail too. + console.warn(`non-fatal error creating ${userLogDir} :`, e.message); + } const consoleLogName = `${userLogDir}/console.log`; const networkLogName = `${userLogDir}/network.log`; const appHtmlName = `${userLogDir}/app.html`; From 432dbab8cb978ececb3071c185208e24efc54960 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Apr 2020 15:06:59 -0600 Subject: [PATCH 69/83] Fix start chat button variable name --- test/end-to-end-tests/src/usecases/create-room.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index c40cbba096..7e219fd159 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -58,8 +58,8 @@ async function createDm(session, invitees) { throw new Error("could not find room list section that contains 'direct messages' in header"); } const dmsHeader = roomListHeaders[dmsIndex]; - const addRoomButton = await dmsHeader.$(".mx_RoomSubList_addRoom"); - await addRoomButton.click(); + const startChatButton = await dmsHeader.$(".mx_RoomSubList_addRoom"); + await startChatButton.click(); const inviteesEditor = await session.query('.mx_InviteDialog_editor textarea'); for (const target of invitees) { From e0d6fa3f514a9e048993041df6dd72fdb3a154ea Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Apr 2020 15:15:51 -0600 Subject: [PATCH 70/83] Rename ref to timelinePanel --- src/components/structures/FilePanel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 926355dd88..f073785df3 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -209,10 +209,10 @@ const FilePanel = createReactClass({ }, _getScrollState: function() { - const messagePanel = this._messagePanel; - const scrollState = messagePanel.getScrollState(); + const timelinePanel = this._timelinePanel; + const scrollState = timelinePanel.getScrollState(); - if (!messagePanel) return null; + if (!timelinePanel) return null; if (!scrollState || scrollState.stuckAtBottom) { return null; @@ -225,7 +225,7 @@ const FilePanel = createReactClass({ }, _getTimelinePanelRef: function(ref) { - this._messagePanel = ref; + this._timelinePanel = ref; }, render: function() { From a0f704f41999b04b1440be21a963cb5c3d8d4e07 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 18 Apr 2020 15:08:15 +0100 Subject: [PATCH 71/83] Network Dropdown fix things not scrolling properly Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/directory/_NetworkDropdown.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss index dd1892c448..bd5c67c7ed 100644 --- a/res/css/views/directory/_NetworkDropdown.scss +++ b/res/css/views/directory/_NetworkDropdown.scss @@ -53,15 +53,16 @@ limitations under the License. font-weight: 600; line-height: $font-20px; margin-bottom: 4px; + position: relative; // remove server button .mx_AccessibleButton { position: absolute; display: inline; - right: 12px; + right: 10px; height: 16px; width: 16px; - margin-top: 4px; + margin-top: 2px; &::after { content: ""; From b86b42c89f3905a7c1cd3eefb49ed5370d2a84f8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 18 Apr 2020 16:18:01 +0100 Subject: [PATCH 72/83] Password Login make sure tab takes user to password field Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/auth/PasswordLogin.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index aeaa91845b..790c837497 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -185,7 +185,7 @@ export default class PasswordLogin extends React.Component { this.props.onPasswordChanged(ev.target.value); } - renderLoginField(loginType) { + renderLoginField(loginType, autoFocus) { const Field = sdk.getComponent('elements.Field'); const classes = {}; @@ -204,7 +204,7 @@ export default class PasswordLogin extends React.Component { onChange={this.onUsernameChanged} onBlur={this.onUsernameBlur} disabled={this.props.disableSubmit} - autoFocus + autoFocus={autoFocus} />; case PasswordLogin.LOGIN_FIELD_MXID: classes.error = this.props.loginIncorrect && !this.state.username; @@ -218,7 +218,7 @@ export default class PasswordLogin extends React.Component { onChange={this.onUsernameChanged} onBlur={this.onUsernameBlur} disabled={this.props.disableSubmit} - autoFocus + autoFocus={autoFocus} />; case PasswordLogin.LOGIN_FIELD_PHONE: { const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); @@ -242,7 +242,7 @@ export default class PasswordLogin extends React.Component { onChange={this.onPhoneNumberChanged} onBlur={this.onPhoneNumberBlur} disabled={this.props.disableSubmit} - autoFocus + autoFocus={autoFocus} />; } } @@ -285,7 +285,10 @@ export default class PasswordLogin extends React.Component { error: this.props.loginIncorrect && !this.isLoginEmpty(), // only error password if error isn't top field }); - const loginField = this.renderLoginField(this.state.loginType); + // If login is empty, autoFocus login, otherwise autoFocus password. + // this is for when auto server discovery remounts us when the user tries to tab from username to password + const autoFocusPassword = !this.isLoginEmpty(); + const loginField = this.renderLoginField(this.state.loginType, !autoFocusPassword); let loginType; if (!SdkConfig.get().disable_3pid_login) { @@ -336,6 +339,7 @@ export default class PasswordLogin extends React.Component { value={this.state.password} onChange={this.onPasswordChanged} disabled={this.props.disableSubmit} + autoFocus={autoFocusPassword} /> {forgotPasswordJsx} { !this.props.busy && Date: Sat, 18 Apr 2020 23:16:40 +0100 Subject: [PATCH 73/83] Update seshat copy to remove trailing full stop Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/dialogs/eventindex/ManageEventIndexDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js index 5f24fb10fa..bb2cf7f0b8 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -140,7 +140,7 @@ export default class ManageEventIndexDialog extends React.Component { crawlerState = _t("Not currently indexing messages for any room."); } else { crawlerState = ( - _t("Currently indexing: %(currentRoom)s.", { currentRoom: this.state.currentRoom }) + _t("Currently indexing: %(currentRoom)s", { currentRoom: this.state.currentRoom }) ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0e39d848d2..0cf2944116 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2239,7 +2239,7 @@ "If disabled, messages from encrypted rooms won't appear in search results.": "If disabled, messages from encrypted rooms won't appear in search results.", "Disable": "Disable", "Not currently indexing messages for any room.": "Not currently indexing messages for any room.", - "Currently indexing: %(currentRoom)s.": "Currently indexing: %(currentRoom)s.", + "Currently indexing: %(currentRoom)s": "Currently indexing: %(currentRoom)s", "Riot is securely caching encrypted messages locally for them to appear in search results:": "Riot is securely caching encrypted messages locally for them to appear in search results:", "Space used:": "Space used:", "Indexed messages:": "Indexed messages:", From 3bc5a0a0b2430589193ebe172750360797528ec3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 19 Apr 2020 12:06:56 +0100 Subject: [PATCH 74/83] Convert submit-rageshake to typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 1 + src/@types/global.d.ts | 40 +++++++++++++++++++ ...ubmit-rageshake.js => submit-rageshake.ts} | 27 ++++++++----- yarn.lock | 5 +++ 4 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 src/@types/global.d.ts rename src/rageshake/{submit-rageshake.js => submit-rageshake.ts} (85%) diff --git a/package.json b/package.json index 7ba69c4272..0e1909f3e3 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@babel/register": "^7.7.4", "@peculiar/webcrypto": "^1.0.22", "@types/classnames": "^2.2.10", + "@types/modernizr": "^3.5.3", "@types/react": "16.9", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts new file mode 100644 index 0000000000..1931c0b1d0 --- /dev/null +++ b/src/@types/global.d.ts @@ -0,0 +1,40 @@ +/* +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 ModernizrStatic from "modernizr"; + +declare global { + interface Window { + Modernizr: ModernizrStatic; + Olm: { + init: () => Promise; + }; + } + + // workaround for https://github.com/microsoft/TypeScript/issues/30933 + interface ObjectConstructor { + fromEntries?(xs: [string|number|symbol, any][]): object + } + + interface Document { + // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess + hasStorageAccess?: () => Promise; + } + + interface StorageEstimate { + usageDetails?: {[key: string]: number}; + } +} diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.ts similarity index 85% rename from src/rageshake/submit-rageshake.js rename to src/rageshake/submit-rageshake.ts index 55c89427c5..921f3fbf40 100644 --- a/src/rageshake/submit-rageshake.js +++ b/src/rageshake/submit-rageshake.ts @@ -33,6 +33,13 @@ if (!TextEncoder) { TextEncoder = TextEncodingUtf8.TextEncoder; } +interface IOpts { + label?: string; + userText?: string; + sendLogs?: boolean; + progressCallback?: (string) => void; +} + /** * Send a bug report. * @@ -48,7 +55,7 @@ if (!TextEncoder) { * * @return {Promise} Resolved when the bug report is sent. */ -export default async function sendBugReport(bugReportEndpoint, opts) { +export default async function sendBugReport(bugReportEndpoint: string, opts: IOpts) { if (!bugReportEndpoint) { throw new Error("No bug report endpoint has been set."); } @@ -70,13 +77,13 @@ export default async function sendBugReport(bugReportEndpoint, opts) { let installedPWA = "UNKNOWN"; try { // Known to work at least for desktop Chrome - installedPWA = window.matchMedia('(display-mode: standalone)').matches; - } catch (e) { } + installedPWA = String(window.matchMedia('(display-mode: standalone)').matches); + } catch (e) {} let touchInput = "UNKNOWN"; try { // MDN claims broad support across browsers - touchInput = window.matchMedia('(pointer: coarse)').matches; + touchInput = String(window.matchMedia('(pointer: coarse)').matches); } catch (e) { } const client = MatrixClientPeg.get(); @@ -118,21 +125,21 @@ export default async function sendBugReport(bugReportEndpoint, opts) { // add storage persistence/quota information if (navigator.storage && navigator.storage.persisted) { try { - body.append("storageManager_persisted", await navigator.storage.persisted()); + body.append("storageManager_persisted", String(await navigator.storage.persisted())); } catch (e) {} } else if (document.hasStorageAccess) { // Safari try { - body.append("storageManager_persisted", await document.hasStorageAccess()); + body.append("storageManager_persisted", String(await document.hasStorageAccess())); } catch (e) {} } if (navigator.storage && navigator.storage.estimate) { try { const estimate = await navigator.storage.estimate(); - body.append("storageManager_quota", estimate.quota); - body.append("storageManager_usage", estimate.usage); + body.append("storageManager_quota", String(estimate.quota)); + body.append("storageManager_usage", String(estimate.usage)); if (estimate.usageDetails) { Object.keys(estimate.usageDetails).forEach(k => { - body.append(`storageManager_usage_${k}`, estimate.usageDetails[k]); + body.append(`storageManager_usage_${k}`, String(estimate.usageDetails[k])); }); } } catch (e) {} @@ -163,7 +170,7 @@ export default async function sendBugReport(bugReportEndpoint, opts) { await _submitReport(bugReportEndpoint, body, progressCallback); } -function _submitReport(endpoint, body, progressCallback) { +function _submitReport(endpoint: string, body: FormData, progressCallback: (string) => void) { return new Promise((resolve, reject) => { const req = new XMLHttpRequest(); req.open("POST", endpoint); diff --git a/yarn.lock b/yarn.lock index c42828e461..c601e37340 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1257,6 +1257,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/modernizr@^3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@types/modernizr/-/modernizr-3.5.3.tgz#8ef99e6252191c1d88647809109dc29884ba6d7a" + integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA== + "@types/node@*": version "13.11.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b" From 170d10a1a6d1d66116a4a020a6f6b2c6e53f7cbe Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 19 Apr 2020 12:09:07 +0100 Subject: [PATCH 75/83] Add rageshake slash command Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 71815dde8c..68bbfa97f3 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -35,6 +35,8 @@ import { abbreviateUrl } from './utils/UrlUtils'; import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils'; import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks"; import {inviteUsersToRoom} from "./RoomInvite"; +import sendBugReport from "./rageshake/submit-rageshake"; +import SdkConfig from "./SdkConfig"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -912,6 +914,19 @@ export const Commands = [ }, category: CommandCategories.advanced, }), + new Command({ + command: "rageshake", + aliases: ["bugreport"], + description: _td("Send a bug report with logs"), + args: "", + runFn: function(roomId, args) { + return success(sendBugReport(SdkConfig.get().bug_report_endpoint_url, { + userText: args, + sendLogs: true, + })); + }, + category: CommandCategories.advanced, + }), // Command definitions for autocompletion ONLY: // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes From d0410ade5e222b6832e9d949ac9c8f8512a533a0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 19 Apr 2020 12:11:40 +0100 Subject: [PATCH 76/83] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0cf2944116..a9076a77a2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -216,6 +216,7 @@ "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", "Displays information about a user": "Displays information about a user", + "Send a bug report with logs": "Send a bug report with logs", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 881848b98b54e8ea921af0a7746c40ca85dd2185 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 19 Apr 2020 12:19:31 +0100 Subject: [PATCH 77/83] Add modal to confirm that rageshake from cmd was sent Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 16 ++++++++++++---- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 68bbfa97f3..76472e4d66 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -920,10 +920,18 @@ export const Commands = [ description: _td("Send a bug report with logs"), args: "", runFn: function(roomId, args) { - return success(sendBugReport(SdkConfig.get().bug_report_endpoint_url, { - userText: args, - sendLogs: true, - })); + return success( + sendBugReport(SdkConfig.get().bug_report_endpoint_url, { + userText: args, + sendLogs: true, + }).then(() => { + const InfoDialog = sdk.getComponent('dialogs.InfoDialog'); + Modal.createTrackedDialog('Slash Commands', 'Rageshake sent', InfoDialog, { + title: _t('Logs sent'), + description: _t('Thank you!'), + }); + }), + ); }, category: CommandCategories.advanced, }), diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a9076a77a2..6ed89e927d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -217,6 +217,8 @@ "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", "Displays information about a user": "Displays information about a user", "Send a bug report with logs": "Send a bug report with logs", + "Logs sent": "Logs sent", + "Thank you!": "Thank you!", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", @@ -1528,8 +1530,6 @@ "Close dialog": "Close dialog", "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.", "Preparing to send logs": "Preparing to send logs", - "Logs sent": "Logs sent", - "Thank you!": "Thank you!", "Failed to send logs: ": "Failed to send logs: ", "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Reminder: Your browser is unsupported, so your experience may be unpredictable.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", From 972baa881b08c6286609beedbb724eb1e2db0e51 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 19 Apr 2020 19:14:08 -0600 Subject: [PATCH 78/83] Revert "Fix Filepanel scroll position state lost when room is changed" --- src/components/structures/FilePanel.js | 44 +------------------------- src/components/structures/RoomView.js | 4 +-- src/stores/RoomScrollStateStore.js | 19 +++-------- 3 files changed, 8 insertions(+), 59 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index f073785df3..f8c03be864 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -25,8 +25,6 @@ import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; -import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; - /* * Component which shows the filtered file using a TimelinePanel */ @@ -43,8 +41,6 @@ const FilePanel = createReactClass({ getInitialState: function() { return { timelineSet: null, - initialEventId: null, - initialEventPixelOffset: null, }; }, @@ -88,16 +84,6 @@ const FilePanel = createReactClass({ await this.updateTimelineSet(this.props.roomId); - if (this.props.roomId) { - const filePanelScrollState = RoomScrollStateStore.getFilePanelScrollState(this.props.roomId); - if (filePanelScrollState) { - this.setState({ - initialEventId: filePanelScrollState.focussedEvent, - initialEventPixelOffset: filePanelScrollState.pixelOffset, - }); - } - } - if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return; // The timelineSets filter makes sure that encrypted events that contain @@ -115,10 +101,6 @@ const FilePanel = createReactClass({ }, componentWillUnmount() { - if (this.props.roomId) { - RoomScrollStateStore.setFilePanelScrollState(this.props.roomId, this._getScrollState()); - } - const client = MatrixClientPeg.get(); if (client === null) return; @@ -208,26 +190,6 @@ const FilePanel = createReactClass({ } }, - _getScrollState: function() { - const timelinePanel = this._timelinePanel; - const scrollState = timelinePanel.getScrollState(); - - if (!timelinePanel) return null; - - if (!scrollState || scrollState.stuckAtBottom) { - return null; - } - - return { - focussedEvent: scrollState.trackedScrollToken, - pixelOffset: scrollState.pixelOffset, - }; - }, - - _getTimelinePanelRef: function(ref) { - this._timelinePanel = ref; - }, - render: function() { if (MatrixClientPeg.get().isGuest()) { return
@@ -253,15 +215,11 @@ const FilePanel = createReactClass({ // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); return (
- Date: Mon, 20 Apr 2020 10:02:40 +0200 Subject: [PATCH 79/83] EventIndex: Filter out events that don't have a propper content value. --- src/indexing/EventIndex.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index e4e8f26031..c09fc73a58 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -275,6 +275,7 @@ export default class EventIndex extends EventEmitter { const validEventType = isUsefulType && !ev.isRedacted() && !ev.isDecryptionFailure(); let validMsgType = true; + let hasContentValue = true; if (ev.getType() === "m.room.message" && !ev.isRedacted()) { // Expand this if there are more invalid msgtypes. @@ -282,9 +283,15 @@ export default class EventIndex extends EventEmitter { if (!msgtype) validMsgType = false; else validMsgType = !msgtype.startsWith("m.key.verification"); + + if (!ev.getContent().body) hasContentValue = false + } else if (ev.getType() === "m.room.topic" && !ev.isRedacted()) { + if (!ev.getContent().topic) hasContentValue = false; + } else if (ev.getType() === "m.room.name" && !ev.isRedacted()) { + if (!ev.getContent().name) hasContentValue = false; } - return validEventType && validMsgType; + return validEventType && validMsgType && hasContentValue; } /** From 3781bdc9758ddb54f9eacc6f56784b0d64d8ca01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 20 Apr 2020 10:10:16 +0200 Subject: [PATCH 80/83] EventIndex: Add a missing semicolon. --- src/indexing/EventIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index c09fc73a58..a6bbbb5f96 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -284,7 +284,7 @@ export default class EventIndex extends EventEmitter { if (!msgtype) validMsgType = false; else validMsgType = !msgtype.startsWith("m.key.verification"); - if (!ev.getContent().body) hasContentValue = false + if (!ev.getContent().body) hasContentValue = false; } else if (ev.getType() === "m.room.topic" && !ev.isRedacted()) { if (!ev.getContent().topic) hasContentValue = false; } else if (ev.getType() === "m.room.name" && !ev.isRedacted()) { From 4494c6cf2bfc96a17c319ce8d8d2ffef7d88abb2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 11:21:51 +0100 Subject: [PATCH 81/83] ImageView make clicking off it easier Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/elements/_ImageView.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 0a4ed2a194..983ef074f2 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -37,7 +37,7 @@ limitations under the License. order: 2; /* min-width hack needed for FF */ min-width: 0px; - height: 90%; + max-height: 90%; flex: 15 15 0; display: flex; align-items: center; From 50cc44f0c4e632490f290873b676785a7d8e4648 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Apr 2020 14:16:34 +0100 Subject: [PATCH 82/83] Fix CSS class in ButtonPlaceholder --- src/components/views/elements/ButtonPlaceholder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ButtonPlaceholder.js b/src/components/views/elements/ButtonPlaceholder.js index 9c40df9db6..6501429e65 100644 --- a/src/components/views/elements/ButtonPlaceholder.js +++ b/src/components/views/elements/ButtonPlaceholder.js @@ -15,5 +15,5 @@ limitations under the License. */ export default function ButtonPlaceholder(props) { - return
{props.children}
; + return
{props.children}
; } From 2546e23a3e63b312c721bee9a82f3d68d548a938 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Apr 2020 14:36:15 +0100 Subject: [PATCH 83/83] Don't recheck DeviceListener until after initial sync is finished Each recheck caused a GET to account data to see if the master key exists if done before the initial sync, which is unnecessary here. This just makes it wait until the initial sync is done to recheck. Fixes https://github.com/vector-im/riot-web/issues/13279 --- src/DeviceListener.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 3201e4af45..4ec2ec0fa0 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -52,6 +52,7 @@ export default class DeviceListener { MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged); MatrixClientPeg.get().on('accountData', this._onAccountData); + MatrixClientPeg.get().on('sync', this._onSync); this._recheck(); } @@ -62,6 +63,7 @@ export default class DeviceListener { MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged); MatrixClientPeg.get().removeListener('accountData', this._onAccountData); + MatrixClientPeg.get().removeListener('sync', this._onSync); } this._dismissed.clear(); } @@ -109,6 +111,10 @@ export default class DeviceListener { } } + _onSync = (state, prevState) => { + if (state === 'PREPARED' && prevState === null) this._recheck(); + } + // The server doesn't tell us when key backup is set up, so we poll // & cache the result async _getKeyBackupInfo() { @@ -129,6 +135,10 @@ export default class DeviceListener { ) return; if (!cli.isCryptoEnabled()) return; + // don't recheck until the initial sync is complete: lots of account data events will fire + // while the initial sync is processing and we don't need to recheck on each one of them + // (we add a listener on sync to do once check after the initial sync is done) + if (!cli.isInitialSyncComplete()) return; const crossSigningReady = await cli.isCrossSigningReady();