feat(network): capture in-app HTTP traffic for React Native sessions

TH
17h ago
+3149-65

Summary

network dump on a React Native session with a Metro dev-server binding now installs a JS fetch/XMLHttpRequest interceptor into the app runtime over the Metro inspector (CDP Runtime.evaluate) and reports real traffic: method, full URL, status, duration, headers, and bounded bodies. Previously it only parsed app-log lines, which on RN name almost nothing.

The first dump arms the hook and answers that window from the app log (source=app-log plus an arming note); later dumps answer from the in-app ring buffer (source=provider, drain-once, newest-first, 200 entries). No Metro binding, an unreachable dev server, several connected apps, or a session device the host cannot name uniquely keeps the old log path with a typed note. The provider injects only into the runtime whose reported device equals the platform-composed Metro device identity — a name that marks exactly one device this host can see (Android reads every adb devices serial in any state and withholds the name when any read fails; Apple checks booted simulators in every simulator set the host can name plus CoreDevice's connected devices, and ADR 0038 records the one residue it cannot see: a set directory the host was never told about); anything weaker fails closed with metro-device-mismatch before any injection. The identity probe resolves lazily inside a dump, so only a session whose transport declared a provider dump pays for it. A session without an app never reaches the provider at all. Bodies/headers are capped and redacted with the shared kernel rules. Provider preference lives inside the Apple/Android runtimes via the existing NetworkTransport slot, so network dump stays always-available. docs/adr/0038 records the source owner, the probe | provider | app-log enum, and the lazy-arming opt-in policy shared with #3928.

agent-device network dump                # arms the capture (answers from app log)
agent-device network dump --include all  # method, URL, status, duration, headers, bodies

Rejected alternatives

  • RN DevTools' built-in Network CDP domain instead of injected code: no app-side injection, but it needs a daemon-held CDP connection for the session's lifetime and drops RN versions without the domain. The injected runtime (~320 lines) is the version-agnostic core; which floor we commit to belongs in the source-selection ADR alongside #3928.
  • Arming at app open when Metro is bound: rejected for now. Injection stays coupled to the first explicit network dump, so nothing is installed in the app under test until a command asks for network data. Open-path arming needs an attach-retry policy for cold-start races and pre-empts the #3928/ADR injection-policy decision.

Validation

  • pnpm check:affected --run at head 3271f8738e: all runnable checks passed (exactCommit=true, base c48d27a819, receipt .tmp/validation/run-DWkJRo/receipt.json); fallow audit --base c48d27a819 passes on changed files.
  • Review round 3 (fail-closed identity): Apple checks every simulator set the host can name (own set, default set, operator set) plus CoreDevice-connected devices and fails closed on any unreadable answer; Android reads every listed serial in any state with bounded timeouts and withholds the identity on any unreadable serial; the probe resolves lazily inside the dump. Live iOS at the final head: arming dump, source=provider capture, drain-once, then a second booted same-named simulator flips the dump to source=app-log with the mismatch note, and shutting the twin restores it. Verbatim outputs in the round-3 reply.
  • Review round 2 (device identity): metroDeviceIdentity(device) is platform-owned — Android composes <model> - <release> - API <level> only when unique across adb devices, Apple passes the simulator name only when unique among booted simulators; anything weaker fails closed with metro-device-mismatch before any injection. Live on two same-image emulators (both sdk_gphone64_arm64): shared model fails closed with no injection on either device; one attached device arms and captures the full sequence. Verbatim outputs in the round-2 reply.
  • Live iOS: simulator rn-net2 ios, final head — arming dump (source=app-log + arming note), then source=provider dumps with full URL/status/durationMs/headers/bodies for the network lab's fetch/POST/XHR (httpbin), x-api-key shown as [REDACTED]; drain-once verified; cold restart re-arms; a failed fetch followed by an XHR to the same URL reports both entries.
  • Live Android: emulator ReactNative_API_35 — same sequence; entries carry the okhttp/4.12.0 User-Agent.
  • Live two-device Metro (one Metro, iOS sim + Android emulator, two sessions): each session's dump returned only its own runtime's entries; draining one left the other at matchedLines=0.
  • Full CLI outputs and per-finding disposition in the review-round reply.

🤖 Generated with Claude Code