Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit c1e7905

Browse files
Properly translate errors in AddThreepid.ts (#10432)
* Properly translate errors in AddThreepid.ts Part of element-hq/element-web#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 #10432 (comment) 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 matrix-org/matrix-js-sdk#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 #10432 (comment)
1 parent 8f8b74b commit c1e7905

File tree

9 files changed

+300
-132
lines changed

9 files changed

+300
-132
lines changed

src/AddThreepid.ts

Lines changed: 83 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,21 @@ limitations under the License.
1717
*/
1818

1919
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
20+
import { MatrixError, HTTPError } from "matrix-js-sdk/src/matrix";
2021

2122
import { MatrixClientPeg } from "./MatrixClientPeg";
2223
import Modal from "./Modal";
23-
import { _t } from "./languageHandler";
24+
import { _t, UserFriendlyError } from "./languageHandler";
2425
import IdentityAuthClient from "./IdentityAuthClient";
2526
import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
2627
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
2728

2829
function getIdServerDomain(): string {
29-
const idBaseUrl = MatrixClientPeg.get().idBaseUrl;
30+
const idBaseUrl = MatrixClientPeg.get().getIdentityServerUrl(true);
3031
if (!idBaseUrl) {
31-
throw new Error("Identity server not set");
32+
throw new UserFriendlyError("Identity server not set");
3233
}
33-
return idBaseUrl.split("://")[1];
34+
return idBaseUrl;
3435
}
3536

3637
export type Binding = {
@@ -67,23 +68,18 @@ export default class AddThreepid {
6768
* @param {string} emailAddress The email address to add
6869
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
6970
*/
70-
public addEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
71-
return MatrixClientPeg.get()
72-
.requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1)
73-
.then(
74-
(res) => {
75-
this.sessionId = res.sid;
76-
return res;
77-
},
78-
function (err) {
79-
if (err.errcode === "M_THREEPID_IN_USE") {
80-
err.message = _t("This email address is already in use");
81-
} else if (err.httpStatus) {
82-
err.message = err.message + ` (Status ${err.httpStatus})`;
83-
}
84-
throw err;
85-
},
86-
);
71+
public async addEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
72+
try {
73+
const res = await MatrixClientPeg.get().requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1);
74+
this.sessionId = res.sid;
75+
return res;
76+
} catch (err) {
77+
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
78+
throw new UserFriendlyError("This email address is already in use", { cause: err });
79+
}
80+
// Otherwise, just blurt out the same error
81+
throw err;
82+
}
8783
}
8884

8985
/**
@@ -98,22 +94,23 @@ export default class AddThreepid {
9894
// For separate bind, request a token directly from the IS.
9995
const authClient = new IdentityAuthClient();
10096
const identityAccessToken = (await authClient.getAccessToken()) ?? undefined;
101-
return MatrixClientPeg.get()
102-
.requestEmailToken(emailAddress, this.clientSecret, 1, undefined, identityAccessToken)
103-
.then(
104-
(res) => {
105-
this.sessionId = res.sid;
106-
return res;
107-
},
108-
function (err) {
109-
if (err.errcode === "M_THREEPID_IN_USE") {
110-
err.message = _t("This email address is already in use");
111-
} else if (err.httpStatus) {
112-
err.message = err.message + ` (Status ${err.httpStatus})`;
113-
}
114-
throw err;
115-
},
97+
try {
98+
const res = await MatrixClientPeg.get().requestEmailToken(
99+
emailAddress,
100+
this.clientSecret,
101+
1,
102+
undefined,
103+
identityAccessToken,
116104
);
105+
this.sessionId = res.sid;
106+
return res;
107+
} catch (err) {
108+
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
109+
throw new UserFriendlyError("This email address is already in use", { cause: err });
110+
}
111+
// Otherwise, just blurt out the same error
112+
throw err;
113+
}
117114
} else {
118115
// For tangled bind, request a token via the HS.
119116
return this.addEmailAddress(emailAddress);
@@ -127,24 +124,24 @@ export default class AddThreepid {
127124
* @param {string} phoneNumber The national or international formatted phone number to add
128125
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
129126
*/
130-
public addMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
131-
return MatrixClientPeg.get()
132-
.requestAdd3pidMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1)
133-
.then(
134-
(res) => {
135-
this.sessionId = res.sid;
136-
this.submitUrl = res.submit_url;
137-
return res;
138-
},
139-
function (err) {
140-
if (err.errcode === "M_THREEPID_IN_USE") {
141-
err.message = _t("This phone number is already in use");
142-
} else if (err.httpStatus) {
143-
err.message = err.message + ` (Status ${err.httpStatus})`;
144-
}
145-
throw err;
146-
},
127+
public async addMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
128+
try {
129+
const res = await MatrixClientPeg.get().requestAdd3pidMsisdnToken(
130+
phoneCountry,
131+
phoneNumber,
132+
this.clientSecret,
133+
1,
147134
);
135+
this.sessionId = res.sid;
136+
this.submitUrl = res.submit_url;
137+
return res;
138+
} catch (err) {
139+
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
140+
throw new UserFriendlyError("This phone number is already in use", { cause: err });
141+
}
142+
// Otherwise, just blurt out the same error
143+
throw err;
144+
}
148145
}
149146

