Properly translate errors in AddThreepid.ts
(#10432)
* Properly translate errors in AddThreepid.ts Part of https://github.com/vector-im/element-web/issues/9597 * Use translated message * Avoid returning undefined ever * More usage * Introduce UserFriendlyError * Use UserFriendlyError * Add more usage instead of normal error * Use types and translatedMessage * Fix lints * Update i18n although it's wrong * Use unknown for easier creation from try/catch * Use types * Use error types * Use types * Update i18n strings * Remove generic re-label of HTTPError See https://github.com/matrix-org/matrix-react-sdk/pull/10432#discussion_r1156468143 The HTTPError already has a good label and it isn't even translated if we re-label it here in this way generically Probably best to just remove in favor of thinking about a translations in general from the `matrix-js-sdk`, see https://github.com/matrix-org/matrix-js-sdk/issues/1309 * Make error message extraction generic * Update i18n strings * Add tests for email addresses * More consistent error logging to actually see error in logs * Consistent error handling * Any is okay because we have a fallback * Check error type * Use dedicated mockResolvedValue function See https://github.com/matrix-org/matrix-react-sdk/pull/10432#discussion_r1163344034
This commit is contained in:
parent
8f8b74b32c
commit
c1e7905ddc
9 changed files with 300 additions and 132 deletions
|
@ -17,20 +17,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { MatrixError, HTTPError } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
import { _t } from "./languageHandler";
|
import { _t, UserFriendlyError } from "./languageHandler";
|
||||||
import IdentityAuthClient from "./IdentityAuthClient";
|
import IdentityAuthClient from "./IdentityAuthClient";
|
||||||
import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
|
import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
|
||||||
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
|
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
|
||||||
|
|
||||||
function getIdServerDomain(): string {
|
function getIdServerDomain(): string {
|
||||||
const idBaseUrl = MatrixClientPeg.get().idBaseUrl;
|
const idBaseUrl = MatrixClientPeg.get().getIdentityServerUrl(true);
|
||||||
if (!idBaseUrl) {
|
if (!idBaseUrl) {
|
||||||
throw new Error("Identity server not set");
|
throw new UserFriendlyError("Identity server not set");
|
||||||
}
|
}
|
||||||
return idBaseUrl.split("://")[1];
|
return idBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Binding = {
|
export type Binding = {
|
||||||
|
@ -67,23 +68,18 @@ export default class AddThreepid {
|
||||||
* @param {string} emailAddress The email address to add
|
* @param {string} emailAddress The email address to add
|
||||||
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
||||||
*/
|
*/
|
||||||
public addEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
|
public async addEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
|
||||||
return MatrixClientPeg.get()
|
try {
|
||||||
.requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1)
|
const res = await MatrixClientPeg.get().requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1);
|
||||||
.then(
|
this.sessionId = res.sid;
|
||||||
(res) => {
|
return res;
|
||||||
this.sessionId = res.sid;
|
} catch (err) {
|
||||||
return res;
|
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
|
||||||
},
|
throw new UserFriendlyError("This email address is already in use", { cause: err });
|
||||||
function (err) {
|
}
|
||||||
if (err.errcode === "M_THREEPID_IN_USE") {
|
// Otherwise, just blurt out the same error
|
||||||
err.message = _t("This email address is already in use");
|
throw err;
|
||||||
} else if (err.httpStatus) {
|
}
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,22 +94,23 @@ export default class AddThreepid {
|
||||||
// For separate bind, request a token directly from the IS.
|
// For separate bind, request a token directly from the IS.
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const identityAccessToken = (await authClient.getAccessToken()) ?? undefined;
|
const identityAccessToken = (await authClient.getAccessToken()) ?? undefined;
|
||||||
return MatrixClientPeg.get()
|
try {
|
||||||
.requestEmailToken(emailAddress, this.clientSecret, 1, undefined, identityAccessToken)
|
const res = await MatrixClientPeg.get().requestEmailToken(
|
||||||
.then(
|
emailAddress,
|
||||||
(res) => {
|
this.clientSecret,
|
||||||
this.sessionId = res.sid;
|
1,
|
||||||
return res;
|
undefined,
|
||||||
},
|
identityAccessToken,
|
||||||
function (err) {
|
|
||||||
if (err.errcode === "M_THREEPID_IN_USE") {
|
|
||||||
err.message = _t("This email address is already in use");
|
|
||||||
} else if (err.httpStatus) {
|
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
this.sessionId = res.sid;
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
|
||||||
|
throw new UserFriendlyError("This email address is already in use", { cause: err });
|
||||||
|
}
|
||||||
|
// Otherwise, just blurt out the same error
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For tangled bind, request a token via the HS.
|
// For tangled bind, request a token via the HS.
|
||||||
return this.addEmailAddress(emailAddress);
|
return this.addEmailAddress(emailAddress);
|
||||||
|
@ -127,24 +124,24 @@ export default class AddThreepid {
|
||||||
* @param {string} phoneNumber The national or international formatted phone number to add
|
* @param {string} phoneNumber The national or international formatted phone number to add
|
||||||
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
|
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
|
||||||
*/
|
*/
|
||||||
public addMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
|
public async addMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
|
||||||
return MatrixClientPeg.get()
|
try {
|
||||||
.requestAdd3pidMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1)
|
const res = await MatrixClientPeg.get().requestAdd3pidMsisdnToken(
|
||||||
.then(
|
phoneCountry,
|
||||||
(res) => {
|
phoneNumber,
|
||||||
this.sessionId = res.sid;
|
this.clientSecret,
|
||||||
this.submitUrl = res.submit_url;
|
1,
|
||||||
return res;
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
if (err.errcode === "M_THREEPID_IN_USE") {
|
|
||||||
err.message = _t("This phone number is already in use");
|
|
||||||
} else if (err.httpStatus) {
|
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
this.sessionId = res.sid;
|
||||||
|
this.submitUrl = res.submit_url;
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
|
||||||
|
throw new UserFriendlyError("This phone number is already in use", { cause: err });
|
||||||
|
}
|
||||||
|
// Otherwise, just blurt out the same error
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,22 +157,24 @@ export default class AddThreepid {
|
||||||
// For separate bind, request a token directly from the IS.
|
// For separate bind, request a token directly from the IS.
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const identityAccessToken = (await authClient.getAccessToken()) ?? undefined;
|
const identityAccessToken = (await authClient.getAccessToken()) ?? undefined;
|
||||||
return MatrixClientPeg.get()
|
try {
|
||||||
.requestMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1, undefined, identityAccessToken)
|
const res = await MatrixClientPeg.get().requestMsisdnToken(
|
||||||
.then(
|
phoneCountry,
|
||||||
(res) => {
|
phoneNumber,
|
||||||
this.sessionId = res.sid;
|
this.clientSecret,
|
||||||
return res;
|
1,
|
||||||
},
|
undefined,
|
||||||
function (err) {
|
identityAccessToken,
|
||||||
if (err.errcode === "M_THREEPID_IN_USE") {
|
|
||||||
err.message = _t("This phone number is already in use");
|
|
||||||
} else if (err.httpStatus) {
|
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
this.sessionId = res.sid;
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
|
||||||
|
throw new UserFriendlyError("This phone number is already in use", { cause: err });
|
||||||
|
}
|
||||||
|
// Otherwise, just blurt out the same error
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For tangled bind, request a token via the HS.
|
// For tangled bind, request a token via the HS.
|
||||||
return this.addMsisdn(phoneCountry, phoneNumber);
|
return this.addMsisdn(phoneCountry, phoneNumber);
|
||||||
|
@ -195,7 +194,7 @@ export default class AddThreepid {
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const identityAccessToken = await authClient.getAccessToken();
|
const identityAccessToken = await authClient.getAccessToken();
|
||||||
if (!identityAccessToken) {
|
if (!identityAccessToken) {
|
||||||
throw new Error("No identity access token found");
|
throw new UserFriendlyError("No identity access token found");
|
||||||
}
|
}
|
||||||
await MatrixClientPeg.get().bindThreePid({
|
await MatrixClientPeg.get().bindThreePid({
|
||||||
sid: this.sessionId,
|
sid: this.sessionId,
|
||||||
|
@ -210,10 +209,10 @@ export default class AddThreepid {
|
||||||
// The spec has always required this to use UI auth but synapse briefly
|
// The spec has always required this to use UI auth but synapse briefly
|
||||||
// implemented it without, so this may just succeed and that's OK.
|
// implemented it without, so this may just succeed and that's OK.
|
||||||
return [true];
|
return [true];
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
if (e.httpStatus !== 401 || !e.data || !e.data.flows) {
|
if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) {
|
||||||
// doesn't look like an interactive-auth failure
|
// doesn't look like an interactive-auth failure
|
||||||
throw e;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogAesthetics = {
|
const dialogAesthetics = {
|
||||||
|
@ -235,7 +234,7 @@ export default class AddThreepid {
|
||||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||||
title: _t("Add Email Address"),
|
title: _t("Add Email Address"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: err.data,
|
||||||
makeRequest: this.makeAddThreepidOnlyRequest,
|
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||||
aestheticsForStagePhases: {
|
aestheticsForStagePhases: {
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
@ -256,11 +255,13 @@ export default class AddThreepid {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.httpStatus === 401) {
|
if (err instanceof HTTPError && err.httpStatus === 401) {
|
||||||
err.message = _t("Failed to verify email address: make sure you clicked the link in the email");
|
throw new UserFriendlyError(
|
||||||
} else if (err.httpStatus) {
|
"Failed to verify email address: make sure you clicked the link in the email",
|
||||||
err.message += ` (Status ${err.httpStatus})`;
|
{ cause: err },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
// Otherwise, just blurt out the same error
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
@ -308,7 +309,7 @@ export default class AddThreepid {
|
||||||
await authClient.getAccessToken(),
|
await authClient.getAccessToken(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("The add / bind with MSISDN flow is misconfigured");
|
throw new UserFriendlyError("The add / bind with MSISDN flow is misconfigured");
|
||||||
}
|
}
|
||||||
if (result.errcode) {
|
if (result.errcode) {
|
||||||
throw result;
|
throw result;
|
||||||
|
@ -329,10 +330,10 @@ export default class AddThreepid {
|
||||||
// The spec has always required this to use UI auth but synapse briefly
|
// The spec has always required this to use UI auth but synapse briefly
|
||||||
// implemented it without, so this may just succeed and that's OK.
|
// implemented it without, so this may just succeed and that's OK.
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
if (e.httpStatus !== 401 || !e.data || !e.data.flows) {
|
if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) {
|
||||||
// doesn't look like an interactive-auth failure
|
// doesn't look like an interactive-auth failure
|
||||||
throw e;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogAesthetics = {
|
const dialogAesthetics = {
|
||||||
|
@ -354,7 +355,7 @@ export default class AddThreepid {
|
||||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||||
title: _t("Add Phone Number"),
|
title: _t("Add Phone Number"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: err.data,
|
||||||
makeRequest: this.makeAddThreepidOnlyRequest,
|
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||||
aestheticsForStagePhases: {
|
aestheticsForStagePhases: {
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
|
|
@ -27,9 +27,26 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t, UserFriendlyError } from "../../../languageHandler";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user friendly error message string from a given error. Useful for the
|
||||||
|
* `description` prop of the `ErrorDialog`
|
||||||
|
* @param err Error object in question to extract a useful message from. To make it easy
|
||||||
|
* to use with try/catch, this is typed as `any` because try/catch will type
|
||||||
|
* the error as `unknown`. And in any case we can use the fallback message.
|
||||||
|
* @param translatedFallbackMessage The fallback message to be used if the error doesn't have any message
|
||||||
|
* @returns a user friendly error message string from a given error
|
||||||
|
*/
|
||||||
|
export function extractErrorMessageFromError(err: any, translatedFallbackMessage: string): string {
|
||||||
|
return (
|
||||||
|
(err instanceof UserFriendlyError && err.translatedMessage) ||
|
||||||
|
(err instanceof Error && err.message) ||
|
||||||
|
translatedFallbackMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (success?: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
|
@ -17,13 +17,14 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import * as Email from "../../../email";
|
import * as Email from "../../../email";
|
||||||
import AddThreepid from "../../../AddThreepid";
|
import AddThreepid from "../../../AddThreepid";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t, UserFriendlyError } from "../../../languageHandler";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
import ErrorDialog from "./ErrorDialog";
|
import ErrorDialog, { extractErrorMessageFromError } from "./ErrorDialog";
|
||||||
import QuestionDialog from "./QuestionDialog";
|
import QuestionDialog from "./QuestionDialog";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import EditableText from "../elements/EditableText";
|
import EditableText from "../elements/EditableText";
|
||||||
|
@ -88,7 +89,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||||
logger.error("Unable to add email address " + emailAddress + " " + err);
|
logger.error("Unable to add email address " + emailAddress + " " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to add email address"),
|
title: _t("Unable to add email address"),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -114,7 +115,13 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.setState({ emailBusy: false });
|
this.setState({ emailBusy: false });
|
||||||
if (err.errcode == "M_THREEPID_AUTH_FAILED") {
|
|
||||||
|
let underlyingError = err;
|
||||||
|
if (err instanceof UserFriendlyError) {
|
||||||
|
underlyingError = err.cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||||
const message =
|
const message =
|
||||||
_t("Unable to verify email address.") +
|
_t("Unable to verify email address.") +
|
||||||
" " +
|
" " +
|
||||||
|
@ -131,7 +138,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||||
logger.error("Unable to verify email address: " + err);
|
logger.error("Unable to verify email address: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify email address."),
|
title: _t("Unable to verify email address."),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,15 +18,16 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t, UserFriendlyError } from "../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||||
import Field from "../../elements/Field";
|
import Field from "../../elements/Field";
|
||||||
import AccessibleButton from "../../elements/AccessibleButton";
|
import AccessibleButton from "../../elements/AccessibleButton";
|
||||||
import * as Email from "../../../../email";
|
import * as Email from "../../../../email";
|
||||||
import AddThreepid from "../../../../AddThreepid";
|
import AddThreepid from "../../../../AddThreepid";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import ErrorDialog from "../../dialogs/ErrorDialog";
|
import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: Improve the UX for everything in here.
|
TODO: Improve the UX for everything in here.
|
||||||
|
@ -190,7 +191,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to add email address"),
|
title: _t("Unable to add email address"),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -218,8 +219,16 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
logger.error("Unable to verify email address: ", err);
|
||||||
|
|
||||||
this.setState({ continueDisabled: false });
|
this.setState({ continueDisabled: false });
|
||||||
if (err.errcode === "M_THREEPID_AUTH_FAILED") {
|
|
||||||
|
let underlyingError = err;
|
||||||
|
if (err instanceof UserFriendlyError) {
|
||||||
|
underlyingError = err.cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Your email address hasn't been verified yet"),
|
title: _t("Your email address hasn't been verified yet"),
|
||||||
description: _t(
|
description: _t(
|
||||||
|
@ -227,10 +236,9 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.error("Unable to verify email address: ", err);
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify email address."),
|
title: _t("Unable to verify email address."),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,14 +19,14 @@ import React from "react";
|
||||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t, UserFriendlyError } from "../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||||
import Field from "../../elements/Field";
|
import Field from "../../elements/Field";
|
||||||
import AccessibleButton from "../../elements/AccessibleButton";
|
import AccessibleButton from "../../elements/AccessibleButton";
|
||||||
import AddThreepid from "../../../../AddThreepid";
|
import AddThreepid from "../../../../AddThreepid";
|
||||||
import CountryDropdown from "../../auth/CountryDropdown";
|
import CountryDropdown from "../../auth/CountryDropdown";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import ErrorDialog from "../../dialogs/ErrorDialog";
|
import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog";
|
||||||
import { PhoneNumberCountryDefinition } from "../../../../phonenumber";
|
import { PhoneNumberCountryDefinition } from "../../../../phonenumber";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -81,7 +81,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
|
||||||
logger.error("Unable to remove contact information: " + err);
|
logger.error("Unable to remove contact information: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to remove contact information"),
|
title: _t("Unable to remove contact information"),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -192,7 +192,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||||
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Error"),
|
title: _t("Error"),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -224,12 +224,18 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
logger.error("Unable to verify phone number: " + err);
|
||||||
this.setState({ continueDisabled: false });
|
this.setState({ continueDisabled: false });
|
||||||
if (err.errcode !== "M_THREEPID_AUTH_FAILED") {
|
|
||||||
logger.error("Unable to verify phone number: " + err);
|
let underlyingError = err;
|
||||||
|
if (err instanceof UserFriendlyError) {
|
||||||
|
underlyingError = err.cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify phone number."),
|
title: _t("Unable to verify phone number."),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ verifyError: _t("Incorrect verification code") });
|
this.setState({ verifyError: _t("Incorrect verification code") });
|
||||||
|
|
|
@ -18,12 +18,13 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IThreepid } from "matrix-js-sdk/src/@types/threepids";
|
import { IThreepid } from "matrix-js-sdk/src/@types/threepids";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t, UserFriendlyError } from "../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import AddThreepid, { Binding } from "../../../../AddThreepid";
|
import AddThreepid, { Binding } from "../../../../AddThreepid";
|
||||||
import ErrorDialog from "../../dialogs/ErrorDialog";
|
import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog";
|
||||||
import AccessibleButton from "../../elements/AccessibleButton";
|
import AccessibleButton from "../../elements/AccessibleButton";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -98,7 +99,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
}
|
}
|
||||||
this.setState({ bound: bind });
|
this.setState({ bound: bind });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Unable to ${label} email address ${address} ${err}`);
|
logger.error(`changeBinding: Unable to ${label} email address ${address}`, err);
|
||||||
this.setState({
|
this.setState({
|
||||||
verifying: false,
|
verifying: false,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
|
@ -106,7 +107,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
});
|
});
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: errorTitle,
|
title: errorTitle,
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
bound: bind,
|
bound: bind,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Unable to ${label} email address ${address} ${err}`);
|
logger.error(`changeBindingTangledAddBind: Unable to ${label} email address ${address}`, err);
|
||||||
this.setState({
|
this.setState({
|
||||||
verifying: false,
|
verifying: false,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
|
@ -141,7 +142,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
});
|
});
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: errorTitle,
|
title: errorTitle,
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,17 +171,23 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Prevent the continue button from being pressed multiple times while we're working
|
||||||
this.setState({ continueDisabled: true });
|
this.setState({ continueDisabled: true });
|
||||||
try {
|
try {
|
||||||
await this.state.addTask?.checkEmailLinkClicked();
|
await this.state.addTask?.checkEmailLinkClicked();
|
||||||
this.setState({
|
this.setState({
|
||||||
addTask: null,
|
addTask: null,
|
||||||
continueDisabled: false,
|
|
||||||
verifying: false,
|
verifying: false,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ continueDisabled: false });
|
logger.error(`Unable to verify email address:`, err);
|
||||||
if (err.errcode === "M_THREEPID_AUTH_FAILED") {
|
|
||||||
|
let underlyingError = err;
|
||||||
|
if (err instanceof UserFriendlyError) {
|
||||||
|
underlyingError = err.cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Your email address hasn't been verified yet"),
|
title: _t("Your email address hasn't been verified yet"),
|
||||||
description: _t(
|
description: _t(
|
||||||
|
@ -191,9 +198,12 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
logger.error("Unable to verify email address: " + err);
|
logger.error("Unable to verify email address: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify email address."),
|
title: _t("Unable to verify email address."),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
// Re-enable the continue button so the user can retry
|
||||||
|
this.setState({ continueDisabled: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,13 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IThreepid } from "matrix-js-sdk/src/@types/threepids";
|
import { IThreepid } from "matrix-js-sdk/src/@types/threepids";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t, UserFriendlyError } from "../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import AddThreepid, { Binding } from "../../../../AddThreepid";
|
import AddThreepid, { Binding } from "../../../../AddThreepid";
|
||||||
import ErrorDialog from "../../dialogs/ErrorDialog";
|
import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog";
|
||||||
import Field from "../../elements/Field";
|
import Field from "../../elements/Field";
|
||||||
import AccessibleButton from "../../elements/AccessibleButton";
|
import AccessibleButton from "../../elements/AccessibleButton";
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
}
|
}
|
||||||
this.setState({ bound: bind });
|
this.setState({ bound: bind });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Unable to ${label} phone number ${address} ${err}`);
|
logger.error(`changeBinding: Unable to ${label} phone number ${address}`, err);
|
||||||
this.setState({
|
this.setState({
|
||||||
verifying: false,
|
verifying: false,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
|
@ -107,7 +108,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
});
|
});
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: errorTitle,
|
title: errorTitle,
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +141,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
bound: bind,
|
bound: bind,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Unable to ${label} phone number ${address} ${err}`);
|
logger.error(`changeBindingTangledAddBind: Unable to ${label} phone number ${address}`, err);
|
||||||
this.setState({
|
this.setState({
|
||||||
verifying: false,
|
verifying: false,
|
||||||
continueDisabled: false,
|
continueDisabled: false,
|
||||||
|
@ -148,7 +149,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
});
|
});
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: errorTitle,
|
title: errorTitle,
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,12 +196,18 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
verificationCode: "",
|
verificationCode: "",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
logger.error("Unable to verify phone number:", err);
|
||||||
|
|
||||||
|
let underlyingError = err;
|
||||||
|
if (err instanceof UserFriendlyError) {
|
||||||
|
underlyingError = err.cause;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ continueDisabled: false });
|
this.setState({ continueDisabled: false });
|
||||||
if (err.errcode !== "M_THREEPID_AUTH_FAILED") {
|
if (underlyingError instanceof MatrixError && underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
||||||
logger.error("Unable to verify phone number: " + err);
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify phone number."),
|
title: _t("Unable to verify phone number."),
|
||||||
description: err && err.message ? err.message : _t("Operation failed"),
|
description: extractErrorMessageFromError(err, _t("Operation failed")),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ verifyError: _t("Incorrect verification code") });
|
this.setState({ verifyError: _t("Incorrect verification code") });
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"Identity server not set": "Identity server not set",
|
||||||
"This email address is already in use": "This email address is already in use",
|
"This email address is already in use": "This email address is already in use",
|
||||||
"This phone number is already in use": "This phone number is already in use",
|
"This phone number is already in use": "This phone number is already in use",
|
||||||
|
"No identity access token found": "No identity access token found",
|
||||||
"Use Single Sign On to continue": "Use Single Sign On to continue",
|
"Use Single Sign On to continue": "Use Single Sign On to continue",
|
||||||
"Confirm adding this email address by using Single Sign On to prove your identity.": "Confirm adding this email address by using Single Sign On to prove your identity.",
|
"Confirm adding this email address by using Single Sign On to prove your identity.": "Confirm adding this email address by using Single Sign On to prove your identity.",
|
||||||
"Single Sign On": "Single Sign On",
|
"Single Sign On": "Single Sign On",
|
||||||
|
@ -9,6 +11,7 @@
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"Add Email Address": "Add Email Address",
|
"Add Email Address": "Add Email Address",
|
||||||
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
|
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
|
||||||
|
"The add / bind with MSISDN flow is misconfigured": "The add / bind with MSISDN flow is misconfigured",
|
||||||
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirm adding this phone number by using Single Sign On to prove your identity.",
|
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirm adding this phone number by using Single Sign On to prove your identity.",
|
||||||
"Confirm adding phone number": "Confirm adding phone number",
|
"Confirm adding phone number": "Confirm adding phone number",
|
||||||
"Click the button below to confirm adding this phone number.": "Click the button below to confirm adding this phone number.",
|
"Click the button below to confirm adding this phone number.": "Click the button below to confirm adding this phone number.",
|
||||||
|
|
|
@ -15,26 +15,135 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||||
|
import { IRequestTokenResponse } from "matrix-js-sdk/src/client";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||||
|
|
||||||
|
import { UserFriendlyError } from "../../../../../src/languageHandler";
|
||||||
import { EmailAddress } from "../../../../../src/components/views/settings/discovery/EmailAddresses";
|
import { EmailAddress } from "../../../../../src/components/views/settings/discovery/EmailAddresses";
|
||||||
|
import { clearAllModals, getMockClientWithEventEmitter } from "../../../../test-utils";
|
||||||
|
|
||||||
|
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
|
||||||
|
jest.mock("../../../../../src/IdentityAuthClient", () =>
|
||||||
|
jest.fn().mockImplementation(() => ({
|
||||||
|
getAccessToken: mockGetAccessToken,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const emailThreepidFixture: IThreepid = {
|
||||||
|
medium: ThreepidMedium.Email,
|
||||||
|
address: "foo@bar.com",
|
||||||
|
validated_at: 12345,
|
||||||
|
added_at: 12342,
|
||||||
|
bound: false,
|
||||||
|
};
|
||||||
|
|
||||||
describe("<EmailAddress/>", () => {
|
describe("<EmailAddress/>", () => {
|
||||||
it("should track props.email.bound changes", async () => {
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
const email: IThreepid = {
|
getIdentityServerUrl: jest.fn().mockReturnValue("https://fake-identity-server"),
|
||||||
medium: ThreepidMedium.Email,
|
generateClientSecret: jest.fn(),
|
||||||
address: "foo@bar.com",
|
doesServerSupportSeparateAddAndBind: jest.fn(),
|
||||||
validated_at: 12345,
|
requestEmailToken: jest.fn(),
|
||||||
added_at: 12342,
|
bindThreePid: jest.fn(),
|
||||||
bound: false,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const { rerender } = render(<EmailAddress email={email} />);
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
await clearAllModals();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should track props.email.bound changes", async () => {
|
||||||
|
const { rerender } = render(<EmailAddress email={emailThreepidFixture} />);
|
||||||
await screen.findByText("Share");
|
await screen.findByText("Share");
|
||||||
|
|
||||||
email.bound = true;
|
rerender(
|
||||||
rerender(<EmailAddress email={{ ...email }} />);
|
<EmailAddress
|
||||||
|
email={{
|
||||||
|
...emailThreepidFixture,
|
||||||
|
bound: true,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
await screen.findByText("Revoke");
|
await screen.findByText("Revoke");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Email verification share phase", () => {
|
||||||
|
it("shows translated error message", async () => {
|
||||||
|
render(<EmailAddress email={emailThreepidFixture} />);
|
||||||
|
mockClient.doesServerSupportSeparateAddAndBind.mockResolvedValue(true);
|
||||||
|
mockClient.requestEmailToken.mockRejectedValue(
|
||||||
|
new MatrixError(
|
||||||
|
{ errcode: "M_THREEPID_IN_USE", error: "Some fake MatrixError occured" },
|
||||||
|
400,
|
||||||
|
"https://fake-url/",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByText("Share"));
|
||||||
|
|
||||||
|
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||||
|
expect(await screen.findByText("This email address is already in use")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Email verification complete phase", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Start these tests out at the "Complete" phase
|
||||||
|
render(<EmailAddress email={emailThreepidFixture} />);
|
||||||
|
mockClient.requestEmailToken.mockResolvedValue({ sid: "123-fake-sid" } satisfies IRequestTokenResponse);
|
||||||
|
mockClient.doesServerSupportSeparateAddAndBind.mockResolvedValue(true);
|
||||||
|
fireEvent.click(screen.getByText("Share"));
|
||||||
|
// Then wait for the completion screen to come up
|
||||||
|
await screen.findByText("Complete");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Shows error dialog when share completion fails (email not verified yet)", async () => {
|
||||||
|
mockClient.bindThreePid.mockRejectedValue(
|
||||||
|
new MatrixError(
|
||||||
|
{ errcode: "M_THREEPID_AUTH_FAILED", error: "Some fake MatrixError occured" },
|
||||||
|
403,
|
||||||
|
"https://fake-url/",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByText("Complete"));
|
||||||
|
|
||||||
|
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||||
|
// Check the title
|
||||||
|
expect(await screen.findByText("Your email address hasn't been verified yet")).toBeInTheDocument();
|
||||||
|
// Check the description
|
||||||
|
expect(
|
||||||
|
await screen.findByText(
|
||||||
|
"Click the link in the email you received to verify and then click continue again.",
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Shows error dialog when share completion fails (UserFriendlyError)", async () => {
|
||||||
|
const fakeErrorText = "Fake UserFriendlyError error in test";
|
||||||
|
mockClient.bindThreePid.mockRejectedValue(new UserFriendlyError(fakeErrorText));
|
||||||
|
fireEvent.click(screen.getByText("Complete"));
|
||||||
|
|
||||||
|
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||||
|
// Check the title
|
||||||
|
expect(await screen.findByText("Unable to verify email address.")).toBeInTheDocument();
|
||||||
|
// Check the description
|
||||||
|
expect(await screen.findByText(fakeErrorText)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Shows error dialog when share completion fails (generic error)", async () => {
|
||||||
|
const fakeErrorText = "Fake plain error in test";
|
||||||
|
mockClient.bindThreePid.mockRejectedValue(new Error(fakeErrorText));
|
||||||
|
fireEvent.click(screen.getByText("Complete"));
|
||||||
|
|
||||||
|
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||||
|
// Check the title
|
||||||
|
expect(await screen.findByText("Unable to verify email address.")).toBeInTheDocument();
|
||||||
|
// Check the description
|
||||||
|
expect(await screen.findByText(fakeErrorText)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue