@@ -87,6 +87,25 @@ export function extractDynamicRouteParams(
8787 return Object . keys ( result ) . length > 0 ? result : undefined ;
8888}
8989
90+ /**
91+ * Optional route override provided by another integration (e.g. Expo Router).
92+ *
93+ * When supplied, the route name and related attributes are derived from this
94+ * override instead of React Navigation's `getCurrentRoute().name`, so we can
95+ * report meaningful templated paths (e.g. `/profile/[id]`) instead of file
96+ * names like `index` or `(tabs)`.
97+ */
98+ export interface RouteOverride {
99+ // templated pathname such as `/profile/[id]`. Used as the span name and `route.path` attribute
100+ templatedPath : string ;
101+ // concrete URL with resolved values, e.g. `/profile/123?foo=bar`
102+ concreteUrl ?: string ;
103+ // merged route params (path + query)
104+ params ?: Record < string , unknown > ;
105+ }
106+
107+ export type RouteOverrideProvider = ( ) => RouteOverride | undefined ;
108+
90109/**
91110 * Builds a full path from the navigation state by traversing nested navigators.
92111 * For example, with nested navigators: "Home/Settings/Profile"
@@ -193,9 +212,15 @@ export const reactNavigationIntegration = ({
193212 * @param navigationContainerRef Ref to a `NavigationContainer`
194213 */
195214 registerNavigationContainer : ( navigationContainerRef : unknown ) => void ;
215+ /**
216+ * Internal API: allow another integration (for example, Expo Router integration) to
217+ * supply the canonical route info on every state change. Use `undefined` to clear.
218+ */
219+ _setRouteOverrideProvider : ( provider : RouteOverrideProvider | undefined ) => void ;
196220 options : ReactNavigationIntegrationOptions ;
197221} => {
198222 let navigationContainer : NavigationContainer | undefined ;
223+ let routeOverrideProvider : RouteOverrideProvider | undefined ;
199224
200225 let tracing : ReactNativeTracingIntegration | undefined ;
201226 let idleSpanOptions : Parameters < typeof startGenericIdleNavigationSpan > [ 1 ] = defaultIdleOptions ;
@@ -474,9 +499,28 @@ export const reactNavigationIntegration = ({
474499
475500 const routeHasBeenSeen = recentRouteKeys . includes ( route . key ) ;
476501
477- // Get the full navigation path for nested navigators
502+ // Resolve route name. Order of preference:
503+ // 1. Route override provider (e.g. Expo Router templated path)
504+ // 2. Full path joined from React Navigation state
505+ // 3. React Navigation's leaf route name
478506 let routeName = route . name ;
479- if ( useFullPathsForNavigationRoutes ) {
507+ let routePath : string | undefined ;
508+ let routeUrl : string | undefined ;
509+ let routeParams : Record < string , unknown > | undefined = route . params ;
510+
511+ let override : RouteOverride | undefined ;
512+ try {
513+ override = routeOverrideProvider ?.( ) ;
514+ } catch ( e ) {
515+ debug . warn ( `${ INTEGRATION_NAME } Route override provider threw, falling back to React Navigation route.` , e ) ;
516+ }
517+
518+ if ( override ?. templatedPath ) {
519+ routeName = override . templatedPath ;
520+ routePath = override . templatedPath ;
521+ routeUrl = override . concreteUrl ;
522+ routeParams = override . params ?? route . params ;
523+ } else if ( useFullPathsForNavigationRoutes ) {
480524 const navigationState = navigationContainer . getState ( ) ;
481525 routeName = getPathFromState ( navigationState ) || route . name ;
482526 }
@@ -493,7 +537,9 @@ export const reactNavigationIntegration = ({
493537 latestNavigationSpan . setAttributes ( {
494538 'route.name' : routeName ,
495539 'route.key' : route . key ,
496- ...( sendDefaultPii ? extractDynamicRouteParams ( routeName , route . params ) : undefined ) ,
540+ ...( routePath ? { 'route.path' : routePath } : undefined ) ,
541+ ...( sendDefaultPii && routeUrl ? { 'route.url' : routeUrl } : undefined ) ,
542+ ...( sendDefaultPii ? extractDynamicRouteParams ( routeName , routeParams ) : undefined ) ,
497543 'route.has_been_seen' : routeHasBeenSeen ,
498544 'previous_route.name' : previousRoute ?. name ,
499545 'previous_route.key' : previousRoute ?. key ,
@@ -517,7 +563,7 @@ export const reactNavigationIntegration = ({
517563 tracing ?. setCurrentRoute ( routeName ) ;
518564
519565 pushRecentRouteKey ( route . key ) ;
520- if ( useFullPathsForNavigationRoutes ) {
566+ if ( override ?. templatedPath || useFullPathsForNavigationRoutes ) {
521567 latestRoute = { ...route , name : routeName } ;
522568 } else {
523569 latestRoute = route ;
@@ -557,10 +603,15 @@ export const reactNavigationIntegration = ({
557603 }
558604 } ;
559605
606+ const _setRouteOverrideProvider = ( provider : RouteOverrideProvider | undefined ) : void => {
607+ routeOverrideProvider = provider ;
608+ } ;
609+
560610 return {
561611 name : INTEGRATION_NAME ,
562612 afterAllSetup,
563613 registerNavigationContainer,
614+ _setRouteOverrideProvider,
564615 options : {
565616 routeChangeTimeoutMs,
566617 enableTimeToInitialDisplay,
0 commit comments