Skip to content

feat(replay): capture fetch (Blob/ArrayBuffer) response bodies in Session Replay network details - #6533

Open
alwx wants to merge 2 commits into
mainfrom
alwx/bugfix/6376
Open

feat(replay): capture fetch (Blob/ArrayBuffer) response bodies in Session Replay network details#6533
alwx wants to merge 2 commits into
mainfrom
alwx/bugfix/6376

Conversation

@alwx

@alwx alwx commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Carries over #6473 by @Cryptoteep.

From #6473:
React Native's fetch is the whatwg-fetch polyfill built on XMLHttpRequest with responseType = 'blob', so every fetch response body landed in the binary branch of _getResponseBodyString and surfaced as [UNPARSEABLE_BODY_TYPE] in the Replay network tab, even for plain JSON. Text-like binary payloads are now read asynchronously (FileReader for Blob, manual UTF-8 decode for ArrayBuffer) and inlined like text bodies, still gated on networkDetailAllowUrls + networkCaptureBodies and capped at NETWORK_BODY_MAX_SIZE.

Second commit — the review fixes.

Known limitation

The breadcrumb attached to a JS error event still shows [UNPARSEABLE_BODY_TYPE] for these responses — the scope holds a normalized copy that can't be back-patched. The resolved body appears in the Replay network tab. This matches current main exactly, so it is not a regression; noted in the CHANGELOG.

💡 Motivation and Context

Fixes #6376. Supersedes #6473.

