Replace newTranslatableError with UserFriendlyError (#10440

* Introduce UserFriendlyError

* Replace newTranslatableError with UserFriendlyError

* Remove ITranslatableError

* Fix up some strict lints

* Document when we/why we can remove

* Update matrix-web-i18n

Includes changes to find `new UserFriendlyError`,
see https://github.com/matrix-org/matrix-web-i18n/pull/6

* Include room ID in error

* Translate fallback error

* Translate better

* Update i18n strings

* Better re-use

* Minor comment fixes
This commit is contained in:
Eric Eastwood 2023-03-31 02:30:43 -05:00 committed by GitHub
parent 567248d5c5
commit ff1468b6d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 285 additions and 99 deletions

View file

@ -30,7 +30,7 @@ import { SlashCommand as SlashCommandEvent } from "@matrix-org/analytics-events/
import { MatrixClientPeg } from "./MatrixClientPeg";
import dis from "./dispatcher/dispatcher";
import { _t, _td, ITranslatableError, newTranslatableError } from "./languageHandler";
import { _t, _td, UserFriendlyError } from "./languageHandler";
import Modal from "./Modal";
import MultiInviter from "./utils/MultiInviter";
import { Linkify, topicToHtml } from "./HtmlUtils";
@ -110,7 +110,7 @@ export const CommandCategories = {
other: _td("Other"),
};
export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise<IContent | undefined> }>;
export type RunResult = XOR<{ error: Error }, { promise: Promise<IContent | undefined> }>;
type RunFn = (this: Command, roomId: string, args?: string) => RunResult;
@ -163,14 +163,15 @@ export class Command {
public run(roomId: string, threadId: string | null, args?: string): RunResult {
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
if (!this.runFn) {
return reject(newTranslatableError("Command error: Unable to handle slash command."));
return reject(new UserFriendlyError("Command error: Unable to handle slash command."));
}
const renderingType = threadId ? TimelineRenderingType.Thread : TimelineRenderingType.Room;
if (this.renderingTypes && !this.renderingTypes?.includes(renderingType)) {
return reject(
newTranslatableError("Command error: Unable to find rendering type (%(renderingType)s)", {
new UserFriendlyError("Command error: Unable to find rendering type (%(renderingType)s)", {
renderingType,
cause: undefined,
}),
);
}
@ -310,7 +311,7 @@ export const Commands = [
const room = cli.getRoom(roomId);
if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
return reject(
newTranslatableError("You do not have the required permissions to use this command."),
new UserFriendlyError("You do not have the required permissions to use this command."),
);
}
@ -345,10 +346,10 @@ export const Commands = [
(async (): Promise<void> => {
const unixTimestamp = Date.parse(args);
if (!unixTimestamp) {
throw newTranslatableError(
throw new UserFriendlyError(
"We were unable to understand the given date (%(inputDate)s). " +
"Try using the format YYYY-MM-DD.",
{ inputDate: args },
{ inputDate: args, cause: undefined },
);
}
@ -496,7 +497,10 @@ export const Commands = [
const room = cli.getRoom(roomId);
if (!room) {
return reject(
newTranslatableError("Failed to get room topic: Unable to find room (%(roomId)s", { roomId }),
new UserFriendlyError("Failed to get room topic: Unable to find room (%(roomId)s", {
roomId,
cause: undefined,
}),
);
}
@ -576,13 +580,13 @@ export const Commands = [
setToDefaultIdentityServer();
return;
}
throw newTranslatableError(
throw new UserFriendlyError(
"Use an identity server to invite by email. Manage in Settings.",
);
});
} else {
return reject(
newTranslatableError("Use an identity server to invite by email. Manage in Settings."),
new UserFriendlyError("Use an identity server to invite by email. Manage in Settings."),
);
}
}
@ -594,7 +598,15 @@ export const Commands = [
})
.then(() => {
if (inviter.getCompletionState(address) !== "invited") {
throw new Error(inviter.getErrorText(address));
const errorStringFromInviterUtility = inviter.getErrorText(address);
if (errorStringFromInviterUtility) {
throw new Error(errorStringFromInviterUtility);
} else {
throw new UserFriendlyError(
"User (%(user)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility",
{ user: address, roomId, cause: undefined },
);
}
}
}),
);
@ -743,7 +755,12 @@ export const Commands = [
return room.getCanonicalAlias() === roomAlias || room.getAltAliases().includes(roomAlias);
})?.roomId;
if (!targetRoomId) {
return reject(newTranslatableError("Unrecognised room address: %(roomAlias)s", { roomAlias }));
return reject(
new UserFriendlyError("Unrecognised room address: %(roomAlias)s", {
roomAlias,
cause: undefined,
}),
);
}
}
}
@ -898,7 +915,10 @@ export const Commands = [
const room = cli.getRoom(roomId);
if (!room) {
return reject(
newTranslatableError("Command failed: Unable to find room (%(roomId)s", { roomId }),
new UserFriendlyError("Command failed: Unable to find room (%(roomId)s", {
roomId,
cause: undefined,
}),
);
}
const member = room.getMember(userId);
@ -906,7 +926,7 @@ export const Commands = [
!member?.membership ||
getEffectiveMembership(member.membership) === EffectiveMembership.Leave
) {
return reject(newTranslatableError("Could not find user in room"));
return reject(new UserFriendlyError("Could not find user in room"));
}
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
@ -940,13 +960,16 @@ export const Commands = [
const room = cli.getRoom(roomId);
if (!room) {
return reject(
newTranslatableError("Command failed: Unable to find room (%(roomId)s", { roomId }),
new UserFriendlyError("Command failed: Unable to find room (%(roomId)s", {
roomId,
cause: undefined,
}),
);
}
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent?.getContent().users[args]) {
return reject(newTranslatableError("Could not find user in room"));
return reject(new UserFriendlyError("Could not find user in room"));
}
return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent));
}
@ -975,7 +998,7 @@ export const Commands = [
!isCurrentLocalRoom(),
runFn: function (roomId, widgetUrl) {
if (!widgetUrl) {
return reject(newTranslatableError("Please supply a widget URL or embed code"));
return reject(new UserFriendlyError("Please supply a widget URL or embed code"));
}
// Try and parse out a widget URL from iframes
@ -988,14 +1011,14 @@ export const Commands = [
if (iframe.tagName.toLowerCase() === "iframe" && iframe.attrs) {
const srcAttr = iframe.attrs.find((a) => a.name === "src");
logger.log("Pulling URL out of iframe (embed code)");
if (!srcAttr) return reject(newTranslatableError("iframe has no src attribute"));
if (!srcAttr) return reject(new UserFriendlyError("iframe has no src attribute"));
widgetUrl = srcAttr.value;
}
}
}
if (!widgetUrl.startsWith("https://") && !widgetUrl.startsWith("http://")) {
return reject(newTranslatableError("Please supply a https:// or http:// widget URL"));
return reject(new UserFriendlyError("Please supply a https:// or http:// widget URL"));
}
if (WidgetUtils.canUserModifyWidgets(roomId)) {
const userId = MatrixClientPeg.get().getUserId();
@ -1017,7 +1040,7 @@ export const Commands = [
return success(WidgetUtils.setRoomWidget(roomId, widgetId, type, widgetUrl, name, data));
} else {
return reject(newTranslatableError("You cannot modify widgets in this room."));
return reject(new UserFriendlyError("You cannot modify widgets in this room."));
}
},
category: CommandCategories.admin,
@ -1041,18 +1064,22 @@ export const Commands = [
(async (): Promise<void> => {
const device = cli.getStoredDevice(userId, deviceId);
if (!device) {
throw newTranslatableError("Unknown (user, session) pair: (%(userId)s, %(deviceId)s)", {
userId,
deviceId,
});
throw new UserFriendlyError(
"Unknown (user, session) pair: (%(userId)s, %(deviceId)s)",
{
userId,
deviceId,
cause: undefined,
},
);
}
const deviceTrust = await cli.checkDeviceTrust(userId, deviceId);
if (deviceTrust.isVerified()) {
if (device.getFingerprint() === fingerprint) {
throw newTranslatableError("Session already verified!");
throw new UserFriendlyError("Session already verified!");
} else {
throw newTranslatableError(
throw new UserFriendlyError(
"WARNING: session already verified, but keys do NOT MATCH!",
);
}
@ -1060,7 +1087,7 @@ export const Commands = [
if (device.getFingerprint() !== fingerprint) {
const fprint = device.getFingerprint();
throw newTranslatableError(
throw new UserFriendlyError(
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session" +
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
'"%(fingerprint)s". This could mean your communications are being intercepted!',
@ -1069,6 +1096,7 @@ export const Commands = [
userId,
deviceId,
fingerprint,
cause: undefined,
},
);
}
@ -1217,7 +1245,7 @@ export const Commands = [
return success(
(async (): Promise<void> => {
const room = await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(roomId);
if (!room) throw newTranslatableError("No virtual room for this room");
if (!room) throw new UserFriendlyError("No virtual room for this room");
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: room.roomId,
@ -1245,7 +1273,7 @@ export const Commands = [
if (isPhoneNumber) {
const results = await LegacyCallHandler.instance.pstnLookup(userId);
if (!results || results.length === 0 || !results[0].userid) {
throw newTranslatableError("Unable to find Matrix ID for phone number");
throw new UserFriendlyError("Unable to find Matrix ID for phone number");
}
userId = results[0].userid;
}
@ -1308,7 +1336,7 @@ export const Commands = [
runFn: function (roomId, args) {
const call = LegacyCallHandler.instance.getCallForRoom(roomId);
if (!call) {
return reject(newTranslatableError("No active call in this room"));
return reject(new UserFriendlyError("No active call in this room"));
}
call.setRemoteOnHold(true);
return success();
@ -1323,7 +1351,7 @@ export const Commands = [
runFn: function (roomId, args) {
const call = LegacyCallHandler.instance.getCallForRoom(roomId);
if (!call) {
return reject(newTranslatableError("No active call in this room"));
return reject(new UserFriendlyError("No active call in this room"));
}
call.setRemoteOnHold(false);
return success();
@ -1337,7 +1365,7 @@ export const Commands = [
isEnabled: () => !isCurrentLocalRoom(),
runFn: function (roomId, args) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return reject(newTranslatableError("Could not find room"));
if (!room) return reject(new UserFriendlyError("Could not find room"));
return success(guessAndSetDMRoom(room, true));
},
renderingTypes: [TimelineRenderingType.Room],
@ -1349,7 +1377,7 @@ export const Commands = [
isEnabled: () => !isCurrentLocalRoom(),
runFn: function (roomId, args) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return reject(newTranslatableError("Could not find room"));
if (!room) return reject(new UserFriendlyError("Could not find room"));
return success(guessAndSetDMRoom(room, false));
},
renderingTypes: [TimelineRenderingType.Room],