diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m index 3cd5b6ae6ff..a4c8e5d6fcf 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m @@ -1,7 +1,6 @@ #import "AppDelegate.h" @import CoreData; @import Sentry; - @interface AppDelegate () diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 65e6bfb116c..f0f7a9f86df 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -813,7 +813,7 @@ D820CDB72BB1895F00BA339D /* SentrySessionReplayIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D820CDB62BB1895F00BA339D /* SentrySessionReplayIntegration.m */; }; D820CDB82BB1895F00BA339D /* SentrySessionReplayIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D820CDB52BB1895F00BA339D /* SentrySessionReplayIntegration.h */; }; D8292D7D2A39A027009872F7 /* UrlSanitizedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */; }; - D82DD1CD2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82DD1CC2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift */; }; + D82DD1CD2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */; }; D8370B6A273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = D8370B68273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m */; }; D8370B6C273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = D8370B6B273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h */; }; D83D079B2B7F9D1C00CC9674 /* SentryMsgPackSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = D83D07992B7F9D1C00CC9674 /* SentryMsgPackSerializer.h */; }; @@ -882,7 +882,6 @@ D8AFC03D2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */; }; D8AFC0572BDA895400118BE1 /* UIRedactBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0562BDA895400118BE1 /* UIRedactBuilder.swift */; }; D8AFC05A2BDA89C100118BE1 /* RedactRegionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */; }; - D8AFC0622BDBEE4200118BE1 /* SentrySessionReplayIntegration+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D8AFC0612BDBEDF100118BE1 /* SentrySessionReplayIntegration+Private.h */; }; D8B0542E2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D8B0542D2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy */; }; D8B088B629C9E3FF00213258 /* SentryTracerConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = D8B088B429C9E3FF00213258 /* SentryTracerConfiguration.h */; }; D8B088B729C9E3FF00213258 /* SentryTracerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = D8B088B529C9E3FF00213258 /* SentryTracerConfiguration.m */; }; @@ -922,7 +921,7 @@ D8F67AEE2BE0D19200C9197B /* UIImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AED2BE0D19200C9197B /* UIImageHelper.swift */; }; D8F67AF12BE0D33F00C9197B /* UIImageHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */; }; D8F67AF42BE10F9600C9197B /* UIRedactBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */; }; - D8F67B1B2BE9728600C9197B /* SentryReplayBreadcrumbConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67B1A2BE9728600C9197B /* SentryReplayBreadcrumbConverter.swift */; }; + D8F67B1B2BE9728600C9197B /* SentrySRDefaultBreadcrumbConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67B1A2BE9728600C9197B /* SentrySRDefaultBreadcrumbConverter.swift */; }; D8F67B222BEAB6CC00C9197B /* SentryRRWebEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67B212BEAB6CC00C9197B /* SentryRRWebEvent.swift */; }; D8F6A2472885512100320515 /* SentryPredicateDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */; }; D8F6A24B2885515C00320515 /* SentryPredicateDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */; }; @@ -1843,6 +1842,7 @@ D801990F286B089000C277F0 /* SentryCrashReportSinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashReportSinkTests.swift; sourceTree = ""; }; D802994D2BA836EF000F0081 /* SentryOnDemandReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryOnDemandReplay.swift; sourceTree = ""; }; D802994F2BA83A88000F0081 /* SentryPixelBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryPixelBuffer.swift; sourceTree = ""; }; + D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySessionReplayIntegration-Hybrid.h"; path = "include/HybridPublic/SentrySessionReplayIntegration-Hybrid.h"; sourceTree = ""; }; D80694C22B7CC86E00B820E6 /* SentryReplayEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayEventTests.swift; sourceTree = ""; }; D80694C52B7CCFA100B820E6 /* SentryReplayRecordingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayRecordingTests.swift; sourceTree = ""; }; D80694CB2B7E0A3E00B820E6 /* SentryReplayType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryReplayType.h; path = include/SentryReplayType.h; sourceTree = ""; }; @@ -1874,7 +1874,7 @@ D820CDB62BB1895F00BA339D /* SentrySessionReplayIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySessionReplayIntegration.m; sourceTree = ""; }; D8292D7A2A38AF04009872F7 /* HTTPHeaderSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderSanitizer.swift; sourceTree = ""; }; D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlSanitizedTests.swift; sourceTree = ""; }; - D82DD1CC2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBreadcrumbReplayConverterTests.swift; sourceTree = ""; }; + D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySRDefaultBreadcrumbConverterTests.swift; sourceTree = ""; }; D8370B68273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSURLSessionTaskSearch.m; sourceTree = ""; }; D8370B6B273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryNSURLSessionTaskSearch.h; path = include/SentryNSURLSessionTaskSearch.h; sourceTree = ""; }; D83D07992B7F9D1C00CC9674 /* SentryMsgPackSerializer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMsgPackSerializer.h; path = include/SentryMsgPackSerializer.h; sourceTree = ""; }; @@ -1994,7 +1994,7 @@ D8F67AED2BE0D19200C9197B /* UIImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageHelper.swift; sourceTree = ""; }; D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageHelperTests.swift; sourceTree = ""; }; D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRedactBuilderTests.swift; sourceTree = ""; }; - D8F67B1A2BE9728600C9197B /* SentryReplayBreadcrumbConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayBreadcrumbConverter.swift; sourceTree = ""; }; + D8F67B1A2BE9728600C9197B /* SentrySRDefaultBreadcrumbConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySRDefaultBreadcrumbConverter.swift; sourceTree = ""; }; D8F67B212BEAB6CC00C9197B /* SentryRRWebEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRRWebEvent.swift; sourceTree = ""; }; D8F6A2452885512100320515 /* SentryPredicateDescriptor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryPredicateDescriptor.m; sourceTree = ""; }; D8F6A24A2885515B00320515 /* SentryPredicateDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryPredicateDescriptor.h; path = include/SentryPredicateDescriptor.h; sourceTree = ""; }; @@ -3600,6 +3600,7 @@ D86130112BB563FD004C0F5E /* SentrySessionReplayIntegrationTests.swift */, D861301B2BB5A267004C0F5E /* SentrySessionReplayTests.swift */, D8AFC0002BD252B900118BE1 /* SentryOnDemandReplayTests.swift */, + D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */, D82DD1CC2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift */, D8DBE0C92C0E093000FAB1FD /* SentryTouchTrackerTests.swift */, D8DBE0D12C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift */, @@ -3628,6 +3629,7 @@ D820CDB52BB1895F00BA339D /* SentrySessionReplayIntegration.h */, D8AFC0612BDBEDF100118BE1 /* SentrySessionReplayIntegration+Private.h */, D820CDB62BB1895F00BA339D /* SentrySessionReplayIntegration.m */, + D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */, ); name = SessionReplay; sourceTree = ""; @@ -3872,7 +3874,7 @@ D802994D2BA836EF000F0081 /* SentryOnDemandReplay.swift */, D802994F2BA83A88000F0081 /* SentryPixelBuffer.swift */, D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */, - D8F67B1A2BE9728600C9197B /* SentryReplayBreadcrumbConverter.swift */, + D8F67B1A2BE9728600C9197B /* SentrySRDefaultBreadcrumbConverter.swift */, D81988BF2BEBFFF70020E36C /* SentryReplayRecording.swift */, D8BC28C72BFF5EBB0054DA4D /* SentryTouchTracker.swift */, ); @@ -4073,7 +4075,6 @@ 6383953623ABA42C000C1594 /* SentryHttpTransport.h in Headers */, 84A8891C28DBD28900C51DFD /* SentryDevice.h in Headers */, 8E564AEF267AF24400FE117D /* SentryNetworkTracker.h in Headers */, - D8AFC0622BDBEE4200118BE1 /* SentrySessionReplayIntegration+Private.h in Headers */, 63FE715120DA4C1100CDBAE8 /* SentryCrashDebug.h in Headers */, 63FE70F520DA4C1000CDBAE8 /* SentryCrashMonitor_System.h in Headers */, 7B31C291277B04A000337126 /* SentryCrashPlatformSpecificDefines.h in Headers */, @@ -4734,7 +4735,7 @@ D81988C72BEC18E20020E36C /* SentryRRWebVideoEvent.swift in Sources */, 621F61F12BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift in Sources */, D88817D826D7149100BF2251 /* SentryTraceContext.m in Sources */, - D8F67B1B2BE9728600C9197B /* SentryReplayBreadcrumbConverter.swift in Sources */, + D8F67B1B2BE9728600C9197B /* SentrySRDefaultBreadcrumbConverter.swift in Sources */, 8EBF870926140D37001A6853 /* SentryPerformanceTracker.m in Sources */, D80CD8D02B75143F002F710B /* UrlSanitized.swift in Sources */, D8F016B32B9622D6007B9AFB /* SentryId.swift in Sources */, @@ -5001,7 +5002,7 @@ 62B86CFC29F052BB008F3947 /* SentryTestLogConfig.m in Sources */, D808FB92281BF6EC009A2A33 /* SentryUIEventTrackingIntegrationTests.swift in Sources */, 7BC6EC04255C235F0059822A /* SentryFrameTests.swift in Sources */, - D82DD1CD2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift in Sources */, + D82DD1CD2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift in Sources */, 0AE455AD28F584D2006680E5 /* SentryReachabilityTests.m in Sources */, 63FE720420DA66EC00CDBAE8 /* SentryCrashString_Tests.m in Sources */, 62872B632BA1B86100A4FA7D /* NSLockTests.swift in Sources */, diff --git a/Sources/Sentry/PrivateSentrySDKOnly.mm b/Sources/Sentry/PrivateSentrySDKOnly.mm index 8be41650187..83878a81134 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.mm +++ b/Sources/Sentry/PrivateSentrySDKOnly.mm @@ -11,6 +11,7 @@ #import "SentryOptions.h" #import "SentrySDK+Private.h" #import "SentrySerialization.h" +#import "SentrySessionReplayIntegration.h" #import "SentrySwift.h" #import "SentryThreadHandle.hpp" #import "SentryUser+Private.h" @@ -304,24 +305,43 @@ + (SentryBreadcrumb *)breadcrumbWithDictionary:(NSDictionary *)dictionary return [[SentryBreadcrumb alloc] initWithDictionary:dictionary]; } +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION ++ (nullable SentrySessionReplayIntegration *)getReplayIntegration +{ + + NSArray *integrations = [[SentrySDK currentHub] installedIntegrations]; + SentrySessionReplayIntegration *replayIntegration; + for (id obj in integrations) { + if ([obj isKindOfClass:[SentrySessionReplayIntegration class]]) { + replayIntegration = obj; + break; + } + } + + return replayIntegration; +} +#endif + + (void)captureReplay { #if SENTRY_HAS_UIKIT && !TARGET_OS_VISION - if (@available(iOS 16.0, *)) { - NSArray *integrations = [[SentrySDK currentHub] installedIntegrations]; - SentrySessionReplayIntegration *replayIntegration; - for (id obj in integrations) { - if ([obj isKindOfClass:[SentrySessionReplayIntegration class]]) { - replayIntegration = obj; - break; - } - } + [[PrivateSentrySDKOnly getReplayIntegration] captureReplay]; +#else + SENTRY_LOG_DEBUG( + @"SentrySessionReplayIntegration only works with UIKit enabled and target is " + @"not visionOS. Ensure you're using the right configuration of Sentry that links UIKit."); +#endif +} - [replayIntegration captureReplay]; - } ++ (void)configureSessionReplayWith:(nullable id)breadcrumbConverter + screenshotProvider:(nullable id)screenshotProvider +{ +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION + [[PrivateSentrySDKOnly getReplayIntegration] configureReplayWith:breadcrumbConverter + screenshotProvider:screenshotProvider]; #else SENTRY_LOG_DEBUG( - @"PrivateSentrySDKOnly.captureReplay only works with UIKit enabled and target is " + @"SentrySessionReplayIntegration only works with UIKit enabled and target is " @"not visionOS. Ensure you're using the right configuration of Sentry that links UIKit."); #endif } @@ -338,11 +358,7 @@ + (NSString *__nullable)getReplayId + (void)addReplayIgnoreClasses:(NSArray *_Nonnull)classes { #if SENTRY_HAS_UIKIT && !TARGET_OS_VISION - if (@available(iOS 16.0, tvOS 16.0, *)) { - [SentryViewPhotographer.shared addIgnoreClasses:classes]; - } else { - SENTRY_LOG_DEBUG(@"PrivateSentrySDKOnly.addIgnoreClasses only works with iOS 16 and newer"); - } + [SentryViewPhotographer.shared addIgnoreClasses:classes]; #else SENTRY_LOG_DEBUG( @"PrivateSentrySDKOnly.addReplayIgnoreClasses only works with UIKit enabled and target is " @@ -353,12 +369,7 @@ + (void)addReplayIgnoreClasses:(NSArray *_Nonnull)classes + (void)addReplayRedactClasses:(NSArray *_Nonnull)classes { #if SENTRY_HAS_UIKIT && !TARGET_OS_VISION - if (@available(iOS 16.0, tvOS 16.0, *)) { - [SentryViewPhotographer.shared addRedactClasses:classes]; - } else { - SENTRY_LOG_DEBUG( - @"PrivateSentrySDKOnly.addReplayRedactClasses only works with iOS 16 and newer"); - } + [SentryViewPhotographer.shared addRedactClasses:classes]; #else SENTRY_LOG_DEBUG( @"PrivateSentrySDKOnly.addReplayRedactClasses only works with UIKit enabled and target is " diff --git a/Sources/Sentry/SentrySessionReplay.m b/Sources/Sentry/SentrySessionReplay.m index 49134e8436e..e3219cd6521 100644 --- a/Sources/Sentry/SentrySessionReplay.m +++ b/Sources/Sentry/SentrySessionReplay.m @@ -39,18 +39,17 @@ @implementation SentrySessionReplay { SentryDisplayLinkWrapper *_displayLink; SentryCurrentDateProvider *_dateProvider; id _sentryRandom; - id _screenshotProvider; int _currentSegmentId; BOOL _processingScreenshot; BOOL _reachedMaximumDuration; SentryTouchTracker *_touchTracker; - SentryReplayBreadcrumbConverter *_breadcrumbConverter; } - (instancetype)initWithSettings:(SentryReplayOptions *)replayOptions replayFolderPath:(NSURL *)folderPath screenshotProvider:(id)screenshotProvider replayMaker:(id)replayMaker + breadcrumbConverter:(id)breadcrumbConverter touchTracker:(SentryTouchTracker *)touchTracker dateProvider:(SentryCurrentDateProvider *)dateProvider random:(id)random @@ -66,8 +65,8 @@ - (instancetype)initWithSettings:(SentryReplayOptions *)replayOptions _urlToCache = folderPath; _replayMaker = replayMaker; _reachedMaximumDuration = NO; + _breadcrumbConverter = breadcrumbConverter; _touchTracker = touchTracker; - _breadcrumbConverter = [[SentryReplayBreadcrumbConverter alloc] init]; } return self; } diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 52a3ba9bf17..7d73f6486cd 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -88,6 +88,17 @@ - (void)newSceneActivate - (void)startWithOptions:(SentryReplayOptions *)replayOptions fullSession:(BOOL)shouldReplayFullSession +{ + [self startWithOptions:replayOptions + screenshotProvider:SentryViewPhotographer.shared + breadcrumbConverter:[[SentrySRDefaultBreadcrumbConverter alloc] init] + fullSession:shouldReplayFullSession]; +} + +- (void)startWithOptions:(SentryReplayOptions *)replayOptions + screenshotProvider:(id)screenshotProvider + breadcrumbConverter:(id)breadcrumbConverter + fullSession:(BOOL)shouldReplayFullSession { if (@available(iOS 16.0, tvOS 16.0, *)) { NSURL *docs = [NSURL @@ -111,14 +122,15 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions : replayOptions.errorReplayDuration); self.sessionReplay = [[SentrySessionReplay alloc] - initWithSettings:replayOptions - replayFolderPath:docs - screenshotProvider:SentryViewPhotographer.shared - replayMaker:replayMaker - touchTracker:_touchTracker - dateProvider:SentryDependencyContainer.sharedInstance.dateProvider - random:SentryDependencyContainer.sharedInstance.random - displayLinkWrapper:[[SentryDisplayLinkWrapper alloc] init]]; + initWithSettings:replayOptions + replayFolderPath:docs + screenshotProvider:screenshotProvider + replayMaker:replayMaker + breadcrumbConverter:breadcrumbConverter + touchTracker:_touchTracker + dateProvider:SentryDependencyContainer.sharedInstance.dateProvider + random:SentryDependencyContainer.sharedInstance.random + displayLinkWrapper:[[SentryDisplayLinkWrapper alloc] init]]; [self.sessionReplay start:SentryDependencyContainer.sharedInstance.application.windows.firstObject @@ -147,6 +159,18 @@ - (void)captureReplay [self.sessionReplay captureReplay]; } +- (void)configureReplayWith:(nullable id)breadcrumbConverter + screenshotProvider:(nullable id)screenshotProvider +{ + if (breadcrumbConverter) { + self.sessionReplay.breadcrumbConverter = breadcrumbConverter; + } + + if (screenshotProvider) { + self.sessionReplay.screenshotProvider = screenshotProvider; + } +} + - (SentryIntegrationOption)integrationOptions { return kIntegrationOptionEnableReplay; diff --git a/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h b/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h index d38adc04577..903f9c9af68 100644 --- a/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h +++ b/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h @@ -9,6 +9,10 @@ @class SentryUser; @class SentryEnvelope; @class SentryId; +@class SentrySessionReplayIntegration; + +@protocol SentryReplayBreadcrumbConverter; +@protocol SentryViewScreenshotProvider; NS_ASSUME_NONNULL_BEGIN @@ -156,8 +160,20 @@ typedef void (^SentryOnAppStartMeasurementAvailable)( * configurations even when targeting iOS or tvOS platforms. */ + (NSData *)captureViewHierarchy; + #endif // SENTRY_UIKIT_AVAILABLE +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION + +/** + * Configure session replay with different breadcrumb converter + * and screeshot provider. Used by the Hybrid SDKs. + * Passing nil will keep the previous value. + */ ++ (void)configureSessionReplayWith:(nullable id)breadcrumbConverter + screenshotProvider:(nullable id)screenshotProvider; + +#endif + (nullable NSDictionary *)appStartMeasurementWithSpans; + (SentryUser *)userWithDictionary:(NSDictionary *)dictionary; diff --git a/Sources/Sentry/include/HybridPublic/SentrySessionReplayIntegration-Hybrid.h b/Sources/Sentry/include/HybridPublic/SentrySessionReplayIntegration-Hybrid.h new file mode 100644 index 00000000000..4c60911cde1 --- /dev/null +++ b/Sources/Sentry/include/HybridPublic/SentrySessionReplayIntegration-Hybrid.h @@ -0,0 +1,19 @@ +#import +#import + +#if SENTRY_UIKIT_AVAILABLE +@class SentryReplayOptions; + +@protocol SentryViewScreenshotProvider; +@protocol SentryReplayBreadcrumbConverter; + +@interface SentrySessionReplayIntegration : NSObject + +- (void)startWithOptions:(SentryReplayOptions *)replayOptions + screenshotProvider:(id)screenshotProvider + breadcrumbConverter:(id)breadcrumbConverter + fullSession:(BOOL)shouldReplayFullSession; + +@end + +#endif diff --git a/Sources/Sentry/include/SentrySessionReplay.h b/Sources/Sentry/include/SentrySessionReplay.h index 2b9179c0809..8383b23bc00 100644 --- a/Sources/Sentry/include/SentrySessionReplay.h +++ b/Sources/Sentry/include/SentrySessionReplay.h @@ -16,17 +16,21 @@ @protocol SentryRedactOptions; @protocol SentryViewScreenshotProvider; @protocol SentryReplayVideoMaker; +@protocol SentryReplayBreadcrumbConverter; NS_ASSUME_NONNULL_BEGIN @interface SentrySessionReplay : NSObject @property (nonatomic, strong, readonly) SentryId *sessionReplayId; +@property (nonatomic, strong) id screenshotProvider; +@property (nonatomic, strong) id breadcrumbConverter; - (instancetype)initWithSettings:(SentryReplayOptions *)replayOptions replayFolderPath:(NSURL *)folderPath - screenshotProvider:(id)photographer + screenshotProvider:(id)screenshotProvider replayMaker:(id)replayMaker + breadcrumbConverter:(id)breadcrumbConverter touchTracker:(SentryTouchTracker *)touchTracker dateProvider:(SentryCurrentDateProvider *)dateProvider random:(id)random diff --git a/Sources/Sentry/include/SentrySessionReplayIntegration.h b/Sources/Sentry/include/SentrySessionReplayIntegration.h index 1f8d091e147..88727e87efb 100644 --- a/Sources/Sentry/include/SentrySessionReplayIntegration.h +++ b/Sources/Sentry/include/SentrySessionReplayIntegration.h @@ -4,6 +4,10 @@ NS_ASSUME_NONNULL_BEGIN #if SENTRY_HAS_UIKIT && !TARGET_OS_VISION + +@protocol SentryReplayBreadcrumbConverter; +@protocol SentryViewScreenshotProvider; + @interface SentrySessionReplayIntegration : SentryBaseIntegration /** @@ -11,6 +15,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)captureReplay; +/** + * Configure session replay with different breadcrumb converter + * and screeshot provider. Used by the Hybrid SDKs. + * If can pass nil to avoid changing the property. + */ +- (void)configureReplayWith:(nullable id)breadcrumbConverter + screenshotProvider:(nullable id)screenshotProvider; + @end #endif // SENTRY_HAS_UIKIT && !TARGET_OS_VISION NS_ASSUME_NONNULL_END diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayBreadcrumbConverter.swift b/Sources/Swift/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverter.swift similarity index 93% rename from Sources/Swift/Integrations/SessionReplay/SentryReplayBreadcrumbConverter.swift rename to Sources/Swift/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverter.swift index 81c71a42c8c..3cda53633b8 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryReplayBreadcrumbConverter.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverter.swift @@ -1,8 +1,13 @@ @_implementationOnly import _SentryPrivate import Foundation +@objc +protocol SentryReplayBreadcrumbConverter: NSObjectProtocol { + func convert(breadcrumbs: [Breadcrumb], from: Date, until: Date) -> [SentryRRWebEvent] +} + @objcMembers -class SentryReplayBreadcrumbConverter: NSObject { +class SentrySRDefaultBreadcrumbConverter: NSObject, SentryReplayBreadcrumbConverter { private let supportedNetworkData = Set([ "status_code", @@ -26,7 +31,7 @@ class SentryReplayBreadcrumbConverter: NSObject { * Any deviation in the information will cause the breadcrumb or the information itself to be discarded * in order to avoid unknown behavior in the front-end. */ - private func convert(from breadcrumb: Breadcrumb) -> SentryRRWebEvent? { + func convert(from breadcrumb: Breadcrumb) -> SentryRRWebEvent? { guard let timestamp = breadcrumb.timestamp else { return nil } if breadcrumb.category == "http" { return networkSpan(breadcrumb) diff --git a/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift b/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift index d42331df3f5..e2d8f031039 100644 --- a/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Breadcrumbs/SentryBreadcrumbTrackerTests.swift @@ -70,7 +70,7 @@ class SentryBreadcrumbTrackerTests: XCTestCase { return } - let breadcrumbConverter = SentryReplayBreadcrumbConverter() + let breadcrumbConverter = SentrySRDefaultBreadcrumbConverter() let result = try XCTUnwrap(breadcrumbConverter.convert(breadcrumbs: [breadcrumb], from: Date(timeIntervalSince1970: 0), until: Date(timeIntervalSinceNow: 60)).first) @@ -140,7 +140,7 @@ class SentryBreadcrumbTrackerTests: XCTestCase { //Call the previous test to create the breadcrumb into the delegate testSwizzlingStarted_ViewControllerAppears_AddsUILifeCycleBreadcrumb() - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() guard let crumb = delegate.addCrumbInvocations.invocations.dropFirst().first else { XCTFail("No navigation breadcrumb") @@ -173,7 +173,7 @@ class SentryBreadcrumbTrackerTests: XCTestCase { NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() guard let crumb = delegate.addCrumbInvocations.invocations.first(where: { $0.category == "app.lifecycle" }) else { XCTFail("No life cycle breadcrumb") return @@ -207,7 +207,7 @@ class SentryBreadcrumbTrackerTests: XCTestCase { swizzlingWrapper.execute(action: "methodPressed:", target: self, sender: self, event: nil) - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() guard let crumb = delegate.addCrumbInvocations.invocations.first(where: { $0.category == "touch" }) else { XCTFail("No touch breadcrumb") return diff --git a/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift b/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift index 2b2346d3d0c..33d536b91c7 100644 --- a/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift +++ b/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift @@ -141,7 +141,7 @@ class SentrySystemEventBreadcrumbsTest: XCTestCase { return } - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let result = try XCTUnwrap(sut.convert(breadcrumbs: [breadcrumb], from: Date(timeIntervalSince1970: 0), until: Date(timeIntervalSinceNow: 60)).first) @@ -218,7 +218,7 @@ class SentrySystemEventBreadcrumbsTest: XCTestCase { return } - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let result = try XCTUnwrap(sut.convert(breadcrumbs: [breadcrumb], from: Date(timeIntervalSince1970: 0), until: Date(timeIntervalSinceNow: 60))) diff --git a/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift index bc70bf273ac..fc34d2e38cb 100644 --- a/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/Network/SentryNetworkTrackerTests.swift @@ -350,7 +350,7 @@ class SentryNetworkTrackerTests: XCTestCase { let breadcrumbs = Dynamic(fixture.scope).breadcrumbArray as [Breadcrumb]? - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() guard let crumb = breadcrumbs?.first else { XCTFail("No touch breadcrumb") return diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentryBreadcrumbReplayConverterTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverterTests.swift similarity index 91% rename from Tests/SentryTests/Integrations/SessionReplay/SentryBreadcrumbReplayConverterTests.swift rename to Tests/SentryTests/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverterTests.swift index 99ff4e2caf2..f0346b88573 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentryBreadcrumbReplayConverterTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverterTests.swift @@ -2,19 +2,19 @@ import Foundation @testable import Sentry import XCTest -class SentryBreadcrumbReplayConverterTests: XCTestCase { +class SentrySRDefaultBreadcrumbConverterTests: XCTestCase { let from = Date(timeIntervalSince1970: 0) - let until = Date(timeIntervalSinceNow: 60) + let until = Date(timeIntervalSinceNow: 3_600) func testReplayBreadcrumbsWithEmptyArray() { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let result = sut.convert(breadcrumbs: [], from: from, until: until) XCTAssertTrue(result.isEmpty) } func testReplayBreadcrumbWithNilTimestamp() { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let breadcrumb = Breadcrumb(level: .debug, category: "Breadcrumb") breadcrumb.timestamp = nil let result = sut.convert(breadcrumbs: [breadcrumb], from: from, until: until) @@ -22,7 +22,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testNavigationBreadcrumbAppLifecycle() { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let crumb = Breadcrumb(level: .info, category: "app.lifecycle") crumb.type = "navigation" crumb.data = ["state": "foreground"] @@ -39,7 +39,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testNavigationBreadcrumbOrientation() { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let crumb = Breadcrumb(level: .info, category: "device.orientation") crumb.type = "navigation" crumb.data = ["position": "portrait"] @@ -58,7 +58,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testNavigationBreadcrumbNavigate() { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let crumb = Breadcrumb(level: .info, category: "ui.lifecycle") crumb.type = "navigation" crumb.data = ["screen": "TestViewController"] @@ -77,7 +77,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testHttpBreadcrumb() throws { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let breadcrumb = Breadcrumb(level: .info, category: "http") let start = Date(timeIntervalSince1970: 5) @@ -108,7 +108,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testTouchBreadcrumb() throws { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let breadcrumb = Breadcrumb(level: .info, category: "touch") breadcrumb.message = "TestTapped:" @@ -121,7 +121,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testConnectivityBreadcrumb() throws { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let breadcrumb = Breadcrumb(level: .info, category: "device.connectivity") breadcrumb.type = "connectivity" breadcrumb.data = ["connectivity": "Wifi"] @@ -136,7 +136,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testBatteryBreadcrumb() throws { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let breadcrumb = Breadcrumb(level: .info, category: "device.event") breadcrumb.type = "system" breadcrumb.data = ["level": 0.5, "plugged": true, "action": "BATTERY_STATE_CHANGE"] @@ -152,7 +152,7 @@ class SentryBreadcrumbReplayConverterTests: XCTestCase { } func testCustomBreadcrumbs() throws { - let sut = SentryReplayBreadcrumbConverter() + let sut = SentrySRDefaultBreadcrumbConverter() let breadcrumb = Breadcrumb(level: .info, category: "MyApp.MyBreadcrumb") breadcrumb.type = "interation" breadcrumb.data = ["SomeInfo": "Info"] diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift index 74a6317bcb7..bfb220f6f93 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift @@ -77,6 +77,7 @@ class SentrySessionReplayTests: XCTestCase { replayFolderPath: cacheFolder, screenshotProvider: screenshotProvider, replay: replayMaker, + breadcrumbConverter: SentrySRDefaultBreadcrumbConverter(), touchTracker: SentryTouchTracker(dateProvider: dateProvider, scale: 0), dateProvider: dateProvider, random: random,