feat(network-details): Extend SentryReplayOptions API with Session Replay Network Details configuration#7580
feat(network-details): Extend SentryReplayOptions API with Session Replay Network Details configuration#7580
Conversation
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. This PR will not appear in the changelog. 🤖 This preview updates automatically when you update the PR. |
|
1d2a165 to
8e08354
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7580 +/- ##
=============================================
- Coverage 86.462% 85.484% -0.978%
=============================================
Files 487 489 +2
Lines 40065 29383 -10682
Branches 18020 12715 -5305
=============================================
- Hits 34641 25118 -9523
+ Misses 5375 4215 -1160
- Partials 49 50 +1
... and 452 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: URL pattern validation runs twice on dictionary init
- I removed pre-validation in the dictionary initializer and routed raw values to the private initializer so URL patterns are validated exactly once in a single source of truth.
Or push these changes by commenting:
@cursor push 95c595cf60
Preview (95c595cf60)
diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift
--- a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift
+++ b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift
@@ -601,8 +601,8 @@
maximumDuration: (dictionary["maximumDuration"] as? NSNumber)?.doubleValue,
excludedViewClasses: (dictionary["excludedViewClasses"] as? [String]).map { Set($0) },
includedViewClasses: (dictionary["includedViewClasses"] as? [String]).map { Set($0) },
- networkDetailAllowUrls: Self.validateNetworkDetailUrlPatterns(from: dictionary["networkDetailAllowUrls"]),
- networkDetailDenyUrls: Self.validateNetworkDetailUrlPatterns(from: dictionary["networkDetailDenyUrls"]),
+ networkDetailAllowUrls: dictionary["networkDetailAllowUrls"],
+ networkDetailDenyUrls: dictionary["networkDetailDenyUrls"],
networkCaptureBodies: (dictionary["networkCaptureBodies"] as? NSNumber)?.boolValue,
networkRequestHeaders: Self.parseStringArray(from: dictionary["networkRequestHeaders"]),
networkResponseHeaders: Self.parseStringArray(from: dictionary["networkResponseHeaders"])
@@ -745,8 +745,8 @@
maximumDuration: TimeInterval?,
excludedViewClasses: Set<String>? = nil,
includedViewClasses: Set<String>? = nil,
- networkDetailAllowUrls: [Any]? = nil,
- networkDetailDenyUrls: [Any]? = nil,
+ networkDetailAllowUrls: Any? = nil,
+ networkDetailDenyUrls: Any? = nil,
networkCaptureBodies: Bool? = nil,
networkRequestHeaders: [String]? = nil,
networkResponseHeaders: [String]? = nil
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| e1e5f3b | 1220.60 ms | 1241.63 ms | 21.04 ms |
| f194b9c | 1216.71 ms | 1249.02 ms | 32.31 ms |
| 45eb835 | 1216.00 ms | 1248.48 ms | 32.48 ms |
| 39fbc4b | 1230.95 ms | 1257.53 ms | 26.58 ms |
| d29a425 | 1209.96 ms | 1239.00 ms | 29.04 ms |
| f38f4e9 | 1221.50 ms | 1242.64 ms | 21.14 ms |
| 1b7b7b4 | 1228.40 ms | 1260.20 ms | 31.80 ms |
| 7b14762 | 1221.13 ms | 1261.41 ms | 40.28 ms |
| 142310c | 1219.87 ms | 1248.71 ms | 28.84 ms |
| f84c826 | 1216.38 ms | 1241.98 ms | 25.60 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| e1e5f3b | 24.14 KiB | 1.06 MiB | 1.04 MiB |
| f194b9c | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| 45eb835 | 24.14 KiB | 1.07 MiB | 1.04 MiB |
| 39fbc4b | 24.14 KiB | 1.12 MiB | 1.09 MiB |
| d29a425 | 24.14 KiB | 1.04 MiB | 1.02 MiB |
| f38f4e9 | 24.14 KiB | 1.10 MiB | 1.07 MiB |
| 1b7b7b4 | 24.14 KiB | 1.13 MiB | 1.11 MiB |
| 7b14762 | 24.14 KiB | 1.13 MiB | 1.11 MiB |
| 142310c | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| f84c826 | 24.14 KiB | 1.04 MiB | 1.02 MiB |
Previous results on branch: mobile-935/sdk-options
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 56fb666 | 1215.11 ms | 1250.19 ms | 35.08 ms |
| 34b098b | 1236.87 ms | 1252.91 ms | 16.04 ms |
| 2816207 | 1228.90 ms | 1259.46 ms | 30.56 ms |
| 340b451 | 1205.93 ms | 1225.39 ms | 19.45 ms |
| 9acc3f3 | 1215.13 ms | 1253.09 ms | 37.97 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 56fb666 | 24.14 KiB | 1.13 MiB | 1.10 MiB |
| 34b098b | 24.14 KiB | 1.14 MiB | 1.12 MiB |
| 2816207 | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| 340b451 | 24.14 KiB | 1.13 MiB | 1.10 MiB |
| 9acc3f3 | 24.14 KiB | 1.13 MiB | 1.10 MiB |
itaybre
left a comment
There was a problem hiding this comment.
Almost LGTM, just some small comments
And you will need to run make generate-public-api due to the new public APIs
cfc450a to
1d0cc82
Compare
📲 Install BuildsiOS
|
1d0cc82 to
037f0e4
Compare
Sentry Build Distribution
|
037f0e4 to
cad52c2
Compare
Sentry Build Distribution
|
cad52c2 to
83310fd
Compare
83310fd to
2cef0e3
Compare
|
rebase to land ✅ |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2cef0e3. Configure here.
| networkDetailDenyUrls: SentryUrlMatcher.convertFromAny(dictionary["networkDetailDenyUrls"]), | ||
| networkCaptureBodies: (dictionary["networkCaptureBodies"] as? NSNumber)?.boolValue, | ||
| networkRequestHeaders: (dictionary["networkRequestHeaders"] as? [Any])?.compactMap { $0 as? String }, | ||
| networkResponseHeaders: (dictionary["networkResponseHeaders"] as? [Any])?.compactMap { $0 as? String } |
There was a problem hiding this comment.
Headers don't filter empty strings unlike URL patterns
Low Severity
The dictionary parsing for networkRequestHeaders and networkResponseHeaders uses compactMap { $0 as? String } which allows empty strings "" through, while SentryUrlMatcher.convertFromAny explicitly trims whitespace and filters out empty strings for URL patterns. An empty string is never a valid HTTP header field name per RFC 7230 (field-name = token = 1*tchar). This inconsistency means "" can end up in the headers list, potentially causing unexpected behavior in downstream header extraction code.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2cef0e3. Configure here.
Implement SDK fields, setters and validation for network details capture: https://docs.sentry.io/platforms/javascript/session-replay/configuration/#network-details - String patterns for prefix matching (e.g., "https://api.example.com" matches subpaths) - NSRegularExpression patterns for complex regex matching - Deny list precedence over allow list - Empty string filtering to handle invalid input gracefully
…URL filtering Replace [Any] with [SentryUrlMatchable] for networkDetailAllowUrls/DenyUrls. Provides compile-time type safety in Swift while maintaining Objective-C compatibility through bridge properties. Follows the API pattern of SentryAttributeValue/Content.
…hables Already implemented in SentryUrlMatcher.convertFromAny, and the new impl had a bug flagged by SentryBot: #7580 (comment)
#7580 (comment) Full ObjC support deferred to #7598. Added SentryReplayOptions#networkDetailHasUrls computed property for: 1) Confirming the feature is disabled in objc (see SentryReplayOptionsObjcTests.m) 2) Using higher up in stack (see SentryRRWebOptionsEvent, SentryNetworkTrackingIntegration.swift)
2cef0e3 to
ddec481
Compare
| * - Note: Request and response bodies are truncated to 150KB maximum. | ||
| * - Note: See ``SentryReplayOptions.DefaultValues.networkDetailAllowUrls`` for the default value. | ||
| */ | ||
| public var networkDetailAllowUrls: [SentryUrlMatchable] |
There was a problem hiding this comment.
Bug: Directly assigning to networkDetailAllowUrls bypasses validation, allowing whitespace-only strings that cause the feature to silently fail to capture network requests.
Severity: MEDIUM
Suggested Fix
Add a didSet observer to the networkDetailAllowUrls property. Inside the observer, apply the same filtering logic used in SentryUrlMatcher.convertFromAny() to trim strings and remove any that are empty after trimming. This will ensure consistent validation for both direct assignment and dictionary-based initialization.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift#L338
Potential issue: Direct assignment to the `networkDetailAllowUrls` property bypasses the
validation that filters out whitespace-only strings. The dictionary-based initialization
correctly filters these invalid values using `SentryUrlMatcher.convertFromAny()`, but
the public setter for the property does not. This inconsistency allows a user to
configure the feature with patterns like `[" "]`. The feature will then appear to be
enabled (`networkDetailHasUrls` returns true) but will silently fail to capture any
network requests, as the matching logic in `matches()` performs a direct substring check
without trimming the pattern.



