Skip to content

Commit 07b8e95

Browse files
arielbackenrothcrisbeto
authored andcommitted
refactor(http): add hooks for propagating traces across XHR callbacks.
Enables propagating a trace across XHR callbacks by providing a hook for wrapping the callback with a function bound to the send trace context.
1 parent 763db3a commit 07b8e95

File tree

3 files changed

+35
-12
lines changed

3 files changed

+35
-12
lines changed

packages/common/http/src/xhr.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88

99
import {XhrFactory} from '../../index';
1010
import {
11+
inject,
1112
Injectable,
1213
ɵRuntimeError as RuntimeError,
1314
ɵformatRuntimeError as formatRuntimeError,
15+
ɵTracingService as TracingService,
16+
ɵTracingSnapshot as TracingSnapshot,
1417
} from '@angular/core';
1518
import {from, Observable, Observer, of} from 'rxjs';
1619
import {switchMap} from 'rxjs/operators';
@@ -103,8 +106,16 @@ function validateXhrCompatibility(req: HttpRequest<any>) {
103106
*/
104107
@Injectable({providedIn: 'root'})
105108
export class HttpXhrBackend implements HttpBackend {
109+
private readonly tracingService: TracingService<TracingSnapshot> | null = inject(TracingService, {
110+
optional: true,
111+
});
112+
106113
constructor(private xhrFactory: XhrFactory) {}
107114

115+
private maybePropagateTrace<T extends Function>(fn: T): T {
116+
return this.tracingService?.propagate ? this.tracingService.propagate(fn) : fn;
117+
}
118+
108119
/**
109120
* Processes a request and returns a stream of response events.
110121
* @param req The request object.
@@ -219,7 +230,7 @@ export class HttpXhrBackend implements HttpBackend {
219230
// emit. This allows them to be unregistered as event listeners later.
220231

221232
// First up is the load event, which represents a response being fully available.
222-
const onLoad = () => {
233+
const onLoad = this.maybePropagateTrace(() => {
223234
// Read response state from the memoized partial data.
224235
let {headers, status, statusText, url} = partialFromXhr();
225236

@@ -296,12 +307,12 @@ export class HttpXhrBackend implements HttpBackend {
296307
}),
297308
);
298309
}
299-
};
310+
});
300311

301312
// The onError callback is called when something goes wrong at the network level.
302313
// Connection timeout, DNS error, offline, etc. These are actual errors, and are
303314
// transmitted on the error channel.
304-
const onError = (error: ProgressEvent) => {
315+
const onError = this.maybePropagateTrace((error: ProgressEvent) => {
305316
const {url} = partialFromXhr();
306317
const res = new HttpErrorResponse({
307318
error,
@@ -310,12 +321,12 @@ export class HttpXhrBackend implements HttpBackend {
310321
url: url || undefined,
311322
});
312323
observer.error(res);
313-
};
324+
});
314325

315326
let onTimeout = onError;
316327

317328
if (req.timeout) {
318-
onTimeout = (_: ProgressEvent) => {
329+
onTimeout = this.maybePropagateTrace((_: ProgressEvent) => {
319330
const {url} = partialFromXhr();
320331
const res = new HttpErrorResponse({
321332
error: new DOMException('Request timed out', 'TimeoutError'),
@@ -324,7 +335,7 @@ export class HttpXhrBackend implements HttpBackend {
324335
url: url || undefined,
325336
});
326337
observer.error(res);
327-
};
338+
});
328339
}
329340

330341
// The sentHeaders flag tracks whether the HttpResponseHeaders event
@@ -335,7 +346,7 @@ export class HttpXhrBackend implements HttpBackend {
335346

336347
// The download progress event handler, which is only registered if
337348
// progress events are enabled.
338-
const onDownProgress = (event: ProgressEvent) => {
349+
const onDownProgress = this.maybePropagateTrace((event: ProgressEvent) => {
339350
// Send the HttpResponseHeaders event if it hasn't been sent already.
340351
if (!sentHeaders) {
341352
observer.next(partialFromXhr());
@@ -363,11 +374,11 @@ export class HttpXhrBackend implements HttpBackend {
363374

364375
// Finally, fire the event.
365376
observer.next(progressEvent);
366-
};
377+
});
367378

368379
// The upload progress event handler, which is only registered if
369380
// progress events are enabled.
370-
const onUpProgress = (event: ProgressEvent) => {
381+
const onUpProgress = this.maybePropagateTrace((event: ProgressEvent) => {
371382
// Upload progress events are simpler. Begin building the progress
372383
// event.
373384
let progress: HttpUploadProgressEvent = {
@@ -383,7 +394,7 @@ export class HttpXhrBackend implements HttpBackend {
383394

384395
// Send the event.
385396
observer.next(progress);
386-
};
397+
});
387398

388399
// By default, register for load and error events.
389400
xhr.addEventListener('load', onLoad);

packages/common/http/test/xhr_spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import {XhrFactory} from '../../index';
910
import {HttpRequest} from '../src/request';
1011
import {
1112
HttpDownloadProgressEvent,
@@ -19,6 +20,7 @@ import {
1920
HttpUploadProgressEvent,
2021
} from '../src/response';
2122
import {HttpXhrBackend} from '../src/xhr';
23+
import {TestBed} from '@angular/core/testing';
2224
import {Observable} from 'rxjs';
2325
import {toArray} from 'rxjs/operators';
2426

@@ -53,8 +55,11 @@ describe('XhrBackend', () => {
5355
let factory: MockXhrFactory = null!;
5456
let backend: HttpXhrBackend = null!;
5557
beforeEach(() => {
56-
factory = new MockXhrFactory();
57-
backend = new HttpXhrBackend(factory);
58+
TestBed.configureTestingModule({
59+
providers: [{provide: XhrFactory, useClass: MockXhrFactory}],
60+
});
61+
factory = TestBed.inject(XhrFactory) as MockXhrFactory;
62+
backend = TestBed.inject(HttpXhrBackend);
5863
});
5964
it('emits status immediately', () => {
6065
const events = trackEvents(backend.handle(TEST_POST));

packages/core/src/application/tracing.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ export interface TracingService<T extends TracingSnapshot> {
4949
*/
5050
snapshot(linkedSnapshot: T | null): T;
5151

52+
/**
53+
* Propagate the current tracing context to the provided function.
54+
* @param fn A function.
55+
* @return A function that will propagate the current tracing context.
56+
*/
57+
propagate?<T extends Function>(fn: T): T;
58+
5259
/**
5360
* Wrap an event listener bound by the framework for tracing.
5461
* @param element Element on which the event is bound.

0 commit comments

Comments
 (0)