From 62591e48f197f6827dc20abd084d0ffb8b8045ee Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 22 Jul 2026 11:49:02 +0200 Subject: [PATCH] revert(ios): Revert "Migrate from PrivateSentrySDKOnly to SentrySDK.internal (#6380)" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts #6380 (eae90203), restoring the pre-8.19.0 iOS native behavior. #6380 made RNSentry access SentrySDK.internal before SentrySDK.start (during init, from RNSentryStart and from JS integrations calling native methods like fetchNativeAppStart/fetchNativeSdkInfo). Constructing SentrySDK.internal eagerly reads SentryDependencyContainer.screenshotSource — a lazy var that returns and permanently caches nil while startOptions is unset — breaking all iOS screenshot capture (Feedback Widget screenshot, attachScreenshot, Sentry.captureScreenshot). A targeted RN-side revert is unreliable (any pre-start SentrySDK.internal access poisons the lazy var, and JS controls the timing), so revert the whole migration as the interim fix. The migration can be re-landed once the underlying sentry-cocoa issue is fixed (screenshotSource should not permanently cache nil). Fixes #6497. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 + .../patch-scripts/rn.patch.podfile.js | 21 +- packages/core/RNSentry.podspec | 26 +- .../project.pbxproj | 44 +-- .../RNSentryReplayOptionsTests.swift | 42 +-- .../RNSentryStartFromFileTests.swift | 11 +- .../RNSentryStartTests.swift | 29 +- packages/core/ios/AGENTS.md | 12 +- packages/core/ios/RNSentry+fetchNativeStack.m | 1 + packages/core/ios/RNSentry.mm | 110 +++++--- packages/core/ios/RNSentryInternal.swift | 259 ------------------ packages/core/ios/RNSentryRNSScreen.m | 26 +- packages/core/ios/RNSentryReplay.mm | 10 +- .../RNSentryReplayBreadcrumbConverterHelper.m | 7 +- packages/core/ios/RNSentrySDK.m | 8 +- packages/core/ios/RNSentryStart.m | 29 +- packages/core/ios/RNSentryTimeToDisplay.h | 8 +- packages/core/ios/RNSentryTimeToDisplay.m | 2 - packages/core/ios/SentrySDKWrapper.m | 9 +- packages/core/ios/SentryScreenFramesWrapper.m | 13 +- 20 files changed, 181 insertions(+), 490 deletions(-) delete mode 100644 packages/core/ios/RNSentryInternal.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b4f0ab52..0335b1f20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ ## Unreleased +### Fixes + +- Fix iOS screenshots no longer being captured since 8.19.0 (Feedback Widget screenshot, `attachScreenshot`, `Sentry.captureScreenshot()`) by reverting the iOS `SentrySDK.internal` migration from #6380 ([#6491](https://github.com/getsentry/sentry-react-native/pull/6491), [#6497](https://github.com/getsentry/sentry-react-native/issues/6497)) + ### Dependencies - Bump JavaScript SDK from v10.65.0 to v10.67.0 ([#6471](https://github.com/getsentry/sentry-react-native/pull/6471), [#6494](https://github.com/getsentry/sentry-react-native/pull/6494)) diff --git a/dev-packages/e2e-tests/patch-scripts/rn.patch.podfile.js b/dev-packages/e2e-tests/patch-scripts/rn.patch.podfile.js index c488ef39fe..8ab2afa36b 100755 --- a/dev-packages/e2e-tests/patch-scripts/rn.patch.podfile.js +++ b/dev-packages/e2e-tests/patch-scripts/rn.patch.podfile.js @@ -63,27 +63,8 @@ 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 || modularPatched) { +if (shouldPatch || currentMatch) { fs.writeFileSync(args['pod-file'], content); debug.log('Podfile patched successfully!'); } else { diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 0b6ee4e414..4162b140dd 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -46,15 +46,23 @@ 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. # - # 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'] + # 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 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 diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index 417be173db..8c47e431b6 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 2639D71D3BD04F17B0BAC987 /* RNSentryTurboModulePerfControllerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E795057A6D534A80A9D06356 /* RNSentryTurboModulePerfControllerTests.mm */; }; 332D33472CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */; }; 3339C4812D6625570088EB3A /* RNSentryUserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3339C4802D6625570088EB3A /* RNSentryUserTests.m */; }; 336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; }; @@ -18,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 */; }; + 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 */; }; B5859A50A3E865EF5E61465A /* libPods-RNSentryCocoaTesterTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 650CB718ACFBD05609BF2126 /* libPods-RNSentryCocoaTesterTests.a */; }; @@ -52,8 +52,8 @@ 33F58ACF2977037D008F60EA /* RNSentryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSentryTests.m; sourceTree = ""; }; 3E3742693F7643C2ADE1BDF2 /* RNSentryUriValidationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryUriValidationTests.m; sourceTree = ""; }; 650CB718ACFBD05609BF2126 /* libPods-RNSentryCocoaTesterTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNSentryCocoaTesterTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - E2321E7CFA55AB617247098E /* Pods-RNSentryCocoaTesterTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.debug.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.debug.xcconfig"; sourceTree = ""; }; E795057A6D534A80A9D06356 /* RNSentryTurboModulePerfControllerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNSentryTurboModulePerfControllerTests.mm; sourceTree = ""; }; + E2321E7CFA55AB617247098E /* Pods-RNSentryCocoaTesterTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.debug.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.debug.xcconfig"; sourceTree = ""; }; F48F26542EA2A481008A185E /* RNSentryEmitNewFrameEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryEmitNewFrameEvent.h; path = ../ios/RNSentryEmitNewFrameEvent.h; sourceTree = SOURCE_ROOT; }; F48F26552EA2A4D4008A185E /* RNSentryFramesTrackerListener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentryFramesTrackerListener.h; path = ../ios/RNSentryFramesTrackerListener.h; sourceTree = SOURCE_ROOT; }; FADF868E2EBD053E00D6652D /* SentrySDKWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySDKWrapper.h; path = ../ios/SentrySDKWrapper.h; sourceTree = SOURCE_ROOT; }; @@ -169,7 +169,6 @@ 3360898929524164007C7730 /* Sources */, BB7D14838753E6599863899B /* Frameworks */, CC7959F3721CB3AD7CB6A047 /* [CP] Copy Pods Resources */, - 2AA6DCCDB9B6D20211E939EC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -215,23 +214,6 @@ /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ - 2AA6DCCDB9B6D20211E939EC /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 30F19D4E16BEEFEC68733838 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -352,23 +334,14 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = ( - "$(inherited)", - "-DRCT_REMOVE_LEGACY_ARCH=1", - ); - OTHER_CPLUSPLUSFLAGS = ( - "$(inherited)", - "-DRCT_REMOVE_LEGACY_ARCH=1", - ); OTHER_LDFLAGS = ( "$(inherited)", " ", ); - PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; - USE_HERMES = true; + USE_HERMES = false; }; name = Debug; }; @@ -420,22 +393,13 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - OTHER_CFLAGS = ( - "$(inherited)", - "-DRCT_REMOVE_LEGACY_ARCH=1", - ); - OTHER_CPLUSPLUSFLAGS = ( - "$(inherited)", - "-DRCT_REMOVE_LEGACY_ARCH=1", - ); OTHER_LDFLAGS = ( "$(inherited)", " ", ); - PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; - USE_HERMES = true; + USE_HERMES = false; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift index da7f1ccad1..7d2c99c7b3 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift @@ -1,4 +1,4 @@ -@_spi(Private) import Sentry +import Sentry import XCTest // File length grows as replay option coverage is added; lint runs with `--strict`. @@ -117,7 +117,7 @@ final class RNSentryReplayOptions: XCTestCase { ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.sessionSampleRate, 0.75) } @@ -128,7 +128,7 @@ final class RNSentryReplayOptions: XCTestCase { ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.onErrorSampleRate, 0.75) } @@ -158,7 +158,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, true) assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTImageView") @@ -173,7 +173,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, false) XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0) @@ -188,7 +188,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.maskAllText, true) assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTTextView") @@ -215,7 +215,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.maskAllText, false) XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0) @@ -229,7 +229,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertTrue(actualOptions.sessionReplay.enableViewRendererV2) } @@ -243,7 +243,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertTrue(actualOptions.sessionReplay.enableViewRendererV2) } @@ -257,7 +257,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertFalse(actualOptions.sessionReplay.enableViewRendererV2) } @@ -270,7 +270,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertFalse(actualOptions.sessionReplay.enableFastViewRendering) } @@ -284,7 +284,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertTrue(actualOptions.sessionReplay.enableFastViewRendering) } @@ -298,7 +298,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertFalse(actualOptions.sessionReplay.enableFastViewRendering) } @@ -311,7 +311,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium) } @@ -325,7 +325,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.low) } @@ -339,7 +339,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium) } @@ -353,7 +353,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.high) } @@ -367,7 +367,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) XCTAssertEqual(actualOptions.sessionReplay.quality, SentryReplayOptions.SentryReplayQuality.medium) } @@ -381,7 +381,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) let includedViewClasses = actualOptions.sessionReplay.includedViewClasses XCTAssertEqual(includedViewClasses.count, 3) @@ -399,7 +399,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) let excludedViewClasses = actualOptions.sessionReplay.excludedViewClasses XCTAssertEqual(excludedViewClasses.count, 3) @@ -420,7 +420,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - let actualOptions = try! SentrySDK.internal.options(fromDictionary: optionsDict as! [String: Any]) + let actualOptions = try! PrivateSentrySDKOnly.options(with: optionsDict as! [String: Any]) let includedViewClasses = actualOptions.sessionReplay.includedViewClasses XCTAssertEqual(includedViewClasses.count, 2) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartFromFileTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartFromFileTests.swift index 38107d3f66..e0269a5961 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartFromFileTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartFromFileTests.swift @@ -1,4 +1,3 @@ -@_spi(Private) import Sentry import XCTest final class RNSentryStartFromFileTests: XCTestCase { @@ -12,7 +11,7 @@ final class RNSentryStartFromFileTests: XCTestCase { XCTAssertTrue(wasConfigurationCalled) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertNil(actualOptions.dsn) XCTAssertNil(actualOptions.parsedDsn) } @@ -26,7 +25,7 @@ final class RNSentryStartFromFileTests: XCTestCase { XCTAssertTrue(wasConfigurationCalled) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertNil(actualOptions.dsn) XCTAssertNil(actualOptions.parsedDsn) } @@ -40,7 +39,7 @@ final class RNSentryStartFromFileTests: XCTestCase { XCTAssertTrue(wasConfigurationCalled) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertNil(actualOptions.dsn) XCTAssertNotNil(actualOptions.parsedDsn) XCTAssertEqual(actualOptions.environment, "environment-from-invalid-file") @@ -55,7 +54,7 @@ final class RNSentryStartFromFileTests: XCTestCase { XCTAssertTrue(wasConfigurationCalled) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertNil(actualOptions.dsn) XCTAssertNotNil(actualOptions.parsedDsn) XCTAssertEqual(actualOptions.environment, "environment-from-valid-file") @@ -77,7 +76,7 @@ final class RNSentryStartFromFileTests: XCTestCase { options.environment = "new-environment" } - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertEqual(actualOptions.environment, "new-environment") } diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index 682c651936..c437b83049 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -1,4 +1,3 @@ -@_spi(Private) import Sentry import XCTest final class RNSentryStartTests: XCTestCase { @@ -30,7 +29,7 @@ final class RNSentryStartTests: XCTestCase { "dsn": "https://abcd@efgh.ingest.sentry.io/123456" ]) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options assertReactDefaults(actualOptions) } @@ -64,10 +63,10 @@ final class RNSentryStartTests: XCTestCase { for startMethod in testCases { try startMethod() - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options - XCTAssertTrue(SentrySDK.internal.appStart.hybridSDKMode) - XCTAssertTrue(SentrySDK.internal.performance.framesTrackingHybridSDKMode) + XCTAssertTrue(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) + XCTAssertTrue(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) } } @@ -90,10 +89,10 @@ final class RNSentryStartTests: XCTestCase { for startMethod in testCases { try startMethod() - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options - XCTAssertFalse(SentrySDK.internal.appStart.hybridSDKMode) - XCTAssertFalse(SentrySDK.internal.performance.framesTrackingHybridSDKMode) + XCTAssertFalse(PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode) + XCTAssertFalse(PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode) } } @@ -114,7 +113,7 @@ final class RNSentryStartTests: XCTestCase { for startMethod in testCases { try startMethod() - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options let actualEvent = actualOptions.beforeSend!(createUnhandledJsExceptionEvent()) @@ -139,7 +138,7 @@ final class RNSentryStartTests: XCTestCase { for startMethod in testCases { try startMethod() - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options let actualEvent = actualOptions.beforeSend!(createNativeEvent()) @@ -161,7 +160,7 @@ final class RNSentryStartTests: XCTestCase { } } - SentrySDK.internal.options.beforeSend!(genericEvent()) + PrivateSentrySDKOnly.options.beforeSend!(genericEvent()) XCTAssertTrue(executed) } @@ -208,7 +207,7 @@ final class RNSentryStartTests: XCTestCase { ] ]) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertTrue(actualOptions.attachScreenshot) XCTAssertFalse(actualOptions.screenshot.maskAllText) XCTAssertTrue(actualOptions.screenshot.maskAllImages) @@ -220,7 +219,7 @@ final class RNSentryStartTests: XCTestCase { "attachScreenshot": true ]) - let actualOptions = SentrySDK.internal.options + let actualOptions = PrivateSentrySDKOnly.options XCTAssertTrue(actualOptions.attachScreenshot) XCTAssertTrue(actualOptions.screenshot.maskAllText) XCTAssertTrue(actualOptions.screenshot.maskAllImages) @@ -259,8 +258,8 @@ final class RNSentryStartTests: XCTestCase { var actualEvent: Event? // This is the closest to the sent event we can get using the actual Sentry start method - let originalBeforeSend = SentrySDK.internal.options.beforeSend - SentrySDK.internal.options.beforeSend = { event in + let originalBeforeSend = PrivateSentrySDKOnly.options.beforeSend + PrivateSentrySDKOnly.options.beforeSend = { event in if let originalBeforeSend = originalBeforeSend { let processedEvent = originalBeforeSend(event) actualEvent = processedEvent diff --git a/packages/core/ios/AGENTS.md b/packages/core/ios/AGENTS.md index 04edda1bad..b13a4c617a 100644 --- a/packages/core/ios/AGENTS.md +++ b/packages/core/ios/AGENTS.md @@ -56,15 +56,5 @@ RCT_EXPORT_METHOD(nativeOperation:(NSString *)param 2. Edit `RNSentry.podspec` to remove version constraint 3. Add local pod to sample's Podfile: ```ruby - pod 'Sentry', :path => '../../../../sentry-cocoa' + pod 'Sentry/HybridSDK', :path => '../../../../sentry-cocoa' ``` - -## Internal API access (`SentrySDK.internal`) - -RNSentry consumes sentry-cocoa's hybrid-SDK surface (`SentrySDK.internal.*`) -through a Swift bridge in `RNSentryInternal.swift`. The bridge imports Sentry -with `@_spi(Private)` because several sub-APIs (`performance.currentScreenFrames`, -`replay.configure`, `envelope.{store,capture,deserialize}`) are SPI-gated. -`.m`/`.mm` callers import the auto-generated `RNSentry-Swift.h` and route -through `[RNSentryInternal …]` instead of touching `PrivateSentrySDKOnly` -(deprecated since cocoa 9.19.0 and slated for removal in the next major). diff --git a/packages/core/ios/RNSentry+fetchNativeStack.m b/packages/core/ios/RNSentry+fetchNativeStack.m index 1298cf10c3..196e726ddf 100644 --- a/packages/core/ios/RNSentry+fetchNativeStack.m +++ b/packages/core/ios/RNSentry+fetchNativeStack.m @@ -2,6 +2,7 @@ #import "RNSentryBreadcrumb.h" #import "RNSentryHexFormatter.h" #import "RNSentryId.h" +#import @import Sentry; // This method was moved to a new category so we can use `@import Sentry` to use Sentry's Swift diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 1207a09a93..6cc9e946f4 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -16,13 +16,9 @@ # define SENTRY_TARGET_PROFILING_SUPPORTED 0 #endif -#if __has_include() -# import -#else -# import "RNSentry-Swift.h" -#endif #import "RNSentryBreadcrumb.h" #import "RNSentryId.h" +#import #import #import #import @@ -407,8 +403,10 @@ - (void)handleShakeDetected RCT_EXPORT_METHOD( fetchNativeSdkInfo : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { - resolve( - @ { @"name" : RNSentryInternal.sdkName, @"version" : RNSentryInternal.sdkVersionString }); + resolve(@ { + @"name" : PrivateSentrySDKOnly.getSdkName, + @"version" : PrivateSentrySDKOnly.getSdkVersionString + }); } RCT_EXPORT_METHOD( @@ -460,7 +458,7 @@ - (void)handleShakeDetected contexts[@"release"] = releaseName; } // Merge extra context - NSDictionary *extraContext = [RNSentryInternal extraContext]; + NSDictionary *extraContext = [PrivateSentrySDKOnly getExtraContext]; if (extraContext) { NSDictionary *extraDevice = extraContext[@"device"]; @@ -498,7 +496,8 @@ - (void)handleShakeDetected NSDictionary *user = [serializedScope valueForKey:@"user"]; if (user == nil) { - [serializedScope setValue:@ { @"id" : RNSentryInternal.installationID } forKey:@"user"]; + [serializedScope setValue:@ { @"id" : PrivateSentrySDKOnly.installationID } + forKey:@"user"]; } if ([SentrySDKWrapper debug]) { @@ -511,7 +510,7 @@ - (void)handleShakeDetected } }]; - NSDictionary *extraContext = [RNSentryInternal extraContext]; + NSDictionary *extraContext = [PrivateSentrySDKOnly getExtraContext]; NSMutableDictionary *> *contexts = [serializedScope[@"context"] mutableCopy]; @@ -548,7 +547,8 @@ - (void)handleShakeDetected fetchNativeAppStart : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { #if SENTRY_HAS_UIKIT - NSDictionary *measurements = [RNSentryInternal appStartMeasurementWithSpans]; + NSDictionary *measurements = + [PrivateSentrySDKOnly appStartMeasurementWithSpans]; if (measurements == nil) { resolve(nil); return; @@ -574,7 +574,7 @@ - (void)handleShakeDetected { #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - if (RNSentryInternal.isFramesTrackingRunning) { + if (PrivateSentrySDKOnly.isFramesTrackingRunning) { if (![SentryScreenFramesWrapper canTrackFrames]) { resolve(nil); return; @@ -626,20 +626,20 @@ - (void)handleShakeDetected { NSData *data = [[NSData alloc] initWithBase64EncodedString:rawBytes options:0]; - SentryEnvelope *envelope = [RNSentryInternal envelopeFromData:data]; + SentryEnvelope *envelope = [PrivateSentrySDKOnly envelopeWithData:data]; if (envelope == nil) { reject(@"SentryReactNative", @"Failed to parse envelope from byte array.", nil); return; } #if DEBUG - [RNSentryInternal capture:envelope]; + [PrivateSentrySDKOnly captureEnvelope:envelope]; #else if ([[options objectForKey:@"hardCrashed"] boolValue]) { // Storing to disk happens asynchronously with captureEnvelope - [RNSentryInternal store:envelope]; + [PrivateSentrySDKOnly storeEnvelope:envelope]; } else { - [RNSentryInternal capture:envelope]; + [PrivateSentrySDKOnly captureEnvelope:envelope]; } #endif resolve(@YES); @@ -649,7 +649,7 @@ - (void)handleShakeDetected captureScreenshot : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - NSArray *rawScreenshots = [RNSentryInternal captureScreenshots]; + NSArray *rawScreenshots = [PrivateSentrySDKOnly captureScreenshots]; NSMutableArray *screenshotsArray = [NSMutableArray arrayWithCapacity:[rawScreenshots count]]; int counter = 1; @@ -682,13 +682,7 @@ - (void)handleShakeDetected fetchViewHierarchy : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST - NSData *rawViewHierarchy = [RNSentryInternal captureViewHierarchy]; - if (rawViewHierarchy == nil) { - // Propagate the capture failure to JS instead of a truthy `[]`, which - // would be treated as a successful (empty) attachment. - resolve(nil); - return; - } + NSData *rawViewHierarchy = [PrivateSentrySDKOnly captureViewHierarchy]; NSMutableArray *viewHierarchy = [NSMutableArray arrayWithCapacity:rawViewHierarchy.length]; const char *bytes = (char *)[rawViewHierarchy bytes]; @@ -739,7 +733,7 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys #if SENTRY_HAS_UIKIT NSString *_Nullable screen = [RNSentryBreadcrumb getCurrentScreenFrom:breadcrumb]; if (screen != nil) { - [RNSentryInternal setCurrentScreen:screen]; + [PrivateSentrySDKOnly setCurrentScreen:screen]; } #endif // SENTRY_HAS_UIKIT } @@ -822,19 +816,52 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys RCT_EXPORT_METHOD(resumeAppHangTracking) { [SentrySDKWrapper resumeAppHangTracking]; } -// Calls `SentrySDK.internal.replay.capture()` via the Swift bridge and returns -// the BOOL result. The `@try`/`@catch` is retained as cheap insurance for one -// release cycle — see getsentry/sentry-react-native#5074 for the historical -// fault path that motivated defensive handling here. +/** + * Calls captureReplay on the native replay integration and returns + * the BOOL result indicating whether the capture succeeded. + * + * PrivateSentrySDKOnly.captureReplay is void and discards the result, + * so we call the integration directly to get the success status. + * This prevents returning a stale buffer-mode replay ID when the + * capture actually failed (e.g., replay not running). + * + * Falls back to the old void captureReplay if the integration + * cannot be accessed directly (e.g., future Cocoa SDK changes). + * + * See https://github.com/getsentry/sentry-react-native/issues/5074 + */ + (BOOL)captureReplayWithReturnValue { #if SENTRY_TARGET_REPLAY_SUPPORTED @try { - return [RNSentryInternal captureReplay]; + if ([PrivateSentrySDKOnly respondsToSelector:@selector(getReplayIntegration)]) { +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id replayIntegration = + [PrivateSentrySDKOnly performSelector:@selector(getReplayIntegration)]; +# pragma clang diagnostic pop + if (replayIntegration && + [replayIntegration respondsToSelector:@selector(captureReplay)]) { + typedef BOOL (*CaptureReplayIMP)(id, SEL); + CaptureReplayIMP captureFunc = (CaptureReplayIMP) + [replayIntegration methodForSelector:@selector(captureReplay)]; + return captureFunc(replayIntegration, @selector(captureReplay)); + } + } + } @catch (NSException *exception) { + NSLog(@"[RNSentry] Failed to call captureReplay on integration: %@", exception); + } + // Fallback: call the void method and assume success if a replay ID exists. + // This preserves the old behavior when the integration isn't directly accessible. + // clang-format off + @try { + [PrivateSentrySDKOnly captureReplay]; + return [PrivateSentrySDKOnly getReplayId] != nil; } @catch (NSException *exception) { - NSLog(@"[RNSentry] Failed to call captureReplay: %@", exception); + NSLog(@"[RNSentry] Failed to call captureReplay fallback: %@", exception); return NO; } + // clang-format on #else return NO; #endif @@ -846,7 +873,7 @@ + (BOOL)captureReplayWithReturnValue #if SENTRY_TARGET_REPLAY_SUPPORTED BOOL captured = [RNSentry captureReplayWithReturnValue]; if (captured) { - resolve([RNSentryInternal replayId]); + resolve([PrivateSentrySDKOnly getReplayId]); } else { resolve(nil); } @@ -936,7 +963,7 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId) { #if SENTRY_TARGET_REPLAY_SUPPORTED - return [RNSentryInternal replayId]; + return [PrivateSentrySDKOnly getReplayId]; #else return nil; #endif @@ -962,7 +989,8 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path if (nativeProfileTraceId == nil && nativeProfileStartTime == 0 && platformProfilers) { # if SENTRY_TARGET_PROFILING_SUPPORTED nativeProfileTraceId = [RNSentryId newId]; - nativeProfileStartTime = [RNSentryInternal startProfilerForTrace:nativeProfileTraceId]; + nativeProfileStartTime = + [PrivateSentrySDKOnly startProfilerForTrace:nativeProfileTraceId]; # endif } else { if (!platformProfilers) { @@ -976,7 +1004,7 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path } catch (const std::exception &ex) { if (nativeProfileTraceId != nil) { # if SENTRY_TARGET_PROFILING_SUPPORTED - [RNSentryInternal discardProfilerForTrace:nativeProfileTraceId]; + [PrivateSentrySDKOnly discardProfilerForTrace:nativeProfileTraceId]; # endif nativeProfileTraceId = nil; } @@ -988,7 +1016,7 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path } catch (...) { if (nativeProfileTraceId != nil) { # if SENTRY_TARGET_PROFILING_SUPPORTED - [RNSentryInternal discardProfilerForTrace:nativeProfileTraceId]; + [PrivateSentrySDKOnly discardProfilerForTrace:nativeProfileTraceId]; # endif nativeProfileTraceId = nil; } @@ -1008,9 +1036,9 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path if (nativeProfileTraceId != nil && nativeProfileStartTime != 0) { # if SENTRY_TARGET_PROFILING_SUPPORTED uint64_t nativeProfileStopTime = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); - nativeProfile = [RNSentryInternal collectProfileBetween:nativeProfileStartTime - and:nativeProfileStopTime - forTrace:nativeProfileTraceId]; + nativeProfile = [PrivateSentrySDKOnly collectProfileBetween:nativeProfileStartTime + and:nativeProfileStopTime + forTrace:nativeProfileTraceId]; # endif } // Cleanup native profiles @@ -1065,7 +1093,7 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path } catch (const std::exception &ex) { if (nativeProfileTraceId != nil) { # if SENTRY_TARGET_PROFILING_SUPPORTED - [RNSentryInternal discardProfilerForTrace:nativeProfileTraceId]; + [PrivateSentrySDKOnly discardProfilerForTrace:nativeProfileTraceId]; # endif nativeProfileTraceId = nil; } @@ -1077,7 +1105,7 @@ + (BOOL)isPathUnderAllowedRootsForTesting:(NSString *)path } catch (...) { if (nativeProfileTraceId != nil) { # if SENTRY_TARGET_PROFILING_SUPPORTED - [RNSentryInternal discardProfilerForTrace:nativeProfileTraceId]; + [PrivateSentrySDKOnly discardProfilerForTrace:nativeProfileTraceId]; # endif nativeProfileTraceId = nil; } diff --git a/packages/core/ios/RNSentryInternal.swift b/packages/core/ios/RNSentryInternal.swift deleted file mode 100644 index 94859b44d6..0000000000 --- a/packages/core/ios/RNSentryInternal.swift +++ /dev/null @@ -1,259 +0,0 @@ -import Foundation -@_spi(Private) import Sentry - -/// Thin Objective-C-visible bridge over `SentrySDK.internal.*`. -/// -/// React Native's iOS code is mostly `.m`/`.mm`, but the new hybrid-SDK API -/// (`SentrySDK.internal`) is Swift-only and several methods are gated by -/// `@_spi(Private)`. This file imports with the SPI name and exposes a flat -/// `@objc` surface that mirrors the call sites we use today. -/// -/// Platform gating mirrors each `SentryInternal*Api` in sentry-cocoa -/// (`Sources/Swift/HybridSDK/`). Where a sub-API is excluded on a platform, -/// stubs return safe defaults so `.m`/`.mm` callers still resolve symbols. -@_spi(Private) @objc public final class RNSentryInternal: NSObject { - - // MARK: - SDK metadata - - @_spi(Private) @objc public static var sdkName: String { SentrySDK.internal.sdk.name } - - @_spi(Private) @objc public static var sdkVersionString: String { SentrySDK.internal.sdk.versionString } - - @_spi(Private) @objc public static func setSdkName(_ name: String, version: String) { - SentrySDK.internal.sdk.setName(name, version: version) - } - - @_spi(Private) @objc public static func addSdkPackage(_ name: String, version: String) { - SentrySDK.internal.sdk.addPackage(name: name, version: version) - } - - @_spi(Private) @objc public static var extraContext: [String: Any] { - SentrySDK.internal.sdk.extraContext - } - - @_spi(Private) @objc public static var installationID: String { - SentrySDK.internal.sdk.installationID - } - - // MARK: - Options - - @_spi(Private) @objc public static var options: Options { SentrySDK.internal.options } - - @_spi(Private) @objc public static func options(fromDictionary dict: [String: Any]) throws -> Options { - try SentrySDK.internal.options(fromDictionary: dict) - } - - // MARK: - App start - - @_spi(Private) @objc public static var appStartMeasurementHybridSDKMode: Bool { - get { SentrySDK.internal.appStart.hybridSDKMode } - set { SentrySDK.internal.appStart.hybridSDKMode = newValue } - } - - @_spi(Private) @objc public static var appStartMeasurementWithSpans: [String: Any]? { - SentrySDK.internal.appStart.measurementWithSpans - } - - // MARK: - Performance / frames - - #if os(iOS) || os(tvOS) || os(visionOS) - @_spi(Private) @objc public static var framesTrackingMeasurementHybridSDKMode: Bool { - get { SentrySDK.internal.performance.framesTrackingHybridSDKMode } - set { SentrySDK.internal.performance.framesTrackingHybridSDKMode = newValue } - } - - @_spi(Private) @objc public static var isFramesTrackingRunning: Bool { - SentrySDK.internal.performance.isFramesTrackingRunning - } - - @_spi(Private) @objc public static var currentScreenFrames: SentryScreenFrames? { - SentrySDK.internal.performance.currentScreenFrames - } - #else - @_spi(Private) @objc public static var framesTrackingMeasurementHybridSDKMode: Bool { - get { false } - set {} - } - - @_spi(Private) @objc public static var isFramesTrackingRunning: Bool { false } - - // `currentScreenFrames` is intentionally NOT declared on non-UIKit - // platforms: sentry-cocoa does not compile `SentryScreenFrames` there, - // so referencing it as a return type would fail to resolve. Every ObjC - // caller (`SentryScreenFramesWrapper.m`) is already gated to - // `TARGET_OS_IPHONE || TARGET_OS_MACCATALYST`, so no consumer needs the - // stub on macOS/watchOS. - #endif - - // MARK: - Envelope - - // Accepts `Data?` (nil-safe) rather than `Data` so the ObjC bridge boundary - // doesn't force-unwrap a nil `NSData*` from a failed base64 decode — that - // would crash before we ever get a chance to check the result. Matches the - // nil-tolerant behaviour of the deprecated `PrivateSentrySDKOnly.envelopeWithData:`. - @_spi(Private) @objc public static func envelope(fromData data: Data?) -> SentryEnvelope? { - guard let data = data else { return nil } - return SentrySDK.internal.envelope.deserialize(from: data) - } - - @_spi(Private) @objc public static func capture(_ envelope: SentryEnvelope) { - SentrySDK.internal.envelope.capture(envelope) - } - - @_spi(Private) @objc public static func store(_ envelope: SentryEnvelope) { - SentrySDK.internal.envelope.store(envelope) - } - - // MARK: - Screenshot / view hierarchy / screen - - // sentry-cocoa's `SentryInternalScreen/Screenshot/ViewHierarchyApi` are all - // gated to `(os(iOS) || os(tvOS)) && !SENTRY_NO_UI_FRAMEWORK`. On visionOS - // the new hybrid-SDK surface is intentionally absent, but the same - // functionality still lives on `PrivateSentrySDKOnly` (gated by - // `SENTRY_HAS_UIKIT`, which covers visionOS). Route the visionOS bridge - // through the deprecated SPI so we preserve pre-migration behaviour and - // keep this PR non-breaking. Remove the fallback once sentry-cocoa - // exposes these APIs on visionOS in the hybrid surface — or once cocoa - // drops `PrivateSentrySDKOnly` in a future major and forces our hand. - #if os(iOS) || os(tvOS) - @_spi(Private) @objc public static var captureScreenshots: [Data]? { - SentrySDK.internal.screenshot.capture() - } - - @_spi(Private) @objc public static var captureViewHierarchy: Data? { - SentrySDK.internal.viewHierarchy.capture() - } - - @_spi(Private) @objc public static func setCurrentScreen(_ screenName: String?) { - SentrySDK.internal.screen.setCurrent(screenName) - } - #elseif os(visionOS) - @_spi(Private) @objc public static var captureScreenshots: [Data]? { - PrivateSentrySDKOnly.captureScreenshots() - } - - @_spi(Private) @objc public static var captureViewHierarchy: Data? { - PrivateSentrySDKOnly.captureViewHierarchy() - } - - @_spi(Private) @objc public static func setCurrentScreen(_ screenName: String?) { - PrivateSentrySDKOnly.setCurrentScreen(screenName) - } - #else - @_spi(Private) @objc public static var captureScreenshots: [Data]? { nil } - @_spi(Private) @objc public static var captureViewHierarchy: Data? { nil } - @_spi(Private) @objc public static func setCurrentScreen(_ screenName: String?) {} - #endif - - // MARK: - Replay - - #if os(iOS) || os(tvOS) - @_spi(Private) @objc public static func captureReplay() -> Bool { - SentrySDK.internal.replay.capture() - } - - @_spi(Private) @objc public static var replayId: String? { - SentrySDK.internal.replay.replayId - } - - @_spi(Private) @objc public static func setReplayRedactContainerClass(_ containerClass: AnyClass) { - SentrySDK.internal.replay.setRedactContainerClass(containerClass) - } - - @_spi(Private) @objc public static func setReplayIgnoreContainerClass(_ containerClass: AnyClass) { - SentrySDK.internal.replay.setIgnoreContainerClass(containerClass) - } - - @_spi(Private) @objc public static func configureReplay( - breadcrumbConverter: SentryReplayBreadcrumbConverter - ) { - SentrySDK.internal.replay.configure( - breadcrumbConverter: breadcrumbConverter, - screenshotProvider: nil - ) - } - #else - @_spi(Private) @objc public static func captureReplay() -> Bool { false } - @_spi(Private) @objc public static var replayId: String? { nil } - @_spi(Private) @objc public static func setReplayRedactContainerClass(_ containerClass: AnyClass) {} - @_spi(Private) @objc public static func setReplayIgnoreContainerClass(_ containerClass: AnyClass) {} - @_spi(Private) @objc public static func configureReplay( - breadcrumbConverter: SentryReplayBreadcrumbConverter - ) {} - #endif - - // MARK: - Swizzle - - // `SentryInternalSwizzleApi` in sentry-cocoa has no platform gating. - // We enable the wrapper on every UIKit-capable platform (iOS/tvOS/visionOS - // — matching `SENTRY_UIKIT_AVAILABLE`, which is what `SENTRY_HAS_UIKIT` - // resolves to in the RNSentry.mm caller). `os(iOS)` in Swift already - // covers Mac Catalyst, so no separate `targetEnvironment(macCatalyst)` is - // needed. - #if os(iOS) || os(tvOS) || os(visionOS) - /// Stable identity for the `RNSScreen.viewDidAppear:` swizzle so the underlying - /// `oncePerClass` bookkeeping in sentry-cocoa can dedupe re-entries. - private static var rnsScreenViewDidAppearKey: UInt8 = 0 - - /// Swizzles `-[RNSScreen viewDidAppear:]`, invoking `hook` before the original - /// implementation on every call. No-op when RNSScreen is unavailable. - @_spi(Private) @objc public static func swizzleRNSScreenViewDidAppear(hook: @escaping () -> Void) { - guard let cls = NSClassFromString("RNSScreen") else { return } - let selector = NSSelectorFromString("viewDidAppear:") - - // `withUnsafePointer(to:)` scopes the pointer's validity to the closure - // body. Perform the entire swizzle call inside so we never rely on the - // pointer surviving beyond the closure. The backing storage is a - // `static var`, so the address itself stays stable across calls — - // sentry-cocoa's `oncePerClass` bookkeeping continues to dedupe. - withUnsafePointer(to: &rnsScreenViewDidAppearKey) { keyPtr in - _ = SentrySDK.internal.swizzle.instanceMethod( - selector, - in: cls, - mode: .oncePerClass, - key: UnsafeRawPointer(keyPtr), - factory: { getOriginal in - let block: @convention(block) (AnyObject, ObjCBool) -> Void = { receiver, animated in - hook() - typealias OriginalIMP = @convention(c) (AnyObject, Selector, ObjCBool) -> Void - let original = unsafeBitCast(getOriginal(), to: OriginalIMP.self) - original(receiver, selector, animated) - } - return block as AnyObject - } - ) - } - } - #else - @_spi(Private) @objc public static func swizzleRNSScreenViewDidAppear(hook: @escaping () -> Void) {} - #endif - - // MARK: - Profiling - - #if !(os(watchOS) || os(tvOS) || os(visionOS)) - @_spi(Private) @objc public static func startProfiler(forTrace traceId: SentryId) -> UInt64 { - SentrySDK.internal.profiling.start(for: traceId) - } - - @_spi(Private) @objc(collectProfileBetween:and:forTrace:) - public static func collectProfile( - between startTime: UInt64, - and endTime: UInt64, - forTrace traceId: SentryId - ) -> [String: Any]? { - SentrySDK.internal.profiling.collect(between: startTime, and: endTime, for: traceId) - } - - @_spi(Private) @objc public static func discardProfiler(forTrace traceId: SentryId) { - SentrySDK.internal.profiling.discard(for: traceId) - } - #else - @_spi(Private) @objc public static func startProfiler(forTrace traceId: SentryId) -> UInt64 { 0 } - @_spi(Private) @objc public static func collectProfile( - between startTime: UInt64, - and endTime: UInt64, - forTrace traceId: SentryId - ) -> [String: Any]? { nil } - @_spi(Private) @objc public static func discardProfiler(forTrace traceId: SentryId) {} - #endif -} diff --git a/packages/core/ios/RNSentryRNSScreen.m b/packages/core/ios/RNSentryRNSScreen.m index 52c3658fba..91bbb58c08 100644 --- a/packages/core/ios/RNSentryRNSScreen.m +++ b/packages/core/ios/RNSentryRNSScreen.m @@ -2,21 +2,31 @@ #if SENTRY_HAS_UIKIT -# if __has_include() -# import -# else -# import "RNSentry-Swift.h" -# endif # import "RNSentryDependencyContainer.h" # import "RNSentryFramesTrackerListener.h" +# if __has_include() +# import +# else +# import "SentrySwizzle.h" +# endif +@import Sentry; @implementation RNSentryRNSScreen + (void)swizzleViewDidAppear { - [RNSentryInternal swizzleRNSScreenViewDidAppearWithHook:^{ - [[[RNSentryDependencyContainer sharedInstance] framesTrackerListener] startListening]; - }]; + Class rnsscreenclass = NSClassFromString(@"RNSScreen"); + if (rnsscreenclass == nil) { + return; + } + + SEL selector = NSSelectorFromString(@"viewDidAppear:"); + SentrySwizzleInstanceMethod(rnsscreenclass, selector, SentrySWReturnType(void), + SentrySWArguments(BOOL animated), SentrySWReplacement({ + [[[RNSentryDependencyContainer sharedInstance] framesTrackerListener] startListening]; + SentrySWCallOriginal(animated); + }), + SentrySwizzleModeOncePerClass, (void *)selector); } @end diff --git a/packages/core/ios/RNSentryReplay.mm b/packages/core/ios/RNSentryReplay.mm index 60c4cfe93f..2c3273687e 100644 --- a/packages/core/ios/RNSentryReplay.mm +++ b/packages/core/ios/RNSentryReplay.mm @@ -1,14 +1,10 @@ #import "RNSentryReplay.h" -#if __has_include() -# import -#else -# import "RNSentry-Swift.h" -#endif #import "RNSentryReplayBreadcrumbConverterHelper.h" #import "RNSentryReplayQuality.h" #import "RNSentryVersion.h" #import "Replay/RNSentryReplayMask.h" #import "Replay/RNSentryReplayUnmask.h" +#import #if SENTRY_TARGET_REPLAY_SUPPORTED @@ -80,8 +76,8 @@ + (void)postInit { // We can't import RNSentryReplayMask.h here because it's Objective-C++ // To avoid typos, we test the class existence in the tests - [RNSentryInternal setReplayRedactContainerClass:[RNSentryReplay getMaskClass]]; - [RNSentryInternal setReplayIgnoreContainerClass:[RNSentryReplay getUnmaskClass]]; + [PrivateSentrySDKOnly setRedactContainerClass:[RNSentryReplay getMaskClass]]; + [PrivateSentrySDKOnly setIgnoreContainerClass:[RNSentryReplay getUnmaskClass]]; [RNSentryReplayBreadcrumbConverterHelper configureSessionReplayWithConverter]; } diff --git a/packages/core/ios/RNSentryReplayBreadcrumbConverterHelper.m b/packages/core/ios/RNSentryReplayBreadcrumbConverterHelper.m index 08ace7cc7a..ef3da7ec38 100644 --- a/packages/core/ios/RNSentryReplayBreadcrumbConverterHelper.m +++ b/packages/core/ios/RNSentryReplayBreadcrumbConverterHelper.m @@ -1,11 +1,6 @@ #import "RNSentryReplayBreadcrumbConverterHelper.h" #if SENTRY_TARGET_REPLAY_SUPPORTED -# if __has_include() -# import -# else -# import "RNSentry-Swift.h" -# endif # import "RNSentryReplayBreadcrumbConverter.h" @implementation RNSentryReplayBreadcrumbConverterHelper @@ -14,7 +9,7 @@ + (void)configureSessionReplayWithConverter { RNSentryReplayBreadcrumbConverter *breadcrumbConverter = [[RNSentryReplayBreadcrumbConverter alloc] init]; - [RNSentryInternal configureReplayWithBreadcrumbConverter:breadcrumbConverter]; + [PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter screenshotProvider:nil]; } @end diff --git a/packages/core/ios/RNSentrySDK.m b/packages/core/ios/RNSentrySDK.m index 1f9de9fe52..0f38cf6c7b 100644 --- a/packages/core/ios/RNSentrySDK.m +++ b/packages/core/ios/RNSentrySDK.m @@ -1,10 +1,6 @@ #import "RNSentrySDK.h" -#if __has_include() -# import -#else -# import "RNSentry-Swift.h" -#endif #import "RNSentryStart.h" +#import #import static NSString *SENTRY_OPTIONS_RESOURCE_NAME = @"sentry.options"; @@ -64,7 +60,7 @@ + (void)start:(NSString *)path configureOptions:(void (^)(SentryOptions *options if (options == nil) { // Fallback in case that options file could not be parsed. NSError *fallbackError = nil; - options = [RNSentryInternal optionsFromDictionary:@{ } error:&fallbackError]; + options = [PrivateSentrySDKOnly optionsWithDictionary:@{ } didFailWithError:&fallbackError]; if (fallbackError != nil) { NSLog(@"[RNSentry] Failed to create fallback options with error: %@", fallbackError.localizedDescription); diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index ce365badea..fa463f116e 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -3,11 +3,7 @@ #import "RNSentryReplay.h" #import "RNSentryVersion.h" -#if __has_include() -# import -#else -# import "RNSentry-Swift.h" -#endif +#import #import @import Sentry; @@ -35,14 +31,14 @@ + (void)startWithOptions:(SentryOptions *)options NS_SWIFT_NAME(start(options:)) + (void)startWithOptions:(SentryOptions *)options jsSdkVersion:(NSString *_Nullable)jsSdkVersion { - NSString *sdkVersion = [RNSentryInternal sdkVersionString]; - [RNSentryInternal setSdkName:NATIVE_SDK_NAME version:sdkVersion]; - [RNSentryInternal addSdkPackage:REACT_NATIVE_SDK_PACKAGE_NAME - version:REACT_NATIVE_SDK_PACKAGE_VERSION]; + NSString *sdkVersion = [PrivateSentrySDKOnly getSdkVersionString]; + [PrivateSentrySDKOnly setSdkName:NATIVE_SDK_NAME andVersionString:sdkVersion]; + [PrivateSentrySDKOnly addSdkPackage:REACT_NATIVE_SDK_PACKAGE_NAME + version:REACT_NATIVE_SDK_PACKAGE_VERSION]; if (jsSdkVersion != nil && ![jsSdkVersion isEqualToString:REACT_NATIVE_SDK_PACKAGE_VERSION]) { NSString *otaPackageName = [REACT_NATIVE_SDK_PACKAGE_NAME stringByAppendingString:@":ota"]; - [RNSentryInternal addSdkPackage:otaPackageName version:jsSdkVersion]; + [PrivateSentrySDKOnly addSdkPackage:otaPackageName version:jsSdkVersion]; } [SentrySDK startWithOptions:options]; @@ -66,8 +62,8 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) [RNSentryReplay updateOptions:mutableOptions]; #endif - SentryOptions *sentryOptions = [RNSentryInternal optionsFromDictionary:mutableOptions - error:errorPointer]; + SentryOptions *sentryOptions = [PrivateSentrySDKOnly optionsWithDictionary:mutableOptions + didFailWithError:errorPointer]; if (*errorPointer != nil) { return nil; } @@ -241,11 +237,12 @@ + (void)updateWithReactFinals:(SentryOptions *)options // App Start Hybrid mode doesn't wait for didFinishLaunchNotification and the // didBecomeVisibleNotification as they will be missed when auto initializing from JS // App Start measurements are created right after the tracking starts - RNSentryInternal.appStartMeasurementHybridSDKMode = options.enableAutoPerformanceTracing; + PrivateSentrySDKOnly.appStartMeasurementHybridSDKMode = options.enableAutoPerformanceTracing; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST // Frames Tracking Hybrid Mode ensures tracking // is enabled without tracing enabled in the native SDK - RNSentryInternal.framesTrackingMeasurementHybridSDKMode = options.enableAutoPerformanceTracing; + PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode + = options.enableAutoPerformanceTracing; #endif } @@ -304,8 +301,8 @@ + (void)postDidBecomeActiveNotification // If the app is active/in foreground, and we have not sent the SentryHybridSdkDidBecomeActive // notification, send it. if (appIsActive && !sentHybridSdkDidBecomeActive - && (RNSentryInternal.options.enableAutoSessionTracking - || RNSentryInternal.options.enableWatchdogTerminationTracking)) { + && (PrivateSentrySDKOnly.options.enableAutoSessionTracking + || PrivateSentrySDKOnly.options.enableWatchdogTerminationTracking)) { // Updates Native App State Manager // https://github.com/getsentry/sentry-cocoa/blob/888a145b144b8077e03151a886520f332e47e297/Sources/Sentry/SentryAppStateManager.m#L136 // Triggers Session Tracker diff --git a/packages/core/ios/RNSentryTimeToDisplay.h b/packages/core/ios/RNSentryTimeToDisplay.h index 06e5132df8..a29a8e1cd3 100644 --- a/packages/core/ios/RNSentryTimeToDisplay.h +++ b/packages/core/ios/RNSentryTimeToDisplay.h @@ -1,12 +1,6 @@ #import -// Declared `extern` (not `static`) so it gets external linkage and can be -// referenced from Swift after RNSentry became a Swift-containing pod -// (see `RNSentryInternal.swift`). With `static const` the constant has -// internal linkage per translation unit and Swift's module import fails -// to resolve it, breaking `RNSentryTimeToDisplayTests.swift` with -// `Undefined symbol: _TIME_TO_DISPLAY_ENTRIES_MAX_SIZE` at link time. -extern const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE; +static const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE = 50; @interface RNSentryTimeToDisplay : NSObject diff --git a/packages/core/ios/RNSentryTimeToDisplay.m b/packages/core/ios/RNSentryTimeToDisplay.m index d46dc23bdc..9d31f41a25 100644 --- a/packages/core/ios/RNSentryTimeToDisplay.m +++ b/packages/core/ios/RNSentryTimeToDisplay.m @@ -2,8 +2,6 @@ #import #import -const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE = 50; - // All static state below is accessed from the main thread (CADisplayLink, UI) and from the // React Native bridge / JS thread (setActiveSpanId, pop). Synchronize every access. @implementation RNSentryTimeToDisplay { diff --git a/packages/core/ios/SentrySDKWrapper.m b/packages/core/ios/SentrySDKWrapper.m index 881d3bc69b..9aa5a62493 100644 --- a/packages/core/ios/SentrySDKWrapper.m +++ b/packages/core/ios/SentrySDKWrapper.m @@ -1,9 +1,4 @@ #import "SentrySDKWrapper.h" -#if __has_include() -# import -#else -# import "RNSentry-Swift.h" -#endif @import Sentry; @implementation SentrySDKWrapper @@ -40,12 +35,12 @@ + (void)configureScope:(void (^)(SentryScope *scope))callback + (BOOL)debug { - return RNSentryInternal.options.debug; + return PrivateSentrySDKOnly.options.debug; } + (NSString *)releaseName { - return RNSentryInternal.options.releaseName; + return PrivateSentrySDKOnly.options.releaseName; } @end diff --git a/packages/core/ios/SentryScreenFramesWrapper.m b/packages/core/ios/SentryScreenFramesWrapper.m index e7db337459..dd79c5a214 100644 --- a/packages/core/ios/SentryScreenFramesWrapper.m +++ b/packages/core/ios/SentryScreenFramesWrapper.m @@ -1,9 +1,4 @@ #import "SentryScreenFramesWrapper.h" -#if __has_include() -# import -#else -# import "RNSentry-Swift.h" -#endif @import Sentry; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST @@ -12,7 +7,7 @@ @implementation SentryScreenFramesWrapper + (BOOL)canTrackFrames { - return RNSentryInternal.currentScreenFrames != nil; + return PrivateSentrySDKOnly.currentScreenFrames != nil; } + (NSNumber *)totalFrames @@ -20,7 +15,7 @@ + (NSNumber *)totalFrames if (![self canTrackFrames]) { return nil; } - return [NSNumber numberWithLong:RNSentryInternal.currentScreenFrames.total]; + return [NSNumber numberWithLong:PrivateSentrySDKOnly.currentScreenFrames.total]; } + (NSNumber *)frozenFrames @@ -28,7 +23,7 @@ + (NSNumber *)frozenFrames if (![self canTrackFrames]) { return nil; } - return [NSNumber numberWithLong:RNSentryInternal.currentScreenFrames.frozen]; + return [NSNumber numberWithLong:PrivateSentrySDKOnly.currentScreenFrames.frozen]; } + (NSNumber *)slowFrames @@ -36,7 +31,7 @@ + (NSNumber *)slowFrames if (![self canTrackFrames]) { return nil; } - return [NSNumber numberWithLong:RNSentryInternal.currentScreenFrames.slow]; + return [NSNumber numberWithLong:PrivateSentrySDKOnly.currentScreenFrames.slow]; } + (NSNumber *)framesDelayForStartTimestamp:(double)startTimestampSeconds