Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/15178

 Conflicts:
	src/settings/Settings.ts
	src/settings/UIFeature.ts
pull/21833/head
Michael Telatynski 2020-09-17 14:10:51 +01:00
commit c904b4f416
13 changed files with 210 additions and 65 deletions

View File

@ -18,6 +18,12 @@ limitations under the License.
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
&.mx_WelcomePage_registrationDisabled {
.mx_ButtonCreateAccount {
display: none;
}
}
} }
.mx_Welcome .mx_AuthBody_language { .mx_Welcome .mx_AuthBody_language {

View File

@ -71,9 +71,12 @@ limitations under the License.
margin-right: 64px; margin-right: 64px;
} }
.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container {
width: 299px;
}
.mx_ShareDialog_social_container { .mx_ShareDialog_social_container {
display: inline-block; display: inline-block;
width: 299px;
} }
.mx_ShareDialog_social_icon { .mx_ShareDialog_social_icon {

View File

@ -1947,7 +1947,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
render() { render() {
const fragmentAfterLogin = this.getFragmentAfterLogin(); const fragmentAfterLogin = this.getFragmentAfterLogin();
let view; let view = null;
if (this.state.view === Views.LOADING) { if (this.state.view === Views.LOADING) {
const Spinner = sdk.getComponent('elements.Spinner'); const Spinner = sdk.getComponent('elements.Spinner');
@ -2026,7 +2026,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else if (this.state.view === Views.WELCOME) { } else if (this.state.view === Views.WELCOME) {
const Welcome = sdk.getComponent('auth.Welcome'); const Welcome = sdk.getComponent('auth.Welcome');
view = <Welcome />; view = <Welcome />;
} 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 Registration = sdk.getComponent('structures.auth.Registration');
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail; const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
view = ( view = (
@ -2044,7 +2044,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
{...this.getServerProperties()} {...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'); const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword');
view = ( view = (
<ForgotPassword <ForgotPassword
@ -2055,6 +2055,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
/> />
); );
} else if (this.state.view === Views.LOGIN) { } else if (this.state.view === Views.LOGIN) {
const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset);
const Login = sdk.getComponent('structures.auth.Login'); const Login = sdk.getComponent('structures.auth.Login');
view = ( view = (
<Login <Login
@ -2063,7 +2064,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onRegisterClick={this.onRegisterClick} onRegisterClick={this.onRegisterClick}
fallbackHsUrl={this.getFallbackHsUrl()} fallbackHsUrl={this.getFallbackHsUrl()}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onForgotPasswordClick={this.onForgotPasswordClick} onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined}
onServerConfigChange={this.onServerConfigChange} onServerConfigChange={this.onServerConfigChange}
fragmentAfterLogin={fragmentAfterLogin} fragmentAfterLogin={fragmentAfterLogin}
{...this.getServerProperties()} {...this.getServerProperties()}

View File

@ -28,6 +28,8 @@ import classNames from "classnames";
import AuthPage from "../../views/auth/AuthPage"; import AuthPage from "../../views/auth/AuthPage";
import SSOButton from "../../views/elements/SSOButton"; import SSOButton from "../../views/elements/SSOButton";
import PlatformPeg from '../../../PlatformPeg'; import PlatformPeg from '../../../PlatformPeg';
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
// For validating phone numbers without country codes // For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; 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")} {_t("If you've joined lots of rooms, this might take a while")}
</div> } </div> }
</div>; </div>;
} else { } else if (SettingsStore.getValue(UIFeature.Registration)) {
footer = ( footer = (
<a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#"> <a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#">
{ _t('Create account') } { _t('Create account') }

View File

@ -15,10 +15,14 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import classNames from "classnames";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import AuthPage from "./AuthPage"; import AuthPage from "./AuthPage";
import {_td} from "../../../languageHandler"; import {_td} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
// translatable strings for Welcome pages // translatable strings for Welcome pages
_td("Sign in with SSO"); _td("Sign in with SSO");
@ -39,7 +43,9 @@ export default class Welcome extends React.PureComponent {
return ( return (
<AuthPage> <AuthPage>
<div className="mx_Welcome"> <div className={classNames("mx_Welcome", {
mx_WelcomePage_registrationDisabled: !SettingsStore.getValue(UIFeature.Registration),
})}>
<EmbeddedPage <EmbeddedPage
className="mx_WelcomePage" className="mx_WelcomePage"
url={pageUrl} url={pageUrl}

View File

@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions";
import {DefaultTagID} from "../../../stores/room-list/models"; import {DefaultTagID} from "../../../stores/room-list/models";
import RoomListStore from "../../../stores/room-list/RoomListStore"; import RoomListStore from "../../../stores/room-list/RoomListStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here. // we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */ /* eslint-disable camelcase */
@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent {
if (this.state.filterText.startsWith('@')) { if (this.state.filterText.startsWith('@')) {
// Assume mxid // Assume mxid
newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null}); newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null});
} else { } else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
// Assume email // Assume email
newMember = new ThreepidMember(this.state.filterText); newMember = new ThreepidMember(this.state.filterText);
} }
@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent {
this.setState({tryingIdentityServer: true}); this.setState({tryingIdentityServer: true});
return; return;
} }
if (term.indexOf('@') > 0 && Email.looksValid(term)) { if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) {
// Start off by suggesting the plain email while we try and resolve it // Start off by suggesting the plain email while we try and resolve it
// to a real account. // to a real account.
this.setState({ this.setState({
@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent {
} }
_renderIdentityServerWarning() { _renderIdentityServerWarning() {
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) { if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
!SettingsStore.getValue(UIFeature.IdentityServer)
) {
return null; return null;
} }
@ -1086,22 +1090,38 @@ export default class InviteDialog extends React.PureComponent {
let buttonText; let buttonText;
let goButtonFn; let goButtonFn;
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
const userId = MatrixClientPeg.get().getUserId(); const userId = MatrixClientPeg.get().getUserId();
if (this.props.kind === KIND_DM) { if (this.props.kind === KIND_DM) {
title = _t("Direct Messages"); title = _t("Direct Messages");
helpText = _t(
"Start a conversation with someone using their name, username (like <userId/>) or email address.", if (identityServersEnabled) {
{}, helpText = _t(
{userId: () => { "Start a conversation with someone using their name, username (like <userId/>) or email address.",
return <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>; {},
}}, {userId: () => {
); return (
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
);
}},
);
} else {
helpText = _t(
"Start a conversation with someone using their name or username (like <userId/>).",
{},
{userId: () => {
return (
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
);
}},
);
}
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
helpText = _t( const inviteText = _t("This won't invite them to %(communityName)s. " +
"Start a conversation with someone using their name, username (like <userId/>) or email address. " + "To invite someone to %(communityName)s, click <a>here</a>",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " +
"<a>here</a>.",
{communityName}, { {communityName}, {
userId: () => { userId: () => {
return ( return (
@ -1122,21 +1142,40 @@ export default class InviteDialog extends React.PureComponent {
}, },
}, },
); );
helpText = <React.Fragment>
{ helpText } {inviteText}
</React.Fragment>;
} }
buttonText = _t("Go"); buttonText = _t("Go");
goButtonFn = this._startDm; goButtonFn = this._startDm;
} else { // KIND_INVITE } else { // KIND_INVITE
title = _t("Invite to this room"); title = _t("Invite to this room");
helpText = _t(
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.", if (identityServersEnabled) {
{}, helpText = _t(
{ "Invite someone using their name, username (like <userId/>), email address or " +
userId: () => "<a>share this room</a>.",
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>, {},
a: (sub) => {
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>, userId: () =>
}, <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
); a: (sub) =>
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
},
);
} else {
helpText = _t(
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
{},
{
userId: () =>
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
a: (sub) =>
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
},
);
}
buttonText = _t("Invite"); buttonText = _t("Invite");
goButtonFn = this._inviteUsers; goButtonFn = this._inviteUsers;
} }

View File

@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings";
import StyledCheckbox from '../elements/StyledCheckbox'; import StyledCheckbox from '../elements/StyledCheckbox';
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
import { IDialogProps } from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
const socials = [ const socials = [
{ {
@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
const matrixToUrl = this.getUrl(); const matrixToUrl = this.getUrl();
const encodedUrl = encodeURIComponent(matrixToUrl); const encodedUrl = encodeURIComponent(matrixToUrl);
const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode);
const showSocials = SettingsStore.getValue(UIFeature.ShareSocial);
let qrSocialSection;
if (showQrCode || showSocials) {
qrSocialSection = <>
<hr />
<div className="mx_ShareDialog_split">
{ showQrCode && <div className="mx_ShareDialog_qrcode_container">
<QRCode data={matrixToUrl} width={256} />
</div> }
{ showSocials && <div className="mx_ShareDialog_social_container">
{ socials.map((social) => (
<a
rel="noreferrer noopener"
target="_blank"
key={social.name}
title={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>
)) }
</div> }
</div>
</>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return <BaseDialog return <BaseDialog
title={title} title={title}
@ -220,27 +251,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
/> />
</div> </div>
{ checkbox } { checkbox }
<hr /> { qrSocialSection }
<div className="mx_ShareDialog_split">
<div className="mx_ShareDialog_qrcode_container">
<QRCode data={matrixToUrl} width={256} />
</div>
<div className="mx_ShareDialog_social_container">
{ socials.map((social) => (
<a
rel="noreferrer noopener"
target="_blank"
key={social.name}
title={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>
)) }
</div>
</div>
</div> </div>
</BaseDialog>; </BaseDialog>;
} }

View File

@ -386,17 +386,31 @@ export default class GeneralUserSettingsTab extends React.Component {
width="18" height="18" alt={_t("Warning")} /> width="18" height="18" alt={_t("Warning")} />
: null; : null;
let accountManagementSection;
if (SettingsStore.getValue(UIFeature.Deactivate)) {
accountManagementSection = <>
<div className="mx_SettingsTab_heading">{_t("Deactivate account")}</div>
{this._renderManagementSection()}
</>;
}
let discoverySection;
if (SettingsStore.getValue(UIFeature.IdentityServer)) {
discoverySection = <>
<div className="mx_SettingsTab_heading">{discoWarning} {_t("Discovery")}</div>
{this._renderDiscoverySection()}
</>;
}
return ( return (
<div className="mx_SettingsTab"> <div className="mx_SettingsTab">
<div className="mx_SettingsTab_heading">{_t("General")}</div> <div className="mx_SettingsTab_heading">{_t("General")}</div>
{this._renderProfileSection()} {this._renderProfileSection()}
{this._renderAccountSection()} {this._renderAccountSection()}
{this._renderLanguageSection()} {this._renderLanguageSection()}
<div className="mx_SettingsTab_heading">{discoWarning} {_t("Discovery")}</div> { discoverySection }
{this._renderDiscoverySection()}
{this._renderIntegrationManagerSection() /* Has its own title */} {this._renderIntegrationManagerSection() /* Has its own title */}
<div className="mx_SettingsTab_heading">{_t("Deactivate account")}</div> { accountManagementSection }
{this._renderManagementSection()}
</div> </div>
); );
} }

View File

@ -832,9 +832,9 @@
"Account management": "Account management", "Account management": "Account management",
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "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", "Deactivate account": "Deactivate account",
"Discovery": "Discovery",
"General": "General",
"Legal": "Legal", "Legal": "Legal",
"Credits": "Credits", "Credits": "Credits",
"For help with using %(brand)s, click <a>here</a>.": "For help with using %(brand)s, click <a>here</a>.", "For help with using %(brand)s, click <a>here</a>.": "For help with using %(brand)s, click <a>here</a>.",
@ -1732,9 +1732,11 @@
"Recently Direct Messaged": "Recently Direct Messaged", "Recently Direct Messaged": "Recently Direct Messaged",
"Direct Messages": "Direct Messages", "Direct Messages": "Direct Messages",
"Start a conversation with someone using their name, username (like <userId/>) or email address.": "Start a conversation with someone using their name, username (like <userId/>) or email address.", "Start a conversation with someone using their name, username (like <userId/>) or email address.": "Start a conversation with someone using their name, username (like <userId/>) or email address.",
"Start a conversation with someone using their name, username (like <userId/>) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>.": "Start a conversation with someone using their name, username (like <userId/>) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>.", "Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
"Go": "Go", "Go": "Go",
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.", "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
"a new master key signature": "a new master key signature", "a new master key signature": "a new master key signature",
"a new cross-signing key signature": "a new cross-signing key signature", "a new cross-signing key signature": "a new cross-signing key signature",
"a device cross-signing signature": "a device cross-signing signature", "a device cross-signing signature": "a device cross-signing signature",

View File

@ -631,6 +631,30 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_UI_FEATURE, supportedLevels: LEVELS_UI_FEATURE,
default: true, 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,
},
[UIFeature.ShareQRCode]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
[UIFeature.ShareSocial]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
[UIFeature.IdentityServer]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
[UIFeature.Flair]: { [UIFeature.Flair]: {
supportedLevels: LEVELS_UI_FEATURE, supportedLevels: LEVELS_UI_FEATURE,
default: true, default: true,

View File

@ -20,5 +20,11 @@ export enum UIFeature {
Widgets = "UIFeature.widgets", Widgets = "UIFeature.widgets",
Voip = "UIFeature.voip", Voip = "UIFeature.voip",
Feedback = "UIFeature.feedback", Feedback = "UIFeature.feedback",
Registration = "UIFeature.registration",
PasswordReset = "UIFeature.passwordReset",
Deactivate = "UIFeature.deactivate",
ShareQRCode = "UIFeature.shareQrCode",
ShareSocial = "UIFeature.shareSocial",
IdentityServer = "UIFeature.identityServer",
Flair = "UIFeature.flair", Flair = "UIFeature.flair",
} }

View File

@ -18,6 +18,7 @@ limitations under the License.
import React from "react"; import React from "react";
import {Store} from 'flux/utils'; import {Store} from 'flux/utils';
import {MatrixError} from "matrix-js-sdk/src/http-api";
import dis from '../dispatcher/dispatcher'; import dis from '../dispatcher/dispatcher';
import {MatrixClientPeg} from '../MatrixClientPeg'; import {MatrixClientPeg} from '../MatrixClientPeg';
@ -26,6 +27,9 @@ import Modal from '../Modal';
import { _t } from '../languageHandler'; import { _t } from '../languageHandler';
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
import {ActionPayload} from "../dispatcher/payloads"; import {ActionPayload} from "../dispatcher/payloads";
import {retry} from "../utils/promise";
const NUM_JOIN_RETRY = 5;
const INITIAL_STATE = { const INITIAL_STATE = {
// Whether we're joining the currently viewed room (see isJoining()) // Whether we're joining the currently viewed room (see isJoining())
@ -259,24 +263,32 @@ class RoomViewStore extends Store<ActionPayload> {
}); });
} }
private joinRoom(payload: ActionPayload) { private async joinRoom(payload: ActionPayload) {
this.setState({ this.setState({
joining: true, joining: true,
}); });
MatrixClientPeg.get().joinRoom(
this.state.roomAlias || this.state.roomId, payload.opts, const cli = MatrixClientPeg.get();
).then(() => { const address = this.state.roomAlias || this.state.roomId;
try {
await retry<void, MatrixError>(() => 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 // 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 // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
// room. // room.
dis.dispatch({ action: 'join_room_ready' }); dis.dispatch({ action: 'join_room_ready' });
}, (err) => { } catch (err) {
dis.dispatch({ dis.dispatch({
action: 'join_room_error', action: 'join_room_error',
err: err, err: err,
}); });
let msg = err.message ? err.message : JSON.stringify(err); let msg = err.message ? err.message : JSON.stringify(err);
console.log("Failed to join room:", msg); console.log("Failed to join room:", msg);
if (err.name === "ConnectionError") { if (err.name === "ConnectionError") {
msg = _t("There was an error joining the room"); msg = _t("There was an error joining the room");
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
@ -296,12 +308,13 @@ class RoomViewStore extends Store<ActionPayload> {
} }
} }
} }
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
title: _t("Failed to join room"), title: _t("Failed to join room"),
description: msg, description: msg,
}); });
}); }
} }
private getInvitingUserId(roomId: string): string { private getInvitingUserId(roomId: string): string {

View File

@ -68,3 +68,21 @@ export function allSettled<T>(promises: Promise<T>[]): Promise<Array<ISettledFul
})); }));
})); }));
} }
// Helper method to retry a Promise a given number of times or until a predicate fails
export async function retry<T, E extends Error>(fn: () => Promise<T>, 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;
}