@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
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { AllStak } from '@allstak/js';
|
|
2
|
+
export { AllStak, Scope, Session, SessionTracker, Span, WebVitalsModule, consoleIntegration, databaseIntegration, dedupeIntegration, defineIntegration, eventFiltersIntegration, httpClientIntegration, inboundFiltersIntegration, isWebVitalsSupported } from '@allstak/js';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { Injectable, Optional, Input, Directive, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, ErrorHandler, APP_INITIALIZER, inject, InjectionToken, NgModule } from '@angular/core';
|
|
5
|
+
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
|
|
6
|
+
import { Observable } from 'rxjs';
|
|
7
|
+
import * as i1 from '@angular/router';
|
|
8
|
+
import { NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
|
|
9
|
+
|
|
10
|
+
// AUTO-GENERATED by scripts/gen-version.mjs — do not edit by hand.
|
|
11
|
+
const SDK_NAME = 'allstak-angular';
|
|
12
|
+
const SDK_VERSION = "0.1.0";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the AllStak SDK for an Angular application.
|
|
16
|
+
*
|
|
17
|
+
* This is a thin shim over `AllStak.init` from `@allstak/js` that stamps the
|
|
18
|
+
* wrapper identity (`sdkName: 'allstak-angular'`, `sdkVersion`) so the backend
|
|
19
|
+
* can tell Angular traffic apart from plain JS traffic. Everything else —
|
|
20
|
+
* buffering, sampling, session health, offline queue, PII scrubbing — is
|
|
21
|
+
* delegated to the underlying core client.
|
|
22
|
+
*
|
|
23
|
+
* Call this once at app boot in `main.ts`, BEFORE `bootstrapApplication(...)`
|
|
24
|
+
* (standalone) or `platformBrowserDynamic().bootstrapModule(...)` (NgModule),
|
|
25
|
+
* so the global `ErrorHandler` and router instrumentation see a live client:
|
|
26
|
+
*
|
|
27
|
+
* import { init } from '@allstak/angular';
|
|
28
|
+
*
|
|
29
|
+
* init({
|
|
30
|
+
* apiKey: environment.allstakApiKey,
|
|
31
|
+
* environment: environment.production ? 'production' : 'development',
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* bootstrapApplication(AppComponent, appConfig);
|
|
35
|
+
*/
|
|
36
|
+
function init(config) {
|
|
37
|
+
return AllStak.init({
|
|
38
|
+
...config,
|
|
39
|
+
sdkName: config.sdkName ?? SDK_NAME,
|
|
40
|
+
sdkVersion: config.sdkVersion ?? SDK_VERSION,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_OPTIONS = {
|
|
45
|
+
logErrors: true,
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Unwrap the common Angular/zone.js error envelopes so we capture the real
|
|
49
|
+
* underlying error rather than the wrapper:
|
|
50
|
+
*
|
|
51
|
+
* - Angular wraps errors thrown during change detection as
|
|
52
|
+
* `{ ngOriginalError }` / `{ originalError }`.
|
|
53
|
+
* - `HttpErrorResponse` carries the server payload on `.error`.
|
|
54
|
+
* - DOM `ErrorEvent` carries the real error on `.error`.
|
|
55
|
+
* - Rejected promises may surface as `{ rejection }`.
|
|
56
|
+
*/
|
|
57
|
+
function defaultExtractor(error) {
|
|
58
|
+
if (error && typeof error === 'object') {
|
|
59
|
+
const candidate = error;
|
|
60
|
+
// PromiseRejectionEvent-style wrapping from zone.js.
|
|
61
|
+
if ('rejection' in candidate && candidate['rejection'] != null) {
|
|
62
|
+
return defaultExtractor(candidate['rejection']);
|
|
63
|
+
}
|
|
64
|
+
// Angular wraps the thrown value during change detection.
|
|
65
|
+
if ('ngOriginalError' in candidate && candidate['ngOriginalError'] != null) {
|
|
66
|
+
return defaultExtractor(candidate['ngOriginalError']);
|
|
67
|
+
}
|
|
68
|
+
if ('originalError' in candidate && candidate['originalError'] != null) {
|
|
69
|
+
return defaultExtractor(candidate['originalError']);
|
|
70
|
+
}
|
|
71
|
+
// DOM ErrorEvent / HttpErrorResponse expose the real error on `.error`.
|
|
72
|
+
if ('error' in candidate && candidate['error'] instanceof Error) {
|
|
73
|
+
return candidate['error'];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return error;
|
|
77
|
+
}
|
|
78
|
+
/** Coerce any extracted value into an `Error` so `captureException` is happy. */
|
|
79
|
+
function toError(value) {
|
|
80
|
+
if (value instanceof Error)
|
|
81
|
+
return value;
|
|
82
|
+
if (typeof value === 'string')
|
|
83
|
+
return new Error(value);
|
|
84
|
+
try {
|
|
85
|
+
return new Error(JSON.stringify(value));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return new Error(String(value));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* AllStak's implementation of Angular's `ErrorHandler`. Captures every error
|
|
93
|
+
* routed through Angular's global error handling — uncaught render/lifecycle
|
|
94
|
+
* errors, change-detection errors, and (when zone.js is present) unhandled
|
|
95
|
+
* promise rejections.
|
|
96
|
+
*
|
|
97
|
+
* Wired manually as `{ provide: ErrorHandler, useValue: createErrorHandler() }`
|
|
98
|
+
* or via {@link provideAllStakErrorHandler}.
|
|
99
|
+
*/
|
|
100
|
+
class AllStakErrorHandler {
|
|
101
|
+
options;
|
|
102
|
+
constructor(options = {}) {
|
|
103
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
104
|
+
}
|
|
105
|
+
handleError(error) {
|
|
106
|
+
const extracted = this.options.extractor
|
|
107
|
+
? this.options.extractor(error, defaultExtractor)
|
|
108
|
+
: defaultExtractor(error);
|
|
109
|
+
const err = toError(extracted);
|
|
110
|
+
try {
|
|
111
|
+
AllStak.captureException(err, { framework: 'angular' });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Never let our handler break the host's error path.
|
|
115
|
+
}
|
|
116
|
+
if (this.options.logErrors !== false) {
|
|
117
|
+
// Preserve Angular's default of surfacing the original (pre-unwrap)
|
|
118
|
+
// value so stack traces and zone context stay intact in the console.
|
|
119
|
+
// eslint-disable-next-line no-console
|
|
120
|
+
console.error(error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
|
|
124
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler });
|
|
125
|
+
}
|
|
126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakErrorHandler, decorators: [{
|
|
127
|
+
type: Injectable
|
|
128
|
+
}], ctorParameters: () => [{ type: undefined }] });
|
|
129
|
+
/**
|
|
130
|
+
* Factory returning an Angular `ErrorHandler` that forwards to
|
|
131
|
+
* `AllStak.captureException`.
|
|
132
|
+
*
|
|
133
|
+
* Standalone (`app.config.ts`):
|
|
134
|
+
*
|
|
135
|
+
* import { ErrorHandler } from '@angular/core';
|
|
136
|
+
* import { createErrorHandler } from '@allstak/angular';
|
|
137
|
+
*
|
|
138
|
+
* export const appConfig = {
|
|
139
|
+
* providers: [{ provide: ErrorHandler, useValue: createErrorHandler() }],
|
|
140
|
+
* };
|
|
141
|
+
*
|
|
142
|
+
* NgModule (`AppModule`):
|
|
143
|
+
*
|
|
144
|
+
* @NgModule({
|
|
145
|
+
* providers: [{ provide: ErrorHandler, useValue: createErrorHandler() }],
|
|
146
|
+
* })
|
|
147
|
+
* export class AppModule {}
|
|
148
|
+
*/
|
|
149
|
+
function createErrorHandler(options) {
|
|
150
|
+
return new AllStakErrorHandler(options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const KNOWN_METHODS = new Set([
|
|
154
|
+
'GET',
|
|
155
|
+
'POST',
|
|
156
|
+
'PUT',
|
|
157
|
+
'DELETE',
|
|
158
|
+
'PATCH',
|
|
159
|
+
'HEAD',
|
|
160
|
+
'OPTIONS',
|
|
161
|
+
]);
|
|
162
|
+
/** Normalise an Angular request method into the core method union. */
|
|
163
|
+
function normalizeMethod(method) {
|
|
164
|
+
const upper = method.toUpperCase();
|
|
165
|
+
return (KNOWN_METHODS.has(upper) ? upper : 'GET');
|
|
166
|
+
}
|
|
167
|
+
/** Split an outbound URL into `host` + `path`, tolerating relative URLs. */
|
|
168
|
+
function splitUrl(url) {
|
|
169
|
+
try {
|
|
170
|
+
const base = typeof window !== 'undefined' && window.location
|
|
171
|
+
? window.location.origin
|
|
172
|
+
: 'http://localhost';
|
|
173
|
+
const parsed = new URL(url, base);
|
|
174
|
+
return { host: parsed.host, path: parsed.pathname + parsed.search };
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return { host: '', path: url };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Open a span + record an outbound request for a single `HttpRequest`, and
|
|
182
|
+
* return the lifecycle hooks the interceptor (class or functional) uses to
|
|
183
|
+
* finish it. Shared so the class-based and functional interceptors stay
|
|
184
|
+
* behaviourally identical.
|
|
185
|
+
*/
|
|
186
|
+
function instrumentRequest(req) {
|
|
187
|
+
const startedAt = Date.now();
|
|
188
|
+
const method = normalizeMethod(req.method);
|
|
189
|
+
const { host, path } = splitUrl(req.urlWithParams);
|
|
190
|
+
let span = null;
|
|
191
|
+
try {
|
|
192
|
+
span = AllStak.startSpan('http.client', {
|
|
193
|
+
op: 'http.client',
|
|
194
|
+
description: `${req.method} ${req.urlWithParams}`,
|
|
195
|
+
attributes: {
|
|
196
|
+
'allstak.origin': 'auto.http.angular',
|
|
197
|
+
'http.method': req.method,
|
|
198
|
+
'http.url': req.urlWithParams,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Never block the request on observability errors.
|
|
204
|
+
}
|
|
205
|
+
const finish = (statusCode, status) => {
|
|
206
|
+
const durationMs = Date.now() - startedAt;
|
|
207
|
+
try {
|
|
208
|
+
span?.setMeasurement('http.duration_ms', durationMs);
|
|
209
|
+
span?.finish(status);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// ignore — span may already be finished
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const item = {
|
|
216
|
+
direction: 'outbound',
|
|
217
|
+
method,
|
|
218
|
+
host,
|
|
219
|
+
path,
|
|
220
|
+
statusCode,
|
|
221
|
+
durationMs,
|
|
222
|
+
};
|
|
223
|
+
AllStak.captureRequest(item);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// ignore — request recording is best-effort
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
onResponse(statusCode) {
|
|
231
|
+
finish(statusCode, statusCode >= 400 ? 'error' : 'ok');
|
|
232
|
+
},
|
|
233
|
+
onError(error) {
|
|
234
|
+
const statusCode = error instanceof HttpErrorResponse ? error.status : 0;
|
|
235
|
+
finish(statusCode, 'error');
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Class-based `HttpInterceptor` that records every outbound HTTP request and
|
|
241
|
+
* opens a `http.client` span around it. Register in the DI-token style used by
|
|
242
|
+
* NgModule apps and standalone apps that opt into `withInterceptorsFromDi()`:
|
|
243
|
+
*
|
|
244
|
+
* import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
245
|
+
* import { AllStakHttpInterceptor } from '@allstak/angular';
|
|
246
|
+
*
|
|
247
|
+
* providers: [
|
|
248
|
+
* { provide: HTTP_INTERCEPTORS, useClass: AllStakHttpInterceptor, multi: true },
|
|
249
|
+
* ]
|
|
250
|
+
*
|
|
251
|
+
* The interceptor never mutates the request and never swallows errors — it
|
|
252
|
+
* only observes the stream.
|
|
253
|
+
*/
|
|
254
|
+
class AllStakHttpInterceptor {
|
|
255
|
+
intercept(req, next) {
|
|
256
|
+
const hooks = instrumentRequest(req);
|
|
257
|
+
return new Observable((subscriber) => {
|
|
258
|
+
const sub = next.handle(req).subscribe({
|
|
259
|
+
next: (event) => {
|
|
260
|
+
if (event instanceof HttpResponse) {
|
|
261
|
+
hooks.onResponse(event.status);
|
|
262
|
+
}
|
|
263
|
+
subscriber.next(event);
|
|
264
|
+
},
|
|
265
|
+
error: (error) => {
|
|
266
|
+
hooks.onError(error);
|
|
267
|
+
subscriber.error(error);
|
|
268
|
+
},
|
|
269
|
+
complete: () => subscriber.complete(),
|
|
270
|
+
});
|
|
271
|
+
return () => sub.unsubscribe();
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
275
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor });
|
|
276
|
+
}
|
|
277
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakHttpInterceptor, decorators: [{
|
|
278
|
+
type: Injectable
|
|
279
|
+
}] });
|
|
280
|
+
/**
|
|
281
|
+
* Functional `HttpInterceptor` for standalone apps using
|
|
282
|
+
* `provideHttpClient(withInterceptors([allStakHttpInterceptor]))`.
|
|
283
|
+
*
|
|
284
|
+
* Behaviourally identical to {@link AllStakHttpInterceptor}.
|
|
285
|
+
*/
|
|
286
|
+
function allStakHttpInterceptor(req, next) {
|
|
287
|
+
const hooks = instrumentRequest(req);
|
|
288
|
+
return new Observable((subscriber) => {
|
|
289
|
+
const sub = next(req).subscribe({
|
|
290
|
+
next: (event) => {
|
|
291
|
+
if (event instanceof HttpResponse) {
|
|
292
|
+
hooks.onResponse(event.status);
|
|
293
|
+
}
|
|
294
|
+
subscriber.next(event);
|
|
295
|
+
},
|
|
296
|
+
error: (error) => {
|
|
297
|
+
hooks.onError(error);
|
|
298
|
+
subscriber.error(error);
|
|
299
|
+
},
|
|
300
|
+
complete: () => subscriber.complete(),
|
|
301
|
+
});
|
|
302
|
+
return () => sub.unsubscribe();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Router-aware navigation instrumentation.
|
|
308
|
+
*
|
|
309
|
+
* `TraceService` subscribes to Angular `Router` events and opens a
|
|
310
|
+
* `navigation` span per route change — `NavigationStart` opens it,
|
|
311
|
+
* `NavigationEnd` finishes it `ok`, and `NavigationCancel` / `NavigationError`
|
|
312
|
+
* finish it `error`. A navigation breadcrumb is recorded alongside each span.
|
|
313
|
+
*
|
|
314
|
+
* Because Angular only constructs a service when something injects it, the
|
|
315
|
+
* service must be *force-instantiated* for its Router subscription to run.
|
|
316
|
+
* Use {@link provideAllStakRouterInstrumentation} (standalone) or inject it in
|
|
317
|
+
* your `AppComponent` constructor (NgModule) — see those helpers for wiring.
|
|
318
|
+
*
|
|
319
|
+
* `Router` is `@Optional()` so apps without `@angular/router` (which is an
|
|
320
|
+
* optional peer dependency) can still construct the service without error; it
|
|
321
|
+
* simply becomes a no-op.
|
|
322
|
+
*/
|
|
323
|
+
class TraceService {
|
|
324
|
+
router;
|
|
325
|
+
activeNavSpan = null;
|
|
326
|
+
subscription = null;
|
|
327
|
+
constructor(router) {
|
|
328
|
+
this.router = router;
|
|
329
|
+
if (!this.router)
|
|
330
|
+
return;
|
|
331
|
+
this.subscription = this.router.events.subscribe((event) => {
|
|
332
|
+
this.handleEvent(event);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
handleEvent(event) {
|
|
336
|
+
try {
|
|
337
|
+
if (event instanceof NavigationStart) {
|
|
338
|
+
this.onNavigationStart(event.url);
|
|
339
|
+
}
|
|
340
|
+
else if (event instanceof NavigationEnd) {
|
|
341
|
+
this.finishActive('ok');
|
|
342
|
+
}
|
|
343
|
+
else if (event instanceof NavigationCancel ||
|
|
344
|
+
event instanceof NavigationError) {
|
|
345
|
+
this.finishActive('error');
|
|
346
|
+
if (event instanceof NavigationError) {
|
|
347
|
+
AllStak.captureException(event.error instanceof Error
|
|
348
|
+
? event.error
|
|
349
|
+
: new Error(String(event.error)), { framework: 'angular', source: '@angular/router' });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// ignore — never block navigation on observability errors
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
onNavigationStart(url) {
|
|
358
|
+
// A new navigation supersedes any span still open from a redirect chain.
|
|
359
|
+
this.finishActive('ok');
|
|
360
|
+
AllStak.addBreadcrumb({
|
|
361
|
+
type: 'navigation',
|
|
362
|
+
message: url,
|
|
363
|
+
level: 'info',
|
|
364
|
+
data: { url },
|
|
365
|
+
});
|
|
366
|
+
this.activeNavSpan = AllStak.startSpan('navigation', {
|
|
367
|
+
op: 'navigation',
|
|
368
|
+
description: url,
|
|
369
|
+
attributes: {
|
|
370
|
+
'allstak.origin': 'auto.navigation.angular',
|
|
371
|
+
'route.url': url,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
finishActive(status) {
|
|
376
|
+
if (!this.activeNavSpan)
|
|
377
|
+
return;
|
|
378
|
+
try {
|
|
379
|
+
this.activeNavSpan.finish(status);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// ignore — span may already be finished
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
this.activeNavSpan = null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
ngOnDestroy() {
|
|
389
|
+
this.subscription?.unsubscribe();
|
|
390
|
+
this.finishActive('ok');
|
|
391
|
+
}
|
|
392
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceService, deps: [{ token: i1.Router, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
393
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceService, providedIn: 'root' });
|
|
394
|
+
}
|
|
395
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceService, decorators: [{
|
|
396
|
+
type: Injectable,
|
|
397
|
+
args: [{ providedIn: 'root' }]
|
|
398
|
+
}], ctorParameters: () => [{ type: i1.Router, decorators: [{
|
|
399
|
+
type: Optional
|
|
400
|
+
}] }] });
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* `[trace]` template directive for component render timing.
|
|
404
|
+
*
|
|
405
|
+
* <app-checkout trace="checkout"></app-checkout>
|
|
406
|
+
*
|
|
407
|
+
* Opens a `ui.angular.init` span when the host component initialises
|
|
408
|
+
* (`ngOnInit`) and finishes it once the component's view has been fully
|
|
409
|
+
* initialised (`ngAfterViewInit`). The span is named after the directive's
|
|
410
|
+
* `trace` value — required, because component class names are not reliable in
|
|
411
|
+
* minified production builds.
|
|
412
|
+
*
|
|
413
|
+
* Standalone components import the directive directly; module-based apps get
|
|
414
|
+
* it via {@link AllStakTraceModule}.
|
|
415
|
+
*/
|
|
416
|
+
class TraceDirective {
|
|
417
|
+
/**
|
|
418
|
+
* Human-readable name for the traced component. Used as the span
|
|
419
|
+
* description. Required so spans stay meaningful after minification.
|
|
420
|
+
*/
|
|
421
|
+
componentName = '';
|
|
422
|
+
span = null;
|
|
423
|
+
ngOnInit() {
|
|
424
|
+
const name = this.componentName || 'unnamed';
|
|
425
|
+
try {
|
|
426
|
+
this.span = AllStak.startSpan('ui.angular.init', {
|
|
427
|
+
op: 'ui.angular.init',
|
|
428
|
+
description: name,
|
|
429
|
+
attributes: {
|
|
430
|
+
'allstak.origin': 'auto.ui.angular',
|
|
431
|
+
'angular.component': name,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// Never let render tracing break a component.
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
ngAfterViewInit() {
|
|
440
|
+
try {
|
|
441
|
+
this.span?.finish('ok');
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// ignore — span may already be finished
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
this.span = null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
451
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: TraceDirective, isStandalone: true, selector: "[trace]", inputs: { componentName: ["trace", "componentName"] }, ngImport: i0 });
|
|
452
|
+
}
|
|
453
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TraceDirective, decorators: [{
|
|
454
|
+
type: Directive,
|
|
455
|
+
args: [{
|
|
456
|
+
selector: '[trace]',
|
|
457
|
+
standalone: true,
|
|
458
|
+
}]
|
|
459
|
+
}], propDecorators: { componentName: [{
|
|
460
|
+
type: Input,
|
|
461
|
+
args: ['trace']
|
|
462
|
+
}] } });
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Class decorator that instruments a component's init lifecycle. Wraps
|
|
466
|
+
* `ngOnInit` so a `ui.angular.init` span is opened on init and finished on
|
|
467
|
+
* `ngAfterViewInit` (or immediately after `ngOnInit` if the component has no
|
|
468
|
+
* `ngAfterViewInit`).
|
|
469
|
+
*
|
|
470
|
+
* @TraceClassDecorator({ name: 'CheckoutComponent' })
|
|
471
|
+
* @Component({ ... })
|
|
472
|
+
* export class CheckoutComponent implements OnInit {}
|
|
473
|
+
*/
|
|
474
|
+
function TraceClassDecorator(options) {
|
|
475
|
+
return function (target) {
|
|
476
|
+
const proto = target.prototype;
|
|
477
|
+
const SPAN_KEY = '__allstak_trace_class_span__';
|
|
478
|
+
const originalOnInit = proto.ngOnInit;
|
|
479
|
+
const originalAfterViewInit = proto.ngAfterViewInit;
|
|
480
|
+
proto.ngOnInit = function () {
|
|
481
|
+
try {
|
|
482
|
+
this[SPAN_KEY] = AllStak.startSpan('ui.angular.init', {
|
|
483
|
+
op: 'ui.angular.init',
|
|
484
|
+
description: options.name,
|
|
485
|
+
attributes: {
|
|
486
|
+
'allstak.origin': 'auto.ui.angular',
|
|
487
|
+
'angular.component': options.name,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// ignore
|
|
493
|
+
}
|
|
494
|
+
originalOnInit?.call(this);
|
|
495
|
+
// If there is no ngAfterViewInit, close the span right after init.
|
|
496
|
+
if (!originalAfterViewInit && !proto.ngAfterViewInit) {
|
|
497
|
+
finishSpan(this[SPAN_KEY]);
|
|
498
|
+
this[SPAN_KEY] = undefined;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
proto.ngAfterViewInit = function () {
|
|
502
|
+
originalAfterViewInit?.call(this);
|
|
503
|
+
finishSpan(this[SPAN_KEY]);
|
|
504
|
+
this[SPAN_KEY] = undefined;
|
|
505
|
+
};
|
|
506
|
+
return target;
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/** Alias for the alternate naming. */
|
|
510
|
+
const TraceClass = TraceClassDecorator;
|
|
511
|
+
/**
|
|
512
|
+
* Method decorator emitting a point-in-time `ui.angular.[hook]` span around a
|
|
513
|
+
* single lifecycle method (e.g. `ngOnInit`, `ngOnChanges`, `ngAfterViewInit`).
|
|
514
|
+
*
|
|
515
|
+
* class Foo {
|
|
516
|
+
* @TraceMethodDecorator({ name: 'Foo.ngOnInit' })
|
|
517
|
+
* ngOnInit() {}
|
|
518
|
+
* }
|
|
519
|
+
*/
|
|
520
|
+
function TraceMethodDecorator(options) {
|
|
521
|
+
return function (_target, propertyKey, descriptor) {
|
|
522
|
+
const original = descriptor.value;
|
|
523
|
+
descriptor.value = function (...args) {
|
|
524
|
+
let span = null;
|
|
525
|
+
try {
|
|
526
|
+
span = AllStak.startSpan(`ui.angular.${propertyKey}`, {
|
|
527
|
+
op: `ui.angular.${propertyKey}`,
|
|
528
|
+
description: options.name,
|
|
529
|
+
attributes: {
|
|
530
|
+
'allstak.origin': 'auto.ui.angular',
|
|
531
|
+
'angular.hook': propertyKey,
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
// ignore
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
return original?.apply(this, args);
|
|
540
|
+
}
|
|
541
|
+
finally {
|
|
542
|
+
finishSpan(span ?? undefined);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
return descriptor;
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/** Alias for the alternate naming. */
|
|
549
|
+
const TraceMethod = TraceMethodDecorator;
|
|
550
|
+
function finishSpan(span) {
|
|
551
|
+
try {
|
|
552
|
+
span?.finish('ok');
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
// ignore — span may already be finished
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Bootstrap the AllStak SDK from a standalone app's `appConfig.providers`.
|
|
561
|
+
*
|
|
562
|
+
* import { provideAllStak } from '@allstak/angular';
|
|
563
|
+
*
|
|
564
|
+
* export const appConfig: ApplicationConfig = {
|
|
565
|
+
* providers: [
|
|
566
|
+
* provideAllStak({ apiKey: environment.allstakApiKey }),
|
|
567
|
+
* ],
|
|
568
|
+
* };
|
|
569
|
+
*
|
|
570
|
+
* This calls {@link init} during DI bootstrap. For the global `ErrorHandler`
|
|
571
|
+
* to catch errors thrown *during bootstrap itself*, prefer calling `init(...)`
|
|
572
|
+
* directly in `main.ts` before `bootstrapApplication`; this provider is the
|
|
573
|
+
* convenient alternative for apps that initialise inside the DI graph.
|
|
574
|
+
*/
|
|
575
|
+
function provideAllStak(config) {
|
|
576
|
+
return makeEnvironmentProviders([
|
|
577
|
+
{
|
|
578
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
579
|
+
multi: true,
|
|
580
|
+
useValue: () => {
|
|
581
|
+
init(config);
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
]);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Provide the AllStak `ErrorHandler` (overriding Angular's default) so every
|
|
588
|
+
* error routed through Angular's global error handling is captured.
|
|
589
|
+
*
|
|
590
|
+
* providers: [provideAllStakErrorHandler()]
|
|
591
|
+
*
|
|
592
|
+
* Equivalent to `{ provide: ErrorHandler, useValue: createErrorHandler() }`.
|
|
593
|
+
*/
|
|
594
|
+
function provideAllStakErrorHandler(options) {
|
|
595
|
+
return { provide: ErrorHandler, useValue: createErrorHandler(options) };
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Register and force-instantiate {@link TraceService} so its Router
|
|
599
|
+
* subscription runs and a navigation span opens per route change.
|
|
600
|
+
*
|
|
601
|
+
* providers: [provideRouter(routes), provideAllStakRouterInstrumentation()]
|
|
602
|
+
*
|
|
603
|
+
* `TraceService` is `providedIn: 'root'`, but Angular only constructs a
|
|
604
|
+
* service when something injects it. The `APP_INITIALIZER` below injects it
|
|
605
|
+
* during bootstrap, guaranteeing the subscription is live before the first
|
|
606
|
+
* navigation completes.
|
|
607
|
+
*/
|
|
608
|
+
function provideAllStakRouterInstrumentation() {
|
|
609
|
+
return makeEnvironmentProviders([
|
|
610
|
+
TraceService,
|
|
611
|
+
{
|
|
612
|
+
provide: APP_INITIALIZER,
|
|
613
|
+
multi: true,
|
|
614
|
+
useFactory: () => {
|
|
615
|
+
// Force-instantiate the service; the factory itself is a no-op.
|
|
616
|
+
inject(TraceService);
|
|
617
|
+
return () => undefined;
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
]);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** DI token carrying the config passed to {@link AllStakModule.forRoot}. */
|
|
624
|
+
const ALLSTAK_CONFIG = new InjectionToken('ALLSTAK_CONFIG');
|
|
625
|
+
/**
|
|
626
|
+
* NgModule that exposes {@link TraceDirective} for module-based apps (or
|
|
627
|
+
* standalone components that prefer NgModule-style `imports`):
|
|
628
|
+
*
|
|
629
|
+
* @NgModule({ imports: [AllStakTraceModule] })
|
|
630
|
+
* export class AppModule {}
|
|
631
|
+
*
|
|
632
|
+
* <app-checkout trace="checkout"></app-checkout>
|
|
633
|
+
*
|
|
634
|
+
* `TraceDirective` is a standalone directive, so it is imported (not declared)
|
|
635
|
+
* and re-exported.
|
|
636
|
+
*/
|
|
637
|
+
class AllStakTraceModule {
|
|
638
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
639
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule, imports: [TraceDirective], exports: [TraceDirective] });
|
|
640
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule });
|
|
641
|
+
}
|
|
642
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakTraceModule, decorators: [{
|
|
643
|
+
type: NgModule,
|
|
644
|
+
args: [{
|
|
645
|
+
imports: [TraceDirective],
|
|
646
|
+
exports: [TraceDirective],
|
|
647
|
+
}]
|
|
648
|
+
}] });
|
|
649
|
+
/**
|
|
650
|
+
* Root NgModule for module-based Angular apps.
|
|
651
|
+
*
|
|
652
|
+
* import { AllStakModule } from '@allstak/angular';
|
|
653
|
+
*
|
|
654
|
+
* @NgModule({
|
|
655
|
+
* imports: [
|
|
656
|
+
* BrowserModule,
|
|
657
|
+
* AllStakModule.forRoot({ apiKey: environment.allstakApiKey }),
|
|
658
|
+
* ],
|
|
659
|
+
* })
|
|
660
|
+
* export class AppModule {}
|
|
661
|
+
*
|
|
662
|
+
* `forRoot(config)`:
|
|
663
|
+
* - calls {@link init} during bootstrap (via `APP_INITIALIZER`);
|
|
664
|
+
* - overrides the global `ErrorHandler` with the AllStak handler;
|
|
665
|
+
* - force-instantiates {@link TraceService} so router navigation spans open;
|
|
666
|
+
* - exposes {@link TraceDirective} via {@link AllStakTraceModule}.
|
|
667
|
+
*
|
|
668
|
+
* Call `forRoot` exactly once, in your root module. Feature modules that only
|
|
669
|
+
* need the `[trace]` directive can import {@link AllStakTraceModule} directly.
|
|
670
|
+
*/
|
|
671
|
+
class AllStakModule {
|
|
672
|
+
// Inject TraceService here so importing the module force-instantiates it and
|
|
673
|
+
// its Router subscription runs. `@Optional` keeps router-less apps working.
|
|
674
|
+
constructor(_trace) { }
|
|
675
|
+
static forRoot(config) {
|
|
676
|
+
return {
|
|
677
|
+
ngModule: AllStakModule,
|
|
678
|
+
providers: [
|
|
679
|
+
{ provide: ALLSTAK_CONFIG, useValue: config },
|
|
680
|
+
{ provide: ErrorHandler, useValue: createErrorHandler() },
|
|
681
|
+
TraceService,
|
|
682
|
+
{
|
|
683
|
+
provide: APP_INITIALIZER,
|
|
684
|
+
multi: true,
|
|
685
|
+
useFactory: appInitializerFactory,
|
|
686
|
+
deps: [ALLSTAK_CONFIG, TraceService],
|
|
687
|
+
},
|
|
688
|
+
],
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, deps: [{ token: TraceService, optional: true }], target: i0.ɵɵFactoryTarget.NgModule });
|
|
692
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, imports: [AllStakTraceModule], exports: [AllStakTraceModule] });
|
|
693
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, imports: [AllStakTraceModule, AllStakTraceModule] });
|
|
694
|
+
}
|
|
695
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AllStakModule, decorators: [{
|
|
696
|
+
type: NgModule,
|
|
697
|
+
args: [{
|
|
698
|
+
imports: [AllStakTraceModule],
|
|
699
|
+
exports: [AllStakTraceModule],
|
|
700
|
+
}]
|
|
701
|
+
}], ctorParameters: () => [{ type: TraceService, decorators: [{
|
|
702
|
+
type: Optional
|
|
703
|
+
}] }] });
|
|
704
|
+
/**
|
|
705
|
+
* `APP_INITIALIZER` factory: initialises the SDK from the injected config and
|
|
706
|
+
* touches `TraceService` so it is constructed (its constructor wires the
|
|
707
|
+
* Router subscription). Declared as a named function so ng-packagr's partial
|
|
708
|
+
* compiler can statically reference it (no arrow-in-decorator-metadata error).
|
|
709
|
+
*/
|
|
710
|
+
function appInitializerFactory(config,
|
|
711
|
+
// TraceService is injected via `deps` purely to force its construction.
|
|
712
|
+
_trace) {
|
|
713
|
+
return () => {
|
|
714
|
+
init(config);
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* AllStak for Angular.
|
|
720
|
+
*
|
|
721
|
+
* Public surface:
|
|
722
|
+
* - `init` — bootstrap (delegates to @allstak/js with Angular tagging)
|
|
723
|
+
* - `createErrorHandler` / `AllStakErrorHandler` — Angular `ErrorHandler` forwarding to captureException
|
|
724
|
+
* - `AllStakHttpInterceptor` / `allStakHttpInterceptor` — records outbound requests + opens spans
|
|
725
|
+
* - `TraceService` — Router-aware navigation span instrumentation
|
|
726
|
+
* - `TraceDirective` / `AllStakTraceModule` — `[trace]` component render-timing directive
|
|
727
|
+
* - `TraceClassDecorator` / `TraceMethodDecorator` — lifecycle-span decorators
|
|
728
|
+
* - `provideAllStak` / `provideAllStakErrorHandler` / `provideAllStakRouterInstrumentation`
|
|
729
|
+
* — standalone provider functions
|
|
730
|
+
* - `AllStakModule.forRoot(config)` — NgModule registration for module-based apps
|
|
731
|
+
* - re-exports — every top-level @allstak/js export
|
|
732
|
+
*/
|
|
733
|
+
// Bootstrap.
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Generated bundle index. Do not edit.
|
|
737
|
+
*/
|
|
738
|
+
|
|
739
|
+
export { ALLSTAK_CONFIG, AllStakErrorHandler, AllStakHttpInterceptor, AllStakModule, AllStakTraceModule, SDK_NAME, SDK_VERSION, TraceClass, TraceClassDecorator, TraceDirective, TraceMethod, TraceMethodDecorator, TraceService, allStakHttpInterceptor, appInitializerFactory, createErrorHandler, init, provideAllStak, provideAllStakErrorHandler, provideAllStakRouterInstrumentation };
|
|
740
|
+
//# sourceMappingURL=allstak-angular.mjs.map
|