diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts index 9ed1c93202..3b92ce0e7b 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts @@ -43,3 +43,11 @@ export const DEFAULT_ERROR_LOCALIZATIONS = { details: 'AbpUi::DefaultErrorMessage', }, }; + +export const CUSTOM_HTTP_ERROR_HANDLER_PRIORITY = Object.freeze({ + veryLow: -9, + low: -9, + normal: 0, + high: 9, + veryHigh: 99, +}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 7a2736be78..721f5069a5 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -1,68 +1,40 @@ - import { - AuthService, - HttpErrorReporterService, - LocalizationParam, - RouterEvents, - SessionStateService, -} from '@abp/ng.core'; +import { AuthService, HttpErrorReporterService } from '@abp/ng.core'; import { HttpErrorResponse } from '@angular/common/http'; -import { - ComponentFactoryResolver, - ComponentRef, - inject, - Injectable, - Injector, - RendererFactory2, -} from '@angular/core'; -import { NavigationError, ResolveEnd } from '@angular/router'; -import { Observable, of, Subject, throwError } from 'rxjs'; +import { inject, Injectable, Injector } from '@angular/core'; +import { Observable, of, throwError } from 'rxjs'; import { catchError, filter, switchMap } from 'rxjs/operators'; import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; -import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common'; +import { CustomHttpErrorHandlerService, HttpErrorConfig } from '../models/common'; import { Confirmation } from '../models/confirmation'; import { ConfirmationService } from '../services/confirmation.service'; -import { HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; +import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; import { CreateErrorComponentService } from '../services/create-error-component.service'; -import { CanCreateCustomErrorService } from '../services/can-create-custom-error.service'; import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/error'; +import { RouterErrorHandlerService } from '../services/router-error-handler.service'; @Injectable({ providedIn: 'root' }) export class ErrorHandler { protected httpErrorHandler = this.injector.get(HTTP_ERROR_HANDLER, (_, err: HttpErrorResponse) => throwError(err), ); - protected httpErrorReporter: HttpErrorReporterService; - protected routerEvents: RouterEvents; protected confirmationService: ConfirmationService; - protected cfRes: ComponentFactoryResolver; - protected rendererFactory: RendererFactory2; protected httpErrorConfig: HttpErrorConfig; - protected sessionStateService: SessionStateService; - private authService: AuthService; - private createErrorComponentService = inject(CreateErrorComponentService); - private canCreateCustomErrorService = inject(CanCreateCustomErrorService); + private customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); + private routerErrorHandlerService = inject(RouterErrorHandlerService); + constructor(protected injector: Injector) { this.httpErrorReporter = injector.get(HttpErrorReporterService); - this.routerEvents = injector.get(RouterEvents); this.confirmationService = injector.get(ConfirmationService); - this.cfRes = injector.get(ComponentFactoryResolver); - this.rendererFactory = injector.get(RendererFactory2); this.httpErrorConfig = injector.get('HTTP_ERROR_CONFIG'); - this.authService = this.injector.get(AuthService); - this.sessionStateService = this.injector.get(SessionStateService); - this.listenToRestError(); this.listenToRouterError(); } protected listenToRouterError() { - this.routerEvents - .getNavigationEvents('Error') - .pipe(filter(this.filterRouteErrors)) - .subscribe(() => this.show404Page()); + this.routerErrorHandlerService.listen(); } protected listenToRestError() { @@ -84,177 +56,45 @@ export class ErrorHandler { ); }; - private handleError(err: any) { - const body = err?.error?.error || { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }; - - if (err instanceof HttpErrorResponse && err.headers.get('Abp-Tenant-Resolve-Error')) { - this.sessionStateService.setTenant(null) - this.authService.logout().subscribe(); - return; - } - - const isAbpErrorFormat = err.headers.get('_AbpErrorFormat'); - if (err instanceof HttpErrorResponse && isAbpErrorFormat) { - const confirmation$ = this.showErrorWithRequestBody(body); - - if (err.status === 401) { - confirmation$.subscribe(() => { - this.navigateToLogin(); - }); - } - } else { - switch (err.status) { - case 401: - this.canCreateCustomError(401) - ? this.show401Page() - : this.showError( - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title, - }, - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details, - }, - ).subscribe(() => this.navigateToLogin()); - break; - case 403: - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title, - }, - details: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError403.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details, - }, - status: 403, - }); - break; - case 404: - this.canCreateCustomError(404) - ? this.show404Page() - : this.showError( - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details, - }, - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title, - }, - ); - break; - case 500: - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.title, - }, - details: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError500.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details, - }, - status: 500, - }); - break; - case 0: - if (err.statusText === 'Unknown Error') { - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }, - details: err.message, - isHomeShow: false, - }); - } - break; - default: - this.showError( - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details, - }, - { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }, - ); - break; - } - } + protected sortHttpErrorHandlers( + a: CustomHttpErrorHandlerService, + b: CustomHttpErrorHandlerService, + ) { + return (b.priority || 0) - (a.priority || 0); } - protected show401Page() { - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError401.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title, - }, - status: 401, - }); - } - - protected show404Page() { - this.createErrorComponent({ - title: { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title, - }, - status: 404, - }); - } - - protected showErrorWithRequestBody(body: any) { - let message: LocalizationParam; - let title: LocalizationParam; - - if (body.details) { - message = body.details; - title = body.message; - } else if (body.message) { - title = { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }; - message = body.message; - } else { - message = body.message || { - key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, - defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, - }; - title = ''; + private handleError(err: unknown) { + if (this.customErrorHandlers && this.customErrorHandlers.length) { + const canHandleService = this.customErrorHandlers + .sort(this.sortHttpErrorHandlers) + .find(service => service.canHandle(err)); + if (canHandleService) { + canHandleService.execute(); + return; + } } - - return this.showError(message, title); + this.showError().subscribe(); } - protected showError( - message: LocalizationParam, - title: LocalizationParam, - ): Observable { + protected showError(): Observable { + const title = { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }; + const message = { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.details, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.details, + }; return this.confirmationService.error(message, title, { hideCancelBtn: true, yesText: 'AbpAccount::Close', }); } - private navigateToLogin() { - this.authService.navigateToLogin(); - } - createErrorComponent(instance: Partial) { this.createErrorComponentService.execute(instance); } - canCreateCustomError(status: ErrorScreenErrorCodes): boolean { - return this.canCreateCustomErrorService.execute(status); - } - protected filterRestErrors = ({ status }: HttpErrorResponse): boolean => { if (typeof status !== 'number') return false; @@ -263,12 +103,4 @@ export class ErrorHandler { this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0 ); }; - - protected filterRouteErrors = (navigationError: NavigationError): boolean => { - return ( - navigationError.error?.message?.indexOf('Cannot match') > -1 && - !!this.httpErrorConfig.skipHandledErrorCodes && - this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0 - ); - }; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts index 9ddbe0f5be..821fe52625 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts @@ -27,3 +27,9 @@ export type HttpErrorHandler = ( ) => Observable; export type LocaleDirection = 'ltr' | 'rtl'; + +export interface CustomHttpErrorHandlerService { + readonly priority: number; + canHandle(error: unknown): boolean; + execute(); +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts new file mode 100644 index 0000000000..9cbf6f41ab --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts @@ -0,0 +1,29 @@ +import { Provider } from '@angular/core'; +import { CUSTOM_ERROR_HANDLERS } from '../tokens'; +import { TenantResolveErrorHandlerService } from '../services/tenant-resolve-error-handler.service'; +import { AbpFormatErrorHandlerService } from '../services/abp-format-error-handler.service'; +import { StatusCodeErrorHandlerService } from '../services/status-code-error-handler.service'; +import { UnknownStatusCodeErrorHandlerService } from '../services/unknown-status-code-error-handler.service'; + +export const errorHandlersProviders: Provider[] = [ + { + provide: CUSTOM_ERROR_HANDLERS, + multi: true, + useClass: TenantResolveErrorHandlerService, + }, + { + provide: CUSTOM_ERROR_HANDLERS, + multi: true, + useClass: AbpFormatErrorHandlerService, + }, + { + provide: CUSTOM_ERROR_HANDLERS, + multi: true, + useClass: StatusCodeErrorHandlerService, + }, + { + provide: CUSTOM_ERROR_HANDLERS, + multi: true, + useClass: UnknownStatusCodeErrorHandlerService, + }, +]; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts index 40efb9df39..cac8aa5d94 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts @@ -1,2 +1,3 @@ export * from './ng-bootstrap-config.provider'; export * from './route.provider'; +export * from './error-handlers.provider'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/abp-format-error-handler.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/abp-format-error-handler.service.ts new file mode 100644 index 0000000000..b7c6fc9d76 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/abp-format-error-handler.service.ts @@ -0,0 +1,41 @@ +import { inject, Injectable } from '@angular/core'; +import { AuthService } from '@abp/ng.core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { getErrorFromRequestBody } from '../utils/error.utils'; +import { CustomHttpErrorHandlerService } from '../models/common'; +import { ConfirmationService } from '../services/confirmation.service'; +import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from '../constants/error'; + +@Injectable({ providedIn: 'root' }) +export class AbpFormatErrorHandlerService implements CustomHttpErrorHandlerService { + priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.high; + private confirmationService = inject(ConfirmationService); + private authService = inject(AuthService); + private error: HttpErrorResponse | undefined = undefined; + + private navigateToLogin() { + return this.authService.navigateToLogin(); + } + + canHandle(error: unknown): boolean { + if (error instanceof HttpErrorResponse && error.headers.get('_AbpErrorFormat')) { + this.error = error; + return true; + } + return false; + } + + execute() { + const { message, title } = getErrorFromRequestBody(this.error?.error?.error); + this.confirmationService + .error(message, title, { + hideCancelBtn: true, + yesText: 'AbpAccount::Close', + }) + .subscribe(() => { + if (this.error?.status === 401) { + this.navigateToLogin(); + } + }); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts index 7ce1a39262..b078e2d404 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { ErrorScreenErrorCodes } from '../models/common'; -import { HTTP_ERROR_CONFIG } from '@abp/ng.theme.shared'; +import { HTTP_ERROR_CONFIG } from '../tokens/http-error.token'; @Injectable({ providedIn: 'root', diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts index 1ca37d2073..a088ebffa2 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts @@ -8,16 +8,14 @@ import { Injector, RendererFactory2, } from '@angular/core'; -import { - ErrorScreenErrorCodes, - HTTP_ERROR_CONFIG, - HttpErrorWrapperComponent, -} from '@abp/ng.theme.shared'; import { Subject } from 'rxjs'; import { ResolveEnd } from '@angular/router'; import { filter } from 'rxjs/operators'; import { RouterEvents } from '@abp/ng.core'; import { CanCreateCustomErrorService } from './can-create-custom-error.service'; +import { HTTP_ERROR_CONFIG } from '../tokens/http-error.token'; +import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; +import { ErrorScreenErrorCodes } from '../models/common'; @Injectable({ providedIn: 'root' }) export class CreateErrorComponentService { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/index.ts index 0f98819137..1985995ce1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/index.ts @@ -3,3 +3,10 @@ export * from './nav-items.service'; export * from './page-alert.service'; export * from './toaster.service'; export * from './user-menu.service'; +export * from './can-create-custom-error.service'; +export * from './create-error-component.service'; +export * from './abp-format-error-handler.service'; +export * from './tenant-resolve-error-handler.service'; +export * from './status-code-error-handler.service'; +export * from './unknown-status-code-error-handler.service'; +export * from './router-error-handler.service'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts new file mode 100644 index 0000000000..cb201444b5 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts @@ -0,0 +1,41 @@ +import { inject, Injectable } from '@angular/core'; +import { filter } from 'rxjs/operators'; +import { RouterEvents } from '@abp/ng.core'; +import { NavigationError } from '@angular/router'; +import { HTTP_ERROR_CONFIG } from '../tokens/'; +import { CreateErrorComponentService } from '../services'; +import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/error'; + +@Injectable({ providedIn: 'root' }) +export class RouterErrorHandlerService { + private readonly routerEvents = inject(RouterEvents); + private httpErrorConfig = inject(HTTP_ERROR_CONFIG); + private createErrorComponentService = inject(CreateErrorComponentService); + + listen() { + this.routerEvents + .getNavigationEvents('Error') + .pipe(filter(this.filterRouteErrors)) + .subscribe(() => this.show404Page()); + } + + protected filterRouteErrors = (navigationError: NavigationError): boolean => { + return ( + navigationError.error?.message?.indexOf('Cannot match') > -1 && + !!this.httpErrorConfig.skipHandledErrorCodes && + this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0 + ); + }; + + show404Page() { + const instance = { + title: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title, + }, + status: 404, + }; + + this.createErrorComponentService.execute(instance); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts new file mode 100644 index 0000000000..9f94769220 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts @@ -0,0 +1,102 @@ +import { Confirmation, CustomHttpErrorHandlerService } from '../models'; +import { + CUSTOM_HTTP_ERROR_HANDLER_PRIORITY, + DEFAULT_ERROR_LOCALIZATIONS, + DEFAULT_ERROR_MESSAGES, +} from '../constants/error'; +import { AuthService, LocalizationParam } from '@abp/ng.core'; +import { Observable } from 'rxjs'; +import { inject, Injectable } from '@angular/core'; +import { ConfirmationService } from './confirmation.service'; +import { CanCreateCustomErrorService } from './can-create-custom-error.service'; +import { CreateErrorComponentService } from './create-error-component.service'; + +@Injectable({ providedIn: 'root' }) +export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerService { + private readonly confirmationService = inject(ConfirmationService); + private readonly canCreateCustomErrorService = inject(CanCreateCustomErrorService); + private readonly createErrorComponentService = inject(CreateErrorComponentService); + private readonly authService = inject(AuthService); + readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.normal; + private status: typeof this.handledStatusCodes[number]; + private readonly handledStatusCodes = [401, 403, 404, 500] as const; + + canHandle({ status }): boolean { + this.status = status; + return this.handledStatusCodes.indexOf(status) > -1; + } + + execute() { + const key = `defaultError${this.status}`; + const title = { + key: DEFAULT_ERROR_LOCALIZATIONS[key]?.title, + defaultValue: DEFAULT_ERROR_MESSAGES[key]?.title, + }; + const message = { + key: DEFAULT_ERROR_LOCALIZATIONS[key]?.details, + defaultValue: DEFAULT_ERROR_MESSAGES[key]?.details, + }; + + const canCreateCustomError = this.canCreateCustomErrorService.execute(this.status); + switch (this.status) { + case 401: + if (canCreateCustomError) { + this.showPage(); + } else { + this.showConfirmation(title, message).subscribe(() => this.navigateToLogin()); + } + break; + + case 403: + this.showPage(); + break; + + case 404: + if (canCreateCustomError) { + this.showPage(); + } else { + this.showConfirmation(title, message).subscribe(); + } + break; + + case 500: + this.showPage(); + break; + } + } + + private navigateToLogin() { + this.authService.navigateToLogin(); + } + + protected showConfirmation( + message: LocalizationParam, + title: LocalizationParam, + ): Observable { + return this.confirmationService.error(message, title, { + hideCancelBtn: true, + yesText: 'AbpAccount::Close', + }); + } + + protected showPage() { + const key = `defaultError${this.status}`; + + const instance = { + title: { + key: DEFAULT_ERROR_LOCALIZATIONS[key]?.title, + defaultValue: DEFAULT_ERROR_MESSAGES[key]?.title, + }, + details: { + key: DEFAULT_ERROR_LOCALIZATIONS[key]?.details, + defaultValue: DEFAULT_ERROR_MESSAGES[key]?.details, + }, + status: this.status, + }; + const shouldRemoveDetail = [401, 404].indexOf(this.status) > -1; + if (shouldRemoveDetail) { + delete instance.details; + } + this.createErrorComponentService.execute(instance); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/tenant-resolve-error-handler.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/tenant-resolve-error-handler.service.ts new file mode 100644 index 0000000000..57dc7dab9b --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/tenant-resolve-error-handler.service.ts @@ -0,0 +1,22 @@ +import { CustomHttpErrorHandlerService } from '../models/common'; +import { inject, Injectable } from '@angular/core'; +import { AuthService } from '@abp/ng.core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from '../constants/error'; + +@Injectable({ providedIn: 'root' }) +export class TenantResolveErrorHandlerService implements CustomHttpErrorHandlerService { + priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.high; + private authService = inject(AuthService); + private isTenantResolveError(error: unknown) { + return error instanceof HttpErrorResponse && !!error.headers.get('Abp-Tenant-Resolve-Error'); + } + + canHandle(error: unknown): boolean { + return this.isTenantResolveError(error); + } + + execute() { + this.authService.logout().subscribe(); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/unknown-status-code-error-handler.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/unknown-status-code-error-handler.service.ts new file mode 100644 index 0000000000..88de56c14d --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/unknown-status-code-error-handler.service.ts @@ -0,0 +1,35 @@ +import { CustomHttpErrorHandlerService } from '../models'; +import { + CUSTOM_HTTP_ERROR_HANDLER_PRIORITY, + DEFAULT_ERROR_LOCALIZATIONS, + DEFAULT_ERROR_MESSAGES, +} from '../constants/error'; +import { inject, Injectable } from '@angular/core'; +import { CreateErrorComponentService } from './create-error-component.service'; + +@Injectable({ providedIn: 'root' }) +export class UnknownStatusCodeErrorHandlerService implements CustomHttpErrorHandlerService { + priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.normal; + private statusText = 'Unknown Error'; + private message = ''; + private createErrorComponentService = inject(CreateErrorComponentService); + + canHandle(error: { status: number; statusText: string; message: string } | undefined): boolean { + if (error && error.status === 0 && error.statusText === this.statusText) { + this.message = error.message; + return true; + } + return false; + } + + execute() { + this.createErrorComponentService.execute({ + title: { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }, + details: this.message, + isHomeShow: false, + }); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 0fea3f166c..3926069a5a 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -29,7 +29,7 @@ import { NgxDatatableListDirective } from './directives/ngx-datatable-list.direc import { DocumentDirHandlerService } from './handlers/document-dir.handler'; import { ErrorHandler } from './handlers/error.handler'; import { RootParams } from './models/common'; -import { NG_BOOTSTRAP_CONFIG_PROVIDERS } from './providers'; +import { errorHandlersProviders, NG_BOOTSTRAP_CONFIG_PROVIDERS } from './providers'; import { THEME_SHARED_ROUTE_PROVIDERS } from './providers/route.provider'; import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token'; import { HTTP_ERROR_CONFIG, httpErrorConfigFactory } from './tokens/http-error.token'; @@ -58,7 +58,7 @@ const declarationsWithExports = [ ModalCloseDirective, AbpVisibleDirective, FormInputComponent, - FormCheckboxComponent + FormCheckboxComponent, ]; @NgModule({ @@ -69,7 +69,6 @@ const declarationsWithExports = [ NgbPaginationModule, EllipsisModule, CardModule, - ], declarations: [...declarationsWithExports, HttpErrorWrapperComponent], exports: [ @@ -77,11 +76,11 @@ const declarationsWithExports = [ EllipsisModule, NgxValidateCoreModule, CardModule, - ...declarationsWithExports + ...declarationsWithExports, ], providers: [DatePipe], }) -export class BaseThemeSharedModule { } +export class BaseThemeSharedModule {} @NgModule({ imports: [BaseThemeSharedModule], @@ -144,6 +143,7 @@ export class ThemeSharedModule { ...(confirmationIcons || {}), }, }, + errorHandlersProviders, ], }; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts index 39cb43a350..a2784f4332 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts @@ -1,5 +1,5 @@ import { InjectionToken } from '@angular/core'; -import { HttpErrorConfig, HttpErrorHandler } from '../models/common'; +import { CustomHttpErrorHandlerService, HttpErrorConfig, HttpErrorHandler } from '../models/common'; export function httpErrorConfigFactory(config = {} as HttpErrorConfig) { if (config.errorScreen && config.errorScreen.component && !config.errorScreen.forWhichErrors) { @@ -16,3 +16,7 @@ export function httpErrorConfigFactory(config = {} as HttpErrorConfig) { export const HTTP_ERROR_CONFIG = new InjectionToken('HTTP_ERROR_CONFIG'); export const HTTP_ERROR_HANDLER = new InjectionToken('HTTP_ERROR_HANDLER'); + +export const CUSTOM_ERROR_HANDLERS = new InjectionToken( + 'CUSTOM_ERROR_HANDLERS', +); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/error.utils.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/error.utils.ts new file mode 100644 index 0000000000..c03f16cf09 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/error.utils.ts @@ -0,0 +1,26 @@ +import { LocalizationParam } from '@abp/ng.core'; +import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/error'; + +export function getErrorFromRequestBody(body: { details?: string; message?: string } | undefined) { + let message: LocalizationParam; + let title: LocalizationParam; + + if (body.details) { + message = body.details; + title = body.message; + } else if (body.message) { + title = { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }; + message = body.message; + } else { + message = { + key: DEFAULT_ERROR_LOCALIZATIONS.defaultError.title, + defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title, + }; + title = ''; + } + + return { message, title }; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts index 8f159e0a49..9bf1e72a8f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/index.ts @@ -1,2 +1,3 @@ export * from './date-parser-formatter'; export * from './validation-utils'; +export * from './error.utils';