fetch is how most RN apps make requests, so before this the network-body half of Session Replay network detail (#6288, #6373) was effectively unusable outside axios/raw XHR.

💚 How did you test it?

Tests were added.

📝 Checklist

  • I added tests to verify changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled. Unchanged opt-in (networkDetailAllowUrls + networkCaptureBodies); auth-like headers still stripped.
  • I updated the docs if needed.
  • I updated the wizard if needed. n/a
  • All tests passing.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec. No public API change — api-report:check clean.
  • No breaking changes.

@github-actions

github-actions Bot commented Jul 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(replay): capture fetch (Blob/ArrayBuffer) response bodies in Session Replay network details by alwx in #6533
  • chore(deps): add sharp ^0.35.0 resolution (dev-only advisory) by antonis in #6525
  • chore(deps): add body-parser ^2.3.0 resolution (dev-only advisory) by antonis in #6524
  • chore(deps): add morgan ^1.11.0 resolution (dev-only advisory) by antonis in #6523
  • chore(deps): bump postcss to ^8.5.18 (dev-only advisory) by antonis in #6522
  • chore(deps): bump brace-expansion 5.x to ^5.0.8 (dev-only advisory) by antonis in #6521
  • ci: Disable yarn age gate when bumping first-party @sentry deps by antonis in #6513
  • feat(core): Attach TurboModule breakdown to active spans on spanEnd by alwx in #6478
  • feat(core): Export instrumentStateGraph and deprecate instrumentLangGraph by antonis in #6520
  • chore(deps): bump the codeql-action group across 1 directory with 3 updates by dependabot in #6517
  • chore(deps): bump ruby/setup-ruby from 1.319.0 to 1.321.0 by dependabot in #6518
  • chore(deps): bump actions/checkout from 7.0.0 to 7.0.1 by dependabot in #6519
  • chore(deps): update JavaScript SDK to v10.68.0 by github-actions in #6516
  • chore(deps): bump tar from 7.5.20 to 7.5.21 by dependabot in #6515
  • chore(deps): bump lerna to ^9.0.7 to resolve remaining dev-tooling advisories by antonis in #6499
  • chore(deps): Migrate to @sentry/bundler-plugins by antonis in #6501
  • chore: update changelog warning with fix version by antonis in #6509
  • chore(deps): bump actions/setup-node from 6.4.0 to 7.0.0 by dependabot in #6463
  • chore(deps): bump json from 2.17.1.2 to 2.19.9 in /performance-tests by dependabot in #6512
  • chore(deps): update CLI to v3.6.2 by github-actions in #6511
  • chore(deps): update Sentry Android Gradle Plugin to v6.16.0 by github-actions in #6508
  • chore(deps): update Android SDK to v8.50.1 by github-actions in #6503

🤖 This preview updates automatically when you update the PR.

…sion Replay network details

React Native's fetch polyfill is built on XMLHttpRequest with responseType
'blob', so every fetch response body previously surfaced as
[UNPARSEABLE_BODY_TYPE] in the Replay network tab even when the payload was
plain JSON or text.

Binary bodies can only be read asynchronously, while xhr breadcrumbs are
forwarded to the native SDKs synchronously. When an allow-listed xhr breadcrumb
carries a text-like (JSON/XML/text/form) Blob or ArrayBuffer response and body
capture is enabled, the breadcrumb is held in beforeBreadcrumb, the body is read
(FileReader for Blob with a 500ms timeout, capped at NETWORK_BODY_MAX_SIZE by
slicing before the read; manual UTF-8 decode for ArrayBuffer since Hermes has no
TextDecoder), and the same breadcrumb is re-added with the resolved body on the
hint. Its original timestamp is preserved. Genuinely binary payloads (images,
octet-stream) keep the UNPARSEABLE_BODY_TYPE marker without being read, and read
failures or timeouts fall back to the same marker.

Squashed from #6473.

Closes #6376
@alwx
alwx force-pushed the alwx/bugfix/6376 branch from e8bc1b2 to b1a1c07 Compare July 28, 2026 13:35
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against fcd8ab3

@alwx
alwx marked this pull request as ready for review July 28, 2026 13:37
…he native sync

Holding the breadcrumb in beforeBreadcrumb (returning null) until the async body
read finished removed it from the scope entirely, so an error captured in that
window lost it from event.breadcrumbs. The common `if (!res.ok) throw` path hits
this reliably: the app never reads the body, so nothing yields long enough for
the FileReader to land before the throw. Breadcrumbs are also filtered by
timestamp into replay segments on Android, so a re-added crumb could be dropped
from the replay when it straddled a segment boundary.

The breadcrumb is now returned as before and lands on the JS scope immediately.
Only the sync to native — which is what feeds the Replay network tab, since the
native converters build the rrweb span from the synced xhr breadcrumb — is
deferred, via deferBreadcrumbNativeSync/syncBreadcrumbToNative in scopeSync, and
performed once with the resolved body. buildResolvedNetworkBreadcrumb builds the
native-bound copy so the breadcrumb already on the scope is never mutated.

Also in this change:

- Retry a truncated blob read with a shorter slice. Slicing at a byte offset can
  cut a multi-byte UTF-8 sequence, and iOS decodes via
  -[NSString initWithData:encoding:], which returns nil for the whole chunk
  rather than substituting U+FFFD — so bodies over the cap silently fell back to
  UNPARSEABLE_BODY_TYPE. readBlobAsText now rejects with a distinguishable
  BLOB_DECODE_FAILED and the read retries with up to 3 fewer bytes, which is
  guaranteed to reach a character boundary. Timeouts and read errors are not
  retried.
- Match javascript/ecmascript content types as text-like.
- Update the networkDetailAllowUrls docs, which still said fetch bodies were
  unsupported.
- Correct the abort() comment: RN's FileReader never enters LOADING, so abort()
  dispatches no events and does not cancel the native read.
- Note in decodeUtf8 that the manual decoder is the path actually taken — neither
  Hermes nor JSC ships TextDecoder, so the TextDecoder branch only runs in tests.

Adds test/replay/networkBodyCapture.test.ts, which exercises the whole flow
against the real @sentry/core pipeline and the real scope sync patch. It guards
the object-identity assumption the deferral relies on: the breadcrumb returned
from beforeBreadcrumb must be the exact object passed to scope.addBreadcrumb.
@alwx
alwx force-pushed the alwx/bugfix/6376 branch from b1a1c07 to fcd8ab3 Compare July 28, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session Replay: capture fetch/binary (Blob, ArrayBuffer) response bodies in network detail

2 participants