diff --git a/src/core/classes/errors/ajaxwserror.ts b/src/core/classes/errors/ajaxwserror.ts index 73dcfa37d..53e8b10e6 100644 --- a/src/core/classes/errors/ajaxwserror.ts +++ b/src/core/classes/errors/ajaxwserror.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreSiteError } from '@classes/errors/siteerror'; +import { CoreSiteError, CoreSiteErrorOptions } from '@classes/errors/siteerror'; /** * Error returned by WS. @@ -29,10 +29,7 @@ export class CoreAjaxWSError extends CoreSiteError { // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(error: any, available?: number) { - super({ - message: error.message || error.error, - errorcode: error.errorcode, - }); + super(getErrorOptions(error)); this.exception = error.exception; this.warningcode = error.warningcode; @@ -40,15 +37,37 @@ export class CoreAjaxWSError extends CoreSiteError { this.moreinfourl = error.moreinfourl; this.debuginfo = error.debuginfo; this.backtrace = error.backtrace; - - this.available = available; - if (this.available === undefined) { - if (this.errorcode) { - this.available = this.errorcode == 'invalidrecord' ? -1 : 1; - } else { - this.available = 0; - } - } + this.available = available ?? ( + this.debug + ? (this.debug.code == 'invalidrecord' ? -1 : 1) + : 0 + ); } } + +/** + * Get error options from unknown error instance. + * + * @param error The error. + * @returns Options + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getErrorOptions(error: any): CoreSiteErrorOptions { + const options: CoreSiteErrorOptions = { + message: error.message || error.error, + }; + + if ('debug' in error) { + options.debug = error.debug; + } + + if ('errorcode' in error) { + options.debug = { + code: error.errorcode, + details: error.message || error.error, + }; + } + + return options; +} diff --git a/src/core/classes/errors/siteerror.ts b/src/core/classes/errors/siteerror.ts index bb31c41b6..3eed531e6 100644 --- a/src/core/classes/errors/siteerror.ts +++ b/src/core/classes/errors/siteerror.ts @@ -20,24 +20,39 @@ import { CoreUserSupportConfig } from '@features/user/classes/support/support-co */ export class CoreSiteError extends CoreError { - errorcode?: string; - errorDetails?: string; + debug?: CoreSiteErrorDebug; supportConfig?: CoreUserSupportConfig; constructor(options: CoreSiteErrorOptions) { super(options.message); - this.errorcode = options.errorcode; - this.errorDetails = options.errorDetails; + this.debug = options.debug; this.supportConfig = options.supportConfig; } + /** + * @deprecated This getter should not be called directly, but it's defined for backwards compatibility with many + * parts of the code that type errors as any and use it. We cannot rename those because the errors could also be + * CoreWSError instances which do have an "errorcode" property. + * + * @returns error code. + */ + get errorcode(): string | undefined { + return this.debug?.code; + } + } +export type CoreSiteErrorDebug = { + code: string; // Technical error code useful for technical assistance. + details: string; // Technical error details useful for technical assistance. +}; + export type CoreSiteErrorOptions = { message: string; - errorcode?: string; // Technical error code useful for technical assistance. - errorDetails?: string; // Technical error details useful for technical assistance. + + // Debugging information. + debug?: CoreSiteErrorDebug; // Configuration to use to contact site support. If this attribute is present, it means // that the error warrants contacting support. diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts index 7769d479f..a71f28237 100644 --- a/src/core/classes/sites/authenticated-site.ts +++ b/src/core/classes/sites/authenticated-site.ts @@ -943,8 +943,10 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite { throw new CoreSiteError({ supportConfig: new CoreUserAuthenticatedSupportConfig(this), message: Translate.instant('core.siteunavailablehelp', { site: this.siteUrl }), - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method: 'tool_mobile_call_external_functions' }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method: 'tool_mobile_call_external_functions' }), + }, }); } diff --git a/src/core/components/error-info/error-info.ts b/src/core/components/error-info/error-info.ts index 4104e7f3e..31666e599 100644 --- a/src/core/components/error-info/error-info.ts +++ b/src/core/components/error-info/error-info.ts @@ -36,7 +36,7 @@ export class CoreErrorInfoComponent implements OnInit, OnChanges { * @param errorCode Error code. * @returns Component HTML. */ - static render(errorDetails: string, errorCode?: string): string { + static render(errorDetails: string, errorCode: string): string { const toggleId = CoreForms.uniqueId('error-info-toggle'); const errorCodeLabel = Translate.instant('core.errorcode', { errorCode }); const hideDetailsLabel = Translate.instant('core.errordetailshide'); @@ -45,7 +45,7 @@ export class CoreErrorInfoComponent implements OnInit, OnChanges { return `
${errorDetails}
${errorMessage}
`; } @@ -438,7 +436,7 @@ export class CoreLoginSitePage implements OnInit { handler: () => CoreUserSupport.contact({ supportConfig: alertSupportConfig, subject: Translate.instant('core.cannotconnect'), - message: `Error: ${errorCode}\n\n${errorDetails}`, + message: `Error: ${debug?.code}\n\n${debug?.details}`, }), } : ( @@ -458,11 +456,10 @@ export class CoreLoginSitePage implements OnInit { buttons: buttons as AlertButton[], }); - if (errorDetails) { - // Avoid sanitizing JS. + if (debug) { const containerElement = alertElement.querySelector('.core-error-info-container'); if (containerElement) { - containerElement.innerHTML = CoreErrorInfoComponent.render(errorDetails, errorCode); + containerElement.innerHTML = CoreErrorInfoComponent.render(debug.details, debug.code); } } } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 98fb1c72c..44ca38f7a 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -39,7 +39,6 @@ import { CorePushNotifications } from '@features/pushnotifications/services/push import { CorePath } from '@singletons/path'; import { CorePromisedValue } from '@classes/promised-value'; import { SafeHtml } from '@angular/platform-browser'; -import { CoreLoginError } from '@classes/errors/loginerror'; import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; import { CoreSiteIdentityProvider, @@ -916,8 +915,8 @@ export class CoreLoginHelperProvider { /** * Show a modal warning that the credentials introduced were not correct. */ - protected showInvalidLoginModal(error: CoreLoginError): void { - CoreDomUtils.showErrorModal(error.errorDetails ?? error.message); + protected showInvalidLoginModal(error: CoreWSError): void { + CoreDomUtils.showErrorModal(error.message); } /** diff --git a/src/core/features/login/tests/credentials.test.ts b/src/core/features/login/tests/credentials.test.ts index e4ca21f81..3e8d83fbd 100644 --- a/src/core/features/login/tests/credentials.test.ts +++ b/src/core/features/login/tests/credentials.test.ts @@ -124,7 +124,10 @@ describe('Credentials page', () => { getUserToken: () => { throw new CoreLoginError({ message: '', - errorcode: 'invalidlogin', + debug: { + code: 'invalidlogin', + details: 'Invalid login', + }, }); }, checkSite: async () => (siteCheck), diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 00e0e400b..09a27bafd 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -66,6 +66,7 @@ import { CoreSiteInfo, CoreSiteInfoResponse, CoreSitePublicConfigResponse } from import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; import { firstValueFrom } from 'rxjs'; import { CoreHTMLClasses } from '@singletons/html-classes'; +import { CoreSiteErrorDebug } from '@classes/errors/siteerror'; export const CORE_SITE_SCHEMAS = new InjectionToken${alertOptions.message}
`; } @@ -1051,7 +1051,7 @@ export class CoreDomUtilsProvider { handler: () => CoreUserSupport.contact({ supportConfig, subject: alertOptions.header, - message: `${error.errorcode}\n\n${error.errorDetails}`, + message: `${error.debug?.code}\n\n${error.debug?.details}`, }), }); } @@ -1061,11 +1061,11 @@ export class CoreDomUtilsProvider { const alertElement = await this.showAlertWithOptions(alertOptions, autocloseTime); - if (error instanceof CoreSiteError && error.errorDetails) { + if (error instanceof CoreSiteError && error.debug) { const containerElement = alertElement.querySelector('.core-error-info-container'); if (containerElement) { - containerElement.innerHTML = CoreErrorInfoComponent.render(error.errorDetails, error.errorcode); + containerElement.innerHTML = CoreErrorInfoComponent.render(error.debug.details, error.debug.code); } } diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts index b1084347e..004da122c 100644 --- a/src/core/services/ws.ts +++ b/src/core/services/ws.ts @@ -497,10 +497,12 @@ export class CoreWSProvider { throw new CoreAjaxError({ message, supportConfig: await CoreUserGuestSupportConfig.forSite(preSets.siteUrl), - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.serverconnection', { - details: Translate.instant('core.errorinvalidresponse', { method }), - }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.serverconnection', { + details: Translate.instant('core.errorinvalidresponse', { method }), + }), + }, }); } else if (data.error) { throw new CoreAjaxWSError(data); @@ -527,54 +529,72 @@ export class CoreWSProvider { if (CorePlatform.isMobile()) { switch (data.status) { case NativeHttp.ErrorCode.SSL_EXCEPTION: - options.errorcode = 'invalidcertificate'; - options.errorDetails = Translate.instant('core.certificaterror', { - details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Invalid certificate', - }); + options.debug = { + code: 'invalidcertificate', + details: Translate.instant('core.certificaterror', { + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Invalid certificate', + }), + }; break; case NativeHttp.ErrorCode.SERVER_NOT_FOUND: - options.errorcode = 'servernotfound'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Server could not be found'; + options.debug = { + code: 'servernotfound', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Server could not be found', + }; break; case NativeHttp.ErrorCode.TIMEOUT: - options.errorcode = 'requesttimeout'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request timed out'; + options.debug = { + code: 'requesttimeout', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request timed out', + }; break; case NativeHttp.ErrorCode.UNSUPPORTED_URL: - options.errorcode = 'unsupportedurl'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Url not supported'; + options.debug = { + code: 'unsupportedurl', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Url not supported', + }; break; case NativeHttp.ErrorCode.NOT_CONNECTED: - options.errorcode = 'connectionerror'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) - ?? 'Connection error, is network available?'; + options.debug = { + code: 'connectionerror', + details: CoreTextUtils.getErrorMessageFromError(data.error) + ?? 'Connection error, is network available?', + }; break; case NativeHttp.ErrorCode.ABORTED: - options.errorcode = 'requestaborted'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request aborted'; + options.debug = { + code: 'requestaborted', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request aborted', + }; break; case NativeHttp.ErrorCode.POST_PROCESSING_FAILED: - options.errorcode = 'requestprocessingfailed'; - options.errorDetails = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request processing failed'; + options.debug = { + code: 'requestprocessingfailed', + details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request processing failed', + }; break; } } - if (!options.errorcode) { + if (!options.debug) { switch (data.status) { case 404: - options.errorcode = 'endpointnotfound'; - options.errorDetails = Translate.instant('core.ajaxendpointnotfound', { - $a: CoreSite.MINIMUM_MOODLE_VERSION, - }); + options.debug = { + code: 'endpointnotfound', + details: Translate.instant('core.ajaxendpointnotfound', { + $a: CoreSite.MINIMUM_MOODLE_VERSION, + }), + }; break; default: { const details = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Unknown error'; - options.errorcode = 'serverconnectionajax'; - options.errorDetails = Translate.instant('core.serverconnection', { - details: `[Response status code: ${data.status}] ${details}`, - }); + options.debug = { + code: 'serverconnectionajax', + details: Translate.instant('core.serverconnection', { + details: `[Response status code: ${data.status}] ${details}`, + }), + }; } break; } @@ -716,10 +736,12 @@ export class CoreWSProvider { if (!data) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'serverconnectionpost', - errorDetails: Translate.instant('core.serverconnection', { - details: Translate.instant('core.errorinvalidresponse', { method }), - }), + debug: { + code: 'serverconnectionpost', + details: Translate.instant('core.serverconnection', { + details: Translate.instant('core.errorinvalidresponse', { method }), + }), + }, }); } else if (typeof data !== typeExpected) { // If responseType is text an string will be returned, parse before returning. @@ -730,8 +752,10 @@ export class CoreWSProvider { this.logger.warn(`Response expected type "${typeExpected}" cannot be parsed to number`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } else if (typeExpected === 'boolean') { @@ -743,24 +767,30 @@ export class CoreWSProvider { this.logger.warn(`Response expected type "${typeExpected}" is not true or false`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } else { this.logger.warn('Response of type "' + typeof data + `" received, expecting "${typeExpected}"`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } else { this.logger.warn('Response of type "' + typeof data + `" received, expecting "${typeExpected}"`); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method }), + }, }); } } @@ -803,10 +833,12 @@ export class CoreWSProvider { return retryPromise; } else if (error.status === -2) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidcertificate', - errorDetails: Translate.instant('core.certificaterror', { - details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error', - }), + debug: { + code: 'invalidcertificate', + details: Translate.instant('core.certificaterror', { + details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error', + }), + }, }); } else if (error.status > 0) { throw this.createHttpError(error, error.status); @@ -1033,24 +1065,30 @@ export class CoreWSProvider { if (data === null) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + }, }); } if (!data) { throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'serverconnectionupload', - errorDetails: Translate.instant('core.serverconnection', { - details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), - }), + debug: { + code: 'serverconnectionupload', + details: Translate.instant('core.serverconnection', { + details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + }), + }, }); } else if (typeof data != 'object') { this.logger.warn('Upload file: Response of type "' + typeof data + '" received, expecting "object"'); throw await this.createCannotConnectSiteError(preSets.siteUrl, { - errorcode: 'invalidresponse', - errorDetails: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + debug: { + code: 'invalidresponse', + details: Translate.instant('core.errorinvalidresponse', { method: 'upload.php' }), + }, }); }