1- /* eslint-disable complexity */
2- import type { Client , Event , Integration , SpanJSON , TransactionEvent } from '@sentry/core' ;
1+ /* eslint-disable complexity, max-lines */
2+ import type { Client , Event , Integration , Span , SpanJSON , TransactionEvent } from '@sentry/core' ;
33import {
44 getCapturedScopesOnSpan ,
55 getClient ,
@@ -17,7 +17,7 @@ import {
1717} from '../../measurements' ;
1818import type { NativeAppStartResponse } from '../../NativeRNSentry' ;
1919import type { ReactNativeClientOptions } from '../../options' ;
20- import { convertSpanToTransaction , setEndTimeValue } from '../../utils/span' ;
20+ import { convertSpanToTransaction , isRootSpan , setEndTimeValue } from '../../utils/span' ;
2121import { NATIVE } from '../../wrapper' ;
2222import {
2323 APP_START_COLD as APP_START_COLD_OP ,
@@ -136,16 +136,18 @@ export const appStartIntegration = ({
136136 let _client : Client | undefined = undefined ;
137137 let isEnabled = true ;
138138 let appStartDataFlushed = false ;
139+ let firstStartedActiveRootSpanId : string | undefined = undefined ;
139140
140141 const setup = ( client : Client ) : void => {
141142 _client = client ;
142- const clientOptions = client . getOptions ( ) as ReactNativeClientOptions ;
143+ const { enableAppStartTracking } = client . getOptions ( ) as ReactNativeClientOptions ;
143144
144- const { enableAppStartTracking } = clientOptions ;
145145 if ( ! enableAppStartTracking ) {
146146 isEnabled = false ;
147147 logger . warn ( '[AppStart] App start tracking is disabled.' ) ;
148148 }
149+
150+ client . on ( 'spanStart' , recordFirstStartedActiveRootSpanId ) ;
149151 } ;
150152
151153 const afterAllSetup = ( _client : Client ) : void => {
@@ -167,6 +169,27 @@ export const appStartIntegration = ({
167169 return event ;
168170 } ;
169171
172+ const recordFirstStartedActiveRootSpanId = ( rootSpan : Span ) : void => {
173+ if ( firstStartedActiveRootSpanId ) {
174+ return ;
175+ }
176+
177+ if ( ! isRootSpan ( rootSpan ) ) {
178+ return ;
179+ }
180+
181+ setFirstStartedActiveRootSpanId ( rootSpan . spanContext ( ) . spanId ) ;
182+ } ;
183+
184+ /**
185+ * For testing purposes only.
186+ * @private
187+ */
188+ const setFirstStartedActiveRootSpanId = ( spanId : string | undefined ) : void => {
189+ firstStartedActiveRootSpanId = spanId ;
190+ logger . debug ( '[AppStart] First started active root span id recorded.' , firstStartedActiveRootSpanId ) ;
191+ } ;
192+
170193 async function captureStandaloneAppStart ( ) : Promise < void > {
171194 if ( ! standalone ) {
172195 logger . debug (
@@ -212,11 +235,23 @@ export const appStartIntegration = ({
212235 return ;
213236 }
214237
238+ if ( ! firstStartedActiveRootSpanId ) {
239+ logger . warn ( '[AppStart] No first started active root span id recorded. Can not attach app start.' ) ;
240+ return ;
241+ }
242+
215243 if ( ! event . contexts || ! event . contexts . trace ) {
216244 logger . warn ( '[AppStart] Transaction event is missing trace context. Can not attach app start.' ) ;
217245 return ;
218246 }
219247
248+ if ( firstStartedActiveRootSpanId !== event . contexts . trace . span_id ) {
249+ logger . warn (
250+ '[AppStart] First started active root span id does not match the transaction event span id. Can not attached app start.' ,
251+ ) ;
252+ return ;
253+ }
254+
220255 const appStart = await NATIVE . fetchNativeAppStart ( ) ;
221256 if ( ! appStart ) {
222257 logger . warn ( '[AppStart] Failed to retrieve the app start metrics from the native layer.' ) ;
@@ -332,7 +367,8 @@ export const appStartIntegration = ({
332367 afterAllSetup,
333368 processEvent,
334369 captureStandaloneAppStart,
335- } ;
370+ setFirstStartedActiveRootSpanId,
371+ } as AppStartIntegration ;
336372} ;
337373
338374function setSpanDurationAsMeasurementOnTransactionEvent ( event : TransactionEvent , label : string , span : SpanJSON ) : void {
0 commit comments