@@ -16,6 +16,7 @@ import {
1616 SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
1717 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
1818 TRACING_DEFAULTS ,
19+ addNonEnumerableProperty ,
1920 browserPerformanceTimeOrigin ,
2021 generateTraceId ,
2122 getActiveSpan ,
@@ -246,7 +247,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
246247 } ;
247248
248249 /** Create routing idle transaction. */
249- function _createRouteSpan ( client : Client , startSpanOptions : StartSpanOptions ) : Span {
250+ function _createRouteSpan ( client : Client , startSpanOptions : StartSpanOptions ) : void {
250251 const isPageloadTransaction = startSpanOptions . op === 'pageload' ;
251252
252253 const finalStartSpanOptions : StartSpanOptions = beforeStartSpan
@@ -274,6 +275,15 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
274275 beforeSpanEnd : span => {
275276 _collectWebVitals ( ) ;
276277 addPerformanceEntries ( span , { recordClsOnPageloadSpan : ! enableStandaloneClsSpans } ) ;
278+
279+ // Ensure that DSC is updated with possibly final transaction etc.
280+ const scope = getCurrentScope ( ) ;
281+ const oldPropagationContext = scope . getPropagationContext ( ) ;
282+
283+ scope . setPropagationContext ( {
284+ ...oldPropagationContext ,
285+ dsc : getDynamicSamplingContextFromSpan ( span ) ,
286+ } ) ;
277287 } ,
278288 } ) ;
279289
@@ -291,16 +301,31 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
291301 emitFinish ( ) ;
292302 }
293303
294- return idleSpan ;
304+ // A trace should to stay the consistent over the entire time span of one route.
305+ // Therefore, when the initial pageload or navigation root span ends, we update the
306+ // scope's propagation context to keep span-specific attributes like the `sampled` decision and
307+ // the dynamic sampling context valid, even after the root span has ended.
308+ // This ensures that the trace data is consistent for the entire duration of the route.
309+ const scope = getCurrentScope ( ) ;
310+ const oldPropagationContext = scope . getPropagationContext ( ) ;
311+
312+ scope . setPropagationContext ( {
313+ ...oldPropagationContext ,
314+ traceId : idleSpan . spanContext ( ) . traceId ,
315+ sampled : spanIsSampled ( idleSpan ) ,
316+ dsc : getDynamicSamplingContextFromSpan ( idleSpan ) ,
317+ } ) ;
318+
319+ setActiveIdleSpan ( client , idleSpan ) ;
295320 }
296321
297322 return {
298323 name : BROWSER_TRACING_INTEGRATION_ID ,
299324 afterAllSetup ( client ) {
300- let activeSpan : Span | undefined ;
301325 let startingUrl : string | undefined = WINDOW . location && WINDOW . location . href ;
302326
303327 function maybeEndActiveSpan ( ) : void {
328+ const activeSpan = getActiveIdleSpan ( client ) ;
304329 if ( activeSpan && ! spanToJSON ( activeSpan ) . timestamp ) {
305330 DEBUG_BUILD && logger . log ( `[Tracing] Finishing current active span with op: ${ spanToJSON ( activeSpan ) . op } ` ) ;
306331 // If there's an open active span, we need to finish it before creating an new one.
@@ -315,7 +340,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
315340
316341 maybeEndActiveSpan ( ) ;
317342
318- activeSpan = _createRouteSpan ( client , {
343+ _createRouteSpan ( client , {
319344 op : 'navigation' ,
320345 ...startSpanOptions ,
321346 } ) ;
@@ -333,33 +358,12 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
333358 const propagationContext = propagationContextFromHeaders ( sentryTrace , baggage ) ;
334359 getCurrentScope ( ) . setPropagationContext ( propagationContext ) ;
335360
336- activeSpan = _createRouteSpan ( client , {
361+ _createRouteSpan ( client , {
337362 op : 'pageload' ,
338363 ...startSpanOptions ,
339364 } ) ;
340365 } ) ;
341366
342- // A trace should to stay the consistent over the entire time span of one route.
343- // Therefore, when the initial pageload or navigation root span ends, we update the
344- // scope's propagation context to keep span-specific attributes like the `sampled` decision and
345- // the dynamic sampling context valid, even after the root span has ended.
346- // This ensures that the trace data is consistent for the entire duration of the route.
347- client . on ( 'spanEnd' , span => {
348- const op = spanToJSON ( span ) . op ;
349- if ( span !== getRootSpan ( span ) || ( op !== 'navigation' && op !== 'pageload' ) ) {
350- return ;
351- }
352-
353- const scope = getCurrentScope ( ) ;
354- const oldPropagationContext = scope . getPropagationContext ( ) ;
355-
356- scope . setPropagationContext ( {
357- ...oldPropagationContext ,
358- sampled : oldPropagationContext . sampled !== undefined ? oldPropagationContext . sampled : spanIsSampled ( span ) ,
359- dsc : oldPropagationContext . dsc || getDynamicSamplingContextFromSpan ( span ) ,
360- } ) ;
361- } ) ;
362-
363367 if ( WINDOW . location ) {
364368 if ( instrumentPageLoad ) {
365369 startBrowserTracingPageLoadSpan ( client , {
@@ -440,12 +444,9 @@ export function startBrowserTracingPageLoadSpan(
440444 traceOptions ?: { sentryTrace ?: string | undefined ; baggage ?: string | undefined } ,
441445) : Span | undefined {
442446 client . emit ( 'startPageLoadSpan' , spanOptions , traceOptions ) ;
443-
444447 getCurrentScope ( ) . setTransactionName ( spanOptions . name ) ;
445448
446- const span = getActiveSpan ( ) ;
447- const op = span && spanToJSON ( span ) . op ;
448- return op === 'pageload' ? span : undefined ;
449+ return getActiveIdleSpan ( client ) ;
449450}
450451
451452/**
@@ -454,15 +455,11 @@ export function startBrowserTracingPageLoadSpan(
454455 */
455456export function startBrowserTracingNavigationSpan ( client : Client , spanOptions : StartSpanOptions ) : Span | undefined {
456457 getIsolationScope ( ) . setPropagationContext ( { traceId : generateTraceId ( ) } ) ;
457- getCurrentScope ( ) . setPropagationContext ( { traceId : generateTraceId ( ) } ) ;
458458
459459 client . emit ( 'startNavigationSpan' , spanOptions ) ;
460-
461460 getCurrentScope ( ) . setTransactionName ( spanOptions . name ) ;
462461
463- const span = getActiveSpan ( ) ;
464- const op = span && spanToJSON ( span ) . op ;
465- return op === 'navigation' ? span : undefined ;
462+ return getActiveIdleSpan ( client ) ;
466463}
467464
468465/** Returns the value of a meta tag */
@@ -536,3 +533,13 @@ function registerInteractionListener(
536533 addEventListener ( 'click' , registerInteractionTransaction , { once : false , capture : true } ) ;
537534 }
538535}
536+
537+ // We store the active idle span on the client object, so we can access it from exported functions
538+ const ACTIVE_IDLE_SPAN_PROPERTY = '_sentry_idleSpan' ;
539+ function getActiveIdleSpan ( client : Client ) : Span | undefined {
540+ return ( client as { [ ACTIVE_IDLE_SPAN_PROPERTY ] ?: Span } ) [ ACTIVE_IDLE_SPAN_PROPERTY ] ;
541+ }
542+
543+ function setActiveIdleSpan ( client : Client , span : Span ) : void {
544+ addNonEnumerableProperty ( client , ACTIVE_IDLE_SPAN_PROPERTY , span ) ;
545+ }
0 commit comments