Skip to content

feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch#6221

Open
alwx wants to merge 9 commits into
mainfrom
alwx/feature/wrap-expo-router-methods
Open

feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch#6221
alwx wants to merge 9 commits into
mainfrom
alwx/feature/wrap-expo-router-methods

Conversation

@alwx
Copy link
Copy Markdown
Contributor

@alwx alwx commented May 27, 2026

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Extends the Expo Router instrumentation beyond prefetch so that programmatic navigation calls produce breadcrumbs and spans, and the resulting idle navigation transaction can be attributed back to the call that triggered it.

What changed:

  • wrapExpoRouter now wraps push, replace, navigate, back, and dismiss in addition to prefetch.
  • Each wrapped call:
    • emits a navigation breadcrumb (category: 'navigation', data.method, data.pathname),
    • opens a short-lived navigation.<method> span tagged with sentry.origin = auto.navigation.expo_router, navigation.method, and route.name,
    • stores a pending hand-off (pendingExpoRouterNavigation) that the next React Navigation idle navigation span consumes to set its own navigation.method attribute, attributing the transaction back to the originating call.
  • A single __sentryWrapped flag guards against double-wrapping if wrapExpoRouter is called multiple times on the same router.
  • PII handling mirrors reactnavigation.ts: raw href and route params (which can encode user identifiers like /users/123 or { id: '123' }) are only attached to breadcrumbs and span attributes when sendDefaultPii is enabled. method, pathname (the route template), and route.name remain unconditional so navigations stay visible and groupable.

💡 Motivation and Context

Fixes #6158.

Previously only prefetch was instrumented, so any programmatic router.push(...) / router.replace(...) / router.back() etc. was invisible in Sentry — no breadcrumb trail, no span, and the resulting React Navigation idle transaction had no way to know which call site initiated it. This PR closes that gap while keeping the existing prefetch behavior intact and aligning PII handling with the rest of the navigation tracing.

💚 How did you test it?

  • Added unit tests in packages/core/test/tracing/expoRouter.test.ts covering:
    • prefetch (string href, object href, object without pathname, sync + async success, sync + async errors),
    • push / replace / navigate via describe.each — span and breadcrumb shape with string and object hrefs, error reporting, and the pending-navigation hand-off,
    • back() and dismiss(count) with no/optional args,
    • double-wrap idempotency,
    • PII gating: default-off assertions (no href, no params) and dedicated sendDefaultPii: true cases that verify href and params reappear on both the breadcrumb and the span.
  • Full JS test suite: yarn jest — 113 suites / 1481 tests passing.
  • Linters: yarn lint:lerna clean.
  • API report: yarn api-report:check up to date.

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPii is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch by alwx in #6221
  • chore(ci): Pin all GitHub Actions to full commit SHAs by antonis in #6243
  • fix(tracing): Enable fetch instrumentation when expo/fetch is active by antonis in #6226
  • fix: Bump tmp to 0.2.7 to resolve path traversal vulnerability by antonis in #6233
  • feat(logs): Add enableAutoConsoleLogs option to opt out of console capture by alwx in #6235
  • chore(deps): update JavaScript SDK to v10.55.0 by github-actions in #6222
  • chore(deps): update Sentry Android Gradle Plugin to v6.9.0 by github-actions in #6230
  • refactor(android): Convert sentry.gradle to Kotlin DSL (sentry.gradle.kts) by antonis in #6119

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 30484d2

Comment thread packages/core/src/js/tracing/expoRouter.ts
@alwx alwx changed the title Expo Router: wrap push, replace, navigate, back, dismiss in addition to prefetch WIP: Expo Router: wrap push, replace, navigate, back, dismiss in addition to prefetch May 28, 2026
alwx added a commit that referenced this pull request May 28, 2026
…nd sendDefaultPii

