refactor(ios): Migrate from PrivateSentrySDKOnly to SentrySDK.internal#6380
refactor(ios): Migrate from PrivateSentrySDKOnly to SentrySDK.internal#6380alwx wants to merge 17 commits into
Conversation
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. |
|
@cursor review |
Replace the deprecated PrivateSentrySDKOnly SPI and SentrySwizzle.h macro with the new SentrySDK.internal Swift API exposed by sentry-cocoa 9.19.0, via a thin in-pod ObjC<->Swift bridge (RNSentryInternal.swift). Imports Sentry with @_spi(Private) for SPI-gated sub-APIs (performance, replay, envelope). Drops the performSelector workaround around getReplayIntegration since SentrySDK.internal.replay.capture() returns Bool directly. Closes #6370 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move RNSentryStartTests, RNSentryStartFromFileTests, and RNSentryReplayOptionsTests
to SentrySDK.internal.{options, options(fromDictionary:), appStart.hybridSDKMode,
performance.framesTrackingHybridSDKMode}.
The ObjC RNSentryTests.m keeps PrivateSentrySDKOnly for now — the test target
is a separate consumer of the RNSentry static-lib pod and cannot see
RNSentry-Swift.h without modulemap surgery. Worth revisiting when sentry-cocoa
removes PrivateSentrySDKOnly in the next major.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`SentryInternalSwizzleApi` in sentry-cocoa has no platform gating, and `SENTRY_HAS_UIKIT` (which guards the `[RNSentryRNSScreen swizzleViewDidAppear]` call in RNSentry.mm) covers visionOS. The Swift bridge was gated to `iOS/tvOS/macCatalyst` only, silently no-oping the swizzle on visionOS where the deprecated `SentrySwizzle` macro used to work. Also documents why `setCurrentScreen` remains a visionOS no-op: sentry-cocoa's `SentryInternalScreenApi` is intentionally gated to iOS/tvOS, so the new API surface itself doesn't expose the setter on visionOS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
b850f46 to
330236b
Compare
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 21a1e70+dirty | 3834.15 ms | 1218.43 ms | -2615.73 ms |
| 882f8ae+dirty | 3840.30 ms | 1224.41 ms | -2615.88 ms |
| 15d4514+dirty | 3840.17 ms | 1225.79 ms | -2614.38 ms |
| 0b5a379+dirty | 3828.91 ms | 1214.12 ms | -2614.79 ms |
| f3215d3+dirty | 3842.73 ms | 1219.33 ms | -2623.40 ms |
| ab203f9+dirty | 3848.07 ms | 1219.71 ms | -2628.35 ms |
| 6177334+dirty | 3834.85 ms | 1217.58 ms | -2617.28 ms |
| 5257d80+dirty | 3854.39 ms | 1234.28 ms | -2620.11 ms |
| 9474ead+dirty | 3864.29 ms | 1223.55 ms | -2640.74 ms |
| 1122a96+dirty | 3823.10 ms | 1218.64 ms | -2604.46 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 21a1e70+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 882f8ae+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 15d4514+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 0b5a379+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| f3215d3+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
| ab203f9+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 6177334+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 5257d80+dirty | 5.15 MiB | 6.69 MiB | 1.54 MiB |
| 9474ead+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| 1122a96+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 3b6e9f9+dirty | 3822.77 ms | 1208.00 ms | -2614.77 ms |
| 71abba0+dirty | 3852.70 ms | 1224.53 ms | -2628.16 ms |
| 7a89652+dirty | 3827.11 ms | 1211.49 ms | -2615.62 ms |
| 0b1b5e3+dirty | 3820.72 ms | 1207.94 ms | -2612.78 ms |
| 3ce5254+dirty | 1217.70 ms | 1224.69 ms | 6.99 ms |
| 5a23c47+dirty | 3843.54 ms | 1226.05 ms | -2617.50 ms |
| 44c8b3f+dirty | 3849.24 ms | 1209.94 ms | -2639.31 ms |
| 4953e94+dirty | 1217.41 ms | 1223.53 ms | 6.12 ms |
| 5789645+dirty | 3841.36 ms | 1214.81 ms | -2626.55 ms |
| 04207c4+dirty | 1228.55 ms | 1226.04 ms | -2.51 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 3b6e9f9+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 71abba0+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
| 7a89652+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 0b1b5e3+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 3ce5254+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
| 5a23c47+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 44c8b3f+dirty | 5.15 MiB | 6.66 MiB | 1.51 MiB |
| 4953e94+dirty | 3.38 MiB | 4.73 MiB | 1.35 MiB |
| 5789645+dirty | 4.98 MiB | 6.50 MiB | 1.52 MiB |
| 04207c4+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
antonis
left a comment
There was a problem hiding this comment.
Overall looks good. I think a conditional import like should fix the build issues 🤞
#if __has_include(<RNSentry/RNSentry-Swift.h>)
#import <RNSentry/RNSentry-Swift.h>
#else
#import "RNSentry-Swift.h"
#endif
`withUnsafePointer(to:)` only guarantees the pointer stays valid inside the closure body. Capturing it in `keyPtr` and using it outside the closure was undefined behaviour by contract (even though `static var` storage happens to be stable in practice). Wrap the entire `instanceMethod` call inside `withUnsafePointer` so the pointer is always used within its documented lifetime; the `static var` backing storage keeps the address itself stable across calls, so sentry-cocoa's `oncePerClass` dedup keeps working. Flagged by Cursor Bugbot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Handle both static-lib and framework-style Pod integrations by falling back from `<RNSentry/RNSentry-Swift.h>` (frameworks path) to the local `"RNSentry-Swift.h"` (static-lib path) via `__has_include`. Applied to every `.m`/`.mm` in packages/core/ios that consumes the Swift bridge. Suggested by @antonis. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two fixes flagged by Warden: 1. `RNSentryInternal.envelope(fromData:)` accepted a non-optional Swift `Data`. If the ObjC caller passed a nil `NSData*` (e.g. from a failed base64 decode of the envelope payload), the ObjC→Swift bridge would force-unwrap it and crash before the method body ran. Change the parameter to `Data?` with an internal `guard let` — matches the nil-tolerant behaviour of the deprecated `PrivateSentrySDKOnly.envelopeWithData:`. 2. Swift Cocoa tests accessed `SentrySDK.internal.*` with plain `import Sentry`. The `.internal` accessor is public today, but the sub-APIs the tests touch (`options`, `appStart`, `performance`) sit next to `@_spi(Private)`-gated siblings on the same struct. Add `@_spi(Private) import Sentry` to `RNSentryStartTests.swift`, `RNSentryStartFromFileTests.swift`, and `RNSentryReplayOptionsTests.swift` so they keep compiling if the surface is ever tightened. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When `captureViewHierarchy` returned nil the ObjC code still constructed an empty `NSMutableArray` and resolved with it. In JS, `[]` is truthy — the caller would treat the failure as a successful (empty) attachment. Bail out with `resolve(nil)` up front so the JS side sees the actual failure signal. Pre-existing since 2023; flagged by Seer while reviewing this PR. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cover the bridge's own contract — nil-guard on `envelope(fromData:)`, metadata accessors, options factory, hybrid-SDK-mode flags, and the `swizzleRNSScreenViewDidAppear` early-return when RNSScreen is unavailable. sentry-cocoa owns the underlying `SentrySDK.internal.*` behaviour; we just assert the wrapper forwards correctly and honours the documented nil / platform guards. Registers the new file in the RNSentryCocoaTester project explicitly (the target does not use file-system-synchronized groups yet). Flagged by Warden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adding Swift to the RNSentry pod means CocoaPods refuses to integrate it against non-modular ObjC dependencies. On older React Native versions (e.g. 0.71 with Hermes) the React-hermes pod does not define modules, so `pod install` fails with: The Swift pod `RNSentry` depends upon `React-hermes`, which does not define modules. Inject `use_modular_headers!` into the Podfile via the existing e2e patch script, and document the same knob for downstream users in the CHANGELOG. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2cb3360. Configure here.
Cherry-picked from #6410. Dependabot bumped `codeql-action/analyze` and `codeql-action/autobuild` to v4.36.3 but left `init` on v4.36.2, so `init` writes a config stamped 4.36.2 that `analyze` (4.36.3) rejects: Loaded a configuration file for version '4.36.2', but running version '4.36.3' Pull this fix into the PR branch so CI stops failing while #6410 is still open on main. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous version unconditionally set `modularPatched = true` inside the `use_modular_headers!` branch, so a Podfile without a `prepare_react_native_project!` anchor would be reported as successfully patched even though `content.replace` returned it unchanged. Compare replace output against the input and log a warning instead when the anchor is missing. Flagged by Cursor Bugbot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The migration to `SentrySDK.internal.screen.setCurrent` drops the current-screen breadcrumb enrichment on visionOS because sentry-cocoa's new `SentryInternalScreenApi` is gated to iOS/tvOS only. Already documented in a code comment; call it out for downstream users too. Flagged by Seer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d2eadf8+dirty | 414.64 ms | 454.56 ms | 39.92 ms |
| 4966363+dirty | 400.04 ms | 431.08 ms | 31.04 ms |
| 0307fa4+dirty | 454.16 ms | 518.35 ms | 64.18 ms |
| 038a6d7+dirty | 524.82 ms | 531.92 ms | 7.10 ms |
| 4e0b819+dirty | 420.56 ms | 470.08 ms | 49.52 ms |
| 1a5721e+dirty | 424.07 ms | 482.32 ms | 58.25 ms |
| a0d8cf8+dirty | 411.71 ms | 467.57 ms | 55.87 ms |
| 9474ead+dirty | 411.45 ms | 446.80 ms | 35.35 ms |
| ad66da3+dirty | 468.46 ms | 533.56 ms | 65.10 ms |
| 0b1b5e3+dirty | 416.42 ms | 470.58 ms | 54.16 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d2eadf8+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 4966363+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 0307fa4+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 038a6d7+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 4e0b819+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 1a5721e+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| a0d8cf8+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 9474ead+dirty | 48.30 MiB | 53.61 MiB | 5.30 MiB |
| ad66da3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 0b1b5e3+dirty | 48.30 MiB | 53.60 MiB | 5.29 MiB |
`RNSentryInternal` is a Swift type compiled into the `RNSentry` pod module, not the `Sentry` module. Without `@_spi(Private) import RNSentry` the test target cannot resolve `RNSentryInternal.*` and fails to build. Flagged by Warden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5b7e8a7+dirty | 601.58 ms | 634.98 ms | 33.40 ms |
| eb93136+dirty | 500.37 ms | 532.58 ms | 32.21 ms |
| 4e0b819+dirty | 599.31 ms | 654.31 ms | 55.00 ms |
| 1a5721e+dirty | 433.44 ms | 462.80 ms | 29.36 ms |
| 3b6e9f9+dirty | 442.39 ms | 486.44 ms | 44.05 ms |
| c823bb5+dirty | 468.26 ms | 516.16 ms | 47.90 ms |
| 3817909+dirty | 357.52 ms | 391.52 ms | 34.00 ms |
| ecf47a2+dirty | 457.21 ms | 498.10 ms | 40.89 ms |
| 88735e9+dirty | 427.04 ms | 487.37 ms | 60.33 ms |
| bc0d8cf+dirty | 407.66 ms | 461.35 ms | 53.69 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5b7e8a7+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| eb93136+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 4e0b819+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 1a5721e+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| 3b6e9f9+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| c823bb5+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 3817909+dirty | 43.94 MiB | 48.94 MiB | 5.00 MiB |
| ecf47a2+dirty | 49.74 MiB | 54.82 MiB | 5.07 MiB |
| 88735e9+dirty | 49.74 MiB | 54.82 MiB | 5.07 MiB |
| bc0d8cf+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| ## 8.16.0 | ||
|
|
There was a problem hiding this comment.
nit changelog merge issue
| ## 8.16.0 |
|
|
||
| - Migrate iOS code from the deprecated `PrivateSentrySDKOnly` SPI (and `SentrySwizzle.h` macro) to the new `SentrySDK.internal` Swift API exposed by sentry-cocoa 9.19.0, via a thin in-pod ObjC↔Swift bridge ([#6370](https://github.com/getsentry/sentry-react-native/issues/6370)) | ||
|
|
||
| Adding Swift to the `RNSentry` pod means CocoaPods needs modular headers for its ObjC dependencies. On React Native versions where `React-hermes` (or another RN pod) is not modularized by default (e.g. RN 0.71), add `use_modular_headers!` to your `ios/Podfile` above the `target` block. Newer React Native versions require no change. |
There was a problem hiding this comment.
If the use_modular_headers! in those cases is needed it is a breaking change for some users and we should communicate it on the changes and not the internal section

📢 Type of change
📜 Description
Replace deprecated
PrivateSentrySDKOnlySPI andSentrySwizzle.hmacro usage in our iOS code with the newSentrySDK.internalSwift API exposed by sentry-cocoa 9.19.0, via a thin in-pod ObjC↔Swift bridge (RNSentryInternal.swift).The bridge uses
@_spi(Private) import Sentryto reach SPI-gated sub-APIs (performance.currentScreenFrames,replay.configure,envelope.{store,capture,deserialize}). Existing.m/.mmcallers route through[RNSentryInternal …]via the auto-generatedRNSentry-Swift.h. TheperformSelector:@selector(getReplayIntegration)workaround incaptureReplayWithReturnValueis gone —SentrySDK.internal.replay.capture()returnsBooldirectly.Notably, we do not use sentry-cocoa's new
SentryObjCumbrella module (which the issue suggested) — that module isn't shipped via theSentryCocoaPod in 9.19.0, only via SPM as a separate xcframework. The Swift bridge works equally on both consumption paths.💡 Motivation and Context
Closes #6370.
PrivateSentrySDKOnlyis deprecated in sentry-cocoa 9.19.0 and slated for removal in the next major. Migrating now avoids future churn and drops the private header search path /performSelector:reliance.💚 How did you test it?
yarn build,yarn test,yarn lint,yarn circularDepCheck— all greenxcodebuildfor theRNSentrypod target (iphonesimulator) — builds cleanxcodebuild testforRNSentryCocoaTester— 158/158 native Cocoa tests pass📝 Checklist
sendDefaultPIIis enabled🔮 Next steps