UNPKG

@rxap/ngx-error

Version:

This package provides a comprehensive error handling solution for Angular applications, including interceptors, services, and UI components to display different types of errors in a user-friendly dialog. It supports handling of generic errors, HTTP errors

583 lines (562 loc) 47.7 kB
import * as i1 from '@angular/common'; import { CommonModule, NgIf, NgClass, NgForOf, DatePipe, KeyValuePipe, JsonPipe } from '@angular/common'; import * as i0 from '@angular/core'; import { InjectionToken, inject, Component, ChangeDetectionStrategy, signal, computed, EventEmitter, INJECTOR, Injector, ViewChild, Output, ApplicationRef, createComponent, Injectable, isDevMode, runInInjectionContext, ErrorHandler } from '@angular/core'; import { CopyToClipboardComponent, JsonViewerComponent } from '@rxap/components'; import * as i1$2 from '@rxap/data-grid'; import { DataGridCellDefDirective, DataGridComponent, DataGridRowDefDirective, DataGridModule } from '@rxap/data-grid'; import { RXAP_ENVIRONMENT, IsNotReleaseVersion } from '@rxap/environment'; import { take } from 'rxjs'; import { tap } from 'rxjs/operators'; import * as i1$1 from '@angular/cdk/portal'; import { ComponentPortal, PortalModule } from '@angular/cdk/portal'; import { HttpErrorResponse } from '@angular/common/http'; import { ConfigService } from '@rxap/config'; import { OpenApiHttpResponseError } from '@rxap/open-api'; import * as Sentry from '@sentry/angular'; const RXAP_ERROR_DIALOG_DATA = new InjectionToken('rxap/error/dialog-data'); const RXAP_ERROR_DIALOG_ERROR = new InjectionToken('rxap/error/dialog-error'); const RXAP_ERROR_DIALOG_COMPONENT = new InjectionToken('rxap/error/dialog-component'); const RXAP_ERROR_INTERCEPTOR_OPTIONS = new InjectionToken('rxap/error/interceptor-options'); class OpenApiHttpResponseErrorComponent { constructor() { this.isProduction = inject(RXAP_ENVIRONMENT, { optional: true })?.production ?? false; this.error = inject(RXAP_ERROR_DIALOG_ERROR); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: OpenApiHttpResponseErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: OpenApiHttpResponseErrorComponent, isStandalone: true, selector: "rxap-open-api-http-response-error", ngImport: i0, template: "<rxap-data-grid [data]=\"error\">\n <ng-container rxapDataGridRowDef=\"name\"></ng-container>\n <ng-container rxapDataGridRowDef=\"timestamp\">\n <td *rxapDataGridCellDef=\"let value\">{{ value | date:'HH:mm:ss' }}</td>\n </ng-container>\n <ng-container rxapDataGridRowDef=\"method\"></ng-container>\n <ng-container rxapDataGridRowDef=\"url\"></ng-container>\n <ng-container rxapDataGridRowDef=\"status\"></ng-container>\n <ng-container rxapDataGridRowDef=\"statusText\"></ng-container>\n <ng-container rxapDataGridRowDef=\"message\"></ng-container>\n <ng-container rxapDataGridRowDef=\"headers\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\">\n <ng-container *ngFor=\"let entry of value | keyvalue\" [rxapDataGridRowDef]=\"$any(entry.key)\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value.length > 1\">\n <ul>\n <li *ngFor=\"let item of value\">{{ item }}</li>\n </ul>\n </ng-container>\n <ng-container *ngIf=\"value.length === 1\">{{ value[0] }}</ng-container>\n <ng-container *ngIf=\"value.length === 0\">NONE</ng-container>\n </td>\n </ng-container>\n </rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"error\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Response Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"body\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Request Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n</rxap-data-grid>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.JsonPipe, name: "json" }, { kind: "pipe", type: i1.DatePipe, name: "date" }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }, { kind: "component", type: CopyToClipboardComponent, selector: "rxap-copy-to-clipboard", inputs: ["active", "disabled", "value"] }, { kind: "directive", type: DataGridCellDefDirective, selector: "[rxapDataGridCellDef]" }, { kind: "component", type: DataGridComponent, selector: "rxap-data-grid", inputs: ["header", "dataSource", "viewer", "data", "displayProperties", "hideEmptyProperties", "mode"], outputs: ["editModeChange"] }, { kind: "directive", type: DataGridRowDefDirective, selector: "[rxapDataGridRowDef]", inputs: ["data", "rxapDataGridRowDef", "flip"] }, { kind: "component", type: JsonViewerComponent, selector: "rxap-json-viewer", inputs: ["json", "expanded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: OpenApiHttpResponseErrorComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-open-api-http-response-error', imports: [ CommonModule, CopyToClipboardComponent, DataGridCellDefDirective, DataGridComponent, DataGridRowDefDirective, JsonViewerComponent, ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<rxap-data-grid [data]=\"error\">\n <ng-container rxapDataGridRowDef=\"name\"></ng-container>\n <ng-container rxapDataGridRowDef=\"timestamp\">\n <td *rxapDataGridCellDef=\"let value\">{{ value | date:'HH:mm:ss' }}</td>\n </ng-container>\n <ng-container rxapDataGridRowDef=\"method\"></ng-container>\n <ng-container rxapDataGridRowDef=\"url\"></ng-container>\n <ng-container rxapDataGridRowDef=\"status\"></ng-container>\n <ng-container rxapDataGridRowDef=\"statusText\"></ng-container>\n <ng-container rxapDataGridRowDef=\"message\"></ng-container>\n <ng-container rxapDataGridRowDef=\"headers\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\">\n <ng-container *ngFor=\"let entry of value | keyvalue\" [rxapDataGridRowDef]=\"$any(entry.key)\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value.length > 1\">\n <ul>\n <li *ngFor=\"let item of value\">{{ item }}</li>\n </ul>\n </ng-container>\n <ng-container *ngIf=\"value.length === 1\">{{ value[0] }}</ng-container>\n <ng-container *ngIf=\"value.length === 0\">NONE</ng-container>\n </td>\n </ng-container>\n </rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"error\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Response Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"body\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Request Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n</rxap-data-grid>\n" }] }] }); class ErrorDialogComponent { constructor() { this.data = inject(RXAP_ERROR_DIALOG_DATA); this.activeIndex = signal(0); this.displayedButtons = computed(() => { const start = Math.max(0, this.activeIndex() - 2); const end = Math.min(this.data().length, start + 5); return Array.from({ length: end - start }, (_, i) => start + i); }); this.closeDialog = new EventEmitter(); this.component = inject(RXAP_ERROR_DIALOG_COMPONENT); this.injector = inject(INJECTOR); this.componentPortal = computed(() => { const index = this.activeIndex(); const data = this.data(); const injector = Injector.create({ parent: this.injector, providers: [ { provide: RXAP_ERROR_DIALOG_ERROR, useValue: data[index], }, ], }); return new ComponentPortal(this.component, null, injector); }); } ngAfterViewInit() { this.dialog.nativeElement.showModal(); } close() { this.dialog.nativeElement.close(); this.closeDialog.emit(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: ErrorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: ErrorDialogComponent, isStandalone: true, selector: "rxap-error-dialog", outputs: { closeDialog: "closeDialog" }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, static: true }], ngImport: i0, template: "<dialog #dialog\n class=\"rounded overflow-auto drop-shadow-2xl bg-white text-black dark:bg-black dark:text-white flex flex-col gap-3 p-6\"\n style=\"max-width: 80vw; max-height: 80vh\">\n <div *ngIf=\"data().length > 1\" class=\"controls flex justify-center space-x-2 mt-4\">\n <button\n (click)=\"activeIndex.set(activeIndex() - 1)\"\n *ngIf=\"activeIndex() > 0\" class=\"px-4 py-2 bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-900 hover:bg-gray-300 transition-colors duration-150 rounded\">Previous\n </button>\n <button (click)=\"activeIndex.set(item)\"\n *ngFor=\"let item of displayedButtons()\"\n [ngClass]=\"{\n 'bg-primary-500': item === activeIndex(),\n 'bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-900 hover:bg-gray-300 transition-colors duration-150': item !== activeIndex()\n }\"\n class=\"px-4 py-2 rounded\">\n {{ item + 1 }}\n </button>\n <button\n (click)=\"activeIndex.set(activeIndex() + 1)\"\n *ngIf=\"activeIndex() < data().length - 1\" class=\"px-4 py-2 bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-900 hover:bg-gray-300 transition-colors duration-150 rounded\">Next\n </button>\n </div>\n <div class=\"flex flex-col overflow-auto\">\n <ng-template [cdkPortalOutlet]=\"componentPortal()\"></ng-template>\n </div>\n <button (click)=\"close()\" class=\"px-4 py-2 rounded bg-primary-500\" i18n>close</button>\n</dialog>\n", styles: ["dialog::backdrop{background:#000000b3}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i1$1.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.Default }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: ErrorDialogComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-error-dialog', changeDetection: ChangeDetectionStrategy.Default, imports: [ NgIf, NgClass, NgForOf, PortalModule, ], template: "<dialog #dialog\n class=\"rounded overflow-auto drop-shadow-2xl bg-white text-black dark:bg-black dark:text-white flex flex-col gap-3 p-6\"\n style=\"max-width: 80vw; max-height: 80vh\">\n <div *ngIf=\"data().length > 1\" class=\"controls flex justify-center space-x-2 mt-4\">\n <button\n (click)=\"activeIndex.set(activeIndex() - 1)\"\n *ngIf=\"activeIndex() > 0\" class=\"px-4 py-2 bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-900 hover:bg-gray-300 transition-colors duration-150 rounded\">Previous\n </button>\n <button (click)=\"activeIndex.set(item)\"\n *ngFor=\"let item of displayedButtons()\"\n [ngClass]=\"{\n 'bg-primary-500': item === activeIndex(),\n 'bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-900 hover:bg-gray-300 transition-colors duration-150': item !== activeIndex()\n }\"\n class=\"px-4 py-2 rounded\">\n {{ item + 1 }}\n </button>\n <button\n (click)=\"activeIndex.set(activeIndex() + 1)\"\n *ngIf=\"activeIndex() < data().length - 1\" class=\"px-4 py-2 bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-900 hover:bg-gray-300 transition-colors duration-150 rounded\">Next\n </button>\n </div>\n <div class=\"flex flex-col overflow-auto\">\n <ng-template [cdkPortalOutlet]=\"componentPortal()\"></ng-template>\n </div>\n <button (click)=\"close()\" class=\"px-4 py-2 rounded bg-primary-500\" i18n>close</button>\n</dialog>\n", styles: ["dialog::backdrop{background:#000000b3}\n"] }] }], propDecorators: { dialog: [{ type: ViewChild, args: ['dialog', { static: true }] }], closeDialog: [{ type: Output }] } }); class ErrorCaptureService { constructor() { this.applicationRef = inject(ApplicationRef); this.errorList = []; } push(error) { if (this.errorList.length) { const last = this.errorList[this.errorList.length - 1]; const value = last(); if (value.some(e => this.compare(e, error))) { last.update(value => { value.push(error); return value.slice(); }); return; } } const newList = signal([error]); this.errorList.push(newList); this.openDialog(newList); } openDialog(errorList) { const body = document.getElementsByTagName('body')[0]; const div = document.createElement('div'); body.appendChild(div); const componentRef = createComponent(ErrorDialogComponent, { hostElement: div, environmentInjector: this.applicationRef.injector, elementInjector: Injector.create({ providers: [ { provide: RXAP_ERROR_DIALOG_DATA, useValue: errorList, }, { provide: RXAP_ERROR_DIALOG_COMPONENT, useValue: this.component, }, ], }), }); this.applicationRef.attachView(componentRef.hostView); componentRef.instance.closeDialog.pipe(take(1), tap(() => { componentRef.destroy(); body.removeChild(div); div.remove(); this.errorList.splice(this.errorList.indexOf(errorList), 1); })).subscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: ErrorCaptureService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: ErrorCaptureService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: ErrorCaptureService, decorators: [{ type: Injectable }] }); class AnyHttpErrorComponent { constructor() { this.isProduction = inject(RXAP_ENVIRONMENT, { optional: true })?.production ?? false; this.error = inject(RXAP_ERROR_DIALOG_ERROR); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AnyHttpErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: AnyHttpErrorComponent, isStandalone: true, selector: "rxap-any-http-error", ngImport: i0, template: "<rxap-data-grid [data]=\"error\">\n <ng-container rxapDataGridRowDef=\"name\"></ng-container>\n <ng-container rxapDataGridRowDef=\"timestamp\">\n <td *rxapDataGridCellDef=\"let value\">{{ value | date:'HH:mm:ss' }}</td>\n </ng-container>\n <ng-container rxapDataGridRowDef=\"method\"></ng-container>\n <ng-container rxapDataGridRowDef=\"url\"></ng-container>\n <ng-container rxapDataGridRowDef=\"status\"></ng-container>\n <ng-container rxapDataGridRowDef=\"statusText\"></ng-container>\n <ng-container rxapDataGridRowDef=\"message\"></ng-container>\n <ng-container rxapDataGridRowDef=\"headers\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\">\n <ng-container *ngFor=\"let entry of value | keyvalue\" [rxapDataGridRowDef]=\"$any(entry.key)\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value.length > 1\">\n <ul>\n <li *ngFor=\"let item of value\">{{ item }}</li>\n </ul>\n </ng-container>\n <ng-container *ngIf=\"value.length === 1\">{{ value[0] }}</ng-container>\n <ng-container *ngIf=\"value.length === 0\">NONE</ng-container>\n </td>\n </ng-container>\n </rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"error\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Response Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"body\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Request Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n</rxap-data-grid>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: DataGridModule }, { kind: "component", type: i1$2.DataGridComponent, selector: "rxap-data-grid", inputs: ["header", "dataSource", "viewer", "data", "displayProperties", "hideEmptyProperties", "mode"], outputs: ["editModeChange"] }, { kind: "directive", type: i1$2.DataGridRowDefDirective, selector: "[rxapDataGridRowDef]", inputs: ["data", "rxapDataGridRowDef", "flip"] }, { kind: "directive", type: i1$2.DataGridCellDefDirective, selector: "[rxapDataGridCellDef]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: CopyToClipboardComponent, selector: "rxap-copy-to-clipboard", inputs: ["active", "disabled", "value"] }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "component", type: JsonViewerComponent, selector: "rxap-json-viewer", inputs: ["json", "expanded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AnyHttpErrorComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-any-http-error', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ DataGridModule, NgIf, DatePipe, KeyValuePipe, NgForOf, CopyToClipboardComponent, JsonPipe, JsonViewerComponent, ], template: "<rxap-data-grid [data]=\"error\">\n <ng-container rxapDataGridRowDef=\"name\"></ng-container>\n <ng-container rxapDataGridRowDef=\"timestamp\">\n <td *rxapDataGridCellDef=\"let value\">{{ value | date:'HH:mm:ss' }}</td>\n </ng-container>\n <ng-container rxapDataGridRowDef=\"method\"></ng-container>\n <ng-container rxapDataGridRowDef=\"url\"></ng-container>\n <ng-container rxapDataGridRowDef=\"status\"></ng-container>\n <ng-container rxapDataGridRowDef=\"statusText\"></ng-container>\n <ng-container rxapDataGridRowDef=\"message\"></ng-container>\n <ng-container rxapDataGridRowDef=\"headers\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\">\n <ng-container *ngFor=\"let entry of value | keyvalue\" [rxapDataGridRowDef]=\"$any(entry.key)\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value.length > 1\">\n <ul>\n <li *ngFor=\"let item of value\">{{ item }}</li>\n </ul>\n </ng-container>\n <ng-container *ngIf=\"value.length === 1\">{{ value[0] }}</ng-container>\n <ng-container *ngIf=\"value.length === 0\">NONE</ng-container>\n </td>\n </ng-container>\n </rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"error\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Response Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n <ng-container *ngIf=\"!isProduction\" rxapDataGridRowDef=\"body\">\n <td *rxapDataGridCellDef=\"let value\">\n <ng-container *ngIf=\"value\">\n <rxap-copy-to-clipboard [value]=\"value | json\">Copy Request Body</rxap-copy-to-clipboard>\n <div class=\"body\">\n <rxap-json-viewer [expanded]=\"false\" [json]=\"value\"></rxap-json-viewer>\n </div>\n </ng-container>\n <ng-container *ngIf=\"!value\">NONE</ng-container>\n </td>\n </ng-container>\n</rxap-data-grid>\n" }] }] }); class AnyHttpErrorService extends ErrorCaptureService { constructor() { super(...arguments); this.component = AnyHttpErrorComponent; } compare(a, b) { return a.url === b.url && a.method === b.method && a.status === b.status && a.message === b.message; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AnyHttpErrorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AnyHttpErrorService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AnyHttpErrorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class OpenApiHttpResponseErrorService extends AnyHttpErrorService { constructor() { super(...arguments); this.component = OpenApiHttpResponseErrorComponent; } compare(a, b) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: OpenApiHttpResponseErrorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: OpenApiHttpResponseErrorService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: OpenApiHttpResponseErrorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class MessageHttpErrorComponent { constructor() { this.isProduction = inject(RXAP_ENVIRONMENT, { optional: true })?.production ?? false; this.error = inject(RXAP_ERROR_DIALOG_ERROR); this.showMore = signal(false); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: MessageHttpErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: MessageHttpErrorComponent, isStandalone: true, selector: "rxap-http-error-message", ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <p class=\"text-xl\">Http Error Message:</p>\n <p>{{ error.error.message }}</p>\n <ng-container *ngIf=\"!isProduction\">\n <hr>\n <button (click)=\"showMore.set(true)\"\n *ngIf=\"!showMore()\"\n class=\"px-4 py-2 rounded bg-accent-500\">\n <ng-container i18n>Show more details</ng-container>\n </button>\n <rxap-any-http-error *ngIf=\"showMore()\"></rxap-any-http-error>\n </ng-container>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: AnyHttpErrorComponent, selector: "rxap-any-http-error" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: MessageHttpErrorComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-http-error-message', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ NgIf, AnyHttpErrorComponent, ], template: "<div class=\"flex flex-col gap-2\">\n <p class=\"text-xl\">Http Error Message:</p>\n <p>{{ error.error.message }}</p>\n <ng-container *ngIf=\"!isProduction\">\n <hr>\n <button (click)=\"showMore.set(true)\"\n *ngIf=\"!showMore()\"\n class=\"px-4 py-2 rounded bg-accent-500\">\n <ng-container i18n>Show more details</ng-container>\n </button>\n <rxap-any-http-error *ngIf=\"showMore()\"></rxap-any-http-error>\n </ng-container>\n</div>\n" }] }] }); class MessageHttpErrorService extends AnyHttpErrorService { constructor() { super(...arguments); this.component = MessageHttpErrorComponent; } compare(a, b) { return super.compare(a, b) && a.error.message === b.error.message; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: MessageHttpErrorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: MessageHttpErrorService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: MessageHttpErrorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class AngularErrorComponent { constructor() { this.error = inject(RXAP_ERROR_DIALOG_ERROR); } get hasTags() { return Object.keys(this.error.tags).length > 0; } get hasContexts() { return Object.keys(this.error.contexts).length > 0; } get hasExtra() { return Object.keys(this.error.extra).length > 0; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AngularErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: AngularErrorComponent, isStandalone: true, selector: "rxap-angular-error", ngImport: i0, template: "<rxap-data-grid [data]=\"error\">\n <ng-container *ngIf=\"error.name\" rxapDataGridRowDef=\"name\"></ng-container>\n <ng-container rxapDataGridRowDef=\"message\"></ng-container>\n <ng-container *ngIf=\"hasExtra || hasTags || hasContexts || error.stack\" rxapDataGridRowDef>\n <th *rxapDataGridHeaderCellDef>\n <span i18n>Details</span>\n </th>\n </ng-container>\n <ng-container *ngIf=\"error.stack\" rxapDataGridRowDef=\"stack\">\n <td *rxapDataGridCellDef=\"let value\">\n <pre>{{ value }}</pre>\n </td>\n </ng-container>\n <ng-container *ngIf=\"hasTags\" rxapDataGridRowDef=\"tags\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\" auto></rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"hasExtra\" rxapDataGridRowDef=\"extra\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\" auto></rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"hasContexts\" rxapDataGridRowDef=\"contexts\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\" auto></rxap-data-grid>\n </td>\n </ng-container>\n</rxap-data-grid>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: DataGridModule }, { kind: "component", type: i1$2.DataGridComponent, selector: "rxap-data-grid", inputs: ["header", "dataSource", "viewer", "data", "displayProperties", "hideEmptyProperties", "mode"], outputs: ["editModeChange"] }, { kind: "directive", type: i1$2.DataGridRowDefDirective, selector: "[rxapDataGridRowDef]", inputs: ["data", "rxapDataGridRowDef", "flip"] }, { kind: "directive", type: i1$2.DataGridHeaderCellDefDirective, selector: "[rxapDataGridHeaderCellDef]" }, { kind: "directive", type: i1$2.DataGridCellDefDirective, selector: "[rxapDataGridCellDef]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AngularErrorComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-angular-error', imports: [DataGridModule, KeyValuePipe, NgForOf, NgIf], template: "<rxap-data-grid [data]=\"error\">\n <ng-container *ngIf=\"error.name\" rxapDataGridRowDef=\"name\"></ng-container>\n <ng-container rxapDataGridRowDef=\"message\"></ng-container>\n <ng-container *ngIf=\"hasExtra || hasTags || hasContexts || error.stack\" rxapDataGridRowDef>\n <th *rxapDataGridHeaderCellDef>\n <span i18n>Details</span>\n </th>\n </ng-container>\n <ng-container *ngIf=\"error.stack\" rxapDataGridRowDef=\"stack\">\n <td *rxapDataGridCellDef=\"let value\">\n <pre>{{ value }}</pre>\n </td>\n </ng-container>\n <ng-container *ngIf=\"hasTags\" rxapDataGridRowDef=\"tags\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\" auto></rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"hasExtra\" rxapDataGridRowDef=\"extra\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\" auto></rxap-data-grid>\n </td>\n </ng-container>\n <ng-container *ngIf=\"hasContexts\" rxapDataGridRowDef=\"contexts\">\n <td *rxapDataGridCellDef=\"let value\">\n <rxap-data-grid [data]=\"value\" auto></rxap-data-grid>\n </td>\n </ng-container>\n</rxap-data-grid>\n" }] }] }); class AngularErrorService extends ErrorCaptureService { constructor() { super(...arguments); this.component = AngularErrorComponent; } compare(a, b) { return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AngularErrorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AngularErrorService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AngularErrorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class CodeHttpErrorComponent { constructor() { this.isProduction = inject(RXAP_ENVIRONMENT, { optional: true })?.production ?? false; this.error = inject(RXAP_ERROR_DIALOG_ERROR); this.showMore = signal(false); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: CodeHttpErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: CodeHttpErrorComponent, isStandalone: true, selector: "rxap-http-error-message", ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <p class=\"text-xl\">Http Error Code:&nbsp;<span class=\"font-bold text-red-800\">{{ error.errorCode }}</span></p>\n <p>{{ error.error.message ?? error.message }}</p>\n <ng-container *ngIf=\"!isProduction\">\n <hr>\n <button (click)=\"showMore.set(true)\"\n *ngIf=\"!showMore()\"\n class=\"px-4 py-2 rounded bg-accent-500\">\n <ng-container i18n>Show more details</ng-container>\n </button>\n <rxap-any-http-error *ngIf=\"showMore()\"></rxap-any-http-error>\n </ng-container>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: AnyHttpErrorComponent, selector: "rxap-any-http-error" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: CodeHttpErrorComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-http-error-message', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ NgIf, AnyHttpErrorComponent, ], template: "<div class=\"flex flex-col gap-2\">\n <p class=\"text-xl\">Http Error Code:&nbsp;<span class=\"font-bold text-red-800\">{{ error.errorCode }}</span></p>\n <p>{{ error.error.message ?? error.message }}</p>\n <ng-container *ngIf=\"!isProduction\">\n <hr>\n <button (click)=\"showMore.set(true)\"\n *ngIf=\"!showMore()\"\n class=\"px-4 py-2 rounded bg-accent-500\">\n <ng-container i18n>Show more details</ng-container>\n </button>\n <rxap-any-http-error *ngIf=\"showMore()\"></rxap-any-http-error>\n </ng-container>\n</div>\n" }] }] }); class CodeHttpErrorService extends AnyHttpErrorService { constructor() { super(...arguments); this.component = CodeHttpErrorComponent; } compare(a, b) { return super.compare(a, b) && a.errorCode === b.errorCode; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: CodeHttpErrorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: CodeHttpErrorService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: CodeHttpErrorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); function SimplifyHttpErrorResponse(event) { let error = event.error; if (typeof error !== 'object') { if (error instanceof Blob) { console.warn('error response object is a blob'); error = '[BLOB]'; } else { try { error = JSON.parse(error); } catch (e) { console.warn('error response object is not a json'); } } } return { errorMessage: (typeof error === 'object' ? error?.message : undefined) ?? event.message, error: error, message: event.message, name: event.name, status: event.status, statusText: event.statusText, url: event.url, ok: event.ok, type: event.type, headers: event.headers.keys() .reduce((map, key) => ({ ...map, [key]: event.headers.getAll(key), }), {}), }; } function ExtractContextFromError(error) { const context = {}; if (error instanceof HttpErrorResponse) { context['response'] = SimplifyHttpErrorResponse(error); } return context; } function ExtractExtraFromError(error) { return {}; } function ExtractTagsFromError(error) { return {}; } function ExtractError(error) { if (error instanceof HttpErrorResponse) { return error; } if (error instanceof Error) { return error; } if (typeof error === 'string') { return error; } return 'Handled unknown error'; } function PrintError(errorCandidate) { if (errorCandidate instanceof HttpErrorResponse) { console.groupCollapsed(errorCandidate.message); const { headers, error, ...map } = SimplifyHttpErrorResponse(errorCandidate); if (typeof error === 'object' && error?.message) { console.log(error?.message); } console.log(error); console.table(map); console.groupCollapsed('Headers'); console.table(headers); console.groupEnd(); console.groupEnd(); } else if (errorCandidate instanceof Error) { console.error(errorCandidate); } else if (typeof errorCandidate === 'string') { console.error(errorCandidate); } } const RXAP_ERROR_HANDLER_OPTIONS = new InjectionToken('rxap-error-handler-options', { providedIn: 'root', factory: () => ({ logErrors: true }), }); class RxapErrorHandler { constructor() { this.options = (() => { const options = inject(RXAP_ERROR_HANDLER_OPTIONS); const environment = inject(RXAP_ENVIRONMENT); const config = inject(ConfigService); options.logErrors ??= true; options.sentry ??= {}; options.sentry.showDialog ??= config.get('sentry.showDialog', false); // enforce sentry showDialog to false in dev mode options.sentry.showDialog = !isDevMode() && options.sentry.showDialog; options.showDialog ??= IsNotReleaseVersion(environment); options.sentry.dialogOptions ??= config.get('sentry.dialogOptions', {}); options.sentry.dialogOptions.user ??= {}; return options; })(); this.injector = inject(INJECTOR); } /** * Method called for every value captured through the ErrorHandler */ handleError(errorCandidate) { let error = errorCandidate; // Try to unwrap zone.js error. // https://github.com/angular/angular/blob/master/packages/core/src/util/errors.ts if (error && error.ngOriginalError) { error = error.ngOriginalError; } const extractedError = ExtractError(error); const contexts = ExtractContextFromError(error); const extra = ExtractExtraFromError(error); const tags = ExtractTagsFromError(error); let eventId; // Capture handled exception and send it to Sentry. if (typeof extractedError === 'string') { eventId = Sentry.captureMessage(extractedError, { level: 'error', contexts, extra, tags, }); } else { eventId = Sentry.captureException(extractedError, { level: 'error', contexts, extra, tags, }); } if (this.options.logErrors) { PrintError(error); } if (this.options.sentry?.showDialog) { if (!(error instanceof HttpErrorResponse)) { Sentry.showReportDialog({ ...(this.options.sentry.dialogOptions ?? {}), eventId, }); } } else if (this.options.showDialog) { const nonMessage = 'unsupported error type for angular error dialog'; const data = { message: nonMessage, contexts, extra, tags, }; if (typeof extractedError === 'string') { data.message = extractedError; } else if (!(extractedError instanceof HttpErrorResponse)) { data.message = extractedError.message; data.stack = extractedError.stack; data.name = extractedError.name; } if (extractedError instanceof OpenApiHttpResponseError) { this.showOpenApiHttpResponseErrorDialog({ ...data, ...SimplifyHttpErrorResponse(extractedError.httpErrorResponse), timestamp: Date.now(), metadata: extractedError.metadata, method: !extractedError.metadata.operation || typeof extractedError.metadata.operation === 'string' ? 'unknown' : extractedError.metadata.operation.method, operationId: extractedError.operationId, serverId: extractedError.serverId }); } else { if (data.message !== nonMessage) { this.showAngularErrorDialog(data); } } } } showOpenApiHttpResponseErrorDialog(data) { runInInjectionContext(this.injector, () => { inject(OpenApiHttpResponseErrorService).push(data); }); } showAngularErrorDialog(data) { runInInjectionContext(this.injector, () => { inject(AngularErrorService).push(data); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: RxapErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: RxapErrorHandler }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: RxapErrorHandler, decorators: [{ type: Injectable }] }); function ProvideErrorHandler() { return { provide: ErrorHandler, useClass: RxapErrorHandler, }; } function DefaultErrorCodeExtractor(error) { return error.error?.code ?? null; } function DefaultErrorFilter(error) { return error.status >= 400 && (!error.url || !/\/api\/status/.test(error.url)); } function HttpErrorInterceptor(req, next) { const anyHttpErrorService = inject(AnyHttpErrorService); const messageHttpErrorService = inject(MessageHttpErrorService); const codeHttpErrorService = inject(CodeHttpErrorService); const options = inject(RXAP_ERROR_INTERCEPTOR_OPTIONS, { optional: true }) ?? {}; options.extractErrorCode ??= DefaultErrorCodeExtractor; options.filter ??= []; if (!options.filter.length) { options.filter.push(DefaultErrorFilter); } return next(req).pipe(tap({ error: async (event) => { if (event instanceof HttpErrorResponse) { if (!options.filter?.some((filter) => filter(event))) { return; } let error = event.error; if (event.error instanceof Blob) { error = await event.error.text(); try { error = JSON.parse(error); } catch (e) { console.warn('error response object is not a json'); } } const data = { ...SimplifyHttpErrorResponse(event), method: req.method, body: req.body, timestamp: Date.now(), }; if (error && typeof error === 'object') { const errorCode = options.extractErrorCode(error); if (errorCode) { Sentry.captureMessage(`Error Code ${errorCode}`, { level: 'warning', contexts: ExtractContextFromError(error), extra: ExtractExtraFromError(error), tags: ExtractTagsFromError(error), }); codeHttpErrorService.push({ ...data, errorCode, }); } else if (event.error.message) { messageHttpErrorService.push(data); } else { anyHttpErrorService.push(data); } } else { anyHttpErrorService.push(data); } } }, })); } // region open-api-http-response-error // endregion /** * Generated bundle index. Do not edit. */ export { AngularErrorComponent, AngularErrorService, AnyHttpErrorComponent, AnyHttpErrorService, CodeHttpErrorComponent, CodeHttpErrorService, DefaultErrorCodeExtractor, DefaultErrorFilter, ErrorCaptureService, ErrorDialogComponent, ExtractContextFromError, ExtractError, ExtractExtraFromError, ExtractTagsFromError, HttpErrorInterceptor, MessageHttpErrorComponent, MessageHttpErrorService, OpenApiHttpResponseErrorComponent, OpenApiHttpResponseErrorService, PrintError, ProvideErrorHandler, RXAP_ERROR_DIALOG_COMPONENT, RXAP_ERROR_DIALOG_DATA, RXAP_ERROR_DIALOG_ERROR, RXAP_ERROR_HANDLER_OPTIONS, RXAP_ERROR_INTERCEPTOR_OPTIONS, RxapErrorHandler, SimplifyHttpErrorResponse }; //# sourceMappingURL=rxap-ngx-error.mjs.map