Skip to content

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

Open
Cryptoteep wants to merge 4 commits into
getsentry:mainfrom
Cryptoteep:feat/replay-fetch-blob-response-bodies
Open

feat(replay): capture fetch (Blob/ArrayBuffer) response bodies in Session Replay network details#6473
Cryptoteep wants to merge 4 commits into
getsentry:mainfrom
Cryptoteep:feat/replay-fetch-blob-response-bodies

Conversation

@Cryptoteep

@Cryptoteep Cryptoteep commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

📜 Description

Closes #6376

In React Native, 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 when the payload was plain JSON or text. This PR inlines parseable Blob/ArrayBuffer response bodies like text bodies, following the approach sketched in the issue.

The async capture path

Binary bodies can only be read asynchronously (FileReader), but xhr breadcrumbs are forwarded to the native SDKs synchronously (the beforeAddBreadcrumb enrichment hook runs right before scope.addBreadcrumb, which syncs to native). So the enrichment itself can't wait for the read.

Instead, when body capture is enabled and an allow-listed xhr breadcrumb carries a text-like binary response, the wrapped beforeBreadcrumb (same wrapping pattern the integration already uses for beforeSend):

  1. holds the breadcrumb (returns null),
  2. reads the body — FileReader for blob with a 500 ms timeout, slicing to NETWORK_BODY_MAX_SIZE before the read so a huge payload is never fully read into memory; manual UTF-8 decode for arraybuffer (Hermes has no TextDecoder; uses it when available),
  3. re-adds the identical breadcrumb via addBreadcrumb with the resolved body carried on the hint. Its timestamp was set on the first pass and is preserved; the user's beforeBreadcrumb runs only on the first pass; the resolved-body hint key makes the second pass skip the hold branch, and the existing beforeAddBreadcrumb enrichment uses the resolved body instead of reading xhr.response.

Guardrails, per the issue's considerations:

  • Still opt-in: only for URLs matching networkDetailAllowUrls (minus networkDetailDenyUrls) with networkCaptureBodies enabled; beforeBreadcrumb isn't wrapped at all otherwise.
  • Genuinely binary payloads (images, octet-stream, video…) are not read: only text-like content types (JSON, XML, text/*, form-urlencoded) qualify; everything else keeps the UNPARSEABLE_BODY_TYPE marker as before.
  • Read failure or timeout falls back to UNPARSEABLE_BODY_TYPE — never worse data than today.
  • Size cap reuses NETWORK_BODY_MAX_SIZE with the existing MAX_BODY_SIZE_EXCEEDED warning.

Trade-off to be aware of: a held breadcrumb reaches the scope (and native) a few ms later than its neighbors, so strict insertion order among breadcrumbs can shift; the breadcrumb's own timestamp stays correct. Request bodies for fetch (Request / RequestInit.body Blobs) are intentionally left for a follow-up, as noted in the issue.

💚 How did you test it?

  • 20 new unit tests across networkUtils, xhrUtils and mobilereplay covering: content-type classification, the manual UTF-8 decoder (multi-byte, invalid sequences, no-TextDecoder path), FileReader success/error/timeout, blob slicing + truncation warning, the hold/re-add flow, single execution of the user's beforeBreadcrumb, user drops staying dropped, and no wrapping when capture is off.
  • Full test/replay suite green (115 tests); tsc -p tsconfig.build.json, oxlint and oxfmt clean.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • No new PII beyond the existing opt-in (networkDetailAllowUrls + networkCaptureBodies, auth-like headers still stripped).
  • I updated the docs if needed.
  • All tests passing.
  • No breaking changes.

🤖 Generated with Claude Code

…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 now 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.

Closes getsentry#6376

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

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 Cryptoteep in #6473

🤖 This preview updates automatically when you update the PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread packages/core/src/js/replay/mobilereplay.ts
Comment thread packages/core/src/js/replay/networkUtils.ts Outdated
…f-8 decoder dropping bytes after an interrupted sequence

- resolveXhrResponseBody now snapshots request headers, raw response
  headers and the response body size synchronously, and the re-added
  breadcrumb is enriched only from that snapshot — the live xhr may have
  been reused or cleared during the async gap (Bugbot: stale XHR data).
- the manual UTF-8 fallback decoder no longer discards bytes following a
  truncated/interrupted multi-byte sequence; the consumed prefix decodes
  to a single U+FFFD (maximal subpart), matching TextDecoder.
- drop non-null assertions flagged by oxlint no-unnecessary-type-assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread packages/core/src/js/replay/networkUtils.ts
…lid code point

A structurally complete sequence that decodes to a surrogate or a code
point above U+10FFFF previously advanced by one byte, so its continuation
bytes were re-decoded as stray bytes and produced extra U+FFFDs.

Co-Authored-By: Claude Fable 5 <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 e479547. Configure here.

.then(undefined, (error: unknown) => {
debug.error(`[Sentry] ${MOBILE_REPLAY_INTEGRATION_NAME} Failed to re-add network breadcrumb`, error);
});
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Held fetch breadcrumbs miss error events

High Severity

Returning null from beforeBreadcrumb while awaiting FileReader drops allow-listed fetch xhr breadcrumbs from the scope until the read finishes. RN fetch always uses responseType: 'blob', and FileReader completion runs after promise microtasks, so errors thrown in fetch response handlers (and hard crashes in that window) no longer include that network breadcrumb — a regression versus the previous synchronous UNPARSEABLE_BODY_TYPE path. Flagged under the PR review correctness guidance for instrumentation that must not lose telemetry on common error paths.

Additional Locations (2)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit e479547. Configure here.

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

1 participant