Expo Router `push`/`replace`/`navigate`/`back`/`dismiss` (and `prefetch`)
wrappers previously emitted the raw `href` and route `params` on both
the navigation breadcrumb and span attributes regardless of the client
options. Dynamic segments like `/users/123` and parameter values such
as `{ id: '123' }` can carry user identifiers and other PII, so the
existing `reactnavigation.ts` integration already gates them behind
`sendDefaultPii`. The new Expo Router wrappers should follow the same
contract.

This change:

- Reads `sendDefaultPii` from the client options and only attaches
  `href` (raw string or stringified object form) and `params` to the
  breadcrumb `data` and span attributes when it is enabled.
- Keeps the non-PII fields — `method`, `pathname` (route template),
  and `route.name` — unconditional so navigations remain visible and
  groupable in Sentry without leaking user data.
- Applies the same guard to the `prefetch` span's `route.href`
  attribute, which was newly added in this PR.
- Adds dedicated `sendDefaultPii` test cases and updates the existing
  default-off assertions for all wrapped methods.
- Adds a changelog entry for #6221.
@alwx alwx changed the title WIP: Expo Router: wrap push, replace, navigate, back, dismiss in addition to prefetch feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch May 28, 2026
Comment thread packages/core/src/js/tracing/expoRouter.ts
Comment thread packages/core/src/js/tracing/expoRouter.ts
Comment thread packages/core/src/js/tracing/expoRouter.ts
Comment thread packages/core/test/tracing/expoRouter.test.ts
alwx added 5 commits June 2, 2026 11:39
…nd sendDefaultPii

Expo Router `push`/`replace`/`navigate`/`back`/`dismiss` (and `prefetch`)
wrappers previously emitted the raw `href` and route `params` on both
the navigation breadcrumb and span attributes regardless of the client
options. Dynamic segments like `/users/123` and parameter values such
as `{ id: '123' }` can carry user identifiers and other PII, so the
existing `reactnavigation.ts` integration already gates them behind
`sendDefaultPii`. The new Expo Router wrappers should follow the same
contract.

This change:

- Reads `sendDefaultPii` from the client options and only attaches
  `href` (raw string or stringified object form) and `params` to the
  breadcrumb `data` and span attributes when it is enabled.
- Keeps the non-PII fields — `method`, `pathname` (route template),
  and `route.name` — unconditional so navigations remain visible and
  groupable in Sentry without leaking user data.
- Applies the same guard to the `prefetch` span's `route.href`
  attribute, which was newly added in this PR.
- Adds dedicated `sendDefaultPii` test cases and updates the existing
  default-off assertions for all wrapped methods.
- Adds a changelog entry for #6221.
…igationIntegration

The Expo Router wrapper sets a pending navigation value that the
React Navigation idle-span listener is supposed to consume and apply
to the resulting transaction as the `navigation.method` attribute.
Previously only the setter side (in expoRouter.test.ts) was tested;
the consumer side had no coverage, so a regression in
reactnavigation.ts would have gone undetected.

Add three integration-level tests against the live
reactNavigationIntegration:

- the next navigation transaction is tagged with `navigation.method`
  matching the pending Expo Router call
- the pending value is consumed exactly once and does not leak into
  the following navigation
- no `navigation.method` attribute is set when nothing is pending

Surfaced by Warden review (4UA-DHD).
@alwx alwx force-pushed the alwx/feature/wrap-expo-router-methods branch from b5d287a to febd51c Compare June 2, 2026 09:39
@alwx alwx marked this pull request as ready for review June 2, 2026 09:41
Comment thread packages/core/src/js/tracing/reactnavigation.ts Outdated
Comment thread packages/core/src/js/tracing/reactnavigation.ts
…an listener short-circuits

`startIdleNavigationSpan` has several early-return paths (dispatched
noops, PRELOAD with prefetch tracking, SET_PARAMS, drawer actions, and
`useDispatchedActionData` calls without a route name in the payload).
Previously `consumePendingExpoRouterNavigation()` lived after those
returns, so a wrapped Expo Router call that ended up taking one of the
short-circuit paths left its pending value in the module-level slot.
The next, unrelated navigation then incorrectly inherited the wrong
`navigation.method` attribute.

