@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
JavaScript
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: <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: <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