Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
<!-- prettier-ignore-end -->

## Unreleased

### Changes

- Migrate iOS code from the deprecated `PrivateSentrySDKOnly` SPI (and `SentrySwizzle.h` macro) to the new `SentrySDK.internal` Swift API, via a thin in-pod ObjCโ†”Swift bridge ([#6541](https://github.com/getsentry/sentry-react-native/pull/6541))

This re-lands [#6380](https://github.com/getsentry/sentry-react-native/pull/6380), which shipped in 8.19.0 and was reverted in 8.20.0 ([#6491](https://github.com/getsentry/sentry-react-native/pull/6491)) because it broke all iOS screenshot capture ([#6497](https://github.com/getsentry/sentry-react-native/issues/6497)). The root cause was in sentry-cocoa โ€” `SentryDependencyContainer.screenshotSource` was a `lazy var` that permanently cached `nil` when first read before `SentrySDK.start` โ€” and is fixed in sentry-cocoa 9.24.0 ([getsentry/sentry-cocoa#8578](https://github.com/getsentry/sentry-cocoa/pull/8578)), which this release bumps to.

Re-landing also restores the two improvements the revert removed: `fetchViewHierarchy` now returns `null` instead of a zero-byte attachment when capture fails, and `RNSentryTimeToDisplay`'s shared state uses `extern` linkage.

<!-- prettier-ignore-start -->
> [!IMPORTANT]
> **iOS:** 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. This is required because the `RNSentry` pod now contains Swift code (the `RNSentryInternal` bridge over `SentrySDK.internal`) and CocoaPods refuses to integrate a Swift pod against non-modular ObjC dependencies. Newer React Native versions require no change.
<!-- prettier-ignore-end -->

### Dependencies

- Bump Cocoa SDK from v9.19.1 to v9.24.0 ([#6541](https://github.com/getsentry/sentry-react-native/pull/6541))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#9240)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/9.19.1...9.24.0)

## 8.21.0

### Features
Expand Down
21 changes: 20 additions & 1 deletion dev-packages/e2e-tests/patch-scripts/rn.patch.podfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,27 @@ if (currentMatch) {
debug.log('Warning: Could not find platform :ios line to patch');
}

// RNSentry now contains Swift code (via the RNSentryInternal bridge over
// SentrySDK.internal). CocoaPods refuses to integrate a Swift pod against
// non-modular ObjC dependencies (e.g. React-hermes on older RN versions),
// so ensure the Podfile requests modular headers globally.
let modularPatched = false;
if (!content.includes('use_modular_headers!')) {
const patched = content.replace(
/prepare_react_native_project!\s*\n/,
"prepare_react_native_project!\nuse_modular_headers!\n",
);
if (patched !== content) {
content = patched;
modularPatched = true;
debug.log('Patching Podfile with use_modular_headers!');
} else {
debug.log('Warning: Could not find prepare_react_native_project! anchor to inject use_modular_headers!');
}
}

// Write the file if any changes were made
if (shouldPatch || currentMatch) {
if (shouldPatch || currentMatch || modularPatched) {
fs.writeFileSync(args['pod-file'], content);
debug.log('Podfile patched successfully!');
} else {
Expand Down
28 changes: 10 additions & 18 deletions packages/core/RNSentry.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,15 @@ Pod::Spec.new do |s|
# in `android/CMakeLists.txt`. The files are guarded with
# `RCT_NEW_ARCH_ENABLED` so they compile to empty TUs on Old Arch.
#
# We include `.swift` (for `RNSentrySwiftLinkStub.swift`) only on RN >=
# 0.75. Adding a Swift file makes CocoaPods treat RNSentry as a Swift
# pod, which then requires modular headers from its ObjC dependencies
# (React-Core, React-hermes) โ€” RN < 0.75 doesn't emit those, so
# `pod install` fails with:
# "The Swift pod `RNSentry` depends upon `React-hermes`, which does
# not define modules."
# The stub is only needed when linking Sentry.xcframework's Swift
# symbols into a dynamic framework anyway (RN 0.86+ `use_frameworks!
# :dynamic`), so gating on RN 0.75 is safe.
supports_swift_stub = rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 75)
if supports_swift_stub
s.source_files = 'ios/**/*.{h,m,mm,swift}', 'cpp/**/*.{h,cpp}'
s.swift_versions = ['5.5']
else
s.source_files = 'ios/**/*.{h,m,mm}', 'cpp/**/*.{h,cpp}'
end
# Swift is compiled unconditionally because `RNSentryInternal.swift` is
# the sole ObjCโ†”Swift bridge over `SentrySDK.internal.*` โ€” every `.m`/
# `.mm` file in this pod calls into it. That makes RNSentry a Swift pod
# in CocoaPods' eyes, which in turn requires modular headers from its
# ObjC dependencies. Users on React Native < 0.75 (where `React-hermes`
# and friends aren't modularized by default) must add
# `use_modular_headers!` to their Podfile โ€” see CHANGELOG.
s.source_files = 'ios/**/*.{h,m,mm,swift}', 'cpp/**/*.{h,cpp}'
s.swift_versions = ['5.5']
s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h', 'ios/RNSentryStart.h', 'ios/RNSentryVersion.h', 'ios/RNSentryBreadcrumb.h', 'ios/RNSentryReplay.h', 'ios/RNSentryReplayBreadcrumbConverter.h', 'ios/Replay/RNSentryReplayMask.h', 'ios/Replay/RNSentryReplayUnmask.h', 'ios/RNSentryTimeToDisplay.h'

s.compiler_flags = other_cflags
Expand All @@ -71,7 +63,7 @@ Pod::Spec.new do |s|
'DEFINES_MODULE' => 'YES'
}

sentry_cocoa_version = '9.19.1'
sentry_cocoa_version = '9.24.0'

# Consume sentry-cocoa as a prebuilt `Sentry.xcframework` by default.
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
33DEDFED2D8DC825006066E4 /* RNSentryOnDrawReporter+Test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33DEDFEC2D8DC820006066E4 /* RNSentryOnDrawReporter+Test.mm */; };
33DEDFF02D9185EB006066E4 /* RNSentryTimeToDisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33DEDFEF2D9185E3006066E4 /* RNSentryTimeToDisplayTests.swift */; };
33F58AD02977037D008F60EA /* RNSentryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33F58ACF2977037D008F60EA /* RNSentryTests.m */; };
33F58AD12977037D008F60EB /* RNSentryScreenshotSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F58ACE2977037D008F60EB /* RNSentryScreenshotSourceTests.swift */; };
2639D71D3BD04F17B0BAC987 /* RNSentryTurboModulePerfControllerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E795057A6D534A80A9D06356 /* RNSentryTurboModulePerfControllerTests.mm */; };
AEFB00422CC90C4B00EC8A9A /* RNSentryBreadcrumbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */; };
B4DEB41739F14AA38202D4D4 /* RNSentryUriValidationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E3742693F7643C2ADE1BDF2 /* RNSentryUriValidationTests.m */; };
Expand Down Expand Up @@ -50,6 +51,7 @@
33DEDFEE2D8DD431006066E4 /* RNSentryTimeToDisplay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryTimeToDisplay.h; path = ../ios/RNSentryTimeToDisplay.h; sourceTree = SOURCE_ROOT; };
33DEDFEF2D9185E3006066E4 /* RNSentryTimeToDisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryTimeToDisplayTests.swift; sourceTree = "<group>"; };
33F58ACF2977037D008F60EA /* RNSentryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSentryTests.m; sourceTree = "<group>"; };
33F58ACE2977037D008F60EB /* RNSentryScreenshotSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryScreenshotSourceTests.swift; sourceTree = "<group>"; };
3E3742693F7643C2ADE1BDF2 /* RNSentryUriValidationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryUriValidationTests.m; sourceTree = "<group>"; };
650CB718ACFBD05609BF2126 /* libPods-RNSentryCocoaTesterTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNSentryCocoaTesterTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
E795057A6D534A80A9D06356 /* RNSentryTurboModulePerfControllerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNSentryTurboModulePerfControllerTests.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -111,6 +113,7 @@
332D33492CDCC8E100547D76 /* RNSentryTests.h */,
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */,
33F58ACF2977037D008F60EA /* RNSentryTests.m */,
33F58ACE2977037D008F60EB /* RNSentryScreenshotSourceTests.swift */,
3339C4802D6625570088EB3A /* RNSentryUserTests.m */,
3E3742693F7643C2ADE1BDF2 /* RNSentryUriValidationTests.m */,
E795057A6D534A80A9D06356 /* RNSentryTurboModulePerfControllerTests.mm */,
Expand Down Expand Up @@ -267,6 +270,7 @@
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */,
33DEDFED2D8DC825006066E4 /* RNSentryOnDrawReporter+Test.mm in Sources */,
33F58AD02977037D008F60EA /* RNSentryTests.m in Sources */,
33F58AD12977037D008F60EB /* RNSentryScreenshotSourceTests.swift in Sources */,
3339C4812D6625570088EB3A /* RNSentryUserTests.m in Sources */,
B4DEB41739F14AA38202D4D4 /* RNSentryUriValidationTests.m in Sources */,
2639D71D3BD04F17B0BAC987 /* RNSentryTurboModulePerfControllerTests.mm in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Sentry
@_spi(Private) import Sentry
import XCTest

// File length grows as replay option coverage is added; lint runs with `--strict`.
Expand Down Expand Up @@ -117,7 +117,7 @@ final class RNSentryReplayOptions: XCTestCase {
] as NSDictionary).mutableCopy() as! NSMutableDictionary
RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])
XCTAssertEqual(actualOptions.sessionReplay.sessionSampleRate, 0.75)
}

Expand All @@ -128,7 +128,7 @@ final class RNSentryReplayOptions: XCTestCase {
] as NSDictionary).mutableCopy() as! NSMutableDictionary
RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])
XCTAssertEqual(actualOptions.sessionReplay.onErrorSampleRate, 0.75)
}

Expand Down Expand Up @@ -158,7 +158,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, true)
assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTImageView")
Expand All @@ -173,7 +173,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, false)
XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0)
Expand All @@ -188,7 +188,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.maskAllText, true)
assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTTextView")
Expand All @@ -215,7 +215,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.maskAllText, false)
XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0)
Expand All @@ -229,7 +229,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertTrue(actualOptions.sessionReplay.enableViewRendererV2)
}
Expand All @@ -243,7 +243,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertTrue(actualOptions.sessionReplay.enableViewRendererV2)
}
Expand All @@ -257,7 +257,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertFalse(actualOptions.sessionReplay.enableViewRendererV2)
}
Expand All @@ -270,7 +270,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertFalse(actualOptions.sessionReplay.enableFastViewRendering)
}
Expand All @@ -284,7 +284,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertTrue(actualOptions.sessionReplay.enableFastViewRendering)
}
Expand All @@ -298,7 +298,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertFalse(actualOptions.sessionReplay.enableFastViewRendering)
}
Expand All @@ -311,7 +311,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
}
Expand All @@ -325,7 +325,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.low)
}
Expand All @@ -339,7 +339,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
}
Expand All @@ -353,7 +353,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.high)
}
Expand All @@ -367,7 +367,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium)
}
Expand All @@ -381,7 +381,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

let includedViewClasses = actualOptions.sessionReplay.includedViewClasses
XCTAssertEqual(includedViewClasses.count, 3)
Expand All @@ -399,7 +399,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

let excludedViewClasses = actualOptions.sessionReplay.excludedViewClasses
XCTAssertEqual(excludedViewClasses.count, 3)
Expand All @@ -420,7 +420,7 @@ final class RNSentryReplayOptions: XCTestCase {

RNSentryReplay.updateOptions(optionsDict)

let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any])
let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any])

let includedViewClasses = actualOptions.sessionReplay.includedViewClasses
XCTAssertEqual(includedViewClasses.count, 2)
Expand Down
Loading
Loading