Move the `consumePendingExpoRouterNavigation()` call to the very top of
the listener so the value is always drained on the listener invocation
triggered by the wrapper. Apply it to the span only if we actually
create one.

Add a regression test covering the dispatched-action-without-payload
early-return path; the test fails on the previous code and passes with
the fix.

Surfaced by sentry-bot and cursor-bot reviews.
Comment thread packages/core/src/js/tracing/origin.ts Outdated
alwx added 2 commits June 2, 2026 13:10
Per the Sentry trace-origin spec
(https://develop.sentry.dev/sdk/telemetry/traces/trace-origin/), the
`sentry.origin` value uses the format `type.category.integration-name`,
where `category` is a span operation category such as `navigation`.

The new origin for wrapped Expo Router push/replace/navigate/back/dismiss
calls was `auto.expo_router.navigation`, which swaps the category and
integration name. Align it with the existing
`auto.navigation.react_navigation` and
`auto.navigation.react_native_navigation` constants so server-side
filtering and downstream tools recognize these as navigation spans.

The pre-existing `auto.expo_router.prefetch` origin has the same
inverted ordering but is left unchanged here \u2014 it is already shipped
and changing it would break consumers that filter by it.

Surfaced by cursor-bot review.
@alwx
Copy link
Copy Markdown
Contributor Author

alwx commented Jun 2, 2026

It might look scary but it's mostly tests.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 27cfa37. Configure here.

Comment thread packages/core/src/js/tracing/expoRouter.ts
The PR also tightened PII handling for the pre-existing `prefetch`
instrumentation: when `sendDefaultPii` is off (the default),
`route.href` is dropped and concrete pathnames derived from string
hrefs (e.g. `/users/42`) are replaced with `'unknown'` for
`route.name`. Templated object hrefs are unaffected.

Document this so users with dashboards or alerts filtering on
prefetch `route.name`/`route.href` notice the change.

Surfaced by cursor-bot review.
},
});

