From 229967aa33c0ffeef1cc59be3fc614d9efd95d48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:16:29 +0100 Subject: [PATCH 01/15] Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT --- src/stores/RoomViewStore.tsx | 25 +++++++++++++++++++------ src/utils/promise.ts | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index a0f0fb8f68..be1141fa1e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import {Store} from 'flux/utils'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import dis from '../dispatcher/dispatcher'; import {MatrixClientPeg} from '../MatrixClientPeg'; @@ -26,6 +27,9 @@ import Modal from '../Modal'; import { _t } from '../languageHandler'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import {ActionPayload} from "../dispatcher/payloads"; +import {retry} from "../utils/promise"; + +const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { // Whether we're joining the currently viewed room (see isJoining()) @@ -259,24 +263,32 @@ class RoomViewStore extends Store { }); } - private joinRoom(payload: ActionPayload) { + private async joinRoom(payload: ActionPayload) { this.setState({ joining: true, }); - MatrixClientPeg.get().joinRoom( - this.state.roomAlias || this.state.roomId, payload.opts, - ).then(() => { + + const cli = MatrixClientPeg.get(); + const address = this.state.roomAlias || this.state.roomId; + try { + await retry(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => { + // if we received a Gateway timeout then retry + return err.httpStatus === 504; + }); + // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. dis.dispatch({ action: 'join_room_ready' }); - }, (err) => { + } catch (err) { dis.dispatch({ action: 'join_room_error', err: err, }); + let msg = err.message ? err.message : JSON.stringify(err); console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -296,12 +308,13 @@ class RoomViewStore extends Store { } } } + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); - }); + } } private getInvitingUserId(roomId: string): string { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index d3ae2c3d1b..f828ddfdaf 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -68,3 +68,21 @@ export function allSettled(promises: Promise[]): Promise(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { + let lastErr: E; + for (let i = 0; i < num; i++) { + try { + const v = await fn(); + // If `await fn()` throws then we won't reach here + return v; + } catch (err) { + if (predicate && !predicate(err)) { + throw err; + } + lastErr = err; + } + } + throw lastErr; +} From 771ab8259844a908f0f22256a1ac90b3055fc47f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:29:21 +0100 Subject: [PATCH 02/15] Hide Analytics sections if piwik config is not provided --- src/Analytics.js | 8 +++- src/components/structures/MatrixChat.tsx | 4 +- .../tabs/user/SecurityUserSettingsTab.js | 38 ++++++++++--------- src/i18n/strings/en_EN.json | 6 +-- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/Analytics.js b/src/Analytics.js index 9966d0845e..135cc2eb7a 100644 --- a/src/Analytics.js +++ b/src/Analytics.js @@ -170,15 +170,19 @@ class Analytics { return !this.baseUrl; } + canEnable() { + const config = SdkConfig.get(); + return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId; + } + /** * Enable Analytics if initialized but disabled * otherwise try and initalize, no-op if piwik config missing */ async enable() { if (!this.disabled) return; - + if (!this.canEnable()) return; const config = SdkConfig.get(); - if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return; this.baseUrl = new URL("piwik.php", config.piwik.url); // set constants diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 4cdb58014e..9dfac86d63 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1214,8 +1214,8 @@ export default class MatrixChat extends React.PureComponent { StorageManager.tryPersistStorage(); - if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") { - showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl); + if (SettingsStore.getValue("showCookieBar") && Analytics.canEnable()) { + showAnalyticsToast(this.props.config.piwik?.policyUrl); } } diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..03093d2dca 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -329,6 +329,26 @@ export default class SecurityUserSettingsTab extends React.Component { ; } + let analyticsSection; + if (Analytics.canEnable()) { + analyticsSection =
+ {_t("Analytics")} +
+ {_t( + "%(brand)s collects anonymous analytics to allow us to improve the application.", + { brand }, + )} +   + {_t("Privacy is important to us, so we don't collect any personal or " + + "identifiable data for our analytics.")} + + {_t("Learn more about how we use analytics.")} + +
+ +
; + } + return (
{warning} @@ -358,23 +378,7 @@ export default class SecurityUserSettingsTab extends React.Component { {this._renderCurrentDeviceInfo()}
{_t("Privacy")}
-
- {_t("Analytics")} -
- {_t( - "%(brand)s collects anonymous analytics to allow us to improve the application.", - { brand }, - )} -   - {_t("Privacy is important to us, so we don't collect any personal or " + - "identifiable data for our analytics.")} - - {_t("Learn more about how we use analytics.")} - -
- -
+ { analyticsSection }
{_t("Advanced")}
{this._renderIgnoredUsers()} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..070cf7e597 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -912,13 +912,13 @@ "Message search": "Message search", "Cross-signing": "Cross-signing", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", + "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", + "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you’re logged in": "Where you’re logged in", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.", "A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with", "Privacy": "Privacy", - "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", - "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", - "Learn more about how we use analytics.": "Learn more about how we use analytics.", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", From d9909864071985f884189aba751755b5ca7cb89f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:37:15 +0100 Subject: [PATCH 03/15] Also hide Privacy heading in settings and fix React Key warning --- .../views/settings/SecureBackupPanel.js | 8 ++-- .../tabs/user/SecurityUserSettingsTab.js | 38 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f0655d54a..c058e27de7 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

{_t("Back up your keys before signing out to avoid losing them.")}

; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 03093d2dca..0c49f108d6 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -329,24 +329,27 @@ export default class SecurityUserSettingsTab extends React.Component {
; } - let analyticsSection; + let privacySection; if (Analytics.canEnable()) { - analyticsSection =
- {_t("Analytics")} -
- {_t( - "%(brand)s collects anonymous analytics to allow us to improve the application.", - { brand }, - )} -   - {_t("Privacy is important to us, so we don't collect any personal or " + - "identifiable data for our analytics.")} - - {_t("Learn more about how we use analytics.")} - + privacySection = +
{_t("Privacy")}
+
+ {_t("Analytics")} +
+ {_t( + "%(brand)s collects anonymous analytics to allow us to improve the application.", + { brand }, + )} +   + {_t("Privacy is important to us, so we don't collect any personal or " + + "identifiable data for our analytics.")} + + {_t("Learn more about how we use analytics.")} + +
+
- -
; + ; } return ( @@ -377,8 +380,7 @@ export default class SecurityUserSettingsTab extends React.Component { {crossSigning} {this._renderCurrentDeviceInfo()}
-
{_t("Privacy")}
- { analyticsSection } + { privacySection }
{_t("Advanced")}
{this._renderIgnoredUsers()} From caef9b27a0f5de9da5937b517768efb13f4c4c51 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:50:53 +0100 Subject: [PATCH 04/15] Also hide bug reporting prompts from the Error Boundaries --- src/components/views/elements/ErrorBoundary.js | 15 ++++++++++++--- .../views/messages/TileErrorBoundary.js | 13 ++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/ErrorBoundary.js b/src/components/views/elements/ErrorBoundary.js index 68bec667d8..9fe6861250 100644 --- a/src/components/views/elements/ErrorBoundary.js +++ b/src/components/views/elements/ErrorBoundary.js @@ -20,6 +20,7 @@ import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import PlatformPeg from '../../../PlatformPeg'; import Modal from '../../../Modal'; +import SdkConfig from "../../../SdkConfig"; /** * This error boundary component can be used to wrap large content areas and @@ -73,9 +74,10 @@ export default class ErrorBoundary extends React.PureComponent { if (this.state.error) { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const newIssueUrl = "https://github.com/vector-im/element-web/issues/new"; - return
-
-

{_t("Something went wrong!")}

+ + let bugReportSection; + if (SdkConfig.get().bug_report_endpoint_url) { + bugReportSection =

{_t( "Please create a new issue " + "on GitHub so that we can investigate this bug.", {}, { @@ -94,6 +96,13 @@ export default class ErrorBoundary extends React.PureComponent { {_t("Submit debug logs")} + ; + } + + return

+
+

{_t("Something went wrong!")}

+ { bugReportSection } {_t("Clear cache and reload")} diff --git a/src/components/views/messages/TileErrorBoundary.js b/src/components/views/messages/TileErrorBoundary.js index e42ddab16a..9b67e32548 100644 --- a/src/components/views/messages/TileErrorBoundary.js +++ b/src/components/views/messages/TileErrorBoundary.js @@ -19,6 +19,7 @@ import classNames from 'classnames'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; +import SdkConfig from "../../../SdkConfig"; export default class TileErrorBoundary extends React.Component { constructor(props) { @@ -54,14 +55,20 @@ export default class TileErrorBoundary extends React.Component { mx_EventTile_content: true, mx_EventTile_tileError: true, }; + + let submitLogsButton; + if (SdkConfig.get().bug_report_endpoint_url) { + submitLogsButton = + {_t("Submit logs")} + ; + } + return (
{_t("Can't load this message")} { mxEvent && ` (${mxEvent.getType()})` } - - {_t("Submit logs")} - + { submitLogsButton }
); From dbd3245e02f0051fd1d5431d919b78c9d3e099ff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:53:30 +0100 Subject: [PATCH 05/15] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7abbd9331b..42c4b9d198 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1441,8 +1441,8 @@ "Click to view edits": "Click to view edits", "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", "edited": "edited", - "Can't load this message": "Can't load this message", "Submit logs": "Submit logs", + "Can't load this message": "Can't load this message", "Failed to load group members": "Failed to load group members", "Filter community members": "Filter community members", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", From 04d7aebad0908654a693324101e7ef6909c541f6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:59:14 +0100 Subject: [PATCH 06/15] UI Feature Flag: Disable feedback button --- src/components/structures/UserMenu.tsx | 22 ++++++++++++---------- src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index b83369d296..369d3b7720 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -50,6 +50,7 @@ import dis from "../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import ErrorDialog from "../views/dialogs/ErrorDialog"; import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog"; +import {UIFeature} from "../../settings/UIFeature"; interface IProps { isMinimized: boolean; @@ -285,6 +286,15 @@ export default class UserMenu extends React.Component { ); } + let feedbackButton; + if (SettingsStore.getValue(UIFeature.Feedback)) { + feedbackButton = ; + } + let primaryHeader = (
@@ -319,11 +329,7 @@ export default class UserMenu extends React.Component { label={_t("Archived rooms")} onClick={this.onShowArchived} /> */} - + { feedbackButton } { aria-label={_t("User settings")} onClick={(e) => this.onSettingsOpen(e, null)} /> - + { feedbackButton } Date: Wed, 16 Sep 2020 11:27:39 +0100 Subject: [PATCH 07/15] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..767dcd390c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2132,10 +2132,10 @@ "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", "Failed to find the general chat for this community": "Failed to find the general chat for this community", + "Feedback": "Feedback", "Notification settings": "Notification settings", "Security & privacy": "Security & privacy", "All settings": "All settings", - "Feedback": "Feedback", "Community settings": "Community settings", "User settings": "User settings", "Switch to light mode": "Switch to light mode", From d1070c05ddd3f7bf3f7c2171f4c5c831466a200b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:40:27 +0100 Subject: [PATCH 08/15] UI Feature Flag: Disable VoIP --- src/components/structures/MatrixChat.tsx | 3 +++ .../views/dialogs/UserSettingsDialog.js | 17 +++++++++++------ src/settings/Settings.ts | 5 +++++ src/settings/UIFeature.ts | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..48dc8a79d1 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1373,6 +1374,8 @@ export default class MatrixChat extends React.PureComponent { }); }); cli.on('Call.incoming', function(call) { + // Check if the VoIP UI has been disabled + if (!SettingsStore.getValue(UIFeature.Voip)) return; // we dispatch this synchronously to make sure that the event // handlers on the call are set up immediately (so that if // we get an immediate hangup, we don't get a stuck call) diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..f74f57b970 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -104,12 +105,16 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_preferencesIcon", , )); - tabs.push(new Tab( - USER_VOICE_TAB, - _td("Voice & Video"), - "mx_UserSettingsDialog_voiceIcon", - , - )); + + if (SettingsStore.getValue(UIFeature.Voip)) { + tabs.push(new Tab( + USER_VOICE_TAB, + _td("Voice & Video"), + "mx_UserSettingsDialog_voiceIcon", + , + )); + } + tabs.push(new Tab( USER_SECURITY_TAB, _td("Security & Privacy"), diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a7250982bf 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -588,6 +588,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "showCallButtonsInComposer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: true, + controller: new UIFeatureController(UIFeature.Voip), }, "e2ee.manuallyVerifyAllSessions": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, @@ -622,4 +623,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Voip]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..ce174ec4b5 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + Voip = "UIFeature.voip", } From dfabe79335b9024a9efd68d6a68bec0589b2c15c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:44:24 +0100 Subject: [PATCH 09/15] tidy up event handler --- src/components/structures/MatrixChat.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 48dc8a79d1..7a207dd9a5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1373,17 +1373,19 @@ export default class MatrixChat extends React.PureComponent { ready: true, }); }); - cli.on('Call.incoming', function(call) { - // Check if the VoIP UI has been disabled - if (!SettingsStore.getValue(UIFeature.Voip)) return; - // we dispatch this synchronously to make sure that the event - // handlers on the call are set up immediately (so that if - // we get an immediate hangup, we don't get a stuck call) - dis.dispatch({ - action: 'incoming_call', - call: call, - }, true); - }); + + if (SettingsStore.getValue(UIFeature.Voip)) { + cli.on('Call.incoming', function(call) { + // we dispatch this synchronously to make sure that the event + // handlers on the call are set up immediately (so that if + // we get an immediate hangup, we don't get a stuck call) + dis.dispatch({ + action: 'incoming_call', + call: call, + }, true); + }); + } + cli.on('Session.logged_out', function(errObj) { if (Lifecycle.isLoggingOut()) return; From 06c4abd65e0da867319fb328eff22657616dd80a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 15:23:08 +0100 Subject: [PATCH 10/15] Clean up UserInfo to not show a blank Power Selector for users not in room --- src/components/views/right_panel/UserInfo.js | 51 +++++++++----------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index fc73c8b542..3171890955 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -952,30 +952,26 @@ function useRoomPermissions(cli, room, user) { const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => { const [isEditing, setEditing] = useState(false); - if (room && user.roomId) { // is in room - if (isEditing) { - return ( setEditing(false)} />); - } else { - const IconButton = sdk.getComponent('elements.IconButton'); - const powerLevelUsersDefault = powerLevels.users_default || 0; - const powerLevel = parseInt(user.powerLevel, 10); - const modifyButton = roomPermissions.canEdit ? - ( setEditing(true)} />) : null; - const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); - const label = _t("%(role)s in %(roomName)s", - {role, roomName: room.name}, - {strong: label => {label}}, - ); - return ( -
-
{label}{modifyButton}
-
- ); - } + if (isEditing) { + return ( setEditing(false)} />); } else { - return null; + const IconButton = sdk.getComponent('elements.IconButton'); + const powerLevelUsersDefault = powerLevels.users_default || 0; + const powerLevel = parseInt(user.powerLevel, 10); + const modifyButton = roomPermissions.canEdit ? + ( setEditing(true)} />) : null; + const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); + const label = _t("%(role)s in %(roomName)s", + {role, roomName: room.name}, + {strong: label => {label}}, + ); + return ( +
+
{label}{modifyButton}
+
+ ); } }; @@ -1268,14 +1264,15 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { spinner = ; } - const memberDetails = ( - - ); + />; + } // only display the devices list if our client supports E2E const cryptoEnabled = cli.isCryptoEnabled(); From 93323febcba986cee7fbead49de882fd17b4c72b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:10:24 +0100 Subject: [PATCH 11/15] undo change --- src/components/views/settings/SecureBackupPanel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index c058e27de7..7f0655d54a 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

