From d58f7d4605f1895c82e69cc6e43500577c5e6f01 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 14 Jun 2024 16:08:20 +0200 Subject: [PATCH 01/11] wip --- Sources/Sentry/SentrySessionReplay.m | 3 + .../Sentry/SentrySessionReplayIntegration.m | 170 +++++++++--------- 2 files changed, 93 insertions(+), 80 deletions(-) diff --git a/Sources/Sentry/SentrySessionReplay.m b/Sources/Sentry/SentrySessionReplay.m index bd6938453cb..20a2da8d7a3 100644 --- a/Sources/Sentry/SentrySessionReplay.m +++ b/Sources/Sentry/SentrySessionReplay.m @@ -115,6 +115,9 @@ - (void)startFullReplay - (void)stop { @synchronized(self) { + if (_isRunning == NO) { + return; + } [_displayLink invalidate]; _isRunning = NO; [self prepareSegmentUntil:_dateProvider.date]; diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index cfc3378c5cd..1f6a9c39c48 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -44,38 +44,38 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options if ([super installWithOptions:options] == NO) { return NO; } - - if (@available(iOS 16.0, tvOS 16.0, *)) { - _replayOptions = options.experimental.sessionReplay; - - _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; - - if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { - return NO; - } - - if (options.enableSwizzling) { - _touchTracker = [[SentryTouchTracker alloc] - initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider - scale:options.experimental.sessionReplay.sizeScale]; - [self swizzleApplicationTouch]; - } - - if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) { - // If a window its already available start replay right away - [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; - } else { - // Wait for a scene to be available to started the replay + + _replayOptions = options.experimental.sessionReplay; + + _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; + + if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { + return NO; + } + + if (options.enableSwizzling) { + _touchTracker = [[SentryTouchTracker alloc] + initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider + scale:options.experimental.sessionReplay.sizeScale]; + [self swizzleApplicationTouch]; + } + + if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) { + // If a window its already available start replay right away + [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; + } else { + // Wait for a scene to be available to started the replay + if (@available(iOS 13.0, *)) { [SentryDependencyContainer.sharedInstance.notificationCenterWrapper - addObserver:self - selector:@selector(newSceneActivate) - name:UISceneDidActivateNotification]; + addObserver:self + selector:@selector(newSceneActivate) + name:UISceneDidActivateNotification]; + } else { + return NO; } - - return YES; - } else { - return NO; } + + return YES; } - (void)newSceneActivate @@ -98,58 +98,55 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions breadcrumbConverter:(id)breadcrumbConverter fullSession:(BOOL)shouldReplayFullSession { - if (@available(iOS 16.0, tvOS 16.0, *)) { - NSURL *docs = [NSURL - fileURLWithPath:[SentryDependencyContainer.sharedInstance.fileManager sentryPath]]; - docs = [docs URLByAppendingPathComponent:SENTRY_REPLAY_FOLDER]; - NSString *currentSession = [NSUUID UUID].UUIDString; - docs = [docs URLByAppendingPathComponent:currentSession]; - - if (![NSFileManager.defaultManager fileExistsAtPath:docs.path]) { - [NSFileManager.defaultManager createDirectoryAtURL:docs - withIntermediateDirectories:YES - attributes:nil - error:nil]; - } - - SentryOnDemandReplay *replayMaker = - [[SentryOnDemandReplay alloc] initWithOutputPath:docs.path]; - replayMaker.bitRate = replayOptions.replayBitRate; - replayMaker.cacheMaxSize - = (NSInteger)(shouldReplayFullSession ? replayOptions.sessionSegmentDuration - : replayOptions.errorReplayDuration); - - self.sessionReplay = [[SentrySessionReplay alloc] - 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 - fullSession:[self shouldReplayFullSession:replayOptions.sessionSampleRate]]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(stop) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(resume) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - - [SentryGlobalEventProcessor.shared - addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { - [self.sessionReplay captureReplayForEvent:event]; - return event; - }]; + NSURL *docs = + [NSURL fileURLWithPath:[SentryDependencyContainer.sharedInstance.fileManager sentryPath]]; + docs = [docs URLByAppendingPathComponent:SENTRY_REPLAY_FOLDER]; + NSString *currentSession = [NSUUID UUID].UUIDString; + docs = [docs URLByAppendingPathComponent:currentSession]; + + if (![NSFileManager.defaultManager fileExistsAtPath:docs.path]) { + [NSFileManager.defaultManager createDirectoryAtURL:docs + withIntermediateDirectories:YES + attributes:nil + error:nil]; } + + SentryOnDemandReplay *replayMaker = [[SentryOnDemandReplay alloc] initWithOutputPath:docs.path]; + replayMaker.bitRate = replayOptions.replayBitRate; + replayMaker.cacheMaxSize + = (NSInteger)(shouldReplayFullSession ? replayOptions.sessionSegmentDuration + : replayOptions.errorReplayDuration); + + self.sessionReplay = [[SentrySessionReplay alloc] + 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 + fullSession:[self shouldReplayFullSession:replayOptions.sessionSampleRate]]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(stop) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(resume) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + [SentryGlobalEventProcessor.shared + addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { + [self.sessionReplay captureReplayForEvent:event]; + return event; + }]; } - (void)stop @@ -162,6 +159,19 @@ - (void)resume [self.sessionReplay resume]; } +- (void)restart +{ + [self.sessionReplay stop]; + + [NSNotificationCenter.defaultCenter removeObserver:self]; + + _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; + if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { + return; + } + [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; +} + - (void)captureReplay { [self.sessionReplay captureReplay]; From f370928439017e6a39ebaec12599607cc9f30c6a Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Mon, 17 Jun 2024 15:16:29 +0200 Subject: [PATCH 02/11] before tests --- Sentry.xcodeproj/project.pbxproj | 5 +- .../SentryTestUtils-ObjC-BridgingHeader.h | 2 +- SentryTestUtils/TestTransportAdapter.swift | 1 + Sources/Sentry/SentryHub.m | 26 +++++++++ .../Sentry/SentrySessionReplayIntegration.m | 53 ++++++++++--------- Sources/Sentry/include/SentryHub+Private.h | 4 ++ Sources/Sentry/include/SentryPrivate.h | 1 + .../SentrySessionReplayIntegration+Private.h | 2 +- .../Protocol/SentrySessionListener.swift | 9 ++++ .../SentrySessionReplayIntegrationTests.swift | 50 +++++++++-------- 10 files changed, 102 insertions(+), 51 deletions(-) create mode 100644 Sources/Swift/Protocol/SentrySessionListener.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index f0f7a9f86df..3e3ad01be4b 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -902,6 +902,7 @@ D8C66A372A77B1F70015696A /* SentryPropagationContext.m in Sources */ = {isa = PBXBuildFile; fileRef = D8C66A352A77B1F70015696A /* SentryPropagationContext.m */; }; D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9928000E23007E326E /* SentryUIApplication.h */; }; D8C67E9C28000E24007E326E /* SentryScreenshot.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9A28000E23007E326E /* SentryScreenshot.h */; }; + D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CA12942C203E71005894F4 /* SentrySessionListener.swift */; }; D8CAC02E2BA0663E00E38F34 /* SentryReplayOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */; }; D8CAC02F2BA0663E00E38F34 /* SentryVideoInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */; }; D8CAC0412BA0984500E38F34 /* SentryIntegrationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */; }; @@ -1972,6 +1973,7 @@ D8C66A352A77B1F70015696A /* SentryPropagationContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryPropagationContext.m; sourceTree = ""; }; D8C67E9928000E23007E326E /* SentryUIApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryUIApplication.h; path = include/SentryUIApplication.h; sourceTree = ""; }; D8C67E9A28000E23007E326E /* SentryScreenshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryScreenshot.h; path = include/SentryScreenshot.h; sourceTree = ""; }; + D8CA12942C203E71005894F4 /* SentrySessionListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySessionListener.swift; sourceTree = ""; }; D8CAC02A2BA0663E00E38F34 /* SentryReplayOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryReplayOptions.swift; sourceTree = ""; }; D8CAC02B2BA0663E00E38F34 /* SentryVideoInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryVideoInfo.swift; sourceTree = ""; }; D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryIntegrationProtocol.swift; sourceTree = ""; }; @@ -3601,7 +3603,6 @@ D861301B2BB5A267004C0F5E /* SentrySessionReplayTests.swift */, D8AFC0002BD252B900118BE1 /* SentryOnDemandReplayTests.swift */, D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */, - D82DD1CC2BEEB1A0001AB556 /* SentryBreadcrumbReplayConverterTests.swift */, D8DBE0C92C0E093000FAB1FD /* SentryTouchTrackerTests.swift */, D8DBE0D12C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift */, ); @@ -3896,6 +3897,7 @@ D8F016B22B9622D6007B9AFB /* SentryId.swift */, D8CAC0402BA0984500E38F34 /* SentryIntegrationProtocol.swift */, D87C89022BC43C9C0086C7DF /* SentryRedactOptions.swift */, + D8CA12942C203E71005894F4 /* SentrySessionListener.swift */, ); path = Protocol; sourceTree = ""; @@ -4536,6 +4538,7 @@ 63FE713B20DA4C1100CDBAE8 /* SentryCrashFileUtils.c in Sources */, 63FE716920DA4C1100CDBAE8 /* SentryCrashStackCursor.c in Sources */, 7BA61CCA247D128B00C130A8 /* SentryThreadInspector.m in Sources */, + D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */, 63FE718D20DA4C1100CDBAE8 /* SentryCrashReportStore.c in Sources */, 62A2F43E2BA9AC10000C9FDD /* DistributionMetric.swift in Sources */, 7BA0C0482805600A003E0326 /* SentryTransportAdapter.m in Sources */, diff --git a/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h b/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h index 5d94b2d0654..00372234869 100644 --- a/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h +++ b/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h @@ -51,7 +51,7 @@ #import "SentrySDK+Private.h" #import "SentrySDK+Tests.h" #import "SentryScopeSyncC.h" -#import "SentrySession.h" + #import "SentrySwizzleWrapper.h" #import "SentrySystemWrapper.h" #import "SentryThreadInspector.h" diff --git a/SentryTestUtils/TestTransportAdapter.swift b/SentryTestUtils/TestTransportAdapter.swift index 73ce0473dfd..ca082607d51 100644 --- a/SentryTestUtils/TestTransportAdapter.swift +++ b/SentryTestUtils/TestTransportAdapter.swift @@ -1,4 +1,5 @@ import Foundation +import _SentryPrivate public class TestTransportAdapter: SentryTransportAdapter { public override func send(_ event: Event, session: SentrySession, attachments: [Attachment]) { diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index e95a70c0540..63d2fd1ed1d 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -44,6 +44,7 @@ @property (nonatomic, strong) SentryCrashWrapper *crashWrapper; @property (nonatomic, strong) NSMutableSet *installedIntegrationNames; @property (nonatomic) NSUInteger errorsBeforeSession; +@property (nonatomic) NSMutableArray> * sessionListeners; @end @@ -77,6 +78,7 @@ - (instancetype)initWithClient:(nullable SentryClient *)client _installedIntegrationNames = [[NSMutableSet alloc] init]; _crashWrapper = [SentryCrashWrapper sharedInstance]; _errorsBeforeSession = 0; + _sessionListeners = [NSMutableArray array]; [SentryDependencyContainer.sharedInstance.crashWrapper enrichScope:scope]; } @@ -107,6 +109,7 @@ - (void)startSession andLevel:kSentryLevelError]; return; } + NSArray>* listeners; @synchronized(_sessionLock) { if (_session != nil) { lastSession = _session; @@ -129,10 +132,15 @@ - (void)startSession [self storeCurrentSession:_session]; [self captureSession:_session]; + listeners = [_sessionListeners copy]; } [lastSession endSessionExitedWithTimestamp:[SentryDependencyContainer.sharedInstance.dateProvider date]]; [self captureSession:lastSession]; + + for (id listener in listeners) { + [listener sentrySessionStarted:_session]; + } } - (void)endSession @@ -143,11 +151,13 @@ - (void)endSession - (void)endSessionWithTimestamp:(NSDate *)timestamp { SentrySession *currentSession = nil; + NSArray>* listeners; @synchronized(_sessionLock) { currentSession = _session; _session = nil; _errorsBeforeSession = 0; [self deleteCurrentSession]; + listeners = [_sessionListeners copy]; } if (currentSession == nil) { @@ -156,6 +166,10 @@ - (void)endSessionWithTimestamp:(NSDate *)timestamp } [currentSession endSessionExitedWithTimestamp:timestamp]; [self captureSession:currentSession]; + + for (id listener in listeners) { + [listener sentrySessionEnded:currentSession]; + } } - (void)storeCurrentSession:(SentrySession *)session @@ -755,6 +769,18 @@ - (LocalMetricsAggregator *_Nullable)getLocalMetricsAggregatorWithSpan:(id)listener { + @synchronized (_sessionLock) { + [_sessionListeners addObject:listener]; + } +} + +- (void)unregisterSessionListener:(id)listener { + @synchronized (_sessionLock) { + [_sessionListeners removeObject:listener]; + } +} + #pragma mark - Protected - (NSMutableArray *)trimmedInstalledIntegrationNames diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 1f6a9c39c48..56cf205e451 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -47,12 +47,6 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options _replayOptions = options.experimental.sessionReplay; - _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; - - if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { - return NO; - } - if (options.enableSwizzling) { _touchTracker = [[SentryTouchTracker alloc] initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider @@ -60,6 +54,24 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options [self swizzleApplicationTouch]; } + [SentrySDK.currentHub registerSessionListener:self]; + + [SentryGlobalEventProcessor.shared + addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { + [self.sessionReplay captureReplayForEvent:event]; + return event; + }]; + + return YES; +} + +- (void)startSession { + _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; + + if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { + return; + } + if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) { // If a window its already available start replay right away [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; @@ -70,12 +82,10 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options addObserver:self selector:@selector(newSceneActivate) name:UISceneDidActivateNotification]; - } else { - return NO; } } - return YES; + return; } - (void)newSceneActivate @@ -141,12 +151,6 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions selector:@selector(resume) name:UIApplicationWillEnterForegroundNotification object:nil]; - - [SentryGlobalEventProcessor.shared - addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { - [self.sessionReplay captureReplayForEvent:event]; - return event; - }]; } - (void)stop @@ -159,17 +163,15 @@ - (void)resume [self.sessionReplay resume]; } -- (void)restart -{ - [self.sessionReplay stop]; - +- (void)sentrySessionEnded:(SentrySession *)session { + [self stop]; [NSNotificationCenter.defaultCenter removeObserver:self]; - - _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; - if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { - return; - } - [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; + _sessionReplay = nil; +} + +- (void)sentrySessionStarted:(SentrySession *)session { + if (_sessionReplay) { return; } + [self startSession]; } - (void)captureReplay @@ -196,6 +198,7 @@ - (SentryIntegrationOption)integrationOptions - (void)uninstall { + [SentrySDK.currentHub unregisterSessionListener:self]; _touchTracker = nil; [self stop]; } diff --git a/Sources/Sentry/include/SentryHub+Private.h b/Sources/Sentry/include/SentryHub+Private.h index f443ff3b13d..238091b4f22 100644 --- a/Sources/Sentry/include/SentryHub+Private.h +++ b/Sources/Sentry/include/SentryHub+Private.h @@ -13,6 +13,7 @@ @class SentryReplayEvent; @class SentryReplayRecording; @protocol SentryIntegrationProtocol; +@protocol SentrySessionListener; NS_ASSUME_NONNULL_BEGIN @@ -62,6 +63,9 @@ SentryHub () - (void)captureEnvelope:(SentryEnvelope *)envelope; +- (void)registerSessionListener:(id)listener; +- (void)unregisterSessionListener:(id)listener; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryPrivate.h b/Sources/Sentry/include/SentryPrivate.h index 23fb7856a16..a58c32da6fb 100644 --- a/Sources/Sentry/include/SentryPrivate.h +++ b/Sources/Sentry/include/SentryPrivate.h @@ -13,3 +13,4 @@ #import "SentryDateUtil.h" #import "SentryLevelHelper.h" #import "SentrySdkInfo.h" +#import "SentrySession.h" diff --git a/Sources/Sentry/include/SentrySessionReplayIntegration+Private.h b/Sources/Sentry/include/SentrySessionReplayIntegration+Private.h index 277b9a6baff..b0f70e1aa4c 100644 --- a/Sources/Sentry/include/SentrySessionReplayIntegration+Private.h +++ b/Sources/Sentry/include/SentrySessionReplayIntegration+Private.h @@ -7,7 +7,7 @@ @class SentrySessionReplay; @interface -SentrySessionReplayIntegration () +SentrySessionReplayIntegration () @property (nonatomic, strong) SentrySessionReplay *sessionReplay; diff --git a/Sources/Swift/Protocol/SentrySessionListener.swift b/Sources/Swift/Protocol/SentrySessionListener.swift new file mode 100644 index 00000000000..4548a759c7f --- /dev/null +++ b/Sources/Swift/Protocol/SentrySessionListener.swift @@ -0,0 +1,9 @@ +import Foundation +@_implementationOnly import _SentryPrivate + +@objc +protocol SentrySessionListener : NSObjectProtocol +{ + func sentrySessionEnded(_ session : SentrySession) + func sentrySessionStarted(_ session : SentrySession) +} diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 9c17ff434b5..3ce5796d760 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -1,5 +1,4 @@ import Foundation -import Nimble @testable import Sentry import SentryTestUtils import XCTest @@ -32,28 +31,37 @@ class SentrySessionReplayIntegrationTests: XCTestCase { clearTestState() } - func startSDK(sessionSampleRate: Float, errorSampleRate: Float, enableSwizzling: Bool = true) { + private func getSut() -> SentrySessionReplayIntegration? { + guard let sut = SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration else { + XCTFail("Did not installed replay integration") + return nil + } + return sut + } + + private func startSDK(sessionSampleRate: Float, errorSampleRate: Float, enableSwizzling: Bool = true) { if #available(iOS 16.0, tvOS 16.0, *) { SentrySDK.start { $0.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: sessionSampleRate, errorSampleRate: errorSampleRate) $0.setIntegrations([SentrySessionReplayIntegration.self]) $0.enableSwizzling = enableSwizzling } + SentrySDK.currentHub().startSession() } } func testNoInstall() { startSDK(sessionSampleRate: 0, errorSampleRate: 0) - expect(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count) == 0 - expect(SentryGlobalEventProcessor.shared().processors.count) == 0 + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,0) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,0) } func testInstallFullSessionReplay() { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - expect(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count) == 1 - expect(SentryGlobalEventProcessor.shared().processors.count) == 1 + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) } func testInstallNoSwizzlingNoTouchTracker() { @@ -76,13 +84,15 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertNotNil(Dynamic(integration).getTouchTracker().asObject) } - func testNoInstallFullSessionReplayBecauseOfRandom() { + func testInstallFullSessionReplayBecauseOfRandomButDontRun() { SentryDependencyContainer.sharedInstance().random = TestRandom(value: 0.3) startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) - expect(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count) == 0 - expect(SentryGlobalEventProcessor.shared().processors.count) == 0 + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + guard let sut = getSut() else { return } + XCTAssertNil(sut.sessionReplay) } func testInstallFullSessionReplayBecauseOfRandom() { @@ -90,39 +100,33 @@ class SentrySessionReplayIntegrationTests: XCTestCase { startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) - expect(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count) == 1 - expect(SentryGlobalEventProcessor.shared().processors.count) == 1 + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) } func testInstallErrorReplay() { startSDK(sessionSampleRate: 0, errorSampleRate: 0.1) - expect(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count) == 1 - expect(SentryGlobalEventProcessor.shared().processors.count) == 1 + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) } func testWaitForNotificationWithNoWindow() { uiApplication.windowsMock = nil startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration else { - fail("Did not installed replay integration") - return - } + guard let sut = getSut() else { return } - expect(Dynamic(sut).sessionReplay.asObject) == nil + XCTAssertNil(sut.sessionReplay) uiApplication.windowsMock = [UIWindow()] NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil) - expect(Dynamic(sut).sessionReplay.asObject) != nil + XCTAssertNil(sut.sessionReplay) } func testPauseAndResumeForApplicationStateChange() { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration else { - XCTFail("Did not find Session Replay Integration") - return - } + guard let sut = getSut() else { return } NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) XCTAssertFalse(Dynamic(sut.sessionReplay).isRunning.asBool ?? true) From 54c9ff11be0cf01624f8e431906283953b390ca4 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 18 Jun 2024 08:25:34 +0200 Subject: [PATCH 03/11] Update SentrySessionReplayIntegrationTests.swift --- .../SessionReplay/SentrySessionReplayIntegrationTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 3ce5796d760..91960937793 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -84,7 +84,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertNotNil(Dynamic(integration).getTouchTracker().asObject) } - func testInstallFullSessionReplayBecauseOfRandomButDontRun() { + func testInstallFullSessionReplayButDontRunBecauseOfRandom() { SentryDependencyContainer.sharedInstance().random = TestRandom(value: 0.3) startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) @@ -102,6 +102,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + guard let sut = getSut() else { return } + XCTAssertNotNil(sut.sessionReplay) } func testInstallErrorReplay() { @@ -120,7 +122,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertNil(sut.sessionReplay) uiApplication.windowsMock = [UIWindow()] NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil) - XCTAssertNil(sut.sessionReplay) + XCTAssertNotNil(sut.sessionReplay) } func testPauseAndResumeForApplicationStateChange() { From 0f34b734b34e68706641bdebf164c8c45179d579 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 18 Jun 2024 13:39:02 +0200 Subject: [PATCH 04/11] Update SentrySessionReplayIntegrationTests.swift --- .../SentrySessionReplayIntegrationTests.swift | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 91960937793..3fe5e3b6984 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -42,6 +42,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { private func startSDK(sessionSampleRate: Float, errorSampleRate: Float, enableSwizzling: Bool = true) { if #available(iOS 16.0, tvOS 16.0, *) { SentrySDK.start { + $0.dsn = "https://user@test.com/test" $0.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: sessionSampleRate, errorSampleRate: errorSampleRate) $0.setIntegrations([SentrySessionReplayIntegration.self]) $0.enableSwizzling = enableSwizzling @@ -76,12 +77,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testInstallWithSwizzlingHasTouchTracker() { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let integration = SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration - else { - XCTFail("Could not find session replay integration") - return - } - XCTAssertNotNil(Dynamic(integration).getTouchTracker().asObject) + guard let sut = getSut() else { return } + XCTAssertNotNil(Dynamic(sut).getTouchTracker().asObject) } func testInstallFullSessionReplayButDontRunBecauseOfRandom() { @@ -135,6 +132,35 @@ class SentrySessionReplayIntegrationTests: XCTestCase { NotificationCenter.default.post(name: UIApplication.willEnterForegroundNotification, object: nil) XCTAssertTrue(Dynamic(sut.sessionReplay).isRunning.asBool ?? false) } + + func testStopReplayAtEndOfSession() { + startSDK(sessionSampleRate: 1, errorSampleRate: 0) + + guard let sut = getSut() else { return } + XCTAssertNotNil(sut.sessionReplay) + SentrySDK.currentHub().endSession() + XCTAssertNil(sut.sessionReplay) + } + + func testStartFullSessionForError() { + startSDK(sessionSampleRate: 0, errorSampleRate: 1) + guard let sut = getSut() else { return } + + XCTAssertFalse(Dynamic(sut.sessionReplay).isFullSession.asBool ?? true) + SentrySDK.capture(error: NSError(domain: "", code: 1)) + XCTAssertTrue(Dynamic(sut.sessionReplay).isFullSession.asBool ?? false) + } + + func testRestartReplayWithNewSession() { + startSDK(sessionSampleRate: 1, errorSampleRate: 0) + + guard let sut = getSut() else { return } + XCTAssertNotNil(sut.sessionReplay) + SentrySDK.currentHub().endSession() + XCTAssertNil(sut.sessionReplay) + SentrySDK.currentHub().startSession() + XCTAssertNotNil(sut.sessionReplay) + } } #endif From 4003dda64a3f2a092950eb16977912b0d8ef7b13 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 18 Jun 2024 14:16:12 +0200 Subject: [PATCH 05/11] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15cb85bbd4e..97ddc12e2db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Restart replay session with mobile session (#4085) + ### Fixes - Fix potential deadlock in app hang detection (#4063) From f38757dcf85110102f1bdb3d5aa2f7aa2192971c Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 19 Jun 2024 09:04:09 +0200 Subject: [PATCH 06/11] ref --- Sources/Sentry/SentryHub.m | 26 ++++-------- .../Sentry/SentrySessionReplayIntegration.m | 32 +++++++++------ .../SentrySessionReplayIntegrationTests.swift | 40 +++++++++---------- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index 63d2fd1ed1d..78b00d6fd5b 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -44,7 +44,7 @@ @property (nonatomic, strong) SentryCrashWrapper *crashWrapper; @property (nonatomic, strong) NSMutableSet *installedIntegrationNames; @property (nonatomic) NSUInteger errorsBeforeSession; -@property (nonatomic) NSMutableArray> * sessionListeners; +@property (nonatomic, weak) id sessionListener; @end @@ -78,8 +78,7 @@ - (instancetype)initWithClient:(nullable SentryClient *)client _installedIntegrationNames = [[NSMutableSet alloc] init]; _crashWrapper = [SentryCrashWrapper sharedInstance]; _errorsBeforeSession = 0; - _sessionListeners = [NSMutableArray array]; - + [SentryDependencyContainer.sharedInstance.crashWrapper enrichScope:scope]; } return self; @@ -109,7 +108,7 @@ - (void)startSession andLevel:kSentryLevelError]; return; } - NSArray>* listeners; + @synchronized(_sessionLock) { if (_session != nil) { lastSession = _session; @@ -132,15 +131,12 @@ - (void)startSession [self storeCurrentSession:_session]; [self captureSession:_session]; - listeners = [_sessionListeners copy]; } [lastSession endSessionExitedWithTimestamp:[SentryDependencyContainer.sharedInstance.dateProvider date]]; [self captureSession:lastSession]; - for (id listener in listeners) { - [listener sentrySessionStarted:_session]; - } + [_sessionListener sentrySessionStarted:_session]; } - (void)endSession @@ -151,13 +147,11 @@ - (void)endSession - (void)endSessionWithTimestamp:(NSDate *)timestamp { SentrySession *currentSession = nil; - NSArray>* listeners; @synchronized(_sessionLock) { currentSession = _session; _session = nil; _errorsBeforeSession = 0; [self deleteCurrentSession]; - listeners = [_sessionListeners copy]; } if (currentSession == nil) { @@ -167,9 +161,7 @@ - (void)endSessionWithTimestamp:(NSDate *)timestamp [currentSession endSessionExitedWithTimestamp:timestamp]; [self captureSession:currentSession]; - for (id listener in listeners) { - [listener sentrySessionEnded:currentSession]; - } + [_sessionListener sentrySessionEnded:currentSession]; } - (void)storeCurrentSession:(SentrySession *)session @@ -770,14 +762,12 @@ - (LocalMetricsAggregator *_Nullable)getLocalMetricsAggregatorWithSpan:(id)listener { - @synchronized (_sessionLock) { - [_sessionListeners addObject:listener]; - } + _sessionListener = listener; } - (void)unregisterSessionListener:(id)listener { - @synchronized (_sessionLock) { - [_sessionListeners removeObject:listener]; + if (_sessionListener == listener) { + _sessionListener = nil; } } diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 56cf205e451..4b5c4396224 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -37,6 +37,7 @@ - (void)newSceneActivate; @implementation SentrySessionReplayIntegration { BOOL _startedAsFullSession; SentryReplayOptions *_replayOptions; + SentryNSNotificationCenterWrapper * _notificationCenter; } - (BOOL)installWithOptions:(nonnull SentryOptions *)options @@ -54,6 +55,8 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options [self swizzleApplicationTouch]; } + _notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; + [SentrySDK.currentHub registerSessionListener:self]; [SentryGlobalEventProcessor.shared @@ -78,14 +81,12 @@ - (void)startSession { } else { // Wait for a scene to be available to started the replay if (@available(iOS 13.0, *)) { - [SentryDependencyContainer.sharedInstance.notificationCenterWrapper + [_notificationCenter addObserver:self selector:@selector(newSceneActivate) name:UISceneDidActivateNotification]; } } - - return; } - (void)newSceneActivate @@ -142,15 +143,15 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions start:SentryDependencyContainer.sharedInstance.application.windows.firstObject fullSession:[self shouldReplayFullSession:replayOptions.sessionSampleRate]]; - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(stop) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - - [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(resume) - name:UIApplicationWillEnterForegroundNotification - object:nil]; + [_notificationCenter addObserver:self + selector:@selector(stop) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + + [_notificationCenter addObserver:self + selector:@selector(resume) + name:UIApplicationWillEnterForegroundNotification + object:nil]; } - (void)stop @@ -165,7 +166,8 @@ - (void)resume - (void)sentrySessionEnded:(SentrySession *)session { [self stop]; - [NSNotificationCenter.defaultCenter removeObserver:self]; + [_notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [_notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; _sessionReplay = nil; } @@ -203,6 +205,10 @@ - (void)uninstall [self stop]; } +- (void)dealloc{ + [self uninstall]; +} + - (BOOL)shouldReplayFullSession:(CGFloat)rate { return [SentryDependencyContainer.sharedInstance.random nextNumber] < rate; diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 3fe5e3b6984..c1b3979b84c 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -31,12 +31,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { clearTestState() } - private func getSut() -> SentrySessionReplayIntegration? { - guard let sut = SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration else { - XCTFail("Did not installed replay integration") - return nil - } - return sut + private func getSut() throws -> SentrySessionReplayIntegration { + return try XCTUnwrap(SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration) } private func startSDK(sessionSampleRate: Float, errorSampleRate: Float, enableSwizzling: Bool = true) { @@ -75,31 +71,31 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertNil(Dynamic(integration).getTouchTracker().asObject) } - func testInstallWithSwizzlingHasTouchTracker() { + func testInstallWithSwizzlingHasTouchTracker() throws { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertNotNil(Dynamic(sut).getTouchTracker().asObject) } - func testInstallFullSessionReplayButDontRunBecauseOfRandom() { + func testInstallFullSessionReplayButDontRunBecauseOfRandom() throws { SentryDependencyContainer.sharedInstance().random = TestRandom(value: 0.3) startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertNil(sut.sessionReplay) } - func testInstallFullSessionReplayBecauseOfRandom() { + func testInstallFullSessionReplayBecauseOfRandom() throws { SentryDependencyContainer.sharedInstance().random = TestRandom(value: 0.1) startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertNotNil(sut.sessionReplay) } @@ -110,11 +106,11 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) } - func testWaitForNotificationWithNoWindow() { + func testWaitForNotificationWithNoWindow() throws { uiApplication.windowsMock = nil startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertNil(sut.sessionReplay) uiApplication.windowsMock = [UIWindow()] @@ -122,10 +118,10 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertNotNil(sut.sessionReplay) } - func testPauseAndResumeForApplicationStateChange() { + func testPauseAndResumeForApplicationStateChange() throws { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = getSut() else { return } + let sut = try getSut() NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) XCTAssertFalse(Dynamic(sut.sessionReplay).isRunning.asBool ?? true) @@ -133,28 +129,28 @@ class SentrySessionReplayIntegrationTests: XCTestCase { XCTAssertTrue(Dynamic(sut.sessionReplay).isRunning.asBool ?? false) } - func testStopReplayAtEndOfSession() { + func testStopReplayAtEndOfSession() throws { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertNotNil(sut.sessionReplay) SentrySDK.currentHub().endSession() XCTAssertNil(sut.sessionReplay) } - func testStartFullSessionForError() { + func testStartFullSessionForError() throws { startSDK(sessionSampleRate: 0, errorSampleRate: 1) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertFalse(Dynamic(sut.sessionReplay).isFullSession.asBool ?? true) SentrySDK.capture(error: NSError(domain: "", code: 1)) XCTAssertTrue(Dynamic(sut.sessionReplay).isFullSession.asBool ?? false) } - func testRestartReplayWithNewSession() { + func testRestartReplayWithNewSession() throws { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - guard let sut = getSut() else { return } + let sut = try getSut() XCTAssertNotNil(sut.sessionReplay) SentrySDK.currentHub().endSession() XCTAssertNil(sut.sessionReplay) From cb9ef8447bea4c93a8a7f160d1227f5a1c2ac276 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 19 Jun 2024 07:06:10 +0000 Subject: [PATCH 07/11] Format code --- SentryTestUtils/TestTransportAdapter.swift | 2 +- Sources/Sentry/SentryHub.m | 12 ++-- .../Sentry/SentrySessionReplayIntegration.m | 61 +++++++++++-------- .../Protocol/SentrySessionListener.swift | 9 ++- .../SentrySessionReplayIntegrationTests.swift | 20 +++--- 5 files changed, 57 insertions(+), 47 deletions(-) diff --git a/SentryTestUtils/TestTransportAdapter.swift b/SentryTestUtils/TestTransportAdapter.swift index ca082607d51..ce542ff88a0 100644 --- a/SentryTestUtils/TestTransportAdapter.swift +++ b/SentryTestUtils/TestTransportAdapter.swift @@ -1,5 +1,5 @@ -import Foundation import _SentryPrivate +import Foundation public class TestTransportAdapter: SentryTransportAdapter { public override func send(_ event: Event, session: SentrySession, attachments: [Attachment]) { diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index 69faf603434..feda90b3e86 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -78,7 +78,7 @@ - (instancetype)initWithClient:(nullable SentryClient *)client _installedIntegrationNames = [[NSMutableSet alloc] init]; _crashWrapper = [SentryCrashWrapper sharedInstance]; _errorsBeforeSession = 0; - + [SentryDependencyContainer.sharedInstance.crashWrapper enrichScope:scope]; } return self; @@ -135,7 +135,7 @@ - (void)startSession [lastSession endSessionExitedWithTimestamp:[SentryDependencyContainer.sharedInstance.dateProvider date]]; [self captureSession:lastSession]; - + [_sessionListener sentrySessionStarted:_session]; } @@ -160,7 +160,7 @@ - (void)endSessionWithTimestamp:(NSDate *)timestamp } [currentSession endSessionExitedWithTimestamp:timestamp]; [self captureSession:currentSession]; - + [_sessionListener sentrySessionEnded:currentSession]; } @@ -773,11 +773,13 @@ - (LocalMetricsAggregator *_Nullable)getLocalMetricsAggregatorWithSpan:(id)listener { +- (void)registerSessionListener:(id)listener +{ _sessionListener = listener; } -- (void)unregisterSessionListener:(id)listener { +- (void)unregisterSessionListener:(id)listener +{ if (_sessionListener == listener) { _sessionListener = nil; } diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 4b5c4396224..cbe9e0b0a94 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -37,7 +37,7 @@ - (void)newSceneActivate; @implementation SentrySessionReplayIntegration { BOOL _startedAsFullSession; SentryReplayOptions *_replayOptions; - SentryNSNotificationCenterWrapper * _notificationCenter; + SentryNSNotificationCenterWrapper *_notificationCenter; } - (BOOL)installWithOptions:(nonnull SentryOptions *)options @@ -45,46 +45,46 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options if ([super installWithOptions:options] == NO) { return NO; } - + _replayOptions = options.experimental.sessionReplay; - + if (options.enableSwizzling) { _touchTracker = [[SentryTouchTracker alloc] - initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider - scale:options.experimental.sessionReplay.sizeScale]; + initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider + scale:options.experimental.sessionReplay.sizeScale]; [self swizzleApplicationTouch]; } - + _notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; - + [SentrySDK.currentHub registerSessionListener:self]; - + [SentryGlobalEventProcessor.shared - addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { - [self.sessionReplay captureReplayForEvent:event]; - return event; - }]; + addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { + [self.sessionReplay captureReplayForEvent:event]; + return event; + }]; return YES; } -- (void)startSession { +- (void)startSession +{ _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; - + if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { return; } - + if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) { // If a window its already available start replay right away [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; } else { // Wait for a scene to be available to started the replay if (@available(iOS 13.0, *)) { - [_notificationCenter - addObserver:self - selector:@selector(newSceneActivate) - name:UISceneDidActivateNotification]; + [_notificationCenter addObserver:self + selector:@selector(newSceneActivate) + name:UISceneDidActivateNotification]; } } } @@ -147,7 +147,7 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions selector:@selector(stop) name:UIApplicationDidEnterBackgroundNotification object:nil]; - + [_notificationCenter addObserver:self selector:@selector(resume) name:UIApplicationWillEnterForegroundNotification @@ -164,15 +164,23 @@ - (void)resume [self.sessionReplay resume]; } -- (void)sentrySessionEnded:(SentrySession *)session { +- (void)sentrySessionEnded:(SentrySession *)session +{ [self stop]; - [_notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [_notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [_notificationCenter removeObserver:self + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [_notificationCenter removeObserver:self + name:UIApplicationWillEnterForegroundNotification + object:nil]; _sessionReplay = nil; } -- (void)sentrySessionStarted:(SentrySession *)session { - if (_sessionReplay) { return; } +- (void)sentrySessionStarted:(SentrySession *)session +{ + if (_sessionReplay) { + return; + } [self startSession]; } @@ -205,7 +213,8 @@ - (void)uninstall [self stop]; } -- (void)dealloc{ +- (void)dealloc +{ [self uninstall]; } diff --git a/Sources/Swift/Protocol/SentrySessionListener.swift b/Sources/Swift/Protocol/SentrySessionListener.swift index 4548a759c7f..06e320efa33 100644 --- a/Sources/Swift/Protocol/SentrySessionListener.swift +++ b/Sources/Swift/Protocol/SentrySessionListener.swift @@ -1,9 +1,8 @@ -import Foundation @_implementationOnly import _SentryPrivate +import Foundation @objc -protocol SentrySessionListener : NSObjectProtocol -{ - func sentrySessionEnded(_ session : SentrySession) - func sentrySessionStarted(_ session : SentrySession) +protocol SentrySessionListener: NSObjectProtocol { + func sentrySessionEnded(_ session: SentrySession) + func sentrySessionStarted(_ session: SentrySession) } diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index c1b3979b84c..b558ab3516f 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -50,15 +50,15 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testNoInstall() { startSDK(sessionSampleRate: 0, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,0) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,0) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 0) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 0) } func testInstallFullSessionReplay() { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) } func testInstallNoSwizzlingNoTouchTracker() { @@ -82,8 +82,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) let sut = try getSut() XCTAssertNil(sut.sessionReplay) } @@ -93,8 +93,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) let sut = try getSut() XCTAssertNotNil(sut.sessionReplay) } @@ -102,8 +102,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testInstallErrorReplay() { startSDK(sessionSampleRate: 0, errorSampleRate: 0.1) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) } func testWaitForNotificationWithNoWindow() throws { From 0cac596d5b4adc1cfe036f3401c55c60ae6170b8 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 19 Jun 2024 09:08:35 +0200 Subject: [PATCH 08/11] format --- SentryTestUtils/TestTransportAdapter.swift | 2 +- Sources/Sentry/SentryHub.m | 12 ++-- .../Sentry/SentrySessionReplayIntegration.m | 61 +++++++++++-------- .../Protocol/SentrySessionListener.swift | 9 ++- .../SentrySessionReplayIntegrationTests.swift | 20 +++--- 5 files changed, 57 insertions(+), 47 deletions(-) diff --git a/SentryTestUtils/TestTransportAdapter.swift b/SentryTestUtils/TestTransportAdapter.swift index ca082607d51..ce542ff88a0 100644 --- a/SentryTestUtils/TestTransportAdapter.swift +++ b/SentryTestUtils/TestTransportAdapter.swift @@ -1,5 +1,5 @@ -import Foundation import _SentryPrivate +import Foundation public class TestTransportAdapter: SentryTransportAdapter { public override func send(_ event: Event, session: SentrySession, attachments: [Attachment]) { diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index 69faf603434..feda90b3e86 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -78,7 +78,7 @@ - (instancetype)initWithClient:(nullable SentryClient *)client _installedIntegrationNames = [[NSMutableSet alloc] init]; _crashWrapper = [SentryCrashWrapper sharedInstance]; _errorsBeforeSession = 0; - + [SentryDependencyContainer.sharedInstance.crashWrapper enrichScope:scope]; } return self; @@ -135,7 +135,7 @@ - (void)startSession [lastSession endSessionExitedWithTimestamp:[SentryDependencyContainer.sharedInstance.dateProvider date]]; [self captureSession:lastSession]; - + [_sessionListener sentrySessionStarted:_session]; } @@ -160,7 +160,7 @@ - (void)endSessionWithTimestamp:(NSDate *)timestamp } [currentSession endSessionExitedWithTimestamp:timestamp]; [self captureSession:currentSession]; - + [_sessionListener sentrySessionEnded:currentSession]; } @@ -773,11 +773,13 @@ - (LocalMetricsAggregator *_Nullable)getLocalMetricsAggregatorWithSpan:(id)listener { +- (void)registerSessionListener:(id)listener +{ _sessionListener = listener; } -- (void)unregisterSessionListener:(id)listener { +- (void)unregisterSessionListener:(id)listener +{ if (_sessionListener == listener) { _sessionListener = nil; } diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index 4b5c4396224..cbe9e0b0a94 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -37,7 +37,7 @@ - (void)newSceneActivate; @implementation SentrySessionReplayIntegration { BOOL _startedAsFullSession; SentryReplayOptions *_replayOptions; - SentryNSNotificationCenterWrapper * _notificationCenter; + SentryNSNotificationCenterWrapper *_notificationCenter; } - (BOOL)installWithOptions:(nonnull SentryOptions *)options @@ -45,46 +45,46 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options if ([super installWithOptions:options] == NO) { return NO; } - + _replayOptions = options.experimental.sessionReplay; - + if (options.enableSwizzling) { _touchTracker = [[SentryTouchTracker alloc] - initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider - scale:options.experimental.sessionReplay.sizeScale]; + initWithDateProvider:SentryDependencyContainer.sharedInstance.dateProvider + scale:options.experimental.sessionReplay.sizeScale]; [self swizzleApplicationTouch]; } - + _notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; - + [SentrySDK.currentHub registerSessionListener:self]; - + [SentryGlobalEventProcessor.shared - addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { - [self.sessionReplay captureReplayForEvent:event]; - return event; - }]; + addEventProcessor:^SentryEvent *_Nullable(SentryEvent *_Nonnull event) { + [self.sessionReplay captureReplayForEvent:event]; + return event; + }]; return YES; } -- (void)startSession { +- (void)startSession +{ _startedAsFullSession = [self shouldReplayFullSession:_replayOptions.sessionSampleRate]; - + if (!_startedAsFullSession && _replayOptions.errorSampleRate == 0) { return; } - + if (SentryDependencyContainer.sharedInstance.application.windows.count > 0) { // If a window its already available start replay right away [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; } else { // Wait for a scene to be available to started the replay if (@available(iOS 13.0, *)) { - [_notificationCenter - addObserver:self - selector:@selector(newSceneActivate) - name:UISceneDidActivateNotification]; + [_notificationCenter addObserver:self + selector:@selector(newSceneActivate) + name:UISceneDidActivateNotification]; } } } @@ -147,7 +147,7 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions selector:@selector(stop) name:UIApplicationDidEnterBackgroundNotification object:nil]; - + [_notificationCenter addObserver:self selector:@selector(resume) name:UIApplicationWillEnterForegroundNotification @@ -164,15 +164,23 @@ - (void)resume [self.sessionReplay resume]; } -- (void)sentrySessionEnded:(SentrySession *)session { +- (void)sentrySessionEnded:(SentrySession *)session +{ [self stop]; - [_notificationCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [_notificationCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [_notificationCenter removeObserver:self + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [_notificationCenter removeObserver:self + name:UIApplicationWillEnterForegroundNotification + object:nil]; _sessionReplay = nil; } -- (void)sentrySessionStarted:(SentrySession *)session { - if (_sessionReplay) { return; } +- (void)sentrySessionStarted:(SentrySession *)session +{ + if (_sessionReplay) { + return; + } [self startSession]; } @@ -205,7 +213,8 @@ - (void)uninstall [self stop]; } -- (void)dealloc{ +- (void)dealloc +{ [self uninstall]; } diff --git a/Sources/Swift/Protocol/SentrySessionListener.swift b/Sources/Swift/Protocol/SentrySessionListener.swift index 4548a759c7f..06e320efa33 100644 --- a/Sources/Swift/Protocol/SentrySessionListener.swift +++ b/Sources/Swift/Protocol/SentrySessionListener.swift @@ -1,9 +1,8 @@ -import Foundation @_implementationOnly import _SentryPrivate +import Foundation @objc -protocol SentrySessionListener : NSObjectProtocol -{ - func sentrySessionEnded(_ session : SentrySession) - func sentrySessionStarted(_ session : SentrySession) +protocol SentrySessionListener: NSObjectProtocol { + func sentrySessionEnded(_ session: SentrySession) + func sentrySessionStarted(_ session: SentrySession) } diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index c1b3979b84c..b558ab3516f 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -50,15 +50,15 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testNoInstall() { startSDK(sessionSampleRate: 0, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,0) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,0) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 0) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 0) } func testInstallFullSessionReplay() { startSDK(sessionSampleRate: 1, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) } func testInstallNoSwizzlingNoTouchTracker() { @@ -82,8 +82,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) let sut = try getSut() XCTAssertNil(sut.sessionReplay) } @@ -93,8 +93,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { startSDK(sessionSampleRate: 0.2, errorSampleRate: 0) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) let sut = try getSut() XCTAssertNotNil(sut.sessionReplay) } @@ -102,8 +102,8 @@ class SentrySessionReplayIntegrationTests: XCTestCase { func testInstallErrorReplay() { startSDK(sessionSampleRate: 0, errorSampleRate: 0.1) - XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count,1) - XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count,1) + XCTAssertEqual(SentrySDK.currentHub().trimmedInstalledIntegrationNames().count, 1) + XCTAssertEqual(SentryGlobalEventProcessor.shared().processors.count, 1) } func testWaitForNotificationWithNoWindow() throws { From 97595153e27e609101ca0d285b76078720bc6165 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 19 Jun 2024 11:57:37 +0200 Subject: [PATCH 09/11] Update SentrySessionReplayIntegration.m --- Sources/Sentry/SentrySessionReplayIntegration.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index cbe9e0b0a94..d52646cc59c 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -81,7 +81,7 @@ - (void)startSession [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; } else { // Wait for a scene to be available to started the replay - if (@available(iOS 13.0, *)) { + if (@available(iOS 13.0,tvOS 13.0, *)) { [_notificationCenter addObserver:self selector:@selector(newSceneActivate) name:UISceneDidActivateNotification]; From 81ffae6aa22f53102a54d97b33171e3bab706b58 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 19 Jun 2024 09:58:49 +0000 Subject: [PATCH 10/11] Format code --- Sources/Sentry/SentrySessionReplayIntegration.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index d52646cc59c..7b3e8de98c6 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -81,7 +81,7 @@ - (void)startSession [self startWithOptions:_replayOptions fullSession:_startedAsFullSession]; } else { // Wait for a scene to be available to started the replay - if (@available(iOS 13.0,tvOS 13.0, *)) { + if (@available(iOS 13.0, tvOS 13.0, *)) { [_notificationCenter addObserver:self selector:@selector(newSceneActivate) name:UISceneDidActivateNotification]; From 5efffc923818d4d5a55e0e62d302d549f2aa7cd3 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 19 Jun 2024 13:29:43 +0200 Subject: [PATCH 11/11] wip --- Sources/Sentry/include/SentrySession.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/Sentry/include/SentrySession.h b/Sources/Sentry/include/SentrySession.h index 3778f8ec5e7..f852f4a0d5f 100644 --- a/Sources/Sentry/include/SentrySession.h +++ b/Sources/Sentry/include/SentrySession.h @@ -1,5 +1,4 @@ #import "SentryDefines.h" -#import "SentrySerializable.h" @class SentryUser; @@ -15,7 +14,7 @@ typedef NS_ENUM(NSUInteger, SentrySessionStatus) { /** * The SDK uses SentrySession to inform Sentry about release and project associated project health. */ -@interface SentrySession : NSObject +@interface SentrySession : NSObject SENTRY_NO_INIT - (instancetype)initWithReleaseName:(NSString *)releaseName distinctId:(NSString *)distinctId;