@allstak/angular 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/esm2022/allstak-angular.mjs +5 -0
- package/esm2022/public-api.mjs +40 -0
- package/esm2022/src/error-handler.mjs +112 -0
- package/esm2022/src/http-interceptor.mjs +158 -0
- package/esm2022/src/init.mjs +32 -0
- package/esm2022/src/module.mjs +102 -0
- package/esm2022/src/providers.mjs +68 -0
- package/esm2022/src/trace-decorators.mjs +96 -0
- package/esm2022/src/trace-directive.mjs +65 -0
- package/esm2022/src/trace-service.mjs +101 -0
- package/esm2022/src/version.mjs +4 -0
- package/fesm2022/allstak-angular.mjs +740 -0
- package/fesm2022/allstak-angular.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/package.json +56 -0
- package/public-api.d.ts +29 -0
- package/src/error-handler.d.ts +66 -0
- package/src/http-interceptor.d.ts +30 -0
- package/src/init.d.ts +32 -0
- package/src/module.d.ts +61 -0
- package/src/providers.d.ts +41 -0
- package/src/trace-decorators.d.ts +36 -0
- package/src/trace-directive.d.ts +28 -0
- package/src/trace-service.d.ts +32 -0
- package/src/version.d.ts +2 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@allstak/angular` are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-05-29
|
|
9
|
+
|
|
10
|
+
Initial release of the official AllStak SDK for Angular.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `init(config)` — thin bootstrap over `@allstak/js` that stamps the wrapper
|
|
15
|
+
identity (`sdkName: 'allstak-angular'`, `sdkVersion`) so backend ingest can
|
|
16
|
+
distinguish Angular traffic. The SDK version is generated at build time from
|
|
17
|
+
`package.json`, never hand-written.
|
|
18
|
+
- `createErrorHandler(options?)` / `AllStakErrorHandler` — an Angular
|
|
19
|
+
`ErrorHandler` that forwards uncaught errors to `AllStak.captureException`.
|
|
20
|
+
Unwraps the common Angular/zone.js envelopes (`ngOriginalError`, promise
|
|
21
|
+
`rejection`, `HttpErrorResponse.error`, `ErrorEvent.error`), coerces
|
|
22
|
+
non-`Error` throws, and supports a custom `extractor` plus a `logErrors`
|
|
23
|
+
toggle.
|
|
24
|
+
- `AllStakHttpInterceptor` (class) and `allStakHttpInterceptor` (functional) —
|
|
25
|
+
an `HttpInterceptor` that records every outbound request as an `outbound`
|
|
26
|
+
HTTP item and opens a `http.client` span around it, finishing `error` on
|
|
27
|
+
`4xx`/`5xx` responses or transport failures. Never mutates the request.
|
|
28
|
+
- `TraceService` — injectable, Router-aware navigation instrumentation that
|
|
29
|
+
opens a `navigation` span per route change (`ok` on `NavigationEnd`, `error`
|
|
30
|
+
on `NavigationCancel` / `NavigationError`) and records a navigation
|
|
31
|
+
breadcrumb. `@angular/router` is an optional peer, so the service degrades to
|
|
32
|
+
a no-op when no `Router` is present.
|
|
33
|
+
- `TraceDirective` (`[trace]`) and `AllStakTraceModule` — component
|
|
34
|
+
render-timing directive that opens a `ui.angular.init` span between
|
|
35
|
+
`ngOnInit` and `ngAfterViewInit`.
|
|
36
|
+
- `TraceClassDecorator` / `TraceClass` and `TraceMethodDecorator` /
|
|
37
|
+
`TraceMethod` — class- and method-level lifecycle span decorators with a
|
|
38
|
+
required `name` so spans survive minification.
|
|
39
|
+
- Provider functions for standalone apps: `provideAllStak(config)`,
|
|
40
|
+
`provideAllStakErrorHandler(options?)`, and
|
|
41
|
+
`provideAllStakRouterInstrumentation()` (which force-instantiates
|
|
42
|
+
`TraceService` via `APP_INITIALIZER`).
|
|
43
|
+
- `AllStakModule.forRoot(config)` for module-based apps — initialises the SDK,
|
|
44
|
+
overrides the global `ErrorHandler`, force-instantiates `TraceService`, and
|
|
45
|
+
exposes the `[trace]` directive.
|
|
46
|
+
- Full re-export of the `@allstak/js` public surface so consumers can pull the
|
|
47
|
+
entire observability API from a single namespaced import.
|
|
48
|
+
- Built with ng-packagr (Angular Package Format): FESM2022 bundles, `esm2022`
|
|
49
|
+
output, and partial-Ivy type declarations across the `@angular` 14–18 peer
|
|
50
|
+
range.
|
|
51
|
+
|
|
52
|
+
[0.1.0]: https://github.com/AllStak/allstak-angular/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 AllStak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# @allstak/angular
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@allstak/angular)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Official AllStak SDK for **Angular**. Captures uncaught exceptions, structured logs, navigation spans, outbound HTTP requests, and component render timing — with first-class support for both standalone and NgModule-based apps.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @allstak/angular
|
|
12
|
+
# or
|
|
13
|
+
pnpm add @allstak/angular
|
|
14
|
+
# or
|
|
15
|
+
yarn add @allstak/angular
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`@angular/core`, `@angular/common`, and `rxjs` are peer dependencies; `@angular/router` is an optional peer (only needed for navigation instrumentation).
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
Initialise the SDK once in `main.ts`, **before** you bootstrap the app, so the global error handler and router instrumentation see a live client:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { init } from '@allstak/angular';
|
|
26
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
27
|
+
import { AppComponent } from './app/app.component';
|
|
28
|
+
import { appConfig } from './app/app.config';
|
|
29
|
+
|
|
30
|
+
init({
|
|
31
|
+
apiKey: 'your-ingest-key',
|
|
32
|
+
environment: 'production',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
bootstrapApplication(AppComponent, appConfig);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Standalone apps (provider functions)
|
|
39
|
+
|
|
40
|
+
Wire the DI pieces in `app.config.ts`:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { ApplicationConfig } from '@angular/core';
|
|
44
|
+
import { provideRouter } from '@angular/router';
|
|
45
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
46
|
+
import {
|
|
47
|
+
provideAllStak,
|
|
48
|
+
provideAllStakErrorHandler,
|
|
49
|
+
provideAllStakRouterInstrumentation,
|
|
50
|
+
allStakHttpInterceptor,
|
|
51
|
+
} from '@allstak/angular';
|
|
52
|
+
import { routes } from './app.routes';
|
|
53
|
+
|
|
54
|
+
export const appConfig: ApplicationConfig = {
|
|
55
|
+
providers: [
|
|
56
|
+
provideRouter(routes),
|
|
57
|
+
|
|
58
|
+
// Bootstrap the SDK from DI (alternative to calling init() in main.ts).
|
|
59
|
+
provideAllStak({ apiKey: 'your-ingest-key', environment: 'production' }),
|
|
60
|
+
|
|
61
|
+
// Override Angular's global ErrorHandler — every uncaught error becomes an Issue.
|
|
62
|
+
provideAllStakErrorHandler(),
|
|
63
|
+
|
|
64
|
+
// Open a navigation span per route change.
|
|
65
|
+
provideAllStakRouterInstrumentation(),
|
|
66
|
+
|
|
67
|
+
// Record every outbound HTTP request + open a http.client span.
|
|
68
|
+
provideHttpClient(withInterceptors([allStakHttpInterceptor])),
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## NgModule apps
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { NgModule, ErrorHandler } from '@angular/core';
|
|
77
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
78
|
+
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
|
79
|
+
import { AllStakModule, AllStakHttpInterceptor } from '@allstak/angular';
|
|
80
|
+
import { AppComponent } from './app.component';
|
|
81
|
+
|
|
82
|
+
@NgModule({
|
|
83
|
+
declarations: [AppComponent],
|
|
84
|
+
imports: [
|
|
85
|
+
BrowserModule,
|
|
86
|
+
|
|
87
|
+
// forRoot() initialises the SDK, overrides ErrorHandler, force-instantiates
|
|
88
|
+
// the router TraceService, and exposes the [trace] directive.
|
|
89
|
+
AllStakModule.forRoot({ apiKey: 'your-ingest-key', environment: 'production' }),
|
|
90
|
+
],
|
|
91
|
+
providers: [
|
|
92
|
+
provideHttpClient(withInterceptorsFromDi()),
|
|
93
|
+
{ provide: HTTP_INTERCEPTORS, useClass: AllStakHttpInterceptor, multi: true },
|
|
94
|
+
],
|
|
95
|
+
bootstrap: [AppComponent],
|
|
96
|
+
})
|
|
97
|
+
export class AppModule {}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Error handling
|
|
101
|
+
|
|
102
|
+
`createErrorHandler()` returns an Angular `ErrorHandler` that forwards to `AllStak.captureException`. It unwraps the common Angular/zone.js error envelopes (`ngOriginalError`, promise `rejection`, `HttpErrorResponse.error`, `ErrorEvent.error`) before capture.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { ErrorHandler } from '@angular/core';
|
|
106
|
+
import { createErrorHandler } from '@allstak/angular';
|
|
107
|
+
|
|
108
|
+
providers: [
|
|
109
|
+
{
|
|
110
|
+
provide: ErrorHandler,
|
|
111
|
+
useValue: createErrorHandler({
|
|
112
|
+
logErrors: true, // also console.error the original (default)
|
|
113
|
+
extractor: (error, defaultExtractor) =>
|
|
114
|
+
(error as { cause?: unknown }).cause ?? defaultExtractor(error),
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Router instrumentation
|
|
121
|
+
|
|
122
|
+
`TraceService` subscribes to Angular `Router` events and opens a `navigation` span per route change (finishing `ok` on `NavigationEnd`, `error` on `NavigationCancel` / `NavigationError`), plus a navigation breadcrumb. It must be force-instantiated — `provideAllStakRouterInstrumentation()` (standalone) or `AllStakModule.forRoot()` (NgModule) does that for you.
|
|
123
|
+
|
|
124
|
+
## Component render timing
|
|
125
|
+
|
|
126
|
+
The `[trace]` directive opens a `ui.angular.init` span between a component's `ngOnInit` and `ngAfterViewInit`. Import `TraceDirective` directly (standalone) or `AllStakTraceModule` (NgModule):
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// standalone component
|
|
130
|
+
import { Component } from '@angular/core';
|
|
131
|
+
import { TraceDirective } from '@allstak/angular';
|
|
132
|
+
|
|
133
|
+
@Component({
|
|
134
|
+
standalone: true,
|
|
135
|
+
imports: [TraceDirective],
|
|
136
|
+
template: `<section trace="dashboard">…</section>`,
|
|
137
|
+
})
|
|
138
|
+
export class DashboardComponent {}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You can also instrument classes/methods with the decorators (the `name` is required so spans survive minification):
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { TraceClassDecorator, TraceMethodDecorator } from '@allstak/angular';
|
|
145
|
+
|
|
146
|
+
@TraceClassDecorator({ name: 'CheckoutComponent' })
|
|
147
|
+
@Component({ /* … */ })
|
|
148
|
+
export class CheckoutComponent {
|
|
149
|
+
@TraceMethodDecorator({ name: 'CheckoutComponent.ngOnInit' })
|
|
150
|
+
ngOnInit() {}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## HTTP instrumentation
|
|
155
|
+
|
|
156
|
+
The interceptor records every outbound request as an `outbound` HTTP item and opens a `http.client` span around it (finished `error` on `4xx`/`5xx` or transport failure). Use the functional `allStakHttpInterceptor` with `withInterceptors([...])` or the class `AllStakHttpInterceptor` with `HTTP_INTERCEPTORS`. It never mutates the request.
|
|
157
|
+
|
|
158
|
+
## Configuration
|
|
159
|
+
|
|
160
|
+
`init(config)`, `provideAllStak(config)`, and `AllStakModule.forRoot(config)` all accept every option that `@allstak/js`'s `AllStak.init` accepts. Common options:
|
|
161
|
+
|
|
162
|
+
| Option | Purpose |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `apiKey` | Ingest key (required) |
|
|
165
|
+
| `host` | Override the ingest host |
|
|
166
|
+
| `environment` | e.g. `'production'` / `'development'` |
|
|
167
|
+
| `release` | Release identifier for issue grouping / release health |
|
|
168
|
+
| `tracesSampleRate` | Span sampling rate (0–1) |
|
|
169
|
+
| `enableWebVitals` | Capture web vitals |
|
|
170
|
+
| `enableAutoSessionTracking` | Release-health sessions |
|
|
171
|
+
| `enableOfflineQueue` | Buffer events while offline |
|
|
172
|
+
| `beforeSend` | Mutate/drop events before they are sent |
|
|
173
|
+
|
|
174
|
+
The full `@allstak/js` API is re-exported, so you can pull the entire observability surface from one import:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import * as AllStakAngular from '@allstak/angular';
|
|
178
|
+
|
|
179
|
+
AllStakAngular.AllStak.captureMessage('checkout started', 'info');
|
|
180
|
+
const span = AllStakAngular.AllStak.startSpan('checkout.flow', { description: 'review → pay' });
|
|
181
|
+
span.finish('ok');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated bundle index. Do not edit.
|
|
3
|
+
*/
|
|
4
|
+
export * from './public-api';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxsc3Rhay1hbmd1bGFyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vYWxsc3Rhay1hbmd1bGFyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBRUgsY0FBYyxjQUFjLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vcHVibGljLWFwaSc7XG4iXX0=
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AllStak for Angular.
|
|
3
|
+
*
|
|
4
|
+
* Public surface:
|
|
5
|
+
* - `init` — bootstrap (delegates to @allstak/js with Angular tagging)
|
|
6
|
+
* - `createErrorHandler` / `AllStakErrorHandler` — Angular `ErrorHandler` forwarding to captureException
|
|
7
|
+
* - `AllStakHttpInterceptor` / `allStakHttpInterceptor` — records outbound requests + opens spans
|
|
8
|
+
* - `TraceService` — Router-aware navigation span instrumentation
|
|
9
|
+
* - `TraceDirective` / `AllStakTraceModule` — `[trace]` component render-timing directive
|
|
10
|
+
* - `TraceClassDecorator` / `TraceMethodDecorator` — lifecycle-span decorators
|
|
11
|
+
* - `provideAllStak` / `provideAllStakErrorHandler` / `provideAllStakRouterInstrumentation`
|
|
12
|
+
* — standalone provider functions
|
|
13
|
+
* - `AllStakModule.forRoot(config)` — NgModule registration for module-based apps
|
|
14
|
+
* - re-exports — every top-level @allstak/js export
|
|
15
|
+
*/
|
|
16
|
+
// Bootstrap.
|
|
17
|
+
export { init } from './src/init';
|
|
18
|
+
// Error handling.
|
|
19
|
+
export { createErrorHandler, AllStakErrorHandler, } from './src/error-handler';
|
|
20
|
+
// HTTP instrumentation.
|
|
21
|
+
export { AllStakHttpInterceptor, allStakHttpInterceptor, } from './src/http-interceptor';
|
|
22
|
+
// Router / navigation instrumentation.
|
|
23
|
+
export { TraceService } from './src/trace-service';
|
|
24
|
+
// Component render-timing directive + module.
|
|
25
|
+
export { TraceDirective } from './src/trace-directive';
|
|
26
|
+
// Lifecycle-span decorators.
|
|
27
|
+
export { TraceClass, TraceClassDecorator, TraceMethod, TraceMethodDecorator, } from './src/trace-decorators';
|
|
28
|
+
// Standalone provider functions.
|
|
29
|
+
export { provideAllStak, provideAllStakErrorHandler, provideAllStakRouterInstrumentation, } from './src/providers';
|
|
30
|
+
// NgModule registration for module-based apps.
|
|
31
|
+
export { AllStakModule, AllStakTraceModule, ALLSTAK_CONFIG, appInitializerFactory, } from './src/module';
|
|
32
|
+
// Wrapper identity.
|
|
33
|
+
export { SDK_NAME, SDK_VERSION } from './src/version';
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Mirror the @allstak/js public surface so consumers can pull the entire
|
|
36
|
+
// observability API from a single namespaced import:
|
|
37
|
+
// import * as AllStakAngular from '@allstak/angular';
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
export { AllStak, Scope, Session, SessionTracker, Span, WebVitalsModule, isWebVitalsSupported, defineIntegration, dedupeIntegration, consoleIntegration, httpClientIntegration, databaseIntegration, eventFiltersIntegration, inboundFiltersIntegration, } from '@allstak/js';
|
|
40
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFFSCxhQUFhO0FBQ2IsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFlBQVksQ0FBQztBQUdsQyxrQkFBa0I7QUFDbEIsT0FBTyxFQUNMLGtCQUFrQixFQUNsQixtQkFBbUIsR0FDcEIsTUFBTSxxQkFBcUIsQ0FBQztBQUc3Qix3QkFBd0I7QUFDeEIsT0FBTyxFQUNMLHNCQUFzQixFQUN0QixzQkFBc0IsR0FDdkIsTUFBTSx3QkFBd0IsQ0FBQztBQUVoQyx1Q0FBdUM7QUFDdkMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRW5ELDhDQUE4QztBQUM5QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFdkQsNkJBQTZCO0FBQzdCLE9BQU8sRUFDTCxVQUFVLEVBQ1YsbUJBQW1CLEVBQ25CLFdBQVcsRUFDWCxvQkFBb0IsR0FDckIsTUFBTSx3QkFBd0IsQ0FBQztBQUdoQyxpQ0FBaUM7QUFDakMsT0FBTyxFQUNMLGNBQWMsRUFDZCwwQkFBMEIsRUFDMUIsbUNBQW1DLEdBQ3BDLE1BQU0saUJBQWlCLENBQUM7QUFFekIsK0NBQStDO0FBQy9DLE9BQU8sRUFDTCxhQUFhLEVBQ2Isa0JBQWtCLEVBQ2xCLGNBQWMsRUFDZCxxQkFBcUIsR0FDdEIsTUFBTSxjQUFjLENBQUM7QUFFdEIsb0JBQW9CO0FBQ3BCLE9BQU8sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXRELDhFQUE4RTtBQUM5RSx5RUFBeUU7QUFDekUscURBQXFEO0FBQ3JELHdEQUF3RDtBQUN4RCw4RUFBOEU7QUFDOUUsT0FBTyxFQUNMLE9BQU8sRUFDUCxLQUFLLEVBQ0wsT0FBTyxFQUNQLGNBQWMsRUFDZCxJQUFJLEVBQ0osZUFBZSxFQUNmLG9CQUFvQixFQUNwQixpQkFBaUIsRUFDakIsaUJBQWlCLEVBQ2pCLGtCQUFrQixFQUNsQixxQkFBcUIsRUFDckIsbUJBQW1CLEVBQ25CLHVCQUF1QixFQUN2Qix5QkFBeUIsR0FDMUIsTUFBTSxhQUFhLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEFsbFN0YWsgZm9yIEFuZ3VsYXIuXG4gKlxuICogUHVibGljIHN1cmZhY2U6XG4gKiAgIC0gYGluaXRgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg4oCUIGJvb3RzdHJhcCAoZGVsZWdhdGVzIHRvIEBhbGxzdGFrL2pzIHdpdGggQW5ndWxhciB0YWdnaW5nKVxuICogICAtIGBjcmVhdGVFcnJvckhhbmRsZXJgIC8gYEFsbFN0YWtFcnJvckhhbmRsZXJgIOKAlCBBbmd1bGFyIGBFcnJvckhhbmRsZXJgIGZvcndhcmRpbmcgdG8gY2FwdHVyZUV4Y2VwdGlvblxuICogICAtIGBBbGxTdGFrSHR0cEludGVyY2VwdG9yYCAvIGBhbGxTdGFrSHR0cEludGVyY2VwdG9yYCDigJQgcmVjb3JkcyBvdXRib3VuZCByZXF1ZXN0cyArIG9wZW5zIHNwYW5zXG4gKiAgIC0gYFRyYWNlU2VydmljZWAgICAgICAgICAgICAgICAgICAgICAg4oCUIFJvdXRlci1hd2FyZSBuYXZpZ2F0aW9uIHNwYW4gaW5zdHJ1bWVudGF0aW9uXG4gKiAgIC0gYFRyYWNlRGlyZWN0aXZlYCAvIGBBbGxTdGFrVHJhY2VNb2R1bGVgIOKAlCBgW3RyYWNlXWAgY29tcG9uZW50IHJlbmRlci10aW1pbmcgZGlyZWN0aXZlXG4gKiAgIC0gYFRyYWNlQ2xhc3NEZWNvcmF0b3JgIC8gYFRyYWNlTWV0aG9kRGVjb3JhdG9yYCDigJQgbGlmZWN5Y2xlLXNwYW4gZGVjb3JhdG9yc1xuICogICAtIGBwcm92aWRlQWxsU3Rha2AgLyBgcHJvdmlkZUFsbFN0YWtFcnJvckhhbmRsZXJgIC8gYHByb3ZpZGVBbGxTdGFrUm91dGVySW5zdHJ1bWVudGF0aW9uYFxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICDigJQgc3RhbmRhbG9uZSBwcm92aWRlciBmdW5jdGlvbnNcbiAqICAgLSBgQWxsU3Rha01vZHVsZS5mb3JSb290KGNvbmZpZylgICAgICDigJQgTmdNb2R1bGUgcmVnaXN0cmF0aW9uIGZvciBtb2R1bGUtYmFzZWQgYXBwc1xuICogICAtIHJlLWV4cG9ydHMgICAgICAgICAgICAgICAgICAgICAgICAgIOKAlCBldmVyeSB0b3AtbGV2ZWwgQGFsbHN0YWsvanMgZXhwb3J0XG4gKi9cblxuLy8gQm9vdHN0cmFwLlxuZXhwb3J0IHsgaW5pdCB9IGZyb20gJy4vc3JjL2luaXQnO1xuZXhwb3J0IHR5cGUgeyBBbGxTdGFrQ2xpZW50SW5zdGFuY2UgfSBmcm9tICcuL3NyYy9pbml0JztcblxuLy8gRXJyb3IgaGFuZGxpbmcuXG5leHBvcnQge1xuICBjcmVhdGVFcnJvckhhbmRsZXIsXG4gIEFsbFN0YWtFcnJvckhhbmRsZXIsXG59IGZyb20gJy4vc3JjL2Vycm9yLWhhbmRsZXInO1xuZXhwb3J0IHR5cGUgeyBFcnJvckhhbmRsZXJPcHRpb25zIH0gZnJvbSAnLi9zcmMvZXJyb3ItaGFuZGxlcic7XG5cbi8vIEhUVFAgaW5zdHJ1bWVudGF0aW9uLlxuZXhwb3J0IHtcbiAgQWxsU3Rha0h0dHBJbnRlcmNlcHRvcixcbiAgYWxsU3Rha0h0dHBJbnRlcmNlcHRvcixcbn0gZnJvbSAnLi9zcmMvaHR0cC1pbnRlcmNlcHRvcic7XG5cbi8vIFJvdXRlciAvIG5hdmlnYXRpb24gaW5zdHJ1bWVudGF0aW9uLlxuZXhwb3J0IHsgVHJhY2VTZXJ2aWNlIH0gZnJvbSAnLi9zcmMvdHJhY2Utc2VydmljZSc7XG5cbi8vIENvbXBvbmVudCByZW5kZXItdGltaW5nIGRpcmVjdGl2ZSArIG1vZHVsZS5cbmV4cG9ydCB7IFRyYWNlRGlyZWN0aXZlIH0gZnJvbSAnLi9zcmMvdHJhY2UtZGlyZWN0aXZlJztcblxuLy8gTGlmZWN5Y2xlLXNwYW4gZGVjb3JhdG9ycy5cbmV4cG9ydCB7XG4gIFRyYWNlQ2xhc3MsXG4gIFRyYWNlQ2xhc3NEZWNvcmF0b3IsXG4gIFRyYWNlTWV0aG9kLFxuICBUcmFjZU1ldGhvZERlY29yYXRvcixcbn0gZnJvbSAnLi9zcmMvdHJhY2UtZGVjb3JhdG9ycyc7XG5leHBvcnQgdHlwZSB7IFRyYWNlRGVjb3JhdG9yT3B0aW9ucyB9IGZyb20gJy4vc3JjL3RyYWNlLWRlY29yYXRvcnMnO1xuXG4vLyBTdGFuZGFsb25lIHByb3ZpZGVyIGZ1bmN0aW9ucy5cbmV4cG9ydCB7XG4gIHByb3ZpZGVBbGxTdGFrLFxuICBwcm92aWRlQWxsU3Rha0Vycm9ySGFuZGxlcixcbiAgcHJvdmlkZUFsbFN0YWtSb3V0ZXJJbnN0cnVtZW50YXRpb24sXG59IGZyb20gJy4vc3JjL3Byb3ZpZGVycyc7XG5cbi8vIE5nTW9kdWxlIHJlZ2lzdHJhdGlvbiBmb3IgbW9kdWxlLWJhc2VkIGFwcHMuXG5leHBvcnQge1xuICBBbGxTdGFrTW9kdWxlLFxuICBBbGxTdGFrVHJhY2VNb2R1bGUsXG4gIEFMTFNUQUtfQ09ORklHLFxuICBhcHBJbml0aWFsaXplckZhY3RvcnksXG59IGZyb20gJy4vc3JjL21vZHVsZSc7XG5cbi8vIFdyYXBwZXIgaWRlbnRpdHkuXG5leHBvcnQgeyBTREtfTkFNRSwgU0RLX1ZFUlNJT04gfSBmcm9tICcuL3NyYy92ZXJzaW9uJztcblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vLyBNaXJyb3IgdGhlIEBhbGxzdGFrL2pzIHB1YmxpYyBzdXJmYWNlIHNvIGNvbnN1bWVycyBjYW4gcHVsbCB0aGUgZW50aXJlXG4vLyBvYnNlcnZhYmlsaXR5IEFQSSBmcm9tIGEgc2luZ2xlIG5hbWVzcGFjZWQgaW1wb3J0OlxuLy8gICBpbXBvcnQgKiBhcyBBbGxTdGFrQW5ndWxhciBmcm9tICdAYWxsc3Rhay9hbmd1bGFyJztcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuZXhwb3J0IHtcbiAgQWxsU3RhayxcbiAgU2NvcGUsXG4gIFNlc3Npb24sXG4gIFNlc3Npb25UcmFja2VyLFxuICBTcGFuLFxuICBXZWJWaXRhbHNNb2R1bGUsXG4gIGlzV2ViVml0YWxzU3VwcG9ydGVkLFxuICBkZWZpbmVJbnRlZ3JhdGlvbixcbiAgZGVkdXBlSW50ZWdyYXRpb24sXG4gIGNvbnNvbGVJbnRlZ3JhdGlvbixcbiAgaHR0cENsaWVudEludGVncmF0aW9uLFxuICBkYXRhYmFzZUludGVncmF0aW9uLFxuICBldmVudEZpbHRlcnNJbnRlZ3JhdGlvbixcbiAgaW5ib3VuZEZpbHRlcnNJbnRlZ3JhdGlvbixcbn0gZnJvbSAnQGFsbHN0YWsvanMnO1xuXG5leHBvcnQgdHlwZSB7XG4gIEFsbFN0YWtDb25maWcsXG4gIEFsbFN0YWtJbnRlZ3JhdGlvbixcbiAgQnJlYWRjcnVtYixcbiAgRXJyb3JFdmVudCxcbiAgRXJyb3JFdmVudFByb2Nlc3NvcixcbiAgTG9nRXZlbnQsXG4gIExvZ0xldmVsLFxuICBIdHRwUmVxdWVzdEl0ZW0sXG4gIEhlYXJ0YmVhdE9wdGlvbnMsXG4gIFNwYW5EYXRhLFxuICBTcGFuT3B0aW9ucyxcbiAgU3BhblByb2Nlc3NvcixcbiAgVHJhY2VzU2FtcGxlcixcbiAgU2FtcGxpbmdDb250ZXh0LFxuICBXZWJWaXRhbHNDb250ZXh0LFxuICBEYlF1ZXJ5SXRlbSxcbiAgU2Vzc2lvblN0YXR1cyxcbn0gZnJvbSAnQGFsbHN0YWsvanMnO1xuIl19
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { AllStak } from '@allstak/js';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
logErrors: true,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Unwrap the common Angular/zone.js error envelopes so we capture the real
|
|
9
|
+
* underlying error rather than the wrapper:
|
|
10
|
+
*
|
|
11
|
+
* - Angular wraps errors thrown during change detection as
|
|
12
|
+
* `{ ngOriginalError }` / `{ originalError }`.
|
|
13
|
+
* - `HttpErrorResponse` carries the server payload on `.error`.
|
|
14
|
+
* - DOM `ErrorEvent` carries the real error on `.error`.
|
|
15
|
+
* - Rejected promises may surface as `{ rejection }`.
|
|
16
|
+
*/
|
|
17
|
+
function defaultExtractor(error) {
|
|
18
|
+
if (error && typeof error === 'object') {
|
|
19
|
+
const candidate = error;
|
|
20
|
+
// PromiseRejectionEvent-style wrapping from zone.js.
|
|
21
|
+
if ('rejection' in candidate && candidate['rejection'] != null) {
|
|
22
|
+
return defaultExtractor(candidate['rejection']);
|
|
23
|
+
}
|
|
24
|
+
// Angular wraps the thrown value during change detection.
|
|
25
|
+
if ('ngOriginalError' in candidate && candidate['ngOriginalError'] != null) {
|
|
26
|
+
return defaultExtractor(candidate['ngOriginalError']);
|
|
27
|
+
}
|
|
28
|
+
if ('originalError' in candidate && candidate['originalError'] != null) {
|
|
29
|
+
return defaultExtractor(candidate['originalError']);
|
|
30
|
+
}
|
|
31
|
+
// DOM ErrorEvent / HttpErrorResponse expose the real error on `.error`.
|
|
32
|
+
if ('error' in candidate && candidate['error'] instanceof Error) {
|
|
33
|
+
return candidate['error'];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
/** Coerce any extracted value into an `Error` so `captureException` is happy. */
|
|
39
|
+
function toError(value) {
|
|
40
|
+
if (value instanceof Error)
|
|
41
|
+
return value;
|
|
42
|
+
if (typeof value === 'string')
|
|
43
|
+
return new Error(value);
|
|
44
|
+
try {
|
|
45
|
+
return new Error(JSON.stringify(value));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return new Error(String(value));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* AllStak's implementation of Angular's `ErrorHandler`. Captures every error
|
|
53
|
+
* routed through Angular's global error handling — uncaught render/lifecycle
|
|
54
|
+
* errors, change-detection errors, and (when zone.js is present) unhandled
|
|
55
|
+
* promise rejections.
|
|
56
|
+
*
|
|
57
|
+
* Wired manually as `{ provide: ErrorHandler, useValue: createErrorHandler() }`
|
|
58
|
+
* or via {@link provideAllStakErrorHandler}.
|
|
59
|
+
*/
|
|
60
|
+
export class AllStakErrorHandler {
|
|
61
|
+
options;
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
64
|
+
}
|
|
65
|
+
handleError(error) {
|
|
66
|
+
const extracted = this.options.extractor
|
|
67
|
+
? this.options.extractor(error, defaultExtractor)
|
|
68
|
+
: defaultExtractor(error);
|
|
69
|
+
const err = toError(extracted);
|
|
70
|
+
try {
|
|
71
|
+
AllStak.captureException(err, { framework: 'angular' });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Never let our handler break the host's error path.
|
|
75
|
+
}
|
|
76
|
+
if (this.options.logErrors !== false) {
|
|
77
|
+
// Preserve Angular's default of surfacing the original (pre-unwrap)
|
|
78
|
+
// value so stack traces and zone context stay intact in the console.
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.error(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
|
|
84
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler });
|
|
85
|
+
}
|
|
86
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler, decorators: [{
|
|
87
|
+
type: Injectable
|
|
88
|
+
}], ctorParameters: () => [{ type: undefined }] });
|
|
89
|
+
/**
|
|
90
|
+
* Factory returning an Angular `ErrorHandler` that forwards to
|
|
91
|
+
* `AllStak.captureException`.
|
|
92
|
+
*
|
|
93
|
+
* Standalone (`app.config.ts`):
|
|
94
|
+
*
|
|
95
|
+
* import { ErrorHandler } from '@angular/core';
|
|
96
|
+
* import { createErrorHandler } from '@allstak/angular';
|
|
97
|
+
*
|
|
98
|
+
* export const appConfig = {
|
|
99
|
+
* providers: [{ provide: ErrorHandler, useValue: createErrorHandler() }],
|
|
100
|
+
* };
|
|
101
|
+
*
|
|
102
|
+
* NgModule (`AppModule`):
|
|
103
|
+
*
|
|
104
|
+
* @NgModule({
|
|
105
|
+
* providers: [{ provide: ErrorHandler, useValue: createErrorHandler() }],
|
|
106
|
+
* })
|
|
107
|
+
* export class AppModule {}
|
|
108
|
+
*/
|
|
109
|
+
export function createErrorHandler(options) {
|
|
110
|
+
return new AllStakErrorHandler(options);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3ItaGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lcnJvci1oYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBZ0IsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxhQUFhLENBQUM7O0FBK0J0QyxNQUFNLGVBQWUsR0FBcUQ7SUFDeEUsU0FBUyxFQUFFLElBQUk7Q0FDaEIsQ0FBQztBQUVGOzs7Ozs7Ozs7R0FTRztBQUNILFNBQVMsZ0JBQWdCLENBQUMsS0FBYztJQUN0QyxJQUFJLEtBQUssSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUN2QyxNQUFNLFNBQVMsR0FBRyxLQUFnQyxDQUFDO1FBQ25ELHFEQUFxRDtRQUNyRCxJQUFJLFdBQVcsSUFBSSxTQUFTLElBQUksU0FBUyxDQUFDLFdBQVcsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQy9ELE9BQU8sZ0JBQWdCLENBQUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUM7UUFDbEQsQ0FBQztRQUNELDBEQUEwRDtRQUMxRCxJQUFJLGlCQUFpQixJQUFJLFNBQVMsSUFBSSxTQUFTLENBQUMsaUJBQWlCLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUMzRSxPQUFPLGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUM7UUFDeEQsQ0FBQztRQUNELElBQUksZUFBZSxJQUFJLFNBQVMsSUFBSSxTQUFTLENBQUMsZUFBZSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7WUFDdkUsT0FBTyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQztRQUN0RCxDQUFDO1FBQ0Qsd0VBQXdFO1FBQ3hFLElBQUksT0FBTyxJQUFJLFNBQVMsSUFBSSxTQUFTLENBQUMsT0FBTyxDQUFDLFlBQVksS0FBSyxFQUFFLENBQUM7WUFDaEUsT0FBTyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDNUIsQ0FBQztJQUNILENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFFRCxpRkFBaUY7QUFDakYsU0FBUyxPQUFPLENBQUMsS0FBYztJQUM3QixJQUFJLEtBQUssWUFBWSxLQUFLO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDekMsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRO1FBQUUsT0FBTyxJQUFJLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN2RCxJQUFJLENBQUM7UUFDSCxPQUFPLElBQUksS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUNsQyxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7OztHQVFHO0FBRUgsTUFBTSxPQUFPLG1CQUFtQjtJQUNiLE9BQU8sQ0FBc0I7SUFFOUMsWUFBWSxVQUErQixFQUFFO1FBQzNDLElBQUksQ0FBQyxPQUFPLEdBQUcsRUFBRSxHQUFHLGVBQWUsRUFBRSxHQUFHLE9BQU8sRUFBRSxDQUFDO0lBQ3BELENBQUM7SUFFRCxXQUFXLENBQUMsS0FBYztRQUN4QixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVM7WUFDdEMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxnQkFBZ0IsQ0FBQztZQUNqRCxDQUFDLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFNUIsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBRS9CLElBQUksQ0FBQztZQUNILE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLEVBQUUsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztRQUMxRCxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AscURBQXFEO1FBQ3ZELENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxLQUFLLEtBQUssRUFBRSxDQUFDO1lBQ3JDLG9FQUFvRTtZQUNwRSxxRUFBcUU7WUFDckUsc0NBQXNDO1lBQ3RDLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdkIsQ0FBQztJQUNILENBQUM7d0dBMUJVLG1CQUFtQjs0R0FBbkIsbUJBQW1COzs0RkFBbkIsbUJBQW1CO2tCQUQvQixVQUFVOztBQThCWDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxPQUE2QjtJQUM5RCxPQUFPLElBQUksbUJBQW1CLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDMUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEVycm9ySGFuZGxlciwgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQWxsU3RhayB9IGZyb20gJ0BhbGxzdGFrL2pzJztcblxuLyoqXG4gKiBPcHRpb25zIGFjY2VwdGVkIGJ5IHtAbGluayBjcmVhdGVFcnJvckhhbmRsZXJ9IC8ge0BsaW5rIEFsbFN0YWtFcnJvckhhbmRsZXJ9LlxuICpcbiAqIE1pcnJvcnMgdGhlIHNoYXBlIGhvc3QgYXBwcyBleHBlY3QgZnJvbSBhIGZyYW1ld29yayBlcnJvci1oYW5kbGVyIGZhY3Rvcnk6XG4gKiBhIGxvZ2dpbmcgdG9nZ2xlIGFuZCBhbiBgZXh0cmFjdG9yYCBlc2NhcGUgaGF0Y2ggc28gdGhlIGhvc3QgY2FuIGN1c3RvbWlzZVxuICogaG93IGEgdGhyb3duIHZhbHVlIGlzIHVud3JhcHBlZCBiZWZvcmUgd2UgaGFuZCBpdCB0byBgY2FwdHVyZUV4Y2VwdGlvbmAuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRXJyb3JIYW5kbGVyT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBBbHNvIGBjb25zb2xlLmVycm9yYCB0aGUgb3JpZ2luYWwgZXJyb3IgYWZ0ZXIgY2FwdHVyaW5nIGl0LiBEZWZhdWx0OlxuICAgKiBgdHJ1ZWAg4oCUIHByZXNlcnZpbmcgQW5ndWxhcidzIGRlZmF1bHQgYmVoYXZpb3VyIG9mIHN1cmZhY2luZyBlcnJvcnMgdG9cbiAgICogdGhlIGNvbnNvbGUgc28gbG9jYWwgZGV2ZWxvcG1lbnQgaXMgbm90IHNpbGVuY2VkLlxuICAgKi9cbiAgbG9nRXJyb3JzPzogYm9vbGVhbjtcblxuICAvKipcbiAgICogQ3VzdG9taXNlIGhvdyB0aGUgdGhyb3duIHZhbHVlIGlzIHJlZHVjZWQgdG8gdGhlIGBFcnJvcmAgd2UgY2FwdHVyZS5cbiAgICogUmVjZWl2ZXMgdGhlIHJhdyB2YWx1ZSBBbmd1bGFyIGhhbmRlZCB0byBgaGFuZGxlRXJyb3JgIHBsdXMgdGhlIFNESydzXG4gICAqIGBkZWZhdWx0RXh0cmFjdG9yYCwgc28gYSBob3N0IGNhbiBmYWxsIGJhY2sgdG8gZGVmYXVsdCBiZWhhdmlvdXIgZm9yXG4gICAqIGNhc2VzIGl0IGRvZXMgbm90IGNhcmUgYWJvdXQuXG4gICAqXG4gICAqICAgY3JlYXRlRXJyb3JIYW5kbGVyKHtcbiAgICogICAgIGV4dHJhY3RvcjogKGVycm9yLCBkZWZhdWx0RXh0cmFjdG9yKSA9PlxuICAgKiAgICAgICBlcnJvcj8uY3VzdG9tQ2F1c2UgPz8gZGVmYXVsdEV4dHJhY3RvcihlcnJvciksXG4gICAqICAgfSk7XG4gICAqL1xuICBleHRyYWN0b3I/OiAoZXJyb3I6IHVua25vd24sIGRlZmF1bHRFeHRyYWN0b3I6IChlcnJvcjogdW5rbm93bikgPT4gdW5rbm93bikgPT4gdW5rbm93bjtcbn1cblxuY29uc3QgREVGQVVMVF9PUFRJT05TOiBSZXF1aXJlZDxQaWNrPEVycm9ySGFuZGxlck9wdGlvbnMsICdsb2dFcnJvcnMnPj4gPSB7XG4gIGxvZ0Vycm9yczogdHJ1ZSxcbn07XG5cbi8qKlxuICogVW53cmFwIHRoZSBjb21tb24gQW5ndWxhci96b25lLmpzIGVycm9yIGVudmVsb3BlcyBzbyB3ZSBjYXB0dXJlIHRoZSByZWFsXG4gKiB1bmRlcmx5aW5nIGVycm9yIHJhdGhlciB0aGFuIHRoZSB3cmFwcGVyOlxuICpcbiAqICAgLSBBbmd1bGFyIHdyYXBzIGVycm9ycyB0aHJvd24gZHVyaW5nIGNoYW5nZSBkZXRlY3Rpb24gYXNcbiAqICAgICBgeyBuZ09yaWdpbmFsRXJyb3IgfWAgLyBgeyBvcmlnaW5hbEVycm9yIH1gLlxuICogICAtIGBIdHRwRXJyb3JSZXNwb25zZWAgY2FycmllcyB0aGUgc2VydmVyIHBheWxvYWQgb24gYC5lcnJvcmAuXG4gKiAgIC0gRE9NIGBFcnJvckV2ZW50YCBjYXJyaWVzIHRoZSByZWFsIGVycm9yIG9uIGAuZXJyb3JgLlxuICogICAtIFJlamVjdGVkIHByb21pc2VzIG1heSBzdXJmYWNlIGFzIGB7IHJlamVjdGlvbiB9YC5cbiAqL1xuZnVuY3Rpb24gZGVmYXVsdEV4dHJhY3RvcihlcnJvcjogdW5rbm93bik6IHVua25vd24ge1xuICBpZiAoZXJyb3IgJiYgdHlwZW9mIGVycm9yID09PSAnb2JqZWN0Jykge1xuICAgIGNvbnN0IGNhbmRpZGF0ZSA9IGVycm9yIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICAgIC8vIFByb21pc2VSZWplY3Rpb25FdmVudC1zdHlsZSB3cmFwcGluZyBmcm9tIHpvbmUuanMuXG4gICAgaWYgKCdyZWplY3Rpb24nIGluIGNhbmRpZGF0ZSAmJiBjYW5kaWRhdGVbJ3JlamVjdGlvbiddICE9IG51bGwpIHtcbiAgICAgIHJldHVybiBkZWZhdWx0RXh0cmFjdG9yKGNhbmRpZGF0ZVsncmVqZWN0aW9uJ10pO1xuICAgIH1cbiAgICAvLyBBbmd1bGFyIHdyYXBzIHRoZSB0aHJvd24gdmFsdWUgZHVyaW5nIGNoYW5nZSBkZXRlY3Rpb24uXG4gICAgaWYgKCduZ09yaWdpbmFsRXJyb3InIGluIGNhbmRpZGF0ZSAmJiBjYW5kaWRhdGVbJ25nT3JpZ2luYWxFcnJvciddICE9IG51bGwpIHtcbiAgICAgIHJldHVybiBkZWZhdWx0RXh0cmFjdG9yKGNhbmRpZGF0ZVsnbmdPcmlnaW5hbEVycm9yJ10pO1xuICAgIH1cbiAgICBpZiAoJ29yaWdpbmFsRXJyb3InIGluIGNhbmRpZGF0ZSAmJiBjYW5kaWRhdGVbJ29yaWdpbmFsRXJyb3InXSAhPSBudWxsKSB7XG4gICAgICByZXR1cm4gZGVmYXVsdEV4dHJhY3RvcihjYW5kaWRhdGVbJ29yaWdpbmFsRXJyb3InXSk7XG4gICAgfVxuICAgIC8vIERPTSBFcnJvckV2ZW50IC8gSHR0cEVycm9yUmVzcG9uc2UgZXhwb3NlIHRoZSByZWFsIGVycm9yIG9uIGAuZXJyb3JgLlxuICAgIGlmICgnZXJyb3InIGluIGNhbmRpZGF0ZSAmJiBjYW5kaWRhdGVbJ2Vycm9yJ10gaW5zdGFuY2VvZiBFcnJvcikge1xuICAgICAgcmV0dXJuIGNhbmRpZGF0ZVsnZXJyb3InXTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGVycm9yO1xufVxuXG4vKiogQ29lcmNlIGFueSBleHRyYWN0ZWQgdmFsdWUgaW50byBhbiBgRXJyb3JgIHNvIGBjYXB0dXJlRXhjZXB0aW9uYCBpcyBoYXBweS4gKi9cbmZ1bmN0aW9uIHRvRXJyb3IodmFsdWU6IHVua25vd24pOiBFcnJvciB7XG4gIGlmICh2YWx1ZSBpbnN0YW5jZW9mIEVycm9yKSByZXR1cm4gdmFsdWU7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnKSByZXR1cm4gbmV3IEVycm9yKHZhbHVlKTtcbiAgdHJ5IHtcbiAgICByZXR1cm4gbmV3IEVycm9yKEpTT04uc3RyaW5naWZ5KHZhbHVlKSk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBuZXcgRXJyb3IoU3RyaW5nKHZhbHVlKSk7XG4gIH1cbn1cblxuLyoqXG4gKiBBbGxTdGFrJ3MgaW1wbGVtZW50YXRpb24gb2YgQW5ndWxhcidzIGBFcnJvckhhbmRsZXJgLiBDYXB0dXJlcyBldmVyeSBlcnJvclxuICogcm91dGVkIHRocm91Z2ggQW5ndWxhcidzIGdsb2JhbCBlcnJvciBoYW5kbGluZyDigJQgdW5jYXVnaHQgcmVuZGVyL2xpZmVjeWNsZVxuICogZXJyb3JzLCBjaGFuZ2UtZGV0ZWN0aW9uIGVycm9ycywgYW5kICh3aGVuIHpvbmUuanMgaXMgcHJlc2VudCkgdW5oYW5kbGVkXG4gKiBwcm9taXNlIHJlamVjdGlvbnMuXG4gKlxuICogV2lyZWQgbWFudWFsbHkgYXMgYHsgcHJvdmlkZTogRXJyb3JIYW5kbGVyLCB1c2VWYWx1ZTogY3JlYXRlRXJyb3JIYW5kbGVyKCkgfWBcbiAqIG9yIHZpYSB7QGxpbmsgcHJvdmlkZUFsbFN0YWtFcnJvckhhbmRsZXJ9LlxuICovXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgQWxsU3Rha0Vycm9ySGFuZGxlciBpbXBsZW1lbnRzIEVycm9ySGFuZGxlciB7XG4gIHByaXZhdGUgcmVhZG9ubHkgb3B0aW9uczogRXJyb3JIYW5kbGVyT3B0aW9ucztcblxuICBjb25zdHJ1Y3RvcihvcHRpb25zOiBFcnJvckhhbmRsZXJPcHRpb25zID0ge30pIHtcbiAgICB0aGlzLm9wdGlvbnMgPSB7IC4uLkRFRkFVTFRfT1BUSU9OUywgLi4ub3B0aW9ucyB9O1xuICB9XG5cbiAgaGFuZGxlRXJyb3IoZXJyb3I6IHVua25vd24pOiB2b2lkIHtcbiAgICBjb25zdCBleHRyYWN0ZWQgPSB0aGlzLm9wdGlvbnMuZXh0cmFjdG9yXG4gICAgICA/IHRoaXMub3B0aW9ucy5leHRyYWN0b3IoZXJyb3IsIGRlZmF1bHRFeHRyYWN0b3IpXG4gICAgICA6IGRlZmF1bHRFeHRyYWN0b3IoZXJyb3IpO1xuXG4gICAgY29uc3QgZXJyID0gdG9FcnJvcihleHRyYWN0ZWQpO1xuXG4gICAgdHJ5IHtcbiAgICAgIEFsbFN0YWsuY2FwdHVyZUV4Y2VwdGlvbihlcnIsIHsgZnJhbWV3b3JrOiAnYW5ndWxhcicgfSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBOZXZlciBsZXQgb3VyIGhhbmRsZXIgYnJlYWsgdGhlIGhvc3QncyBlcnJvciBwYXRoLlxuICAgIH1cblxuICAgIGlmICh0aGlzLm9wdGlvbnMubG9nRXJyb3JzICE9PSBmYWxzZSkge1xuICAgICAgLy8gUHJlc2VydmUgQW5ndWxhcidzIGRlZmF1bHQgb2Ygc3VyZmFjaW5nIHRoZSBvcmlnaW5hbCAocHJlLXVud3JhcClcbiAgICAgIC8vIHZhbHVlIHNvIHN0YWNrIHRyYWNlcyBhbmQgem9uZSBjb250ZXh0IHN0YXkgaW50YWN0IGluIHRoZSBjb25zb2xlLlxuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIGNvbnNvbGUuZXJyb3IoZXJyb3IpO1xuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIEZhY3RvcnkgcmV0dXJuaW5nIGFuIEFuZ3VsYXIgYEVycm9ySGFuZGxlcmAgdGhhdCBmb3J3YXJkcyB0b1xuICogYEFsbFN0YWsuY2FwdHVyZUV4Y2VwdGlvbmAuXG4gKlxuICogU3RhbmRhbG9uZSAoYGFwcC5jb25maWcudHNgKTpcbiAqXG4gKiAgIGltcG9ydCB7IEVycm9ySGFuZGxlciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuICogICBpbXBvcnQgeyBjcmVhdGVFcnJvckhhbmRsZXIgfSBmcm9tICdAYWxsc3Rhay9hbmd1bGFyJztcbiAqXG4gKiAgIGV4cG9ydCBjb25zdCBhcHBDb25maWcgPSB7XG4gKiAgICAgcHJvdmlkZXJzOiBbeyBwcm92aWRlOiBFcnJvckhhbmRsZXIsIHVzZVZhbHVlOiBjcmVhdGVFcnJvckhhbmRsZXIoKSB9XSxcbiAqICAgfTtcbiAqXG4gKiBOZ01vZHVsZSAoYEFwcE1vZHVsZWApOlxuICpcbiAqICAgQE5nTW9kdWxlKHtcbiAqICAgICBwcm92aWRlcnM6IFt7IHByb3ZpZGU6IEVycm9ySGFuZGxlciwgdXNlVmFsdWU6IGNyZWF0ZUVycm9ySGFuZGxlcigpIH1dLFxuICogICB9KVxuICogICBleHBvcnQgY2xhc3MgQXBwTW9kdWxlIHt9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVFcnJvckhhbmRsZXIob3B0aW9ucz86IEVycm9ySGFuZGxlck9wdGlvbnMpOiBFcnJvckhhbmRsZXIge1xuICByZXR1cm4gbmV3IEFsbFN0YWtFcnJvckhhbmRsZXIob3B0aW9ucyk7XG59XG4iXX0=
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { HttpResponse, HttpErrorResponse } from '@angular/common/http';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { AllStak } from '@allstak/js';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
const KNOWN_METHODS = new Set([
|
|
7
|
+
'GET',
|
|
8
|
+
'POST',
|
|
9
|
+
'PUT',
|
|
10
|
+
'DELETE',
|
|
11
|
+
'PATCH',
|
|
12
|
+
'HEAD',
|
|
13
|
+
'OPTIONS',
|
|
14
|
+
]);
|
|
15
|
+
/** Normalise an Angular request method into the core method union. */
|
|
16
|
+
function normalizeMethod(method) {
|
|
17
|
+
const upper = method.toUpperCase();
|
|
18
|
+
return (KNOWN_METHODS.has(upper) ? upper : 'GET');
|
|
19
|
+
}
|
|
20
|
+
/** Split an outbound URL into `host` + `path`, tolerating relative URLs. */
|
|
21
|
+
function splitUrl(url) {
|
|
22
|
+
try {
|
|
23
|
+
const base = typeof window !== 'undefined' && window.location
|
|
24
|
+
? window.location.origin
|
|
25
|
+
: 'http://localhost';
|
|
26
|
+
const parsed = new URL(url, base);
|
|
27
|
+
return { host: parsed.host, path: parsed.pathname + parsed.search };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { host: '', path: url };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Open a span + record an outbound request for a single `HttpRequest`, and
|
|
35
|
+
* return the lifecycle hooks the interceptor (class or functional) uses to
|
|
36
|
+
* finish it. Shared so the class-based and functional interceptors stay
|
|
37
|
+
* behaviourally identical.
|
|
38
|
+
*/
|
|
39
|
+
function instrumentRequest(req) {
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
const method = normalizeMethod(req.method);
|
|
42
|
+
const { host, path } = splitUrl(req.urlWithParams);
|
|
43
|
+
let span = null;
|
|
44
|
+
try {
|
|
45
|
+
span = AllStak.startSpan('http.client', {
|
|
46
|
+
op: 'http.client',
|
|
47
|
+
description: `${req.method} ${req.urlWithParams}`,
|
|
48
|
+
attributes: {
|
|
49
|
+
'allstak.origin': 'auto.http.angular',
|
|
50
|
+
'http.method': req.method,
|
|
51
|
+
'http.url': req.urlWithParams,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Never block the request on observability errors.
|
|
57
|
+
}
|
|
58
|
+
const finish = (statusCode, status) => {
|
|
59
|
+
const durationMs = Date.now() - startedAt;
|
|
60
|
+
try {
|
|
61
|
+
span?.setMeasurement('http.duration_ms', durationMs);
|
|
62
|
+
span?.finish(status);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore — span may already be finished
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const item = {
|
|
69
|
+
direction: 'outbound',
|
|
70
|
+
method,
|
|
71
|
+
host,
|
|
72
|
+
path,
|
|
73
|
+
statusCode,
|
|
74
|
+
durationMs,
|
|
75
|
+
};
|
|
76
|
+
AllStak.captureRequest(item);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore — request recording is best-effort
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
onResponse(statusCode) {
|
|
84
|
+
finish(statusCode, statusCode >= 400 ? 'error' : 'ok');
|
|
85
|
+
},
|
|
86
|
+
onError(error) {
|
|
87
|
+
const statusCode = error instanceof HttpErrorResponse ? error.status : 0;
|
|
88
|
+
finish(statusCode, 'error');
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Class-based `HttpInterceptor` that records every outbound HTTP request and
|
|
94
|
+
* opens a `http.client` span around it. Register in the DI-token style used by
|
|
95
|
+
* NgModule apps and standalone apps that opt into `withInterceptorsFromDi()`:
|
|
96
|
+
*
|
|
97
|
+
* import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
98
|
+
* import { AllStakHttpInterceptor } from '@allstak/angular';
|
|
99
|
+
*
|
|
100
|
+
* providers: [
|
|
101
|
+
* { provide: HTTP_INTERCEPTORS, useClass: AllStakHttpInterceptor, multi: true },
|
|
102
|
+
* ]
|
|
103
|
+
*
|
|
104
|
+
* The interceptor never mutates the request and never swallows errors — it
|
|
105
|
+
* only observes the stream.
|
|
106
|
+
*/
|
|
107
|
+
export class AllStakHttpInterceptor {
|
|
108
|
+
intercept(req, next) {
|
|
109
|
+
const hooks = instrumentRequest(req);
|
|
110
|
+
return new Observable((subscriber) => {
|
|
111
|
+
const sub = next.handle(req).subscribe({
|
|
112
|
+
next: (event) => {
|
|
113
|
+
if (event instanceof HttpResponse) {
|
|
114
|
+
hooks.onResponse(event.status);
|
|
115
|
+
}
|
|
116
|
+
subscriber.next(event);
|
|
117
|
+
},
|
|
118
|
+
error: (error) => {
|
|
119
|
+
hooks.onError(error);
|
|
120
|
+
subscriber.error(error);
|
|
121
|
+
},
|
|
122
|
+
complete: () => subscriber.complete(),
|
|
123
|
+
});
|
|
124
|
+
return () => sub.unsubscribe();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
128
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor });
|
|
129
|
+
}
|
|
130
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor, decorators: [{
|
|
131
|
+
type: Injectable
|
|
132
|
+
}] });
|
|
133
|
+
/**
|
|
134
|
+
* Functional `HttpInterceptor` for standalone apps using
|
|
135
|
+
* `provideHttpClient(withInterceptors([allStakHttpInterceptor]))`.
|
|
136
|
+
*
|
|
137
|
+
* Behaviourally identical to {@link AllStakHttpInterceptor}.
|
|
138
|
+
*/
|
|
139
|
+
export function allStakHttpInterceptor(req, next) {
|
|
140
|
+
const hooks = instrumentRequest(req);
|
|
141
|
+
return new Observable((subscriber) => {
|
|
142
|
+
const sub = next(req).subscribe({
|
|
143
|
+
next: (event) => {
|
|
144
|
+
if (event instanceof HttpResponse) {
|
|
145
|
+
hooks.onResponse(event.status);
|
|
146
|
+
}
|
|
147
|
+
subscriber.next(event);
|
|
148
|
+
},
|
|
149
|
+
error: (error) => {
|
|
150
|
+
hooks.onError(error);
|
|
151
|
+
subscriber.error(error);
|
|
152
|
+
},
|
|
153
|
+
complete: () => subscriber.complete(),
|
|
154
|
+
});
|
|
155
|
+
return () => sub.unsubscribe();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1pbnRlcmNlcHRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9odHRwLWludGVyY2VwdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFRM0MsT0FBTyxFQUFFLFlBQVksRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDbEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGFBQWEsQ0FBQzs7QUFNdEMsTUFBTSxhQUFhLEdBQXdCLElBQUksR0FBRyxDQUFDO0lBQ2pELEtBQUs7SUFDTCxNQUFNO0lBQ04sS0FBSztJQUNMLFFBQVE7SUFDUixPQUFPO0lBQ1AsTUFBTTtJQUNOLFNBQVM7Q0FDVixDQUFDLENBQUM7QUFFSCxzRUFBc0U7QUFDdEUsU0FBUyxlQUFlLENBQUMsTUFBYztJQUNyQyxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDbkMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFlLENBQUM7QUFDbEUsQ0FBQztBQUVELDRFQUE0RTtBQUM1RSxTQUFTLFFBQVEsQ0FBQyxHQUFXO0lBQzNCLElBQUksQ0FBQztRQUNILE1BQU0sSUFBSSxHQUNSLE9BQU8sTUFBTSxLQUFLLFdBQVcsSUFBSSxNQUFNLENBQUMsUUFBUTtZQUM5QyxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUFNO1lBQ3hCLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQztRQUN6QixNQUFNLE1BQU0sR0FBRyxJQUFJLEdBQUcsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDbEMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLENBQUMs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QyxDQUFDO1lBQ3pCLENBQUM7WUFDRCxLQUFLLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRTtnQkFDZixLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNyQixVQUFVLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQzFCLENBQUM7WUFDRCxRQUFRLEVBQUUsR0FBRyxFQUFFLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRTtTQUN0QyxDQUFDLENBQUM7UUFDSCxPQUFPLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNqQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgdHlwZSB7XG4gIEh0dHBFdmVudCxcbiAgSHR0cEhhbmRsZXIsXG4gIEh0dHBJbnRlcmNlcHRvcixcbiAgSHR0cEhhbmRsZXJGbixcbiAgSHR0cFJlcXVlc3QsXG59IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbi9odHRwJztcbmltcG9ydCB7IEh0dHBSZXNwb25zZSwgSHR0cEVycm9yUmVzcG9uc2UgfSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XG5pbXBvcnQgeyBPYnNlcnZhYmxlIH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBBbGxTdGFrIH0gZnJvbSAnQGFsbHN0YWsvanMnO1xuaW1wb3J0IHR5cGUgeyBIdHRwUmVxdWVzdEl0ZW0sIFNwYW4gfSBmcm9tICdAYWxsc3Rhay9qcyc7XG5cbi8qKiBIVFRQIHZlcmJzIHRoZSBjb3JlIGBIdHRwUmVxdWVzdEl0ZW1gIGFjY2VwdHMuICovXG50eXBlIEh0dHBNZXRob2QgPSBIdHRwUmVxdWVzdEl0ZW1bJ21ldGhvZCddO1xuXG5jb25zdCBLTk9XTl9NRVRIT0RTOiBSZWFkb25seVNldDxzdHJpbmc+ID0gbmV3IFNldChbXG4gICdHRVQnLFxuICAnUE9TVCcsXG4gICdQVVQnLFxuICAnREVMRVRFJyxcbiAgJ1BBVENIJyxcbiAgJ0hFQUQnLFxuICAnT1BUSU9OUycsXG5dKTtcblxuLyoqIE5vcm1hbGlzZSBhbiBBbmd1bGFyIHJlcXVlc3QgbWV0aG9kIGludG8gdGhlIGNvcmUgbWV0aG9kIHVuaW9uLiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplTWV0aG9kKG1ldGhvZDogc3RyaW5nKTogSHR0cE1ldGhvZCB7XG4gIGNvbnN0IHVwcGVyID0gbWV0aG9kLnRvVXBwZXJDYXNlKCk7XG4gIHJldHVybiAoS05PV05fTUVUSE9EUy5oYXModXBwZXIpID8gdXBwZXIgOiAnR0VUJykgYXMgSHR0cE1ldGhvZDtcbn1cblxuLyoqIFNwbGl0IGFuIG91dGJvdW5kIFVSTCBpbnRvIGBob3N0YCArIGBwYXRoYCwgdG9sZXJhdGluZyByZWxhdGl2ZSBVUkxzLiAqL1xuZnVuY3Rpb24gc3BsaXRVcmwodXJsOiBzdHJpbmcpOiB7IGhvc3Q6IHN0cmluZzsgcGF0aDogc3RyaW5nIH0ge1xuICB0cnkge1xuICAgIGNvbnN0IGJhc2UgPVxuICAgICAgdHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LmxvY2F0aW9uXG4gICAgICAgID8gd2luZG93LmxvY2F0aW9uLm9yaWdpblxuICAgICAgICA6ICdodHRwOi8vbG9jYWxob3N0JztcbiAgICBjb25zdCBwYXJzZWQgPSBuZXcgVVJMKHVybCwgYmFzZSk7XG4gICAgcmV0dXJuIHsgaG9zdDogcGFyc2VkLmhvc3QsIHBhdGg6IHBhcnNlZC5wYXRobmFtZSArIHBhcnNlZC5zZWFyY2ggfTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHsgaG9zdDogJycsIHBhdGg6IHVybCB9O1xuICB9XG59XG5cbi8qKlxuICogT3BlbiBhIHNwYW4gKyByZWNvcmQgYW4gb3V0Ym91bmQgcmVxdWVzdCBmb3IgYSBzaW5nbGUgYEh0dHBSZXF1ZXN0YCwgYW5kXG4gKiByZXR1cm4gdGhlIGxpZmVjeWNsZSBob29rcyB0aGUgaW50ZXJjZXB0b3IgKGNsYXNzIG9yIGZ1bmN0aW9uYWwpIHVzZXMgdG9cbiAqIGZpbmlzaCBpdC4gU2hhcmVkIHNvIHRoZSBjbGFzcy1iYXNlZCBhbmQgZnVuY3Rpb25hbCBpbnRlcmNlcHRvcnMgc3RheVxuICogYmVoYXZpb3VyYWxseSBpZGVudGljYWwuXG4gKi9cbmZ1bmN0aW9uIGluc3RydW1lbnRSZXF1ZXN0KHJlcTogSHR0cFJlcXVlc3Q8dW5rbm93bj4pOiB7XG4gIG9uUmVzcG9uc2U6IChzdGF0dXM6IG51bWJlcikgPT4gdm9pZDtcbiAgb25FcnJvcjogKGVycm9yOiB1bmtub3duKSA9PiB2b2lkO1xufSB7XG4gIGNvbnN0IHN0YXJ0ZWRBdCA9IERhdGUubm93KCk7XG4gIGNvbnN0IG1ldGhvZCA9IG5vcm1hbGl6ZU1ldGhvZChyZXEubWV0aG9kKTtcbiAgY29uc3QgeyBob3N0LCBwYXRoIH0gPSBzcGxpdFVybChyZXEudXJsV2l0aFBhcmFtcyk7XG4gIGxldCBzcGFuOiBTcGFuIHwgbnVsbCA9IG51bGw7XG5cbiAgdHJ5IHtcbiAgICBzcGFuID0gQWxsU3Rhay5zdGFydFNwYW4oJ2h0dHAuY2xpZW50Jywge1xuICAgICAgb3A6ICdodHRwLmNsaWVudCcsXG4gICAgICBkZXNjcmlwdGlvbjogYCR7cmVxLm1ldGhvZH0gJHtyZXEudXJsV2l0aFBhcmFtc31gLFxuICAgICAgYXR0cmlidXRlczoge1xuICAgICAgICAnYWxsc3Rhay5vcmlnaW4nOiAnYXV0by5odHRwLmFuZ3VsYXInLFxuICAgICAgICAnaHR0cC5tZXRob2QnOiByZXEubWV0aG9kLFxuICAgICAgICAnaHR0cC51cmwnOiByZXEudXJsV2l0aFBhcmFtcyxcbiAgICAgIH0sXG4gICAgfSk7XG4gIH0gY2F0Y2gge1xuICAgIC8vIE5ldmVyIGJsb2NrIHRoZSByZXF1ZXN0IG9uIG9ic2VydmFiaWxpdHkgZXJyb3JzLlxuICB9XG5cbiAgY29uc3QgZmluaXNoID0gKHN0YXR1c0NvZGU6IG51bWJlciwgc3RhdHVzOiAnb2snIHwgJ2Vycm9yJykgPT4ge1xuICAgIGNvbnN0IGR1cmF0aW9uTXMgPSBEYXRlLm5vdygpIC0gc3RhcnRlZEF0O1xuICAgIHRyeSB7XG4gICAgICBzcGFuPy5zZXRNZWFzdXJlbWVudCgnaHR0cC5kdXJhdGlvbl9tcycsIGR1cmF0aW9uTXMpO1xuICAgICAgc3Bhbj8uZmluaXNoKHN0YXR1cyk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBpZ25vcmUg4oCUIHNwYW4gbWF5IGFscmVhZHkgYmUgZmluaXNoZWRcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGl0ZW06IEh0dHBSZXF1ZXN0SXRlbSA9IHtcbiAgICAgICAgZGlyZWN0aW9uOiAnb3V0Ym91bmQnLFxuICAgICAgICBtZXRob2QsXG4gICAgICAgIGhvc3QsXG4gICAgICAgIHBhdGgsXG4gICAgICAgIHN0YXR1c0NvZGUsXG4gICAgICAgIGR1cmF0aW9uTXMsXG4gICAgICB9O1xuICAgICAgQWxsU3Rhay5jYXB0dXJlUmVxdWVzdChpdGVtKTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIGlnbm9yZSDigJQgcmVxdWVzdCByZWNvcmRpbmcgaXMgYmVzdC1lZmZvcnRcbiAgICB9XG4gIH07XG5cbiAgcmV0dXJuIHtcbiAgICBvblJlc3BvbnNlKHN0YXR1c0NvZGU6IG51bWJlcikge1xuICAgICAgZmluaXNoKHN0YXR1c0NvZGUsIHN0YXR1c0NvZGUgPj0gNDAwID8gJ2Vycm9yJyA6ICdvaycpO1xuICAgIH0sXG4gICAgb25FcnJvcihlcnJvcjogdW5rbm93bikge1xuICAgICAgY29uc3Qgc3RhdHVzQ29kZSA9IGVycm9yIGluc3RhbmNlb2YgSHR0cEVycm9yUmVzcG9uc2UgPyBlcnJvci5zdGF0dXMgOiAwO1xuICAgICAgZmluaXNoKHN0YXR1c0NvZGUsICdlcnJvcicpO1xuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogQ2xhc3MtYmFzZWQgYEh0dHBJbnRlcmNlcHRvcmAgdGhhdCByZWNvcmRzIGV2ZXJ5IG91dGJvdW5kIEhUVFAgcmVxdWVzdCBhbmRcbiAqIG9wZW5zIGEgYGh0dHAuY2xpZW50YCBzcGFuIGFyb3VuZCBpdC4gUmVnaXN0ZXIgaW4gdGhlIERJLXRva2VuIHN0eWxlIHVzZWQgYnlcbiAqIE5nTW9kdWxlIGFwcHMgYW5kIHN0YW5kYWxvbmUgYXBwcyB0aGF0IG9wdCBpbnRvIGB3aXRoSW50ZXJjZXB0b3JzRnJvbURpKClgOlxuICpcbiAqICAgaW1wb3J0IHsgSFRUUF9JTlRFUkNFUFRPUlMgfSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XG4gKiAgIGltcG9ydCB7IEFsbFN0YWtIdHRwSW50ZXJjZXB0b3IgfSBmcm9tICdAYWxsc3Rhay9hbmd1bGFyJztcbiAqXG4gKiAgIHByb3ZpZGVyczogW1xuICogICAgIHsgcHJvdmlkZTogSFRUUF9JTlRFUkNFUFRPUlMsIHVzZUNsYXNzOiBBbGxTdGFrSHR0cEludGVyY2VwdG9yLCBtdWx0aTogdHJ1ZSB9LFxuICogICBdXG4gKlxuICogVGhlIGludGVyY2VwdG9yIG5ldmVyIG11dGF0ZXMgdGhlIHJlcXVlc3QgYW5kIG5ldmVyIHN3YWxsb3dzIGVycm9ycyDigJQgaXRcbiAqIG9ubHkgb2JzZXJ2ZXMgdGhlIHN0cmVhbS5cbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIEFsbFN0YWtIdHRwSW50ZXJjZXB0b3IgaW1wbGVtZW50cyBIdHRwSW50ZXJjZXB0b3Ige1xuICBpbnRlcmNlcHQoXG4gICAgcmVxOiBIdHRwUmVxdWVzdDx1bmtub3duPixcbiAgICBuZXh0OiBIdHRwSGFuZGxlcixcbiAgKTogT2JzZXJ2YWJsZTxIdHRwRXZlbnQ8dW5rbm93bj4+IHtcbiAgICBjb25zdCBob29rcyA9IGluc3RydW1lbnRSZXF1ZXN0KHJlcSk7XG5cbiAgICByZXR1cm4gbmV3IE9ic2VydmFibGU8SHR0cEV2ZW50PHVua25vd24+Pigoc3Vic2NyaWJlcikgPT4ge1xuICAgICAgY29uc3Qgc3ViID0gbmV4dC5oYW5kbGUocmVxKS5zdWJzY3JpYmUoe1xuICAgICAgICBuZXh0OiAoZXZlbnQpID0+IHtcbiAgICAgICAgICBpZiAoZXZlbnQgaW5zdGFuY2VvZiBIdHRwUmVzcG9uc2UpIHtcbiAgICAgICAgICAgIGhvb2tzLm9uUmVzcG9uc2UoZXZlbnQuc3RhdHVzKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgc3Vic2NyaWJlci5uZXh0KGV2ZW50KTtcbiAgICAgICAgfSxcbiAgICAgICAgZXJyb3I6IChlcnJvcikgPT4ge1xuICAgICAgICAgIGhvb2tzLm9uRXJyb3IoZXJyb3IpO1xuICAgICAgICAgIHN1YnNjcmliZXIuZXJyb3IoZXJyb3IpO1xuICAgICAgICB9LFxuICAgICAgICBjb21wbGV0ZTogKCkgPT4gc3Vic2NyaWJlci5jb21wbGV0ZSgpLFxuICAgICAgfSk7XG4gICAgICByZXR1cm4gKCkgPT4gc3ViLnVuc3Vic2NyaWJlKCk7XG4gICAgfSk7XG4gIH1cbn1cblxuLyoqXG4gKiBGdW5jdGlvbmFsIGBIdHRwSW50ZXJjZXB0b3JgIGZvciBzdGFuZGFsb25lIGFwcHMgdXNpbmdcbiAqIGBwcm92aWRlSHR0cENsaWVudCh3aXRoSW50ZXJjZXB0b3JzKFthbGxTdGFrSHR0cEludGVyY2VwdG9yXSkpYC5cbiAqXG4gKiBCZWhhdmlvdXJhbGx5IGlkZW50aWNhbCB0byB7QGxpbmsgQWxsU3Rha0h0dHBJbnRlcmNlcHRvcn0uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhbGxTdGFrSHR0cEludGVyY2VwdG9yKFxuICByZXE6IEh0dHBSZXF1ZXN0PHVua25vd24+LFxuICBuZXh0OiBIdHRwSGFuZGxlckZuLFxuKTogT2JzZXJ2YWJsZTxIdHRwRXZlbnQ8dW5rbm93bj4+IHtcbiAgY29uc3QgaG9va3MgPSBpbnN0cnVtZW50UmVxdWVzdChyZXEpO1xuXG4gIHJldHVybiBuZXcgT2JzZXJ2YWJsZTxIdHRwRXZlbnQ8dW5rbm93bj4+KChzdWJzY3JpYmVyKSA9PiB7XG4gICAgY29uc3Qgc3ViID0gbmV4dChyZXEpLnN1YnNjcmliZSh7XG4gICAgICBuZXh0OiAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgSHR0cFJlc3BvbnNlKSB7XG4gICAgICAgICAgaG9va3Mub25SZXNwb25zZShldmVudC5zdGF0dXMpO1xuICAgICAgICB9XG4gICAgICAgIHN1YnNjcmliZXIubmV4dChldmVudCk7XG4gICAgICB9LFxuICAgICAgZXJyb3I6IChlcnJvcikgPT4ge1xuICAgICAgICBob29rcy5vbkVycm9yKGVycm9yKTtcbiAgICAgICAgc3Vic2NyaWJlci5lcnJvcihlcnJvcik7XG4gICAgICB9LFxuICAgICAgY29tcGxldGU6ICgpID0+IHN1YnNjcmliZXIuY29tcGxldGUoKSxcbiAgICB9KTtcbiAgICByZXR1cm4gKCkgPT4gc3ViLnVuc3Vic2NyaWJlKCk7XG4gIH0pO1xufVxuIl19
|