setPendingExpoRouterNavigation({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The href, pathname, and params seem to be unused

@antonis antonis added the ready-to-merge Triggers the full CI test suite label Jun 2, 2026
Copy link
Copy Markdown
Contributor

@antonis antonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀
Left a nit comment and added the ready-to-merge to run the full test suite.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3844.67 ms 1231.19 ms -2613.48 ms
Size 5.15 MiB 6.70 MiB 1.55 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
b0d3373+dirty 3831.75 ms 1227.29 ms -2604.46 ms
ef27341+dirty 3856.13 ms 1231.42 ms -2624.71 ms
23598c3+dirty 1207.00 ms 1209.90 ms 2.90 ms
3ce5254+dirty 1219.93 ms 1221.90 ms 1.96 ms
04207c4+dirty 1191.27 ms 1189.78 ms -1.48 ms
5748023+dirty 3840.49 ms 1227.43 ms -2613.05 ms
9210ae6+dirty 3815.93 ms 1214.14 ms -2601.79 ms
4e0ba9c+dirty 3839.22 ms 1221.06 ms -2618.16 ms
7ac3378+dirty 1213.37 ms 1218.15 ms 4.78 ms
0b1b5e3+dirty 3823.96 ms 1220.12 ms -2603.84 ms

App size

Revision Plain With Sentry Diff
b0d3373+dirty 5.15 MiB 6.68 MiB 1.53 MiB
ef27341+dirty 5.15 MiB 6.68 MiB 1.53 MiB
23598c3+dirty 3.38 MiB 4.80 MiB 1.42 MiB
3ce5254+dirty 3.38 MiB 4.76 MiB 1.38 MiB
04207c4+dirty 3.38 MiB 4.76 MiB 1.38 MiB
5748023+dirty 5.15 MiB 6.68 MiB 1.53 MiB
9210ae6+dirty 5.15 MiB 6.68 MiB 1.53 MiB
4e0ba9c+dirty 5.15 MiB 6.67 MiB 1.51 MiB
7ac3378+dirty 3.38 MiB 4.76 MiB 1.38 MiB
0b1b5e3+dirty 5.15 MiB 6.70 MiB 1.54 MiB

@sentry
Copy link
Copy Markdown

sentry Bot commented Jun 2, 2026

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Sentry RN io.sentry.reactnative.sample 8.13.0 (90) Release

⚙️ sentry-react-native Build Distribution Settings

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3838.94 ms 1226.89 ms -2612.05 ms
Size 5.15 MiB 6.70 MiB 1.55 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
b0d3373+dirty 3842.49 ms 1218.49 ms -2624.00 ms
ef27341+dirty 3835.20 ms 1212.23 ms -2622.97 ms
23598c3+dirty 1223.59 ms 1229.13 ms 5.53 ms
3ce5254+dirty 1217.70 ms 1224.69 ms 6.99 ms
04207c4+dirty 1228.55 ms 1226.04 ms -2.51 ms
5748023+dirty 3844.74 ms 1225.49 ms -2619.26 ms
9210ae6+dirty 3834.11 ms 1216.64 ms -2617.47 ms
4e0ba9c+dirty 3856.39 ms 1234.44 ms -2621.95 ms
7ac3378+dirty 1202.35 ms 1198.31 ms -4.04 ms
0b1b5e3+dirty 3820.72 ms 1207.94 ms -2612.78 ms

App size

Revision Plain With Sentry Diff
b0d3373+dirty 5.15 MiB 6.68 MiB 1.53 MiB
ef27341+dirty 5.15 MiB 6.68 MiB 1.53 MiB
23598c3+dirty 3.38 MiB 4.80 MiB 1.42 MiB
3ce5254+dirty 3.38 MiB 4.76 MiB 1.38 MiB
04207c4+dirty 3.38 MiB 4.76 MiB 1.38 MiB
5748023+dirty 5.15 MiB 6.68 MiB 1.53 MiB
9210ae6+dirty 5.15 MiB 6.68 MiB 1.53 MiB
4e0ba9c+dirty 5.15 MiB 6.67 MiB 1.51 MiB
7ac3378+dirty 3.38 MiB 4.76 MiB 1.38 MiB
0b1b5e3+dirty 5.15 MiB 6.70 MiB 1.54 MiB

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 426.96 ms 448.29 ms 21.33 ms
Size 48.30 MiB 53.60 MiB 5.30 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ad66da3+dirty 411.49 ms 449.38 ms 37.89 ms
bc0d8cf+dirty 407.66 ms 461.35 ms 53.69 ms
a736b76+dirty 405.78 ms 458.74 ms 52.96 ms
a0d8cf8+dirty 533.71 ms 564.25 ms 30.54 ms
41d6254+dirty 406.20 ms 445.52 ms 39.32 ms
7d8c8bd+dirty 406.06 ms 460.88 ms 54.81 ms
c823bb5+dirty 468.26 ms 516.16 ms 47.90 ms
0b1b5e3+dirty 425.58 ms 476.02 ms 50.44 ms
4953e94+dirty 398.80 ms 431.81 ms 33.01 ms
d2eadf8+dirty 468.02 ms 530.37 ms 62.35 ms

App size

Revision Plain With Sentry Diff
ad66da3+dirty 48.30 MiB 53.49 MiB 5.19 MiB
bc0d8cf+dirty 48.30 MiB 53.48 MiB 5.18 MiB
a736b76+dirty 48.30 MiB 53.48 MiB 5.18 MiB
a0d8cf8+dirty 48.30 MiB 53.49 MiB 5.19 MiB
41d6254+dirty 48.30 MiB 53.60 MiB 5.30 MiB
7d8c8bd+dirty 48.30 MiB 53.54 MiB 5.23 MiB
c823bb5+dirty 48.30 MiB 53.58 MiB 5.28 MiB
0b1b5e3+dirty 48.30 MiB 53.60 MiB 5.29 MiB
4953e94+dirty 43.94 MiB 48.94 MiB 5.00 MiB
d2eadf8+dirty 48.30 MiB 53.48 MiB 5.18 MiB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrap push, replace, navigate, back, dismiss in addition to prefetch

2 participants