150147
/**
@@ -160,22 +157,24 @@ export default class AddThreepid {
160157
// For separate bind, request a token directly from the IS.
161158
const authClient = new IdentityAuthClient();
162159
const identityAccessToken = (await authClient.getAccessToken()) ?? undefined;
163-
return MatrixClientPeg.get()
164-
.requestMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1, undefined, identityAccessToken)
165-
.then(
166-
(res) => {
167-
this.sessionId = res.sid;
168-
return res;
169-
},
170-
function (err) {
171-
if (err.errcode === "M_THREEPID_IN_USE") {
172-
err.message = _t("This phone number is already in use");
173-
} else if (err.httpStatus) {
174-
err.message = err.message + ` (Status ${err.httpStatus})`;
175-
}
176-
throw err;
177-
},
160+
try {
161+
const res = await MatrixClientPeg.get().requestMsisdnToken(
162+
phoneCountry,
163+
phoneNumber,
164+
this.clientSecret,
165+
1,
166+
undefined,
167+
identityAccessToken,
178168
);
169+
this.sessionId = res.sid;
170+
return res;
171+
} catch (err) {
172+
if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
173+
throw new UserFriendlyError("This phone number is already in use", { cause: err });
174+
}
175+
// Otherwise, just blurt out the same error
176+
throw err;
177+
}
179178
} else {
180179
// For tangled bind, request a token via the HS.
181180
return this.addMsisdn(phoneCountry, phoneNumber);
@@ -195,7 +194,7 @@ export default class AddThreepid {
195194
const authClient = new IdentityAuthClient();
196195
const identityAccessToken = await authClient.getAccessToken();
197196
if (!identityAccessToken) {
198-
throw new Error("No identity access token found");
197+
throw new UserFriendlyError("No identity access token found");
199198
}
200199
await MatrixClientPeg.get().bindThreePid({
201200
sid: this.sessionId,
@@ -210,10 +209,10 @@ export default class AddThreepid {
210209
// The spec has always required this to use UI auth but synapse briefly
211210
// implemented it without, so this may just succeed and that's OK.
212211
return [true];
213-
} catch (e) {
214-
if (e.httpStatus !== 401 || !e.data || !e.data.flows) {
212+
} catch (err) {
213+
if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) {
215214
// doesn't look like an interactive-auth failure
216-
throw e;
215+
throw err;
217216
}
218217

219218
const dialogAesthetics = {
@@ -235,7 +234,7 @@ export default class AddThreepid {
235234
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
236235
title: _t("Add Email Address"),
237236
matrixClient: MatrixClientPeg.get(),
238-
authData: e.data,
237+
authData: err.data,
239238
makeRequest: this.makeAddThreepidOnlyRequest,
240239
aestheticsForStagePhases: {
241240
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
@@ -256,11 +255,13 @@ export default class AddThreepid {
256255
);
257256
}
258257
} catch (err) {
259-
if (err.httpStatus === 401) {
260-
err.message = _t("Failed to verify email address: make sure you clicked the link in the email");
261-
} else if (err.httpStatus) {
262-
err.message += ` (Status ${err.httpStatus})`;
258+
if (err instanceof HTTPError && err.httpStatus === 401) {
259+
throw new UserFriendlyError(
260+
"Failed to verify email address: make sure you clicked the link in the email",
261+
{ cause: err },
262+
);
263263
}
264+
// Otherwise, just blurt out the same error
264265
throw err;
265266
}
266267
return [];
@@ -308,7 +309,7 @@ export default class AddThreepid {
308309
await authClient.getAccessToken(),
309310
);
310311
} else {
311-
throw new Error("The add / bind with MSISDN flow is misconfigured");
312+
throw new UserFriendlyError("The add / bind with MSISDN flow is misconfigured");
312313
}
313314
if (result.errcode) {
314315
throw result;
@@ -329,10 +330,10 @@ export default class AddThreepid {
329330
// The spec has always required this to use UI auth but synapse briefly
330331
// implemented it without, so this may just succeed and that's OK.
331332
return;
332-
} catch (e) {
333-
if (e.httpStatus !== 401 || !e.data || !e.data.flows) {
333+
} catch (err) {
334+
if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) {
334335
// doesn't look like an interactive-auth failure
335-
throw e;
336+
throw err;
336337
}
337338

338339
const dialogAesthetics = {
@@ -354,7 +355,7 @@ export default class AddThreepid {
354355
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
355356
title: _t("Add Phone Number"),
356357
matrixClient: MatrixClientPeg.get(),
357-
authData: e.data,
358+
authData: err.data,
358359
makeRequest: this.makeAddThreepidOnlyRequest,
359360
aestheticsForStagePhases: {
360361
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,

src/components/views/dialogs/ErrorDialog.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,26 @@ limitations under the License.
2727

2828
import React from "react";
2929

30-
import { _t } from "../../../languageHandler";
30+
import { _t, UserFriendlyError } from "../../../languageHandler";
3131
import BaseDialog from "./BaseDialog";
3232

33+
/**
34+
* Get a user friendly error message string from a given error. Useful for the
35+
* `description` prop of the `ErrorDialog`
36+
* @param err Error object in question to extract a useful message from. To make it easy
37+
* to use with try/catch, this is typed as `any` because try/catch will type
38+
* the error as `unknown`. And in any case we can use the fallback message.
39+
* @param translatedFallbackMessage The fallback message to be used if the error doesn't have any message
40+
* @returns a user friendly error message string from a given error
41+
*/
42+
export function extractErrorMessageFromError(err: any, translatedFallbackMessage: string): string {
43+
return (
44+
(err instanceof UserFriendlyError && err.translatedMessage) ||
45+
(err instanceof Error && err.message) ||
46+
translatedFallbackMessage
47+
);
48+
}
49+
3350
interface IProps {
3451
onFinished: (success?: boolean) => void;
3552
title?: string;

src/components/views/dialogs/SetEmailDialog.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ limitations under the License.
1717

1818
import React from "react";
1919
import { logger } from "matrix-js-sdk/src/logger";
20+
import { MatrixError } from "matrix-js-sdk/src/matrix";
2021

2122
import * as Email from "../../../email";
2223
import AddThreepid from "../../../AddThreepid";
23-
import { _t } from "../../../languageHandler";
24+
import { _t, UserFriendlyError } from "../../../languageHandler";
2425
import Modal from "../../../Modal";
2526
import Spinner from "../elements/Spinner";
26-
import ErrorDialog from "./ErrorDialog";
27+
import ErrorDialog, { extractErrorMessageFromError } from "./ErrorDialog";
2728
import QuestionDialog from "./QuestionDialog";
2829
import BaseDialog from "./BaseDialog";
2930
import EditableText from "../elements/EditableText";
@@ -88,7 +89,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
8889
logger.error("Unable to add email address " + emailAddress + " " + err);
8990
Modal.createDialog(ErrorDialog, {
9091
title: _t("Unable to add email address"),
91-
description: err && err.message ? err.message : _t("Operation failed"),
92+
description: extractErrorMessageFromError(err, _t("Operation failed")),
9293
});
9394
},
9495
);
@@ -114,7 +115,13 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
114115
},
115116
(err) => {
116117
this.setState({ emailBusy: false });
117-
if (err.errcode == "M_THREEPID_AUTH_FAILED") {
118+
119+
let underlyingError = err;
120+
if (err instanceof UserFriendlyError) {
121+
underlyingError = err.cause;
122+
}
123+
124+
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
118125
const message =
119126
_t("Unable to verify email address.") +
120127
" " +
@@ -131,7 +138,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
131138
logger.error("Unable to verify email address: " + err);
132139
Modal.createDialog(ErrorDialog, {
133140
title: _t("Unable to verify email address."),
134-
description: err && err.message ? err.message : _t("Operation failed"),
141+
description: extractErrorMessageFromError(err, _t("Operation failed")),
135142
});
136143
}
137144
},

0 commit comments

Comments
 (0)