Implement SDK fields, setters and validation for network details capture: https://docs.sentry.io/platforms/javascript/session-replay/configuration/#network-details
networkDetailAllowUrls(string|RegExp)[][]networkDetailDenyUrls(string|RegExp)[][]networkDetailAllowUrls.networkCaptureBodiesbooleantruenetworkDetailAllowUrls.networkRequestHeadersstring[][]networkDetailAllowUrls.networkResponseHeadersstring[][]networkDetailAllowUrls.networkDetail[Allow|Deny]Urlsnetwork[Request|Response]HeadersnetworkCaptureBodies📜 Description
PR 1/N. Extend SentryReplayOptions API with Session Replay Network Details configuration
PR 2/N. Adds test app UI to configure and test network details collection.
PR 3/N. Adds data holder classes to define structure of data being extracted
PR 4/N. Adds new swizzling introduced to capture response bodies.
PR 5/N. Implements extraction logic for headers & bodies.
PR 6/N. Hook into existing SentryNetworkTracker.m|h
PR 7/N. Implement conversion from breadcrumb data -> Session Replay compatible RRWebEvent
💡 Motivation and Context
Parent issue (android, cocoa, RN) - getsentry/sentry#84596
Cocoa sub-issue - #4944
This PR adds the SDK fields required for developers to enable network details extraction for network requests made during a session replay capture, by following the impl in sentry-javascript and sentry-java (android).
💚 How did you test it?
Unit tests
SentryReplayOptionsTests
SentryReplayOptionsNetworkTests
Additional unit tests for network details (I didn't put them in SentryReplayOptionsTests to avoid over-crowding that file / I assumed SentryReplayOptionsTests should be for high level testing of SentryReplayOptions).
SentryReplayOptionsObjcTests
ObjC <> Swift compatibility tests. Mostly for Regex matching
📝 Checklist
You have to check all boxes before merging:
sendDefaultPIIis enabled. Nothing extracted in this PR - SDKOptions available but no backing implementation.