{_t("Back up your keys before signing out to avoid losing them.")}

; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); From 9be04f8a67790a6a849c213aa1a9b696f1a0fa59 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:10:43 +0100 Subject: [PATCH 12/15] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 070cf7e597..e8803ddc32 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -912,13 +912,13 @@ "Message search": "Message search", "Cross-signing": "Cross-signing", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", + "Privacy": "Privacy", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you’re logged in": "Where you’re logged in", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.", "A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with", - "Privacy": "Privacy", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", From 815a1559ffa8af51ab9794771a5915549e3cb9e2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 15:05:14 -0600 Subject: [PATCH 13/15] Fix setState() usage in the constructor of RoomDirectory React doesn't like it when we setState() in the constructor --- src/components/structures/RoomDirectory.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 16ab8edbed..44652e5073 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -70,10 +70,10 @@ export default class RoomDirectory extends React.Component { this.scrollPanel = null; this.protocols = null; - this.setState({protocolsLoading: true}); + this.state.protocolsLoading = true; if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; return; } @@ -102,14 +102,16 @@ export default class RoomDirectory extends React.Component { }); } else { // We don't use the protocols in the communities v2 prototype experience - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; // Grab the profile info async FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { this.setState({communityName: profile.name}); }); } + } + componentDidMount() { this.refreshRoomList(); } From d340dd58d1f4828f45dbf243d436bb21f32e1dca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 11:55:10 +0100 Subject: [PATCH 14/15] UI Feature Flag: Registration, Password Reset, Deactivate --- res/css/views/auth/_Welcome.scss | 6 ++++++ src/components/structures/MatrixChat.tsx | 10 ++++++---- src/components/structures/auth/Login.js | 4 +++- src/components/views/auth/Welcome.js | 8 +++++++- .../settings/tabs/user/GeneralUserSettingsTab.js | 11 +++++++++-- src/settings/Settings.ts | 12 ++++++++++++ src/settings/UIFeature.ts | 3 +++ 7 files changed, 46 insertions(+), 8 deletions(-) diff --git a/res/css/views/auth/_Welcome.scss b/res/css/views/auth/_Welcome.scss index 9043289184..f0e2b3de33 100644 --- a/res/css/views/auth/_Welcome.scss +++ b/res/css/views/auth/_Welcome.scss @@ -18,6 +18,12 @@ limitations under the License. display: flex; flex-direction: column; align-items: center; + + &.mx_WelcomePage_registrationDisabled { + .mx_ButtonCreateAccount { + display: none; + } + } } .mx_Welcome .mx_AuthBody_language { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 1875d80fa4..26d1941574 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1942,7 +1943,7 @@ export default class MatrixChat extends React.PureComponent { render() { const fragmentAfterLogin = this.getFragmentAfterLogin(); - let view; + let view = null; if (this.state.view === Views.LOADING) { const Spinner = sdk.getComponent('elements.Spinner'); @@ -2021,7 +2022,7 @@ export default class MatrixChat extends React.PureComponent { } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); view = ; - } else if (this.state.view === Views.REGISTER) { + } else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) { const Registration = sdk.getComponent('structures.auth.Registration'); const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail; view = ( @@ -2039,7 +2040,7 @@ export default class MatrixChat extends React.PureComponent { {...this.getServerProperties()} /> ); - } else if (this.state.view === Views.FORGOT_PASSWORD) { + } else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) { const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword'); view = ( { /> ); } else if (this.state.view === Views.LOGIN) { + const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset); const Login = sdk.getComponent('structures.auth.Login'); view = ( { onRegisterClick={this.onRegisterClick} fallbackHsUrl={this.getFallbackHsUrl()} defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} - onForgotPasswordClick={this.onForgotPasswordClick} + onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined} onServerConfigChange={this.onServerConfigChange} fragmentAfterLogin={fragmentAfterLogin} {...this.getServerProperties()} diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index a20bf0dd0a..118eed59e3 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -28,6 +28,8 @@ import classNames from "classnames"; import AuthPage from "../../views/auth/AuthPage"; import SSOButton from "../../views/elements/SSOButton"; import PlatformPeg from '../../../PlatformPeg'; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // For validating phone numbers without country codes const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; @@ -679,7 +681,7 @@ export default class LoginComponent extends React.Component { {_t("If you've joined lots of rooms, this might take a while")}
}
; - } else { + } else if (SettingsStore.getValue(UIFeature.Registration)) { footer = ( { _t('Create account') } diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index 5a30a02490..21032f4f1a 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -15,10 +15,14 @@ limitations under the License. */ import React from 'react'; +import classNames from "classnames"; + import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import AuthPage from "./AuthPage"; import {_td} from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // translatable strings for Welcome pages _td("Sign in with SSO"); @@ -39,7 +43,9 @@ export default class Welcome extends React.PureComponent { return ( -
+
: null; + let accountManagementSection; + if (SettingsStore.getValue(UIFeature.Deactivate)) { + accountManagementSection = <> +
{_t("Deactivate account")}
+ {this._renderManagementSection()} + ; + } + return (
{_t("General")}
@@ -395,8 +403,7 @@ export default class GeneralUserSettingsTab extends React.Component {
{discoWarning} {_t("Discovery")}
{this._renderDiscoverySection()} {this._renderIntegrationManagerSection() /* Has its own title */} -
{_t("Deactivate account")}
- {this._renderManagementSection()} + { accountManagementSection }
); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 21b3935c3e..f7a1b6655c 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -626,4 +626,16 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Registration]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.PasswordReset]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.Deactivate]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index dddef82df1..71821917bf 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -19,4 +19,7 @@ export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", Feedback = "UIFeature.feedback", + Registration = "UIFeature.registration", + PasswordReset = "UIFeature.passwordReset", + Deactivate = "UIFeature.deactivate", } From f52b267bd39a19872a0ff3300925f0ce9e403123 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 12:07:17 +0100 Subject: [PATCH 15/15] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2b4e01202..d91fe475df 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -832,9 +832,9 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", + "Deactivate account": "Deactivate account", "General": "General", "Discovery": "Discovery", - "Deactivate account": "Deactivate account", "Legal": "Legal", "Credits": "Credits", "For help with using %(brand)s, click
here.": "For help with using %(brand)s, click here.",