Repository navigation
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.
| * ``` | ||
| */ | ||
| export function sentryWebViewProps(options: SentryWebViewOptions): { | ||
| injectedJavaScriptBeforeContentLoaded: string; |
There was a problem hiding this comment.
m: See my comment above w.r.t. future-proofing
| 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.
# Conflicts: # CHANGELOG.md
The root span name is propagated to the WebView via the `baggage` meta tag as `sentry-transaction`. It is free-form text that can carry user data (for example a parameterized URL), so remove it before the trace context leaves the app. Every other Dynamic Sampling Context entry (release, environment, sample rate, sample rand, sampled) and any third-party baggage entries are preserved, so dynamic sampling stays consistent across the connected traces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feed hostile values (quote/backtick/template-literal, </script>, backslash, newline/CR, U+2028/U+2029 line separators, null byte, statement + comment) and 200 random dangerous-charset strings through every value embedded in the injected script — the sentry-trace value, a baggage entry value, and an allowedHosts entry. Assert a window canary never executes and the values are only ever read back as inert meta content, proving JSON.stringify is the escaping boundary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51dc667 to
87043de
Compare
`sentryWebViewProps` returns a single `injectedJavaScriptBeforeContentLoaded` string. Spreading the props and then setting your own value after the spread silently replaces Sentry's injection and disables trace linking (and setting it before drops yours). Unlike `onMessage` — where any handler keeps the Android injection running — this prop cannot be safely overridden, so document that it must be concatenated with the returned string, with an example. Addresses Warden review finding 9C6-FDS. 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 1627eee. Configure here.
| const rnTrace = getTraceData()['sentry-trace']; | ||
| const [received, setReceived] = React.useState<string | null>(null); | ||
|
|
||
| const matches = received != null && received === rnTrace; |
There was a problem hiding this comment.
Demo compares live trace not snapshot
Low Severity
rnTrace is read from getTraceData on every render, so it is not the mount-time snapshot the comment describes. After the navigation span ends, getTraceData returns a different span_id, and the status can flip to not connected even when the WebView received the injected header. That also makes the Maestro assertion on webview-trace-status timing-dependent.
Reviewed by Cursor Bugbot for commit 1627eee. Configure here.


📢 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