From ad83e63f1b66525858d8d86d3b9538d8fbe09cd0 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 23 Mar 2023 21:44:54 -0500 Subject: [PATCH 01/12] Introduce UserFriendlyError --- package.json | 3 +- src/@types/global.d.ts | 18 ++++++++ src/languageHandler.tsx | 71 ++++++++++++++++++++++++---- test/languageHandler-test.ts | 89 ++++++++++++++++++++++++++++-------- 4 files changed, 153 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 15a5e65e12f..fad5e3232d2 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "matrix_lib_main": "./lib/index.ts", "matrix_lib_typings": "./lib/index.d.ts", "matrix_i18n_extra_translation_funcs": [ - "newTranslatableError" + "newTranslatableError", + "UserFriendlyError" ], "scripts": { "prepublishOnly": "yarn build", diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 9d3b64fd6af..c9f91707145 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -186,7 +186,17 @@ declare global { scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void; } + interface ErrorOptions { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + cause?: unknown; + } + interface Error { + // Standard + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + cause?: unknown; + + // Non-standard // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/fileName fileName?: string; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/lineNumber @@ -195,6 +205,14 @@ declare global { columnNumber?: number; } + interface ErrorConstructor { + new (message?: string, options?: ErrorOptions): Error; + (message?: string, options?: ErrorOptions): Error; + } + + // eslint-disable-next-line no-var + var Error: ErrorConstructor; + // https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278 interface AudioWorkletProcessor { readonly port: MessagePort; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index ec37d329657..8a89f9d552a 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -45,6 +45,51 @@ counterpart.setSeparator("|"); const FALLBACK_LOCALE = "en"; counterpart.setFallbackLocale(FALLBACK_LOCALE); +interface ErrorOptions { + // Because we're mixing the subsitution variables and cause into the same object + // below, we want them to always explicitly say whether there is an underlying error + // or not to avoid typos of "cause" slipping through unnoticed. + cause: unknown | undefined; +} + +/** + * Used to rethrow an error with a user-friendly translatable message while maintaining + * access to that original underlying error. Downstream consumers can display the + * `translatedMessage` property in the UI and inspect the underlying error with the + * `cause` property. + * + * The error message will display as English in the console and logs so Element + * developers can easily understand the error and find the source in the code. It also + * helps tools like Sentry deduplicate the error, or just generally searching in + * rageshakes to find all instances regardless of the users locale. + * + * @param message - The untranslated error message text, e.g "Something went wrong with %(foo)s". + * @param substitutionVariablesAndCause - Variable substitutions for the translation and + * original cause of the error. If there is no cause, just pass `undefined`, e.g { foo: + * 'bar', cause: err || undefined } + */ +export class UserFriendlyError extends Error { + public readonly translatedMessage: string; + + public constructor(message: string, substitutionVariablesAndCause?: IVariables & ErrorOptions) { + const errorOptions = { + cause: substitutionVariablesAndCause?.cause, + }; + // Prevent "Could not find /%\(cause\)s/g in x" logs to the console by removing + // it from the list + const substitutionVariables = { ...substitutionVariablesAndCause }; + delete substitutionVariables["cause"]; + + // Create the error with the English version of the message that we want to show + // up in the logs + const englishTranslatedMessage = _t(message, { ...substitutionVariables, locale: "en" }); + super(englishTranslatedMessage, errorOptions); + + // Also provide a translated version of the error in the users locale to display + this.translatedMessage = _t(message, substitutionVariables); + } +} + export interface ITranslatableError extends Error { translatedMessage: string; } @@ -372,12 +417,18 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri } } if (!matchFoundSomewhere) { - // The current regexp did not match anything in the input - // Missing matches is entirely possible because you might choose to show some variables only in the case - // of e.g. plurals. It's still a bit suspicious, and could be due to an error, so log it. - // However, not showing count is so common that it's not worth logging. And other commonly unused variables - // here, if there are any. - if (regexpString !== "%\\(count\\)s") { + if ( + // The current regexp did not match anything in the input Missing + // matches is entirely possible because you might choose to show some + // variables only in the case of e.g. plurals. It's still a bit + // suspicious, and could be due to an error, so log it. However, not + // showing count is so common that it's not worth logging. And other + // commonly unused variables here, if there are any. + regexpString !== "%\\(count\\)s" && + // Ignore the `locale` option which can be used to override the locale + // in counterpart + regexpString !== "%\\(locale\\)s" + ) { logger.log(`Could not find ${regexp} in ${text}`); } } @@ -656,7 +707,11 @@ function doRegisterTranslations(customTranslations: ICustomTranslations): void { * This function should be called *after* registering other translations data to * ensure it overrides strings properly. */ -export async function registerCustomTranslations(): Promise { +export async function registerCustomTranslations({ + testOnlyIgnoreCustomTranslationsCache = false, +}: { + testOnlyIgnoreCustomTranslationsCache?: boolean; +} = {}): Promise { const moduleTranslations = ModuleRunner.instance.allTranslations; doRegisterTranslations(moduleTranslations); @@ -665,7 +720,7 @@ export async function registerCustomTranslations(): Promise { try { let json: Optional; - if (Date.now() >= cachedCustomTranslationsExpire) { + if (testOnlyIgnoreCustomTranslationsCache || Date.now() >= cachedCustomTranslationsExpire) { json = CustomTranslationOptions.lookupFn ? CustomTranslationOptions.lookupFn(lookupUrl) : ((await (await fetch(lookupUrl)).json()) as ICustomTranslations); diff --git a/test/languageHandler-test.ts b/test/languageHandler-test.ts index f0c38064226..556c12fe05d 100644 --- a/test/languageHandler-test.ts +++ b/test/languageHandler-test.ts @@ -21,8 +21,25 @@ import { ICustomTranslations, registerCustomTranslations, setLanguage, + UserFriendlyError, } from "../src/languageHandler"; +async function setupTranslationOverridesForTests(overrides: ICustomTranslations) { + const lookupUrl = "/translations.json"; + const fn = (url: string): ICustomTranslations => { + expect(url).toEqual(lookupUrl); + return overrides; + }; + + SdkConfig.add({ + custom_translations_url: lookupUrl, + }); + CustomTranslationOptions.lookupFn = fn; + await registerCustomTranslations({ + testOnlyIgnoreCustomTranslationsCache: true, + }); +} + describe("languageHandler", () => { afterEach(() => { SdkConfig.unset(); @@ -33,38 +50,72 @@ describe("languageHandler", () => { const str = "This is a test string that does not exist in the app."; const enOverride = "This is the English version of a custom string."; const deOverride = "This is the German version of a custom string."; - const overrides: ICustomTranslations = { - [str]: { - en: enOverride, - de: deOverride, - }, - }; - - const lookupUrl = "/translations.json"; - const fn = (url: string): ICustomTranslations => { - expect(url).toEqual(lookupUrl); - return overrides; - }; // First test that overrides aren't being used - await setLanguage("en"); expect(_t(str)).toEqual(str); - await setLanguage("de"); expect(_t(str)).toEqual(str); - // Now test that they *are* being used - SdkConfig.add({ - custom_translations_url: lookupUrl, + await setupTranslationOverridesForTests({ + [str]: { + en: enOverride, + de: deOverride, + }, }); - CustomTranslationOptions.lookupFn = fn; - await registerCustomTranslations(); + // Now test that they *are* being used await setLanguage("en"); expect(_t(str)).toEqual(enOverride); await setLanguage("de"); expect(_t(str)).toEqual(deOverride); }); + + describe("UserFriendlyError", () => { + const testErrorMessage = "This email address is already in use (%(email)s)"; + beforeEach(async () => { + // Setup some strings with variable substituations that we can use in the tests. + const deOverride = "Diese E-Mail-Adresse wird bereits verwendet (%(email)s)"; + await setupTranslationOverridesForTests({ + [testErrorMessage]: { + en: testErrorMessage, + de: deOverride, + }, + }); + }); + + it("includes English message and localized translated message", async () => { + await setLanguage("de"); + + const friendlyError = new UserFriendlyError(testErrorMessage, { + email: "test@example.com", + cause: undefined, + }); + + // Ensure message is in English so it's readable in the logs + expect(friendlyError.message).toStrictEqual("This email address is already in use (test@example.com)"); + // Ensure the translated message is localized appropriately + expect(friendlyError.translatedMessage).toStrictEqual( + "Diese E-Mail-Adresse wird bereits verwendet (test@example.com)", + ); + }); + + it("includes underlying cause error", async () => { + await setLanguage("de"); + + const underlyingError = new Error("Fake underlying error"); + const friendlyError = new UserFriendlyError(testErrorMessage, { + email: "test@example.com", + cause: underlyingError, + }); + + expect(friendlyError.cause).toStrictEqual(underlyingError); + }); + + it("ok to omit the substitution variables and cause object, there just won't be any cause", async () => { + const friendlyError = new UserFriendlyError("foo error"); + expect(friendlyError.cause).toBeUndefined(); + }); + }); }); From 6c2635279a7b82c163f4b38d51b580a6f12f8459 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 23 Mar 2023 21:53:25 -0500 Subject: [PATCH 02/12] Replace newTranslatableError with UserFriendlyError --- package.json | 1 - src/SlashCommands.tsx | 84 ++++++++++++++++++++------------ src/editor/commands.tsx | 7 ++- src/languageHandler.tsx | 13 ----- src/utils/AutoDiscoveryUtils.tsx | 16 +++--- 5 files changed, 63 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index fad5e3232d2..f2614fdf53b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "matrix_lib_main": "./lib/index.ts", "matrix_lib_typings": "./lib/index.d.ts", "matrix_i18n_extra_translation_funcs": [ - "newTranslatableError", "UserFriendlyError" ], "scripts": { diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 0cdd9299317..78105d41a98 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -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 }>; +export type RunResult = XOR<{ error: Error }, { promise: Promise }>; 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 => { 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."), ); } } @@ -743,7 +747,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 +907,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 +918,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 +952,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 +990,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 +1003,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 +1032,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 +1056,22 @@ export const Commands = [ (async (): Promise => { 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 +1079,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 +1088,7 @@ export const Commands = [ userId, deviceId, fingerprint, + cause: undefined, }, ); } @@ -1217,7 +1237,7 @@ export const Commands = [ return success( (async (): Promise => { 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({ action: Action.ViewRoom, room_id: room.roomId, @@ -1245,7 +1265,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 +1328,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 +1343,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 +1357,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 +1369,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], diff --git a/src/editor/commands.tsx b/src/editor/commands.tsx index 88db48a8991..7761aad5f08 100644 --- a/src/editor/commands.tsx +++ b/src/editor/commands.tsx @@ -21,7 +21,7 @@ import { IContent } from "matrix-js-sdk/src/models/event"; import EditorModel from "./model"; import { Type } from "./parts"; import { Command, CommandCategories, getCommand } from "../SlashCommands"; -import { ITranslatableError, _t, _td } from "../languageHandler"; +import { ITranslatableError, UserFriendlyError, _t, _td } from "../languageHandler"; import Modal from "../Modal"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import QuestionDialog from "../components/views/dialogs/QuestionDialog"; @@ -86,9 +86,8 @@ export async function runSlashCommand( let errText; if (typeof error === "string") { errText = error; - } else if ((error as ITranslatableError).translatedMessage) { - // Check for translatable errors (newTranslatableError) - errText = (error as ITranslatableError).translatedMessage; + } else if (error instanceof UserFriendlyError) { + errText = error.translatedMessage; } else if (error.message) { errText = error.message; } else { diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 8a89f9d552a..dd5ba2ad175 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -94,19 +94,6 @@ export interface ITranslatableError extends Error { translatedMessage: string; } -/** - * Helper function to create an error which has an English message - * with a translatedMessage property for use by the consumer. - * @param {string} message Message to translate. - * @param {object} variables Variable substitutions, e.g { foo: 'bar' } - * @returns {Error} The constructed error. - */ -export function newTranslatableError(message: string, variables?: IVariables): ITranslatableError { - const error = new Error(message) as ITranslatableError; - error.translatedMessage = _t(message, variables); - return error; -} - export function getUserLanguage(): string { const language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); if (language) { diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index 4297950b42b..b25de6a9716 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -19,7 +19,7 @@ import { AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/autodiscovery"; import { logger } from "matrix-js-sdk/src/logger"; import { IClientWellKnown } from "matrix-js-sdk/src/matrix"; -import { _t, _td, newTranslatableError } from "../languageHandler"; +import { _t, UserFriendlyError } from "../languageHandler"; import { makeType } from "./TypeUtils"; import SdkConfig from "../SdkConfig"; import { ValidatedServerConfig } from "./ValidatedServerConfig"; @@ -147,7 +147,7 @@ export default class AutoDiscoveryUtils { syntaxOnly = false, ): Promise { if (!homeserverUrl) { - throw newTranslatableError(_td("No homeserver URL provided")); + throw new UserFriendlyError("No homeserver URL provided"); } const wellknownConfig: IClientWellKnown = { @@ -199,7 +199,7 @@ export default class AutoDiscoveryUtils { // This shouldn't happen without major misconfiguration, so we'll log a bit of information // in the log so we can find this bit of codee but otherwise tell teh user "it broke". logger.error("Ended up in a state of not knowing which homeserver to connect to."); - throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); + throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } const hsResult = discoveryResult["m.homeserver"]; @@ -221,9 +221,9 @@ export default class AutoDiscoveryUtils { logger.error("Error determining preferred identity server URL:", isResult); if (isResult.state === AutoDiscovery.FAIL_ERROR) { if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error as string) !== -1) { - throw newTranslatableError(isResult.error as string); + throw new UserFriendlyError(String(isResult.error)); } - throw newTranslatableError(_td("Unexpected error resolving identity server configuration")); + throw new UserFriendlyError("Unexpected error resolving identity server configuration"); } // else the error is not related to syntax - continue anyways. // rewrite homeserver error since we don't care about problems @@ -237,9 +237,9 @@ export default class AutoDiscoveryUtils { logger.error("Error processing homeserver config:", hsResult); if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(hsResult.error)) { if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error as string) !== -1) { - throw newTranslatableError(hsResult.error as string); + throw new UserFriendlyError(String(hsResult.error)); } - throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); + throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } // else the error is not related to syntax - continue anyways. } @@ -252,7 +252,7 @@ export default class AutoDiscoveryUtils { // It should have been set by now, so check it if (!preferredHomeserverName) { logger.error("Failed to parse homeserver name from homeserver URL"); - throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); + throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } return makeType(ValidatedServerConfig, { From e515f488ba7d4926ff8afb2fc90fcacd82d3d09b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 23 Mar 2023 21:59:06 -0500 Subject: [PATCH 03/12] Remove ITranslatableError --- src/editor/commands.tsx | 2 +- src/languageHandler.tsx | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/editor/commands.tsx b/src/editor/commands.tsx index 7761aad5f08..c388ec6d38d 100644 --- a/src/editor/commands.tsx +++ b/src/editor/commands.tsx @@ -21,7 +21,7 @@ import { IContent } from "matrix-js-sdk/src/models/event"; import EditorModel from "./model"; import { Type } from "./parts"; import { Command, CommandCategories, getCommand } from "../SlashCommands"; -import { ITranslatableError, UserFriendlyError, _t, _td } from "../languageHandler"; +import { UserFriendlyError, _t, _td } from "../languageHandler"; import Modal from "../Modal"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import QuestionDialog from "../components/views/dialogs/QuestionDialog"; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index dd5ba2ad175..6c1596c6c3f 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -90,10 +90,6 @@ export class UserFriendlyError extends Error { } } -export interface ITranslatableError extends Error { - translatedMessage: string; -} - export function getUserLanguage(): string { const language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); if (language) { From 9b2a07795c3d8dca4845163a1ecf39bc965f3333 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Mar 2023 00:18:30 -0500 Subject: [PATCH 04/12] Fix up some strict lints --- src/SlashCommands.tsx | 5 ++++- src/components/views/right_panel/UserInfo.tsx | 5 ++++- src/editor/commands.tsx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 78105d41a98..52afa1649c5 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -598,7 +598,10 @@ export const Commands = [ }) .then(() => { if (inviter.getCompletionState(address) !== "invited") { - throw new Error(inviter.getErrorText(address)); + throw new Error( + inviter.getErrorText(address) || + `User (${address}) did not end up as invited but no error was given from the inviter utility`, + ); } }), ); diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index a3dd4cdd392..26b1c086805 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -448,7 +448,10 @@ export const UserOptionsSection: React.FC<{ const inviter = new MultiInviter(roomId || ""); await inviter.invite([member.userId]).then(() => { if (inviter.getCompletionState(member.userId) !== "invited") { - throw new Error(inviter.getErrorText(member.userId)); + throw new Error( + inviter.getErrorText(member.userId) || + `User (${member.userId}) did not end up as invited but no error was given from the inviter utility`, + ); } }); } catch (err) { diff --git a/src/editor/commands.tsx b/src/editor/commands.tsx index c388ec6d38d..ed711f9c1d1 100644 --- a/src/editor/commands.tsx +++ b/src/editor/commands.tsx @@ -65,7 +65,7 @@ export async function runSlashCommand( ): Promise<[content: IContent | null, success: boolean]> { const result = cmd.run(roomId, threadId, args); let messageContent: IContent | null = null; - let error = result.error; + let error: any = result.error; if (result.promise) { try { if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { From 04084ab7266cdaeac60b7ecaf54e3795931f87c8 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Mar 2023 16:05:17 -0500 Subject: [PATCH 05/12] Document when we/why we can remove --- src/@types/global.d.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index c9f91707145..d96033ec042 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -186,11 +186,6 @@ declare global { scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void; } - interface ErrorOptions { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause - cause?: unknown; - } - interface Error { // Standard // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause @@ -205,6 +200,14 @@ declare global { columnNumber?: number; } + // We can remove these pieces if we ever update to `target: "es2022"` in our + // TypeScript config which supports the new `cause` property, see + // https://github.com/vector-im/element-web/issues/24913 + interface ErrorOptions { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + cause?: unknown; + } + interface ErrorConstructor { new (message?: string, options?: ErrorOptions): Error; (message?: string, options?: ErrorOptions): Error; From bac54852415887809cc82ac51e71433d7673aa83 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 27 Mar 2023 13:12:12 -0500 Subject: [PATCH 06/12] Update matrix-web-i18n Includes changes to find `new UserFriendlyError`, see https://github.com/matrix-org/matrix-web-i18n/pull/6 --- package.json | 2 +- yarn.lock | 56 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f2614fdf53b..f162db0b6dd 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,7 @@ "jest-mock": "^29.2.2", "jest-raw-loader": "^1.0.1", "matrix-mock-request": "^2.5.0", - "matrix-web-i18n": "^1.3.0", + "matrix-web-i18n": "^1.4.0", "mocha-junit-reporter": "^2.2.0", "node-fetch": "2", "postcss-scss": "^4.0.4", diff --git a/yarn.lock b/yarn.lock index ca1ba5485b3..1ac62b7fb46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -110,7 +110,7 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.21.0", "@babel/generator@^7.21.1": +"@babel/generator@^7.21.0": version "7.21.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== @@ -120,6 +120,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.21.1", "@babel/generator@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce" + integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA== + dependencies: + "@babel/types" "^7.21.3" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/generator@^7.7.2": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" @@ -405,11 +415,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.21.0": version "7.21.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== +"@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.21.2", "@babel/parser@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" + integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1163,7 +1178,7 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.12.12", "@babel/traverse@^7.18.5", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.12.12", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.7.2": version "7.21.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== @@ -1179,6 +1194,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.18.5": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" + integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.3" + "@babel/types" "^7.21.3" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" @@ -1197,7 +1228,16 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2": +"@babel/types@^7.18.6", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" + integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@babel/types@^7.20.0", "@babel/types@^7.20.2": version "7.21.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1" integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw== @@ -6501,10 +6541,10 @@ matrix-mock-request@^2.5.0: dependencies: expect "^28.1.0" -matrix-web-i18n@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/matrix-web-i18n/-/matrix-web-i18n-1.3.0.tgz#d85052635215173541f56ea1af0cbefd6e09ecb3" - integrity sha512-4QumouFjd4//piyRCtkfr24kjMPHkzNQNz09B1oEX4W3d4gdd5F+lwErqcQrys7Yl09U0S0iKCD8xPBRV178qg== +matrix-web-i18n@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/matrix-web-i18n/-/matrix-web-i18n-1.4.0.tgz#f383a3ebc29d3fd6eb137d38cc4c3198771cc073" + integrity sha512-+NP2h4zdft+2H/6oFQ0i2PBm00Ei6HpUHke8rklgpe/yCABBG5Q7gIQdZoxazi0DXWWtcvvIfgamPZmkg6oRwA== dependencies: "@babel/parser" "^7.18.5" "@babel/traverse" "^7.18.5" From ef2411fa943322453cbfea48ce04ab362fe0adff Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 27 Mar 2023 13:16:55 -0500 Subject: [PATCH 07/12] Include room ID in error --- src/SlashCommands.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 52afa1649c5..ec057eff63b 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -600,7 +600,7 @@ export const Commands = [ if (inviter.getCompletionState(address) !== "invited") { throw new Error( inviter.getErrorText(address) || - `User (${address}) did not end up as invited but no error was given from the inviter utility`, + `User (${address}) did not end up as invited to ${roomId} but no error was given from the inviter utility`, ); } }), diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index ea838673c0e..e5356dcc49f 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -450,7 +450,7 @@ export const UserOptionsSection: React.FC<{ if (inviter.getCompletionState(member.userId) !== "invited") { throw new Error( inviter.getErrorText(member.userId) || - `User (${member.userId}) did not end up as invited but no error was given from the inviter utility`, + `User (${member.userId}) did not end up as invited to ${roomId} but no error was given from the inviter utility`, ); } }); From 05ec56aced1aaca6e520e34c0951236e5ea08051 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 27 Mar 2023 13:19:10 -0500 Subject: [PATCH 08/12] Translate fallback error --- src/SlashCommands.tsx | 5 ++++- src/components/views/right_panel/UserInfo.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index ec057eff63b..4b127b4960d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -600,7 +600,10 @@ export const Commands = [ if (inviter.getCompletionState(address) !== "invited") { throw new Error( inviter.getErrorText(address) || - `User (${address}) did not end up as invited to ${roomId} but no error was given from the inviter utility`, + _t( + "User (%(address)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility", + { address, roomId, cause: undefined }, + ), ); } }), diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index e5356dcc49f..3f1649010e8 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -450,7 +450,10 @@ export const UserOptionsSection: React.FC<{ if (inviter.getCompletionState(member.userId) !== "invited") { throw new Error( inviter.getErrorText(member.userId) || - `User (${member.userId}) did not end up as invited to ${roomId} but no error was given from the inviter utility`, + _t( + `User (%(userId)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility`, + { userId: member.userId, roomId }, + ), ); } }); From 81e94600f1192d38dd41f32fc90da62357fc7b16 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 27 Mar 2023 13:21:57 -0500 Subject: [PATCH 09/12] Translate better --- src/SlashCommands.tsx | 15 ++++++++------- src/components/views/right_panel/UserInfo.tsx | 17 +++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 4b127b4960d..469cec40006 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -598,13 +598,14 @@ export const Commands = [ }) .then(() => { if (inviter.getCompletionState(address) !== "invited") { - throw new Error( - inviter.getErrorText(address) || - _t( - "User (%(address)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility", - { address, roomId, cause: undefined }, - ), - ); + if (inviter.getErrorText(address)) { + throw new Error(inviter.getErrorText(address)); + } else { + throw new UserFriendlyError( + "User (%(address)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility", + { address, roomId, cause: undefined }, + ); + } } }), ); diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 3f1649010e8..e46b85f2468 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -34,7 +34,7 @@ import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import dis from "../../../dispatcher/dispatcher"; import Modal from "../../../Modal"; -import { _t } from "../../../languageHandler"; +import { _t, UserFriendlyError } from "../../../languageHandler"; import DMRoomMap from "../../../utils/DMRoomMap"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import SdkConfig from "../../../SdkConfig"; @@ -448,13 +448,14 @@ export const UserOptionsSection: React.FC<{ const inviter = new MultiInviter(roomId || ""); await inviter.invite([member.userId]).then(() => { if (inviter.getCompletionState(member.userId) !== "invited") { - throw new Error( - inviter.getErrorText(member.userId) || - _t( - `User (%(userId)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility`, - { userId: member.userId, roomId }, - ), - ); + if (inviter.getErrorText(member.userId)) { + throw new Error(inviter.getErrorText(member.userId)); + } else { + throw new UserFriendlyError( + `User (%(userId)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility`, + { userId: member.userId, roomId, cause: undefined }, + ); + } } }); } catch (err) { From 5487715c16751fd7ccff743236dd6fd29bf60f24 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 27 Mar 2023 13:29:02 -0500 Subject: [PATCH 10/12] Update i18n strings --- src/SlashCommands.tsx | 4 ++-- src/components/views/right_panel/UserInfo.tsx | 4 ++-- src/i18n/strings/en_EN.json | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 469cec40006..c1b3b43b084 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -602,8 +602,8 @@ export const Commands = [ throw new Error(inviter.getErrorText(address)); } else { throw new UserFriendlyError( - "User (%(address)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility", - { address, roomId, cause: undefined }, + "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 }, ); } } diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index e46b85f2468..c7f1298c581 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -452,8 +452,8 @@ export const UserOptionsSection: React.FC<{ throw new Error(inviter.getErrorText(member.userId)); } else { throw new UserFriendlyError( - `User (%(userId)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility`, - { userId: member.userId, roomId, cause: undefined }, + `User (%(user)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility`, + { user: member.userId, roomId, cause: undefined }, ); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 66c5f18102d..cf178d11ae6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -435,6 +435,7 @@ "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.", "Continue": "Continue", "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", + "User (%(user)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility": "User (%(user)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility", "Joins room with given address": "Joins room with given address", "Leave room": "Leave room", "Unrecognised room address: %(roomAlias)s": "Unrecognised room address: %(roomAlias)s", From 89277a30b38de089af9fbbe24abc108b46aeaa3d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 27 Mar 2023 13:39:04 -0500 Subject: [PATCH 11/12] Better re-use --- src/SlashCommands.tsx | 5 +++-- src/components/views/right_panel/UserInfo.tsx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index c1b3b43b084..ae9a618d601 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -598,8 +598,9 @@ export const Commands = [ }) .then(() => { if (inviter.getCompletionState(address) !== "invited") { - if (inviter.getErrorText(address)) { - 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", diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index c7f1298c581..b389dd4dcdd 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -448,8 +448,9 @@ export const UserOptionsSection: React.FC<{ const inviter = new MultiInviter(roomId || ""); await inviter.invite([member.userId]).then(() => { if (inviter.getCompletionState(member.userId) !== "invited") { - if (inviter.getErrorText(member.userId)) { - throw new Error(inviter.getErrorText(member.userId)); + const errorStringFromInviterUtility = inviter.getErrorText(member.userId); + 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`, From ca40d713578e90d6dc7314effd6a9c2e90fdfe9b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 30 Mar 2023 16:44:55 -0500 Subject: [PATCH 12/12] Minor comment fixes --- src/languageHandler.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 8d3d738beb4..0829282b6c8 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -47,7 +47,7 @@ const FALLBACK_LOCALE = "en"; counterpart.setFallbackLocale(FALLBACK_LOCALE); interface ErrorOptions { - // Because we're mixing the subsitution variables and cause into the same object + // Because we're mixing the subsitution variables and `cause` into the same object // below, we want them to always explicitly say whether there is an underlying error // or not to avoid typos of "cause" slipping through unnoticed. cause: unknown | undefined; @@ -402,7 +402,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri } if (!matchFoundSomewhere) { if ( - // The current regexp did not match anything in the input Missing + // The current regexp did not match anything in the input. Missing // matches is entirely possible because you might choose to show some // variables only in the case of e.g. plurals. It's still a bit // suspicious, and could be due to an error, so log it. However, not