11/* eslint-disable max-lines */
2- import { getCurrentHub , hasTracingEnabled } from '@sentry/core' ;
3- import type { DynamicSamplingContext , Span } from '@sentry/types' ;
2+ import { getCurrentHub , getDynamicSamplingContextFromClient , hasTracingEnabled } from '@sentry/core' ;
3+ import type { Client , Scope , Span } from '@sentry/types' ;
44import {
55 addInstrumentationHandler ,
66 BAGGAGE_HEADER_NAME ,
77 browserPerformanceTimeOrigin ,
88 dynamicSamplingContextToSentryBaggageHeader ,
9+ generateSentryTraceHeader ,
910 isInstanceOf ,
1011 SENTRY_XHR_DATA_KEY ,
1112 stringMatchesSomePattern ,
@@ -219,12 +220,14 @@ export function fetchCallback(
219220 shouldCreateSpan : ( url : string ) => boolean ,
220221 shouldAttachHeaders : ( url : string ) => boolean ,
221222 spans : Record < string , Span > ,
222- ) : Span | void {
223- if ( ! hasTracingEnabled ( ) || ! ( handlerData . fetchData && shouldCreateSpan ( handlerData . fetchData . url ) ) ) {
224- return ;
223+ ) : Span | undefined {
224+ if ( ! hasTracingEnabled ( ) || ! handlerData . fetchData ) {
225+ return undefined ;
225226 }
226227
227- if ( handlerData . endTimestamp ) {
228+ const shouldCreateSpanResult = shouldCreateSpan ( handlerData . fetchData . url ) ;
229+
230+ if ( handlerData . endTimestamp && shouldCreateSpanResult ) {
228231 const spanId = handlerData . fetchData . __span ;
229232 if ( ! spanId ) return ;
230233
@@ -251,27 +254,35 @@ export function fetchCallback(
251254 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
252255 delete spans [ spanId ] ;
253256 }
254- return ;
257+ return undefined ;
255258 }
256259
257- const currentSpan = getCurrentHub ( ) . getScope ( ) . getSpan ( ) ;
258- const activeTransaction = currentSpan && currentSpan . transaction ;
259-
260- if ( currentSpan && activeTransaction ) {
261- const { method, url } = handlerData . fetchData ;
262- const span = currentSpan . startChild ( {
263- data : {
264- url,
265- type : 'fetch' ,
266- 'http.method' : method ,
267- } ,
268- description : `${ method } ${ url } ` ,
269- op : 'http.client' ,
270- } ) ;
271-
260+ const hub = getCurrentHub ( ) ;
261+ const scope = hub . getScope ( ) ;
262+ const client = hub . getClient ( ) ;
263+ const parentSpan = scope . getSpan ( ) ;
264+
265+ const { method, url } = handlerData . fetchData ;
266+
267+ const span =
268+ shouldCreateSpanResult && parentSpan
269+ ? parentSpan . startChild ( {
270+ data : {
271+ url,
272+ type : 'fetch' ,
273+ 'http.method' : method ,
274+ } ,
275+ description : `${ method } ${ url } ` ,
276+ op : 'http.client' ,
277+ } )
278+ : undefined ;
279+
280+ if ( span ) {
272281 handlerData . fetchData . __span = span . spanId ;
273282 spans [ span . spanId ] = span ;
283+ }
274284
285+ if ( shouldAttachHeaders ( handlerData . fetchData . url ) && client ) {
275286 const request : string | Request = handlerData . args [ 0 ] ;
276287
277288 // In case the user hasn't set the second argument of a fetch call we default it to `{}`.
@@ -280,35 +291,42 @@ export function fetchCallback(
280291 // eslint-disable-next-line @typescript-eslint/no-explicit-any
281292 const options : { [ key : string ] : any } = handlerData . args [ 1 ] ;
282293
283- if ( shouldAttachHeaders ( handlerData . fetchData . url ) ) {
284- options . headers = addTracingHeadersToFetchRequest (
285- request ,
286- activeTransaction . getDynamicSamplingContext ( ) ,
287- span ,
288- options ,
289- ) ;
290- }
291- return span ;
294+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
295+ options . headers = addTracingHeadersToFetchRequest ( request , client , scope , options ) ;
292296 }
297+
298+ return span ;
293299}
294300
295301/**
296302 * Adds sentry-trace and baggage headers to the various forms of fetch headers
297303 */
298304export function addTracingHeadersToFetchRequest (
299305 request : string | unknown , // unknown is actually type Request but we can't export DOM types from this package,
300- dynamicSamplingContext : Partial < DynamicSamplingContext > ,
301- span : Span ,
306+ client : Client ,
307+ scope : Scope ,
302308 options : {
303309 headers ?:
304310 | {
305311 [ key : string ] : string [ ] | string | undefined ;
306312 }
307313 | PolymorphicRequestHeaders ;
308314 } ,
309- ) : PolymorphicRequestHeaders {
315+ ) : PolymorphicRequestHeaders | undefined {
316+ const span = scope . getSpan ( ) ;
317+
318+ const transaction = span && span . transaction ;
319+
320+ const { traceId, sampled, dsc } = scope . getPropagationContext ( ) ;
321+
322+ const sentryTraceHeader = span ? span . toTraceparent ( ) : generateSentryTraceHeader ( traceId , undefined , sampled ) ;
323+ const dynamicSamplingContext = transaction
324+ ? transaction . getDynamicSamplingContext ( )
325+ : dsc
326+ ? dsc
327+ : getDynamicSamplingContextFromClient ( traceId , client , scope ) ;
328+
310329 const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
311- const sentryTraceHeader = span . toTraceparent ( ) ;
312330
313331 const headers =
314332 typeof Request !== 'undefined' && isInstanceOf ( request , Request ) ? ( request as Request ) . headers : options . headers ;
@@ -364,25 +382,24 @@ export function addTracingHeadersToFetchRequest(
364382 *
365383 * @returns Span if a span was created, otherwise void.
366384 */
385+ // eslint-disable-next-line complexity
367386export function xhrCallback (
368387 handlerData : XHRData ,
369388 shouldCreateSpan : ( url : string ) => boolean ,
370389 shouldAttachHeaders : ( url : string ) => boolean ,
371390 spans : Record < string , Span > ,
372- ) : Span | void {
391+ ) : Span | undefined {
373392 const xhr = handlerData . xhr ;
374393 const sentryXhrData = xhr && xhr [ SENTRY_XHR_DATA_KEY ] ;
375394
376- if (
377- ! hasTracingEnabled ( ) ||
378- ( xhr && xhr . __sentry_own_request__ ) ||
379- ! ( xhr && sentryXhrData && shouldCreateSpan ( sentryXhrData . url ) )
380- ) {
381- return ;
395+ if ( ! hasTracingEnabled ( ) || ( xhr && xhr . __sentry_own_request__ ) || ! xhr || ! sentryXhrData ) {
396+ return undefined ;
382397 }
383398
399+ const shouldCreateSpanResult = shouldCreateSpan ( sentryXhrData . url ) ;
400+
384401 // check first if the request has finished and is tracked by an existing span which should now end
385- if ( handlerData . endTimestamp ) {
402+ if ( handlerData . endTimestamp && shouldCreateSpanResult ) {
386403 const spanId = xhr . __sentry_xhr_span_id__ ;
387404 if ( ! spanId ) return ;
388405
@@ -394,45 +411,68 @@ export function xhrCallback(
394411 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
395412 delete spans [ spanId ] ;
396413 }
397- return ;
414+ return undefined ;
398415 }
399416
400- const currentSpan = getCurrentHub ( ) . getScope ( ) . getSpan ( ) ;
401- const activeTransaction = currentSpan && currentSpan . transaction ;
402-
403- if ( currentSpan && activeTransaction ) {
404- const span = currentSpan . startChild ( {
405- data : {
406- ...sentryXhrData . data ,
407- type : 'xhr' ,
408- 'http.method' : sentryXhrData . method ,
409- url : sentryXhrData . url ,
410- } ,
411- description : `${ sentryXhrData . method } ${ sentryXhrData . url } ` ,
412- op : 'http.client' ,
413- } ) ;
414-
417+ const hub = getCurrentHub ( ) ;
418+ const scope = hub . getScope ( ) ;
419+ const parentSpan = scope . getSpan ( ) ;
420+
421+ const span =
422+ shouldCreateSpanResult && parentSpan
423+ ? parentSpan . startChild ( {
424+ data : {
425+ ...sentryXhrData . data ,
426+ type : 'xhr' ,
427+ 'http.method' : sentryXhrData . method ,
428+ url : sentryXhrData . url ,
429+ } ,
430+ description : `${ sentryXhrData . method } ${ sentryXhrData . url } ` ,
431+ op : 'http.client' ,
432+ } )
433+ : undefined ;
434+
435+ if ( span ) {
415436 xhr . __sentry_xhr_span_id__ = span . spanId ;
416437 spans [ xhr . __sentry_xhr_span_id__ ] = span ;
438+ }
417439
418- if ( xhr . setRequestHeader && shouldAttachHeaders ( sentryXhrData . url ) ) {
419- try {
420- xhr . setRequestHeader ( 'sentry-trace' , span . toTraceparent ( ) ) ;
440+ if ( xhr . setRequestHeader && shouldAttachHeaders ( sentryXhrData . url ) ) {
441+ if ( span ) {
442+ const transaction = span && span . transaction ;
443+ const dynamicSamplingContext = transaction && transaction . getDynamicSamplingContext ( ) ;
444+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
445+ setHeaderOnXhr ( xhr , span . toTraceparent ( ) , sentryBaggageHeader ) ;
446+ } else {
447+ const client = hub . getClient ( ) ;
448+ const { traceId, sampled, dsc } = scope . getPropagationContext ( ) ;
449+ const sentryTraceHeader = generateSentryTraceHeader ( traceId , undefined , sampled ) ;
450+ const dynamicSamplingContext =
451+ dsc || ( client ? getDynamicSamplingContextFromClient ( traceId , client , scope ) : undefined ) ;
452+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
453+ setHeaderOnXhr ( xhr , sentryTraceHeader , sentryBaggageHeader ) ;
454+ }
455+ }
421456
422- const dynamicSamplingContext = activeTransaction . getDynamicSamplingContext ( ) ;
423- const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
457+ return span ;
458+ }
424459
425- if ( sentryBaggageHeader ) {
426- // From MDN: "If this method is called several times with the same header, the values are merged into one single request header."
427- // We can therefore simply set a baggage header without checking what was there before
428- // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
429- xhr . setRequestHeader ( BAGGAGE_HEADER_NAME , sentryBaggageHeader ) ;
430- }
431- } catch ( _ ) {
432- // Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
433- }
460+ function setHeaderOnXhr (
461+ xhr : NonNullable < XHRData [ 'xhr' ] > ,
462+ sentryTraceHeader : string ,
463+ sentryBaggageHeader : string | undefined ,
464+ ) : void {
465+ try {
466+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
467+ xhr . setRequestHeader ! ( 'sentry-trace' , sentryTraceHeader ) ;
468+ if ( sentryBaggageHeader ) {
469+ // From MDN: "If this method is called several times with the same header, the values are merged into one single request header."
470+ // We can therefore simply set a baggage header without checking what was there before
471+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
472+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
473+ xhr . setRequestHeader ! ( BAGGAGE_HEADER_NAME , sentryBaggageHeader ) ;
434474 }
435-
436- return span ;
475+ } catch ( _ ) {
476+ // Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
437477 }
438478}
0 commit comments