refactor(ios): Migrate from PrivateSentrySDKOnly to SentrySDK.internal#6380
refactor(ios): Migrate from PrivateSentrySDKOnly to SentrySDK.internal#6380alwx wants to merge 13 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 |
b850f46 to
330236b
Compare
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d0e3b3e+dirty | 3854.77 ms | 1218.58 ms | -2636.19 ms |
| 5125c43+dirty | 3846.45 ms | 1221.12 ms | -2625.32 ms |
| 580fb5c+dirty | 3836.13 ms | 1218.72 ms | -2617.41 ms |
| 7887847+dirty | 3849.83 ms | 1227.35 ms | -2622.48 ms |
| a216cb9+dirty | 3839.33 ms | 1225.26 ms | -2614.07 ms |
| 40c9884+dirty | 3837.72 ms | 1226.61 ms | -2611.11 ms |
| a5d243c+dirty | 3842.35 ms | 1214.29 ms | -2628.06 ms |
| f170ec3+dirty | 3822.26 ms | 1218.33 ms | -2603.93 ms |
| 68672fc+dirty | 3841.58 ms | 1228.89 ms | -2612.69 ms |
| ae37560+dirty | 3832.10 ms | 1219.09 ms | -2613.02 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| d0e3b3e+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 5125c43+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 580fb5c+dirty | 4.98 MiB | 6.46 MiB | 1.48 MiB |
| 7887847+dirty | 4.98 MiB | 6.46 MiB | 1.48 MiB |
| a216cb9+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 40c9884+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| a5d243c+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| f170ec3+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| 68672fc+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| ae37560+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 580fb5c+dirty | 3835.45 ms | 1218.61 ms | -2616.84 ms |
| d0e3b3e+dirty | 3846.64 ms | 1226.27 ms | -2620.38 ms |
| a216cb9+dirty | 3853.25 ms | 1224.77 ms | -2628.48 ms |
| 37a2091+dirty | 3865.27 ms | 1223.92 ms | -2641.35 ms |
| 40c9884+dirty | 3826.11 ms | 1217.04 ms | -2609.07 ms |
| 4e0ba9c+dirty | 3856.39 ms | 1234.44 ms | -2621.95 ms |
| 0b5a379+dirty | 3857.69 ms | 1230.34 ms | -2627.35 ms |
| 68672fc+dirty | 3832.22 ms | 1228.29 ms | -2603.93 ms |
| 2c735cc+dirty | 1223.33 ms | 1224.38 ms | 1.04 ms |
| 6acdf1d+dirty | 3835.35 ms | 1218.30 ms | -2617.06 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 580fb5c+dirty | 4.98 MiB | 6.46 MiB | 1.48 MiB |
| d0e3b3e+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| a216cb9+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 37a2091+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 40c9884+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 4e0ba9c+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 0b5a379+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 68672fc+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| 2c735cc+dirty | 3.38 MiB | 4.74 MiB | 1.35 MiB |
| 6acdf1d+dirty | 4.98 MiB | 6.51 MiB | 1.53 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
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.
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| eb93136+dirty | 416.18 ms | 467.32 ms | 51.14 ms |
| ae37560+dirty | 470.40 ms | 564.12 ms | 93.72 ms |
| 5b7e8a7+dirty | 418.55 ms | 488.46 ms | 69.91 ms |
| c823bb5+dirty | 409.87 ms | 478.57 ms | 68.70 ms |
| 3817909+dirty | 406.67 ms | 416.58 ms | 9.91 ms |
| bc0d8cf+dirty | 412.37 ms | 466.26 ms | 53.89 ms |
| 5a23c47+dirty | 423.85 ms | 466.47 ms | 42.62 ms |
| 0a147b2+dirty | 464.71 ms | 538.81 ms | 74.09 ms |
| a50b33d+dirty | 500.81 ms | 532.11 ms | 31.30 ms |
| 7887847+dirty | 416.61 ms | 462.04 ms | 45.43 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| eb93136+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| ae37560+dirty | 48.30 MiB | 53.60 MiB | 5.29 MiB |
| 5b7e8a7+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| c823bb5+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 3817909+dirty | 43.75 MiB | 48.08 MiB | 4.33 MiB |
| bc0d8cf+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 5a23c47+dirty | 49.74 MiB | 54.82 MiB | 5.07 MiB |
| 0a147b2+dirty | 49.74 MiB | 55.08 MiB | 5.34 MiB |
| a50b33d+dirty | 43.75 MiB | 48.08 MiB | 4.33 MiB |
| 7887847+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1122a96+dirty | 510.16 ms | 542.00 ms | 31.84 ms |
| 7ac3378+dirty | 410.67 ms | 442.60 ms | 31.92 ms |
| b04af96+dirty | 430.33 ms | 485.98 ms | 55.65 ms |
| 3817909+dirty | 357.52 ms | 391.52 ms | 34.00 ms |
| 20fbd51+dirty | 594.38 ms | 655.35 ms | 60.97 ms |
| 0a147b2+dirty | 442.80 ms | 522.24 ms | 79.44 ms |
| a5d243c+dirty | 499.20 ms | 525.62 ms | 26.42 ms |
| 2363c0f+dirty | 419.48 ms | 463.92 ms | 44.44 ms |
| 5257d80+dirty | 472.37 ms | 494.54 ms | 22.17 ms |
| 0a9e622+dirty | 425.22 ms | 457.00 ms | 31.78 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1122a96+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 7ac3378+dirty | 43.94 MiB | 48.99 MiB | 5.05 MiB |
| b04af96+dirty | 49.74 MiB | 55.00 MiB | 5.26 MiB |
| 3817909+dirty | 43.94 MiB | 48.94 MiB | 5.00 MiB |
| 20fbd51+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 0a147b2+dirty | 49.74 MiB | 55.08 MiB | 5.34 MiB |
| a5d243c+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| 2363c0f+dirty | 49.74 MiB | 54.84 MiB | 5.10 MiB |
| 5257d80+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 0a9e622+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
b5e317f to
67f363b
Compare
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>
`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>
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>
`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>
Antonis review nit: the use_modular_headers! requirement is a user-facing Podfile change on older RN versions, not internal-only — promote it from ### Internal into ### Changes alongside the visionOS setCurrentScreen note. Also reorder the Unreleased sections to the usual Features / Changes / Fixes / Internal layout used elsewhere in this file. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
67f363b to
23f1729
Compare

📢 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