Repository navigation
feat(tracing): Connect React Native traces with embedded WebViews - #6794
Conversation
Add `sentryWebViewProps({ allowedHosts })` which injects the React Native
trace context (`sentry-trace`/`baggage` meta tags, via
`injectedJavaScriptBeforeContentLoaded`) into a `react-native-webview`, so the
page's `@sentry/browser` continues the same trace on pageload. Host-gated by
`allowedHosts` (case-insensitive, subdomain match) so the context never reaches
non-allow-listed pages. Pure JS, no native/bridge changes, both architectures.
Also adds a sample WebView demo screen and an iOS/Android Maestro E2E.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
|
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5125c43+dirty | 3846.45 ms | 1221.12 ms | -2625.32 ms |
| 5fe1c6c+dirty | 1220.79 ms | 1217.63 ms | -3.16 ms |
| 5257d80+dirty | 3854.39 ms | 1234.28 ms | -2620.11 ms |
| 0c50e94+dirty | 3863.88 ms | 1235.45 ms | -2628.43 ms |
| 3e3142c+dirty | 3867.38 ms | 1232.48 ms | -2634.90 ms |
| ae37560+dirty | 3832.10 ms | 1219.09 ms | -2613.02 ms |
| f170ec3+dirty | 3822.26 ms | 1218.33 ms | -2603.93 ms |
| 1e5d96d+dirty | 3851.45 ms | 1212.05 ms | -2639.41 ms |
| 6177334+dirty | 3834.85 ms | 1217.58 ms | -2617.28 ms |
| 5a010b7+dirty | 3838.85 ms | 1214.98 ms | -2623.87 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5125c43+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 5fe1c6c+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| 5257d80+dirty | 5.15 MiB | 6.69 MiB | 1.54 MiB |
| 0c50e94+dirty | 5.15 MiB | 6.95 MiB | 1.80 MiB |
| 3e3142c+dirty | 4.98 MiB | 6.56 MiB | 1.58 MiB |
| ae37560+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| f170ec3+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| 1e5d96d+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 6177334+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 5a010b7+dirty | 5.15 MiB | 6.69 MiB | 1.54 MiB |
Previous results on branch: feat/webview-trace-connection
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 3859.26 ms | 1234.82 ms | -2624.44 ms |
| cae40ac+dirty | 3867.38 ms | 1221.07 ms | -2646.31 ms |
| 38369a5+dirty | 3865.45 ms | 1234.82 ms | -2630.63 ms |
| 3f38932+dirty | 3848.33 ms | 1207.79 ms | -2640.54 ms |
| 42ab8cd+dirty | 3841.44 ms | 1217.98 ms | -2623.46 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| cae40ac+dirty | 5.15 MiB | 6.94 MiB | 1.79 MiB |
| 38369a5+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| 3f38932+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| 42ab8cd+dirty | 5.15 MiB | 6.95 MiB | 1.80 MiB |
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 23598c3+dirty | 414.12 ms | 426.24 ms | 12.12 ms |
| bc8f61e+dirty | 442.98 ms | 506.56 ms | 63.58 ms |
| 0a9e622+dirty | 420.73 ms | 464.83 ms | 44.10 ms |
| bf168a4+dirty | 418.21 ms | 489.74 ms | 71.53 ms |
| b0d3373+dirty | 557.66 ms | 579.42 ms | 21.76 ms |
| be120d6+dirty | 536.61 ms | 552.02 ms | 15.41 ms |
| 57e0069+dirty | 413.82 ms | 453.47 ms | 39.65 ms |
| b9bebee+dirty | 438.86 ms | 452.21 ms | 13.35 ms |
| 0d9949d+dirty | 403.57 ms | 437.00 ms | 33.43 ms |
| 580fb5c+dirty | 436.34 ms | 471.63 ms | 35.28 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 23598c3+dirty | 43.75 MiB | 48.16 MiB | 4.41 MiB |
| bc8f61e+dirty | 49.74 MiB | 55.09 MiB | 5.35 MiB |
| 0a9e622+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| bf168a4+dirty | 49.74 MiB | 55.09 MiB | 5.35 MiB |
| b0d3373+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| be120d6+dirty | 49.74 MiB | 55.36 MiB | 5.62 MiB |
| 57e0069+dirty | 49.74 MiB | 54.85 MiB | 5.11 MiB |
| b9bebee+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 0d9949d+dirty | 43.75 MiB | 48.13 MiB | 4.37 MiB |
| 580fb5c+dirty | 49.74 MiB | 54.79 MiB | 5.05 MiB |
Previous results on branch: feat/webview-trace-connection
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 424.09 ms | 468.34 ms | 44.25 ms |
| cae40ac+dirty | 453.83 ms | 511.20 ms | 57.37 ms |
| 3f38932+dirty | 478.31 ms | 537.14 ms | 58.83 ms |
| 38369a5+dirty | 460.42 ms | 490.15 ms | 29.73 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| cae40ac+dirty | 50.56 MiB | 56.59 MiB | 6.03 MiB |
| 3f38932+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| 38369a5+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
…roid `react-native-webview` only executes `injectedJavaScriptBeforeContentLoaded` on Android when an `onMessage` handler is set. `sentryWebViewProps` now returns a no-op `onMessage` so the trace-context injection also runs on Android for the documented usage; users who need their own handler spread the props first and set `onMessage` after. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Poll for the injected meta tag in the sample page: on Android the injected script runs shortly after the page's initial script, so an immediate read misses it (real apps' @sentry/browser read on pageload init, later). - Spread sentryWebViewProps before the sample's own onMessage so the sample's handler wins now that the helper returns one. - Add Android status-bar top padding to the Playground screen so the first button is not under the status bar. - Increase the button wait to 30s for slower Android cold starts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| }, | ||
| // RN's SafeAreaView is a no-op on Android, so clear the status bar explicitly | ||
| // to keep the first button tappable (and out from under the status bar). | ||
| scrollContent: { |
There was a problem hiding this comment.
Note: this change is unrelated to the core pr. It just fixes a preexisting issue were the first button of the playground tab was hidden below the status bar/camera.
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7436d0f+dirty | 3861.51 ms | 1231.07 ms | -2630.44 ms |
| c2e182c+dirty | 3841.76 ms | 1220.76 ms | -2621.01 ms |
| 0c50e94+dirty | 3856.18 ms | 1238.30 ms | -2617.87 ms |
| 61cc206+dirty | 3822.60 ms | 1206.17 ms | -2616.43 ms |
| c151573+dirty | 3835.64 ms | 1216.10 ms | -2619.53 ms |
| 2d5d963+dirty | 3858.87 ms | 1225.92 ms | -2632.95 ms |
| 84cba31+dirty | 3830.40 ms | 1204.44 ms | -2625.96 ms |
| 4e0b819+dirty | 3828.96 ms | 1205.64 ms | -2623.32 ms |
| 68ae91b+dirty | 3836.36 ms | 1222.04 ms | -2614.32 ms |
| ed70c7f+dirty | 3869.51 ms | 1240.25 ms | -2629.26 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7436d0f+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| c2e182c+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 0c50e94+dirty | 5.15 MiB | 6.95 MiB | 1.80 MiB |
| 61cc206+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| c151573+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 2d5d963+dirty | 5.15 MiB | 6.88 MiB | 1.73 MiB |
| 84cba31+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| 4e0b819+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 68ae91b+dirty | 4.98 MiB | 6.46 MiB | 1.48 MiB |
| ed70c7f+dirty | 5.15 MiB | 6.95 MiB | 1.80 MiB |
Previous results on branch: feat/webview-trace-connection
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 3836.43 ms | 1220.55 ms | -2615.87 ms |
| cae40ac+dirty | 3870.87 ms | 1227.49 ms | -2643.38 ms |
| 38369a5+dirty | 3859.80 ms | 1231.95 ms | -2627.84 ms |
| 3f38932+dirty | 3850.69 ms | 1221.91 ms | -2628.78 ms |
| 42ab8cd+dirty | 3868.29 ms | 1253.61 ms | -2614.67 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| cae40ac+dirty | 5.15 MiB | 6.94 MiB | 1.79 MiB |
| 38369a5+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| 3f38932+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| 42ab8cd+dirty | 5.15 MiB | 6.95 MiB | 1.80 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 038a6d7+dirty | 499.02 ms | 527.68 ms | 28.66 ms |
| 27d9693+dirty | 438.63 ms | 514.08 ms | 75.46 ms |
| 3d377b5+dirty | 425.38 ms | 440.67 ms | 15.30 ms |
| 0668149+dirty | 410.52 ms | 454.12 ms | 43.60 ms |
| 580fb5c+dirty | 515.72 ms | 585.38 ms | 69.66 ms |
| 0bd8916+dirty | 400.15 ms | 442.72 ms | 42.57 ms |
| 44c8b3f+dirty | 492.13 ms | 563.47 ms | 71.34 ms |
| 822d35b+dirty | 472.69 ms | 561.24 ms | 88.56 ms |
| 7d6fd3a+dirty | 435.06 ms | 458.78 ms | 23.72 ms |
| 7ff4d0f+dirty | 403.38 ms | 427.06 ms | 23.68 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 038a6d7+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 27d9693+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| 3d377b5+dirty | 43.94 MiB | 49.00 MiB | 5.06 MiB |
| 0668149+dirty | 50.56 MiB | 56.59 MiB | 6.03 MiB |
| 580fb5c+dirty | 49.74 MiB | 54.79 MiB | 5.05 MiB |
| 0bd8916+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| 44c8b3f+dirty | 48.30 MiB | 53.46 MiB | 5.15 MiB |
| 822d35b+dirty | 49.74 MiB | 54.84 MiB | 5.10 MiB |
| 7d6fd3a+dirty | 43.94 MiB | 49.00 MiB | 5.06 MiB |
| 7ff4d0f+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
Previous results on branch: feat/webview-trace-connection
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 478.42 ms | 535.94 ms | 57.52 ms |
| cae40ac+dirty | 468.24 ms | 523.41 ms | 55.17 ms |
| 3f38932+dirty | 506.24 ms | 560.89 ms | 54.65 ms |
| 38369a5+dirty | 453.41 ms | 480.22 ms | 26.81 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 38369a5+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| cae40ac+dirty | 50.56 MiB | 56.59 MiB | 6.03 MiB |
| 3f38932+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| 38369a5+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
Skip injection if a `sentry-trace` meta tag already exists in the page, so the trace context is never duplicated when more than one path could inject it (e.g. a future native injector alongside the RN helper, or the script running twice). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
||
| // @public | ||
| export function sentryWebViewProps(options: SentryWebViewOptions): { | ||
| injectedJavaScriptBeforeContentLoaded: string; |
There was a problem hiding this comment.
m: Thinking forward this could potentially grow into multiple events being supported. Maybe it makes sense to prepare and use a structured approach, e.g.:
| injectedJavaScriptBeforeContentLoaded: string; | |
| injectedCustomScripts: { | |
| beforeContentLoaded: string | |
| } |
There was a problem hiding this comment.
Thank you @philprime 🙇 I surfaced the underlying component api for this one but I'll consider future proofing it.
| const sentryTraceLiteral = JSON.stringify(sentryTrace); | ||
| const baggageLiteral = JSON.stringify(traceData.baggage ?? null); | ||
|
|
||
| return `(function(){try{ |
There was a problem hiding this comment.
m: Is this safe for code injection attacks? Just thinking if any of the values are from external systems/cookies etc. and could be abused by an attacker.
cc @NinjaLikesCheez as you are a security expert
There was a problem hiding this comment.
We should absolutely engage the security team as early as possible in the design of this feature - here be dragons.
There was a problem hiding this comment.
Good point 👍 Marking as blocked to get more feedback on the security side of the changes.
There was a problem hiding this comment.
Check RN-796 for a link to the security review
- Move the webview helper out of `integrations/` (it is not an integration) to `src/js/tracing/webview.ts` and re-export it via the tracing barrel. The public API is unchanged. - Normalize `allowedHosts` entries (trim, strip scheme/path/port/trailing dot, lowercase) so bare-hostname matching is robust. - Replace the `allowedHosts ?? []` fallback on the required field with an explicit validation guard that degrades to a no-op with a warning instead of throwing during render. - Reword the no-trace debug log to reflect the real cause (the SDK being disabled or not initialized, not "no active trace"). - Make the fuzz test deterministic with a seeded PRNG and add tests for the normalization and empty-allowedHosts cases. Addresses review feedback from @alwx. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CodeQL flagged `/[/?#].*$/` in `normalizeHost` as a polynomial regular expression (backtracking on inputs with many `#`). Replace it with a `search` + `slice` that strips everything from the first path/query/fragment separator without a `.*` to backtrack on. Normalization output is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b2eb4fa. Configure here.
The JSDoc claimed the trace tags are written "before @sentry/browser initializes", which only holds on iOS. On Android `react-native-webview` injects asynchronously as the page starts, so the tags can land after the page's first synchronous <head> scripts. Reword to describe the real timing: a @sentry/browser loaded the usual way (script tag, bundle, or async/deferred init) reads the tags once present and continues the trace; only a synchronous inline init at the very top of <head> may miss them on Android and start a new trace (which fails safe — the WebView just gets its own trace). Verified on a Pixel 8 emulator: real @sentry/browser continued the trace 5/5 runs; a probe confirmed the meta is not present at the first synchronous head script. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nnection # Conflicts: # CHANGELOG.md
`normalizeHost` stripped a trailing `:<digits>` unconditionally, mistaking an unbracketed IPv6 hextet for a port (`::1` -> `:`, `2001:db8::1` -> `2001:db8:`), so an IPv6-hosted page could never match and the trace was skipped. Browsers bracket IPv6 in `location.hostname` (e.g. `[::1]`), so: keep a bracketed host up to the closing bracket (dropping any `:port` after it), wrap an unbracketed IPv6 literal (more than one colon) in brackets so it still matches, and keep normal `:port` stripping for hostnames and IPv4. Still no `.*` regex (CodeQL-safe). Addresses Warden review finding on allowedHosts normalization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…w traces (#19659) ## DESCRIBE YOUR PR⚠️ Should be merged after getsentry/sentry-react-native#6794 is released Documents the new `sentryWebViewProps` helper (React Native SDK **8.32.0**) for connecting React Native traces with embedded WebViews. Updates `docs/platforms/react-native/common/configuration/webview.mdx`: - Adds a **Connect Traces** section: spread-onto-`<WebView>` usage, `allowedHosts` gating (host + subdomain, case-insensitive), the requirement that the web page run a Browser SDK with `browserTracingIntegration`, the Android `onMessage` requirement + spread order, entry-pageload scope, and cross-project (same-org) trace stitching. - Removes the outdated "the SDK doesn't interact with the mobile application's web views" statement and clarifies error ownership between the native and web SDKs. Corresponding SDK PR: getsentry/sentry-react-native#6794 > **Note:** please hold merge until `@sentry/react-native` **8.232.0** is released (the page states that as the minimum version). ## IS YOUR CHANGE URGENT? - [x] No deadline: Not urgent, can wait up to 1 week+ (but hold until SDK 8.29.0 ships) ## PRE-MERGE CHECKLIST - [ ] Checked Vercel preview for correctness, including links - [ ] PR was reviewed and approved by any necessary SMEs (subject matter experts) - [ ] PR was reviewed and approved by a member of the [Sentry docs team](https://github.com/orgs/getsentry/teams/docs) ## EXTRA RESOURCES - [Sentry Docs contributor guide](https://docs.sentry.io/contributing/) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`sentryWebViewProps` is a helper rather than an integration, so it was invisible to Sentry's feature-adoption telemetry. Register a no-op `WebViewTracePropagation` marker when the helper is used, so the feature flows through to `event.sdk.integrations` — the channel used to measure adoption across SDKs (same approach as GlobalErrorBoundary, NavigationContainer, MobileReplay). Idempotent and fails quietly. Follow-up to #6794. Aligns with the WebView adoption markers planned for the Apple, Android, and Flutter SDKs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

📢 Type of change
📜 Description
Adds the ability to inject the React Native SDK's current trace context into a webview.
Usage:
The
sentry-tracemetatag is used to propagate the distributed tracing.Alternatives considered
Also considered the wrapper component approach but decided to proceed with an implementation using props.
react-native-webview<WebView>, custom wrappers, refs'use dom')domprop:dom={{ ...sentryWebViewProps(...) }}domtakes a props object, not a componentinjectedJavaScriptBeforeContentLoaded(low-sensitivity, spreadable)💡 Motivation and Context
Closes #6779
Closes #6780
Closes #6781
💚 How did you test it?
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps