feat(extension): Network Interceptor v2 — request/response bodies + headers (XHR/Fetch via web-sdk)#56
Merged
nafees87n merged 9 commits intoJun 3, 2026
Conversation
…ders for XHR/Fetch via web-sdk Captures full request + response bodies and headers for XHR/Fetch by reusing the web-sdk Network interceptor (the module session recording uses), filling the HAR fields v1 left empty (request.postData, response.content.text). - SDK is the SOLE source for XHR/Fetch: webRequest is hard-suppressed for "xmlhttprequest" in onBeforeSendHeaders/onRequestCompleted/onRequestError, so there's exactly one source and no correlation. The v1 correlationMap is left intact for the non-xhr/fetch resource types that still come from webRequest. - New MAIN-world page script (networkBodyRecorder) calls the global Requestly.Network.intercept; the web-sdk UMD is injected per recorded tab (re-injected on webNavigation.onCommitted, single-tab scoped). overrideResponse is false (observe only — never alters the real request/response). - Size caps re-implemented in the page script (Network.intercept has no options; maxPayloadSize/ignoreMediaResponse live only on SessionRecorder): media-skip + per-body maxPayloadSize, surfaced as a _truncated extension field on the entry. - maxPayloadSize is configurable via startNetworkRecording config (default 100KB). - Stop gates the page callback off (never Network.clearInterceptors() — that would nuke other SDK consumers like session recording). - SDK-sourced entries flow through the same deliverEntry/stream path as v1, so LTS and the side panel consume them identically. No new stream message type. Known limitation: an XHR/Fetch the SDK can't capture (pre-injection race, no-cors/opaque, CSP-blocked injection) is dropped entirely (not backfilled from webRequest) — accepted trade for the no-correlation simplicity. Builds clean; message wiring verified static end-to-end. Live-browser recording not yet exercised. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… window.Requestly The web-sdk UMD declares a top-level `var Requestly` (no explicit window attach). Referencing it bare — as the shipping sessionRecorderHelper.js does with `Requestly.SessionRecorder` — resolves the global binding regardless of how the injected file's scope reflects onto window; `window.Requestly` was a fragile stronger assumption. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… feat/network-recording-bodies-v2 # Conflicts: # browser-extension/mv3/src/service-worker/services/networkRecording/index.ts
… feat/network-recording-bodies-v2 # Conflicts: # browser-extension/mv3/src/service-worker/services/networkRecording/index.ts
…ed start guard (v2) The decoupled CLIENT_PAGE_LOADED panel-open path didn't open the side panel (a page-load event is not a user gesture), and the isExtensionEnabled await before tabs.create pushed sidePanel.open() past the gesture window. Restore the synchronous start path with eager openPanel(tab.id), and drop the start-guard. (Converges with base's 53d9d75; reconciled in the following merge.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… feat/network-recording-bodies-v2 # Conflicts: # browser-extension/mv3/src/service-worker/services/networkRecording/index.ts
… feat/network-recording-bodies-v2 # Conflicts: # browser-extension/mv3/src/service-worker/services/networkRecording/index.ts
… feat/network-recording-bodies-v2 # Conflicts: # browser-extension/mv3/src/service-worker/services/networkRecording/index.ts
… feat/network-recording-bodies-v2
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.
What
v2 of the LT Network Interceptor: captures full request + response bodies and headers for XHR/Fetch, filling the HAR fields v1 left empty (
request.postData,response.content.text). Builds on v1 (PR #53).Proposal: https://browserstack.atlassian.net/wiki/spaces/ENG/pages/6238732295 (v2 section)
How it works
Source-of-truth split — one source per request class, no correlation:
Networkinterceptor onlychrome.webRequest(v1, unchanged)Networkmodule (the same one session recording uses in production) — consumed directly (not viaSessionRecorder, which strips headers), so headers survive. A new MAIN-world page script (networkBodyRecorder) calls the globalRequestly.Network.intercept(..., overrideResponse=false)— observe only, never alters the real request/response.xmlhttprequest(the resource type for both XHR and fetch) → exactly one source, no dedupe/supersede/correlation. The v1correlationMapis untouched and stays load-bearing for non-xhr/fetch types.webNavigation.onCommitted(single-tab scoped). Stop gates the page callback off (neverNetwork.clearInterceptors()— that would nuke other SDK consumers).Config & caps
startNetworkRecordingconfig gainsmaxPayloadSize(bytes, default 100 KB, LTS-overridable).Network.intercepthas no size options — they live only onSessionRecorder): media-response skip + per-body cap. Over-cap/dropped bodies are flagged via a_truncatedHAR extension field (101 = request too large, 102 = response too large), so LTS can tell "dropped (too large)" from genuinely empty.SDK-sourced entries flow through the same stream/buffer path as v1 — LTS and the side panel consume them identically. No new stream message type. Side panel stays metadata-only this increment (bodies stream to LTS; in-panel body display deferred).
Known limitation
An XHR/Fetch the SDK can't capture — fired before the page script injected, a
no-cors/opaque fetch, or a page where MAIN-world injection is blocked by strict CSP — is dropped entirely (not backfilled from webRequest). Accepted trade for the no-correlation simplicity. (CSP is mitigable via the existinghandleCSPErrorDNR strip, but that alters page behavior / load-test fidelity — left as a separate, LTS-confirmed decision.)Testing
requestly:client/requestly:extensionsources).mv3/distunpacked and confirm: XHR/fetch entries carry headers + bodies, no duplicate xhr/fetch entries,request.postData/response.content.textpopulate, over-cap →_truncated, media response skipped, navigation re-injects.🤖 Generated with Claude Code