feat(replay): capture fetch (Blob/ArrayBuffer) response bodies in Session Replay network details - #6533
Open
alwx wants to merge 2 commits into
Open
feat(replay): capture fetch (Blob/ArrayBuffer) response bodies in Session Replay network details#6533alwx wants to merge 2 commits into
alwx wants to merge 2 commits into
Conversation
Contributor
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. |
6 tasks
…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
Contributor
|
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📢 Type of change
📜 Description
Carries over #6473 by @Cryptoteep.
From #6473:
React Native's
fetchis thewhatwg-fetchpolyfill built onXMLHttpRequestwithresponseType = 'blob', so everyfetchresponse body landed in the binary branch of_getResponseBodyStringand surfaced as[UNPARSEABLE_BODY_TYPE]in the Replay network tab, even for plain JSON. Text-like binary payloads are now read asynchronously (FileReaderforBlob, manual UTF-8 decode forArrayBuffer) and inlined like text bodies, still gated onnetworkDetailAllowUrls+networkCaptureBodiesand capped atNETWORK_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 currentmainexactly, so it is not a regression; noted in the CHANGELOG.💡 Motivation and Context
Fixes #6376. Supersedes #6473.
fetchis how most RN apps make requests, so before this the network-body half of Session Replay network detail (#6288, #6373) was effectively unusable outsideaxios/raw XHR.💚 How did you test it?
Tests were added.
📝 Checklist
sendDefaultPIIis enabled. Unchanged opt-in (networkDetailAllowUrls+networkCaptureBodies); auth-like headers still stripped.api-report:checkclean.