Skip to content

feat(tracing): Connect React Native traces with embedded WebViews - #6794

Draft
antonis wants to merge 14 commits into
mainfrom
feat/webview-trace-connection
Draft

antonis wants to merge 14 commits into
mainfrom
feat/webview-trace-connection

Conversation

@antonis

@antonis antonis commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

📢 Type of change

  • New feature
  • Bugfix
  • Enhancement
  • Refactoring

📜 Description

Adds the ability to inject the React Native SDK's current trace context into a webview.

Usage:

import { sentryWebViewProps } from '@sentry/react-native';

<WebView
  source={{ uri: 'https://example.com' }}
  {...sentryWebViewProps({ allowedHosts: ['example.com'] })}
/>

The sentry-trace meta tag is used to propagate the distributed tracing.

Alternatives considered

Also considered the wrapper component approach but decided to proceed with an implementation using props.

Props-helper (this PR) Wrapper component
Dependency on react-native-webview None — returns plain props Pulls it in / must use our component
Composability Spreads onto any <WebView>, custom wrappers, refs Must use our component; friction with custom setups
Expo DOM components ('use dom') ✅ Spread into the dom prop: dom={{ ...sentryWebViewProps(...) }} ❌ dom takes a props object, not a component
Encapsulation Exposes injectedJavaScriptBeforeContentLoaded (low-sensitivity, spreadable) Hides injection internals

💡 Motivation and Context

Closes #6779
Closes #6780
Closes #6781

💚 How did you test it?

  • Added tests
  • Manual testing (example)

📝 Checklist

🔮 Next steps

antonis and others added 2 commits September 28, 2026 13:50
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>
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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): Connect React Native traces with embedded WebViews by antonis in #6794

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actions Bot commented Sep 28, 2026 •

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

Generated by 🚫 dangerJS against 8927fb9

@antonis antonis added the ready-to-merge Triggers the full CI test suite label Sep 28, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/core/src/js/integrations/webview.ts

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@sentry

sentry Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-react-native Build Distribution Settings

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3837.81 ms 1219.17 ms -2618.64 ms
Size 5.15 MiB 6.96 MiB 1.81 MiB

Baseline results on branch: main

Startup times

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

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 442.87 ms 466.57 ms 23.71 ms
Size 50.56 MiB 56.62 MiB 6.06 MiB

Baseline results on branch: main

Startup times

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

antonis and others added 2 commits September 28, 2026 14:32
…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: {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3872.88 ms 1245.69 ms -2627.18 ms
Size 5.15 MiB 6.96 MiB 1.81 MiB

Baseline results on branch: main

Startup times

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

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 502.56 ms 553.02 ms 50.46 ms
Size 50.56 MiB 56.62 MiB 6.06 MiB

Baseline results on branch: main

Startup times

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>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/js/integrations/webview.ts
@antonis
antonis marked this pull request as ready for review September 28, 2026 14:08
@antonis
antonis requested review from a team, alwx and lucas-zimerman as code owners September 28, 2026 14:08

// @public
export function sentryWebViewProps(options: SentryWebViewOptions): {
injectedJavaScriptBeforeContentLoaded: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.:

Suggested change
injectedJavaScriptBeforeContentLoaded: string;
injectedCustomScripts: {
beforeContentLoaded: string
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should absolutely engage the security team as early as possible in the design of this feature - here be dragons.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point 👍 Marking as blocked to get more feedback on the security side of the changes.

@antonis
antonis marked this pull request as draft September 29, 2026 11:50
Comment thread CHANGELOG.md
antonis and others added 3 commits October 14, 2026 18:05
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>
@antonis
antonis force-pushed the feat/webview-trace-connection branch from 51dc667 to 87043de Compare October 14, 2026 16:35
Comment thread packages/core/src/js/integrations/webview.ts
antonis and others added 2 commits October 14, 2026 19:33
`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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 1627eee. Configure here.

const rnTrace = getTraceData()['sentry-trace'];
const [received, setReceived] = React.useState<string | null>(null);

const matches = received != null && received === rnTrace;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1627eee. Configure here.

@antonis antonis added ready-to-merge Triggers the full CI test suite and removed ready-to-merge Triggers the full CI test suite labels Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Blocked ready-to-merge Triggers the full CI test suite

Projects

None yet

3 participants