From 66b14d865df90e19e070b3b244e430b205163ece Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 31 May 2024 14:13:15 +0200 Subject: [PATCH 01/19] touch events on android --- .../io/sentry/react/RNSentryModuleImpl.java | 3 ++ .../RNSentryReplayBreadcrumbConverter.java | 37 +++++++++++++++++++ src/js/touchevents.tsx | 5 ++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java diff --git a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 7bc43cfbb6..59793476f0 100644 --- a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -294,6 +294,9 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { } }); + Sentry.getCurrentHub().getOptions().getReplayController() + .setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); + promise.resolve(true); } diff --git a/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java b/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java new file mode 100644 index 0000000000..888e6e49a9 --- /dev/null +++ b/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java @@ -0,0 +1,37 @@ +package io.sentry.react; + +import io.sentry.Breadcrumb; +import io.sentry.android.replay.DefaultReplayBreadcrumbConverter; +import io.sentry.rrweb.RRWebEvent; +import io.sentry.rrweb.RRWebBreadcrumbEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class RNSentryReplayBreadcrumbConverter extends DefaultReplayBreadcrumbConverter { + public RNSentryReplayBreadcrumbConverter() { + } + + @Override + public @Nullable RRWebEvent convert(final @NotNull Breadcrumb breadcrumb) { + var rrwebBreadcrumb = new RRWebBreadcrumbEvent(); + assert rrwebBreadcrumb.getCategory() == null; + + if (breadcrumb.getCategory().equals("touch")) { + rrwebBreadcrumb.setCategory("ui.tap"); + Object target = breadcrumb.getData("target"); + if (target != null) { + rrwebBreadcrumb.setMessage(target.toString()); + } + rrwebBreadcrumb.setData(breadcrumb.getData()); + } + + if (rrwebBreadcrumb.getCategory() != null && !rrwebBreadcrumb.getCategory().isEmpty()) { + rrwebBreadcrumb.setTimestamp(breadcrumb.getTimestamp().getTime()); + rrwebBreadcrumb.setBreadcrumbTimestamp(breadcrumb.getTimestamp().getTime() / 1000.0); + rrwebBreadcrumb.setBreadcrumbType("default"); + return rrwebBreadcrumb; + } + + return super.convert(breadcrumb); + } +} diff --git a/src/js/touchevents.tsx b/src/js/touchevents.tsx index fa58273baf..804bfff4a4 100644 --- a/src/js/touchevents.tsx +++ b/src/js/touchevents.tsx @@ -120,7 +120,10 @@ class TouchEventBoundary extends React.Component { const level = 'info' as SeverityLevel; const crumb = { category: this.props.breadcrumbCategory, - data: { componentTree: componentTreeNames }, + data: { + componentTree: componentTreeNames, + ...(activeLabel && { target: activeLabel }), + }, level: level, message: activeLabel ? `Touch event within element: ${activeLabel}` From a9139c63a695df1f14b5d8a8bee61cdf048ff82d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 3 Jun 2024 20:38:31 +0200 Subject: [PATCH 02/19] fix touchevent tests --- test/touchevents.test.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/touchevents.test.tsx b/test/touchevents.test.tsx index c3a18b246e..e541a56df7 100644 --- a/test/touchevents.test.tsx +++ b/test/touchevents.test.tsx @@ -102,6 +102,7 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { componentTree: ['View', 'Connect(View)', 'LABEL!'], + target: "LABEL!", }, level: 'info' as SeverityLevel, message: 'Touch event within element: LABEL!', @@ -161,6 +162,7 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { componentTree: ['Styled(View2)', 'Styled(View)'], + target: 'Styled(View2)', }, level: 'info' as SeverityLevel, message: 'Touch event within element: Styled(View2)', @@ -211,6 +213,7 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { componentTree: ['Connect(View)', 'Styled(View)'], + target: 'Connect(View)', }, level: 'info' as SeverityLevel, message: 'Touch event within element: Connect(View)', From fb1bb909e63cd326f04574391b6bf43c366a2d8a Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:56:50 +0200 Subject: [PATCH 03/19] Update android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java Co-authored-by: Roman Zavarnitsyn --- .../java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java b/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java index 888e6e49a9..ed2e9ed861 100644 --- a/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java +++ b/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java @@ -13,7 +13,7 @@ public RNSentryReplayBreadcrumbConverter() { @Override public @Nullable RRWebEvent convert(final @NotNull Breadcrumb breadcrumb) { - var rrwebBreadcrumb = new RRWebBreadcrumbEvent(); + RRWebBreadcrumbEvent rrwebBreadcrumb = new RRWebBreadcrumbEvent(); assert rrwebBreadcrumb.getCategory() == null; if (breadcrumb.getCategory().equals("touch")) { From e725aa98795c4345d4a96e1d538e28e21c98d32d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 5 Jun 2024 15:42:25 +0200 Subject: [PATCH 04/19] ios breadcrumb converter --- RNSentry.podspec | 2 +- ios/RNSentry.mm | 4 ++++ ios/RNSentryBreadcrumbConverter.h | 13 +++++++++++++ ios/RNSentryBreadcrumbConverter.m | 32 +++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 ios/RNSentryBreadcrumbConverter.h create mode 100644 ios/RNSentryBreadcrumbConverter.m diff --git a/RNSentry.podspec b/RNSentry.podspec index 393a8f6171..4d5cd7f281 100644 --- a/RNSentry.podspec +++ b/RNSentry.podspec @@ -14,7 +14,7 @@ is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] == "1" is_using_hermes = (ENV['USE_HERMES'] == nil && is_hermes_default) || ENV['USE_HERMES'] == '1' new_arch_enabled_flag = (is_new_arch_enabled ? folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED" : "") sentry_profiling_supported_flag = (is_profiling_supported ? " -DSENTRY_PROFILING_SUPPORTED=1" : "") -other_cflags = "$(inherited)" + new_arch_enabled_flag + sentry_profiling_supported_flag +other_cflags = "$(inherited) -fcxx-modules -fmodules" + new_arch_enabled_flag + sentry_profiling_supported_flag Pod::Spec.new do |s| s.name = 'RNSentry' diff --git a/ios/RNSentry.mm b/ios/RNSentry.mm index 79da19826b..1093187c4d 100644 --- a/ios/RNSentry.mm +++ b/ios/RNSentry.mm @@ -36,6 +36,7 @@ #import "RNSentryEvents.h" #import "RNSentryDependencyContainer.h" +#import "RNSentryBreadcrumbConverter.h" #if SENTRY_HAS_UIKIT #import "RNSentryRNSScreen.h" @@ -90,6 +91,9 @@ + (BOOL)requiresMainQueueSetup { [SentrySDK startWithOptions:sentryOptions]; + RNSentryBreadcrumbConverter* breadcrumbConverter = [[RNSentryBreadcrumbConverter alloc] init]; + [PrivateSentrySDKOnly configureSessionReplayWith: breadcrumbConverter screenshotProvider: nil]; + #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; #else diff --git a/ios/RNSentryBreadcrumbConverter.h b/ios/RNSentryBreadcrumbConverter.h new file mode 100644 index 0000000000..cfabcfee09 --- /dev/null +++ b/ios/RNSentryBreadcrumbConverter.h @@ -0,0 +1,13 @@ +@import Sentry; + +@class SentryRRWebEvent; + +@interface RNSentryBreadcrumbConverter + : NSObject + +- (NSArray *_Nonnull) + convertWithBreadcrumbs:(NSArray *_Nonnull)breadcrumbs + from:(NSDate *_Nonnull)from + until:(NSDate *_Nonnull)until; + +@end diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m new file mode 100644 index 0000000000..250282eaf2 --- /dev/null +++ b/ios/RNSentryBreadcrumbConverter.m @@ -0,0 +1,32 @@ +#import "RNSentryBreadcrumbConverter.h" + +@implementation RNSentryBreadcrumbConverter { +} + +- (NSArray *_Nonnull) + convertWithBreadcrumbs:(NSArray *_Nonnull)breadcrumbs + from:(NSDate *_Nonnull)from + until:(NSDate *_Nonnull)until { + NSMutableArray *outBreadcrumbs = [NSMutableArray array]; + for (SentryBreadcrumb *breadcrumb in breadcrumbs) { + if (breadcrumb.timestamp && + [breadcrumb.timestamp compare:from] != NSOrderedAscending && + [breadcrumb.timestamp compare:until] != NSOrderedDescending) { + if ([breadcrumb.category isEqualToString:@"touch"]) { + SentryRRWebBreadcrumbEvent *rrwebBreadcrumb = + [[SentryRRWebBreadcrumbEvent alloc] + initWithTimestamp:breadcrumb.timestamp + category:@"ui.tap" + message:breadcrumb.data ? [breadcrumb.data valueForKey:@"target"] : nil + level:breadcrumb.level + data:breadcrumb.data]; + [outBreadcrumbs addObject:rrwebBreadcrumb]; + } else { + // TODO delegate to the default breadcrumb converter + } + } + } + return outBreadcrumbs; +} + +@end From d5c8e677860ae050ae8991cec3479f5118316aee Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 10 Jun 2024 16:55:48 +0200 Subject: [PATCH 05/19] delegate to default breadcrumb converter --- ios/RNSentryBreadcrumbConverter.h | 2 ++ ios/RNSentryBreadcrumbConverter.m | 31 ++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/ios/RNSentryBreadcrumbConverter.h b/ios/RNSentryBreadcrumbConverter.h index cfabcfee09..0334ffd9f5 100644 --- a/ios/RNSentryBreadcrumbConverter.h +++ b/ios/RNSentryBreadcrumbConverter.h @@ -5,6 +5,8 @@ @interface RNSentryBreadcrumbConverter : NSObject +- (instancetype _Nonnull)init; + - (NSArray *_Nonnull) convertWithBreadcrumbs:(NSArray *_Nonnull)breadcrumbs from:(NSDate *_Nonnull)from diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m index 250282eaf2..3800ce429e 100644 --- a/ios/RNSentryBreadcrumbConverter.m +++ b/ios/RNSentryBreadcrumbConverter.m @@ -1,6 +1,14 @@ #import "RNSentryBreadcrumbConverter.h" @implementation RNSentryBreadcrumbConverter { + SentrySRDefaultBreadcrumbConverter *defaultConverter; +} + +- (instancetype _Nonnull)init { + if (self = [super init]) { + self->defaultConverter = [[SentrySRDefaultBreadcrumbConverter alloc] init]; + } + return self; } - (NSArray *_Nonnull) @@ -8,21 +16,26 @@ @implementation RNSentryBreadcrumbConverter { from:(NSDate *_Nonnull)from until:(NSDate *_Nonnull)until { NSMutableArray *outBreadcrumbs = [NSMutableArray array]; + SentryRRWebEvent *rrwebBreadcrumb; for (SentryBreadcrumb *breadcrumb in breadcrumbs) { if (breadcrumb.timestamp && [breadcrumb.timestamp compare:from] != NSOrderedAscending && [breadcrumb.timestamp compare:until] != NSOrderedDescending) { if ([breadcrumb.category isEqualToString:@"touch"]) { - SentryRRWebBreadcrumbEvent *rrwebBreadcrumb = - [[SentryRRWebBreadcrumbEvent alloc] - initWithTimestamp:breadcrumb.timestamp - category:@"ui.tap" - message:breadcrumb.data ? [breadcrumb.data valueForKey:@"target"] : nil - level:breadcrumb.level - data:breadcrumb.data]; - [outBreadcrumbs addObject:rrwebBreadcrumb]; + rrwebBreadcrumb = [[SentryRRWebBreadcrumbEvent alloc] + initWithTimestamp:breadcrumb.timestamp + category:@"ui.tap" + message:breadcrumb.data + ? [breadcrumb.data valueForKey:@"target"] + : nil + level:breadcrumb.level + data:breadcrumb.data]; } else { - // TODO delegate to the default breadcrumb converter + rrwebBreadcrumb = [self->defaultConverter convertFrom:breadcrumb]; + } + + if (rrwebBreadcrumb) { + [outBreadcrumbs addObject:rrwebBreadcrumb]; } } } From 3d76fae9fdcbdfc94a18cd9dd6a481c60f3459fa Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 11 Jun 2024 10:12:15 +0200 Subject: [PATCH 06/19] fix: module access --- RNSentry.podspec | 2 +- ios/RNSentry.mm | 5 ++--- ios/RNSentrySessionReplay.h | 6 ++++++ ios/RNSentrySessionReplay.m | 13 +++++++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 ios/RNSentrySessionReplay.h create mode 100644 ios/RNSentrySessionReplay.m diff --git a/RNSentry.podspec b/RNSentry.podspec index 4d5cd7f281..393a8f6171 100644 --- a/RNSentry.podspec +++ b/RNSentry.podspec @@ -14,7 +14,7 @@ is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] == "1" is_using_hermes = (ENV['USE_HERMES'] == nil && is_hermes_default) || ENV['USE_HERMES'] == '1' new_arch_enabled_flag = (is_new_arch_enabled ? folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED" : "") sentry_profiling_supported_flag = (is_profiling_supported ? " -DSENTRY_PROFILING_SUPPORTED=1" : "") -other_cflags = "$(inherited) -fcxx-modules -fmodules" + new_arch_enabled_flag + sentry_profiling_supported_flag +other_cflags = "$(inherited)" + new_arch_enabled_flag + sentry_profiling_supported_flag Pod::Spec.new do |s| s.name = 'RNSentry' diff --git a/ios/RNSentry.mm b/ios/RNSentry.mm index 1093187c4d..5138126648 100644 --- a/ios/RNSentry.mm +++ b/ios/RNSentry.mm @@ -36,7 +36,7 @@ #import "RNSentryEvents.h" #import "RNSentryDependencyContainer.h" -#import "RNSentryBreadcrumbConverter.h" +#import "RNSentrySessionReplay.h" #if SENTRY_HAS_UIKIT #import "RNSentryRNSScreen.h" @@ -91,8 +91,7 @@ + (BOOL)requiresMainQueueSetup { [SentrySDK startWithOptions:sentryOptions]; - RNSentryBreadcrumbConverter* breadcrumbConverter = [[RNSentryBreadcrumbConverter alloc] init]; - [PrivateSentrySDKOnly configureSessionReplayWith: breadcrumbConverter screenshotProvider: nil]; + [RNSentrySessionReplay setup]; #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; diff --git a/ios/RNSentrySessionReplay.h b/ios/RNSentrySessionReplay.h new file mode 100644 index 0000000000..6d6ebb3679 --- /dev/null +++ b/ios/RNSentrySessionReplay.h @@ -0,0 +1,6 @@ + +@interface RNSentrySessionReplay : NSObject + ++ (void)setup; + +@end diff --git a/ios/RNSentrySessionReplay.m b/ios/RNSentrySessionReplay.m new file mode 100644 index 0000000000..70daa94dd4 --- /dev/null +++ b/ios/RNSentrySessionReplay.m @@ -0,0 +1,13 @@ +#import "RNSentrySessionReplay.h" +#import "RNSentryBreadcrumbConverter.h" + +@implementation RNSentrySessionReplay {} + ++ (void)setup { + RNSentryBreadcrumbConverter *breadcrumbConverter = + [[RNSentryBreadcrumbConverter alloc] init]; + [PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter + screenshotProvider:nil]; +} + +@end From b4a1d8f7ecca42cdb329190d3704192c536a134b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 19 Jun 2024 13:34:24 +0200 Subject: [PATCH 07/19] move replay stuff to RNSentrySessionReplay --- ios/RNSentry.mm | 54 ++++++++++++++----------------------- ios/RNSentrySessionReplay.h | 4 ++- ios/RNSentrySessionReplay.m | 48 +++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/ios/RNSentry.mm b/ios/RNSentry.mm index 11300ffec9..ac571f60ea 100644 --- a/ios/RNSentry.mm +++ b/ios/RNSentry.mm @@ -37,7 +37,13 @@ #import "RNSentryEvents.h" #import "RNSentryDependencyContainer.h" + +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION +#define SENTRY_TARGET_REPLAY_SUPPORTED 1 // TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 #import "RNSentrySessionReplay.h" +#else +#define SENTRY_TARGET_REPLAY_SUPPORTED 0 +#endif #if SENTRY_HAS_UIKIT #import "RNSentryRNSScreen.h" @@ -92,7 +98,9 @@ + (BOOL)requiresMainQueueSetup { [SentrySDK startWithOptions:sentryOptions]; - [RNSentrySessionReplay setup]; +#if SENTRY_TARGET_REPLAY_SUPPORTED + [RNSentrySessionReplay postInit]; +#endif #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; @@ -138,27 +146,9 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull) [mutableOptions removeObjectForKey:@"tracesSampler"]; [mutableOptions removeObjectForKey:@"enableTracing"]; - if ([mutableOptions valueForKey:@"_experiments"] != nil) { - NSDictionary *experiments = mutableOptions[@"_experiments"]; - if (experiments[@"replaysSessionSampleRate"] != nil || experiments[@"replaysOnErrorSampleRate"] != nil) { - [mutableOptions setValue:@{ - @"sessionReplay": @{ - @"sessionSampleRate": experiments[@"replaysSessionSampleRate"] ?: [NSNull null], - @"errorSampleRate": experiments[@"replaysOnErrorSampleRate"] ?: [NSNull null], - @"redactAllImages": mutableOptions[@"mobileReplayOptions"] != nil && - mutableOptions[@"mobileReplayOptions"][@"maskAllImages"] != nil - ? mutableOptions[@"mobileReplayOptions"][@"maskAllImages"] - : [NSNull null], - @"redactAllText": mutableOptions[@"mobileReplayOptions"] != nil && - mutableOptions[@"mobileReplayOptions"][@"maskAllText"] != nil - ? mutableOptions[@"mobileReplayOptions"][@"maskAllText"] - : [NSNull null], - } - } forKey:@"experimental"]; - [self addReplayRNRedactClasses: mutableOptions[@"mobileReplayOptions"]]; - } - [mutableOptions removeObjectForKey:@"_experiments"]; - } +#if SENTRY_TARGET_REPLAY_SUPPORTED + [RNSentrySessionReplay updateOptions:mutableOptions]; +#endif SentryOptions *sentryOptions = [[SentryOptions alloc] initWithDict:mutableOptions didFailWithError:errorPointer]; if (*errorPointer != nil) { @@ -638,25 +628,21 @@ - (NSDictionary*) fetchNativeStackFramesBy: (NSArray*)instructionsAdd resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { +#if SENTRY_TARGET_REPLAY_SUPPORTED [PrivateSentrySDKOnly captureReplay]; resolve([PrivateSentrySDKOnly getReplayId]); +#else + resolve(nil); +#endif } RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId) { +#if SENTRY_TARGET_REPLAY_SUPPORTED return [PrivateSentrySDKOnly getReplayId]; -} - -- (void) addReplayRNRedactClasses: (NSDictionary *_Nullable)replayOptions -{ - NSMutableArray *_Nonnull classesToRedact = [[NSMutableArray alloc] init]; - if ([replayOptions[@"maskAllImages"] boolValue] == YES) { - [classesToRedact addObject: NSClassFromString(@"RCTImageView")]; - } - if ([replayOptions[@"maskAllText"] boolValue] == YES) { - [classesToRedact addObject: NSClassFromString(@"RCTTextView")]; - } - [PrivateSentrySDKOnly addReplayRedactClasses: classesToRedact]; +#else + return nil; +#endif } static NSString* const enabledProfilingMessage = @"Enable Hermes to use Sentry Profiling."; diff --git a/ios/RNSentrySessionReplay.h b/ios/RNSentrySessionReplay.h index 6d6ebb3679..57762b0b7b 100644 --- a/ios/RNSentrySessionReplay.h +++ b/ios/RNSentrySessionReplay.h @@ -1,6 +1,8 @@ @interface RNSentrySessionReplay : NSObject -+ (void)setup; ++ (void)updateOptions:(NSMutableDictionary *)options; + ++ (void)postInit; @end diff --git a/ios/RNSentrySessionReplay.m b/ios/RNSentrySessionReplay.m index 70daa94dd4..e6dd0a0492 100644 --- a/ios/RNSentrySessionReplay.m +++ b/ios/RNSentrySessionReplay.m @@ -1,9 +1,53 @@ #import "RNSentrySessionReplay.h" #import "RNSentryBreadcrumbConverter.h" -@implementation RNSentrySessionReplay {} +@implementation RNSentrySessionReplay { +} + ++ (void)updateOptions:(NSMutableDictionary *)options { + NSDictionary *experiments = options[@"_experiments"]; + [options removeObjectForKey:@"_experiments"]; + if (experiments == nil) { + NSLog(@"Session replay disabled via configuration"); + return; + } + + if (experiments[@"replaysSessionSampleRate"] == nil && + experiments[@"replaysOnErrorSampleRate"] == nil) { + NSLog(@"Session replay disabled via configuration"); + return; + } + + NSLog(@"Setting up session replay"); + NSDictionary *replayOptions = options[@"mobileReplayOptions"] ?: @{}; + + [options setValue:@{ + @"sessionReplay" : @{ + @"sessionSampleRate" : experiments[@"replaysSessionSampleRate"] + ?: [NSNull null], + @"errorSampleRate" : experiments[@"replaysOnErrorSampleRate"] + ?: [NSNull null], + @"redactAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null], + @"redactAllText" : replayOptions[@"maskAllText"] ?: [NSNull null], + } + } + forKey:@"experimental"]; + + [RNSentrySessionReplay addReplayRNRedactClasses:replayOptions]; +} + ++ (void)addReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions { + NSMutableArray *_Nonnull classesToRedact = [[NSMutableArray alloc] init]; + if ([replayOptions[@"maskAllImages"] boolValue] == YES) { + [classesToRedact addObject:NSClassFromString(@"RCTImageView")]; + } + if ([replayOptions[@"maskAllText"] boolValue] == YES) { + [classesToRedact addObject:NSClassFromString(@"RCTTextView")]; + } + [PrivateSentrySDKOnly addReplayRedactClasses:classesToRedact]; +} -+ (void)setup { ++ (void)postInit { RNSentryBreadcrumbConverter *breadcrumbConverter = [[RNSentryBreadcrumbConverter alloc] init]; [PrivateSentrySDKOnly configureSessionReplayWith:breadcrumbConverter From 2ebf6387efa58c7d2bc6221a684d2224f2427898 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 19 Jun 2024 13:43:52 +0200 Subject: [PATCH 08/19] move setBreadcrumbConverter to init --- .../src/main/java/io/sentry/react/RNSentryModuleImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 738fa02c92..d32b0aab17 100644 --- a/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -188,7 +188,7 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); options.setNativeSdkName(NATIVE_SDK_NAME); - options.setSdkVersion(sdkVersion); + options.setSdkVersion(sdkVersion); if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) { options.setDebug(true); @@ -256,6 +256,7 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { } if (rnOptions.hasKey("_experiments")) { options.getExperimental().setSessionReplay(getReplayOptions(rnOptions)); + options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); } options.setBeforeSend((event, hint) -> { // React native internally throws a JavascriptException @@ -294,9 +295,6 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { } }); - Sentry.getCurrentHub().getOptions().getReplayController() - .setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); - promise.resolve(true); } From 0f17b725337adf01ed97f802a5dc24bcdf59f4b2 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 19 Jun 2024 14:25:00 +0200 Subject: [PATCH 09/19] try to fix CI --- ios/RNSentrySessionReplay.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ios/RNSentrySessionReplay.m b/ios/RNSentrySessionReplay.m index e6dd0a0492..9c8eda83b0 100644 --- a/ios/RNSentrySessionReplay.m +++ b/ios/RNSentrySessionReplay.m @@ -1,6 +1,9 @@ #import "RNSentrySessionReplay.h" #import "RNSentryBreadcrumbConverter.h" +// TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION + @implementation RNSentrySessionReplay { } @@ -55,3 +58,5 @@ + (void)postInit { } @end + +#endif From ae558fea1bd6e44e5c343daaecc48db7aafe680d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 19 Jun 2024 15:25:31 +0200 Subject: [PATCH 10/19] navigation breadcrumbs --- ios/RNSentryBreadcrumbConverter.m | 58 ++++++++++++++++++++----------- ios/RNSentrySessionReplay.m | 1 - 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m index 3800ce429e..5068170419 100644 --- a/ios/RNSentryBreadcrumbConverter.m +++ b/ios/RNSentryBreadcrumbConverter.m @@ -1,12 +1,15 @@ #import "RNSentryBreadcrumbConverter.h" +// TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION + @implementation RNSentryBreadcrumbConverter { SentrySRDefaultBreadcrumbConverter *defaultConverter; } - (instancetype _Nonnull)init { if (self = [super init]) { - self->defaultConverter = [[SentrySRDefaultBreadcrumbConverter alloc] init]; + self->defaultConverter = [[SentrySRDefaultBreadcrumbConverter alloc] init]; } return self; } @@ -16,30 +19,45 @@ - (instancetype _Nonnull)init { from:(NSDate *_Nonnull)from until:(NSDate *_Nonnull)until { NSMutableArray *outBreadcrumbs = [NSMutableArray array]; - SentryRRWebEvent *rrwebBreadcrumb; + SentryRRWebEvent *rrwebBreadcrumb; for (SentryBreadcrumb *breadcrumb in breadcrumbs) { - if (breadcrumb.timestamp && - [breadcrumb.timestamp compare:from] != NSOrderedAscending && - [breadcrumb.timestamp compare:until] != NSOrderedDescending) { - if ([breadcrumb.category isEqualToString:@"touch"]) { - rrwebBreadcrumb = [[SentryRRWebBreadcrumbEvent alloc] - initWithTimestamp:breadcrumb.timestamp - category:@"ui.tap" - message:breadcrumb.data - ? [breadcrumb.data valueForKey:@"target"] - : nil - level:breadcrumb.level - data:breadcrumb.data]; - } else { - rrwebBreadcrumb = [self->defaultConverter convertFrom:breadcrumb]; - } + // - (NSComparisonResult)compare:(NSDate *)other; + // If: + // The receiver and `other` are exactly equal to each other, NSOrderedSame + // The receiver is later in time than `other`, NSOrderedDescending + // The receiver is earlier in time than `other`, NSOrderedAscending. + if (!breadcrumb.timestamp || + [breadcrumb.timestamp compare:from] == NSOrderedAscending || + [breadcrumb.timestamp compare:until] == NSOrderedDescending) { + continue; + } + + if ([breadcrumb.category isEqualToString:@"touch"]) { + rrwebBreadcrumb = [[SentryRRWebBreadcrumbEvent alloc] + initWithTimestamp:breadcrumb.timestamp + category:@"ui.tap" + message:breadcrumb.data + ? [breadcrumb.data valueForKey:@"target"] + : nil + level:breadcrumb.level + data:breadcrumb.data]; + } else if ([breadcrumb.category isEqualToString:@"navigation"]) { + rrwebBreadcrumb = [[SentryRRWebBreadcrumbEvent alloc] + initWithTimestamp:breadcrumb.timestamp + category:breadcrumb.category + message:nil + level:breadcrumb.level + data:breadcrumb.data]; + } else { + rrwebBreadcrumb = [self->defaultConverter convertFrom:breadcrumb]; + } - if (rrwebBreadcrumb) { - [outBreadcrumbs addObject:rrwebBreadcrumb]; - } + if (rrwebBreadcrumb) { + [outBreadcrumbs addObject:rrwebBreadcrumb]; } } return outBreadcrumbs; } @end +#endif diff --git a/ios/RNSentrySessionReplay.m b/ios/RNSentrySessionReplay.m index 9c8eda83b0..9672826390 100644 --- a/ios/RNSentrySessionReplay.m +++ b/ios/RNSentrySessionReplay.m @@ -58,5 +58,4 @@ + (void)postInit { } @end - #endif From 04c5c9d7b34495d4502803987be45d052bfd78de Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 19 Jun 2024 16:26:59 +0200 Subject: [PATCH 11/19] try to fix CI --- ios/RNSentryBreadcrumbConverter.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/RNSentryBreadcrumbConverter.h b/ios/RNSentryBreadcrumbConverter.h index 0334ffd9f5..33cddd4326 100644 --- a/ios/RNSentryBreadcrumbConverter.h +++ b/ios/RNSentryBreadcrumbConverter.h @@ -1,5 +1,7 @@ @import Sentry; +// TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 +#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION @class SentryRRWebEvent; @interface RNSentryBreadcrumbConverter @@ -13,3 +15,4 @@ until:(NSDate *_Nonnull)until; @end +#endif From 6211bbf4934ba0deb4b182411b8486163f5b73b5 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 20 Jun 2024 16:07:04 +0200 Subject: [PATCH 12/19] pin xcode to 15.4 for e2e and sample app builds --- .github/workflows/e2e.yml | 3 +++ .github/workflows/sample-application.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 16e54b79dc..b819760880 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -231,6 +231,9 @@ jobs: - run: sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer if: ${{ matrix.platform == 'ios' && matrix.rn-version == '0.65.3' }} + - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer + if: ${{ matrix.platform == 'ios' && matrix.rn-version == '0.73.2' }} + - uses: actions/setup-node@v4 with: node-version: 18 diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 8fa985d9b9..8b0c18d0a4 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -91,6 +91,9 @@ jobs: if: ${{ matrix.platform == 'ios' }} run: which xcbeautify || brew install xcbeautify + - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer + if: ${{ matrix.platform == 'ios' }} + - name: Install SDK Dependencies run: yarn install --frozen-lockfile From 96c7aabd26d0030d47ca42003d3c71ecca709433 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 24 Jun 2024 11:10:08 +0200 Subject: [PATCH 13/19] chore: update cocoa breadcrumb converter to match latest changes in the native SDK --- ios/RNSentryBreadcrumbConverter.m | 53 ++++++++++++++++++------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m index 5068170419..85f699751a 100644 --- a/ios/RNSentryBreadcrumbConverter.m +++ b/ios/RNSentryBreadcrumbConverter.m @@ -1,5 +1,7 @@ #import "RNSentryBreadcrumbConverter.h" +@import Sentry; + // TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 #if SENTRY_HAS_UIKIT && !TARGET_OS_VISION @@ -9,7 +11,8 @@ @implementation RNSentryBreadcrumbConverter { - (instancetype _Nonnull)init { if (self = [super init]) { - self->defaultConverter = [[SentrySRDefaultBreadcrumbConverter alloc] init]; + self->defaultConverter = + [SentrySessionReplayIntegration createDefaultBreadcrumbConverter]; } return self; } @@ -19,7 +22,6 @@ - (instancetype _Nonnull)init { from:(NSDate *_Nonnull)from until:(NSDate *_Nonnull)until { NSMutableArray *outBreadcrumbs = [NSMutableArray array]; - SentryRRWebEvent *rrwebBreadcrumb; for (SentryBreadcrumb *breadcrumb in breadcrumbs) { // - (NSComparisonResult)compare:(NSDate *)other; // If: @@ -32,26 +34,7 @@ - (instancetype _Nonnull)init { continue; } - if ([breadcrumb.category isEqualToString:@"touch"]) { - rrwebBreadcrumb = [[SentryRRWebBreadcrumbEvent alloc] - initWithTimestamp:breadcrumb.timestamp - category:@"ui.tap" - message:breadcrumb.data - ? [breadcrumb.data valueForKey:@"target"] - : nil - level:breadcrumb.level - data:breadcrumb.data]; - } else if ([breadcrumb.category isEqualToString:@"navigation"]) { - rrwebBreadcrumb = [[SentryRRWebBreadcrumbEvent alloc] - initWithTimestamp:breadcrumb.timestamp - category:breadcrumb.category - message:nil - level:breadcrumb.level - data:breadcrumb.data]; - } else { - rrwebBreadcrumb = [self->defaultConverter convertFrom:breadcrumb]; - } - + SentryRRWebEvent *rrwebBreadcrumb = [self convertFrom:breadcrumb]; if (rrwebBreadcrumb) { [outBreadcrumbs addObject:rrwebBreadcrumb]; } @@ -59,5 +42,31 @@ - (instancetype _Nonnull)init { return outBreadcrumbs; } +- (id _Nullable)convertFrom: + (SentryBreadcrumb *_Nonnull)breadcrumb { + assert(breadcrumb.timestamp != nil); + + if ([breadcrumb.category isEqualToString:@"touch"]) { + return [SentrySessionReplayIntegration + createBreadcrumbwithTimestamp:breadcrumb.timestamp + category:@"ui.tap" + message:breadcrumb.data + ? [breadcrumb.data + valueForKey:@"target"] + : nil + level:breadcrumb.level + data:breadcrumb.data]; + } else if ([breadcrumb.category isEqualToString:@"navigation"]) { + return [SentrySessionReplayIntegration + createBreadcrumbwithTimestamp:breadcrumb.timestamp ?: 0 + category:breadcrumb.category + message:nil + level:breadcrumb.level + data:breadcrumb.data]; + } else { + return [self->defaultConverter convertFrom:breadcrumb]; + } +} + @end #endif From 1a13827777c3cf54eedf45e62db24ecd6bba6cc3 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 24 Jun 2024 11:28:24 +0200 Subject: [PATCH 14/19] more updates based on upstream changes --- ios/RNSentryBreadcrumbConverter.h | 5 ----- ios/RNSentryBreadcrumbConverter.m | 31 +++---------------------------- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/ios/RNSentryBreadcrumbConverter.h b/ios/RNSentryBreadcrumbConverter.h index 33cddd4326..41c5fe1d82 100644 --- a/ios/RNSentryBreadcrumbConverter.h +++ b/ios/RNSentryBreadcrumbConverter.h @@ -9,10 +9,5 @@ - (instancetype _Nonnull)init; -- (NSArray *_Nonnull) - convertWithBreadcrumbs:(NSArray *_Nonnull)breadcrumbs - from:(NSDate *_Nonnull)from - until:(NSDate *_Nonnull)until; - @end #endif diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m index 85f699751a..c7e0cac571 100644 --- a/ios/RNSentryBreadcrumbConverter.m +++ b/ios/RNSentryBreadcrumbConverter.m @@ -17,35 +17,10 @@ - (instancetype _Nonnull)init { return self; } -- (NSArray *_Nonnull) - convertWithBreadcrumbs:(NSArray *_Nonnull)breadcrumbs - from:(NSDate *_Nonnull)from - until:(NSDate *_Nonnull)until { - NSMutableArray *outBreadcrumbs = [NSMutableArray array]; - for (SentryBreadcrumb *breadcrumb in breadcrumbs) { - // - (NSComparisonResult)compare:(NSDate *)other; - // If: - // The receiver and `other` are exactly equal to each other, NSOrderedSame - // The receiver is later in time than `other`, NSOrderedDescending - // The receiver is earlier in time than `other`, NSOrderedAscending. - if (!breadcrumb.timestamp || - [breadcrumb.timestamp compare:from] == NSOrderedAscending || - [breadcrumb.timestamp compare:until] == NSOrderedDescending) { - continue; - } - - SentryRRWebEvent *rrwebBreadcrumb = [self convertFrom:breadcrumb]; - if (rrwebBreadcrumb) { - [outBreadcrumbs addObject:rrwebBreadcrumb]; - } - } - return outBreadcrumbs; -} - - (id _Nullable)convertFrom: (SentryBreadcrumb *_Nonnull)breadcrumb { - assert(breadcrumb.timestamp != nil); - + assert(breadcrumb.timestamp != nil); + if ([breadcrumb.category isEqualToString:@"touch"]) { return [SentrySessionReplayIntegration createBreadcrumbwithTimestamp:breadcrumb.timestamp @@ -58,7 +33,7 @@ - (instancetype _Nonnull)init { data:breadcrumb.data]; } else if ([breadcrumb.category isEqualToString:@"navigation"]) { return [SentrySessionReplayIntegration - createBreadcrumbwithTimestamp:breadcrumb.timestamp ?: 0 + createBreadcrumbwithTimestamp:breadcrumb.timestamp ?: 0 category:breadcrumb.category message:nil level:breadcrumb.level From 5564c88a5be00e560826cc84a878723b611262ca Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 25 Jun 2024 10:57:42 +0200 Subject: [PATCH 15/19] Revert "pin xcode to 15.4 for e2e and sample app builds" This reverts commit 6211bbf4934ba0deb4b182411b8486163f5b73b5. --- .github/workflows/e2e.yml | 3 --- .github/workflows/sample-application.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b819760880..16e54b79dc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -231,9 +231,6 @@ jobs: - run: sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer if: ${{ matrix.platform == 'ios' && matrix.rn-version == '0.65.3' }} - - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - if: ${{ matrix.platform == 'ios' && matrix.rn-version == '0.73.2' }} - - uses: actions/setup-node@v4 with: node-version: 18 diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 8b0c18d0a4..8fa985d9b9 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -91,9 +91,6 @@ jobs: if: ${{ matrix.platform == 'ios' }} run: which xcbeautify || brew install xcbeautify - - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - if: ${{ matrix.platform == 'ios' }} - - name: Install SDK Dependencies run: yarn install --frozen-lockfile From 8d4f9372f682b4e6fe38fb5c206d7b2d49cf8892 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 25 Jun 2024 13:21:39 +0200 Subject: [PATCH 16/19] review changes --- CHANGELOG.md | 4 ++++ ios/RNSentry.mm | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ce51f32a..acb6b7ec16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 5.23.0-alpha.1 +### Features + +- Add replay breadcrumbs for touch & navigation events ([#3846](https://github.com/getsentry/sentry-react-native/pull/3846)) + ### Fixes - Pass `replaysSessionSampleRate` option to Android ([#3714](https://github.com/getsentry/sentry-react-native/pull/3714)) diff --git a/ios/RNSentry.mm b/ios/RNSentry.mm index ac571f60ea..951d4dce26 100644 --- a/ios/RNSentry.mm +++ b/ios/RNSentry.mm @@ -98,10 +98,6 @@ + (BOOL)requiresMainQueueSetup { [SentrySDK startWithOptions:sentryOptions]; -#if SENTRY_TARGET_REPLAY_SUPPORTED - [RNSentrySessionReplay postInit]; -#endif - #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST BOOL appIsActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; #else @@ -117,6 +113,10 @@ + (BOOL)requiresMainQueueSetup { sentHybridSdkDidBecomeActive = true; } +#if SENTRY_TARGET_REPLAY_SUPPORTED + [RNSentrySessionReplay postInit]; +#endif + resolve(@YES); } From 272b4bbef80f9b12797cfe743aaba11718f13507 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 25 Jun 2024 13:23:37 +0200 Subject: [PATCH 17/19] fix changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acb6b7ec16..1b236c1edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## 5.23.0-alpha.1 +## Unreleased ### Features - Add replay breadcrumbs for touch & navigation events ([#3846](https://github.com/getsentry/sentry-react-native/pull/3846)) +## 5.23.0-alpha.1 + ### Fixes - Pass `replaysSessionSampleRate` option to Android ([#3714](https://github.com/getsentry/sentry-react-native/pull/3714)) From 30ed290c0dd182880f792d8ed25b85c43b090db3 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 26 Jun 2024 08:39:27 +0200 Subject: [PATCH 18/19] cleanup TODOs --- ios/RNSentry.mm | 5 +---- ios/RNSentryBreadcrumbConverter.h | 3 +-- ios/RNSentryBreadcrumbConverter.m | 3 +-- ios/RNSentrySessionReplay.m | 3 +-- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/ios/RNSentry.mm b/ios/RNSentry.mm index 951d4dce26..9d391ee2b8 100644 --- a/ios/RNSentry.mm +++ b/ios/RNSentry.mm @@ -38,11 +38,8 @@ #import "RNSentryEvents.h" #import "RNSentryDependencyContainer.h" -#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION -#define SENTRY_TARGET_REPLAY_SUPPORTED 1 // TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 +#if SENTRY_TARGET_REPLAY_SUPPORTED #import "RNSentrySessionReplay.h" -#else -#define SENTRY_TARGET_REPLAY_SUPPORTED 0 #endif #if SENTRY_HAS_UIKIT diff --git a/ios/RNSentryBreadcrumbConverter.h b/ios/RNSentryBreadcrumbConverter.h index 41c5fe1d82..e269265b4d 100644 --- a/ios/RNSentryBreadcrumbConverter.h +++ b/ios/RNSentryBreadcrumbConverter.h @@ -1,7 +1,6 @@ @import Sentry; -// TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 -#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION +#if SENTRY_TARGET_REPLAY_SUPPORTED @class SentryRRWebEvent; @interface RNSentryBreadcrumbConverter diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m index c7e0cac571..a09ecdd651 100644 --- a/ios/RNSentryBreadcrumbConverter.m +++ b/ios/RNSentryBreadcrumbConverter.m @@ -2,8 +2,7 @@ @import Sentry; -// TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 -#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION +#if SENTRY_TARGET_REPLAY_SUPPORTED @implementation RNSentryBreadcrumbConverter { SentrySRDefaultBreadcrumbConverter *defaultConverter; diff --git a/ios/RNSentrySessionReplay.m b/ios/RNSentrySessionReplay.m index 9672826390..fe763ad13e 100644 --- a/ios/RNSentrySessionReplay.m +++ b/ios/RNSentrySessionReplay.m @@ -1,8 +1,7 @@ #import "RNSentrySessionReplay.h" #import "RNSentryBreadcrumbConverter.h" -// TODO update after https://github.com/getsentry/sentry-cocoa/pull/4089 -#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION +#if SENTRY_TARGET_REPLAY_SUPPORTED @implementation RNSentrySessionReplay { } From f71fb669ebb31bf17e07851b4daa2dbb949df808 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 26 Jun 2024 12:29:34 +0200 Subject: [PATCH 19/19] touch event path for replay breadcrumbs --- .../RNSentryReplayBreadcrumbConverter.java | 29 ++++++++++++++-- ios/RNSentryBreadcrumbConverter.m | 34 ++++++++++++++++--- src/js/touchevents.tsx | 23 +++++++++---- test/touchevents.test.tsx | 5 --- 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java b/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java index ed2e9ed861..d2989343a7 100644 --- a/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java +++ b/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java @@ -4,6 +4,8 @@ import io.sentry.android.replay.DefaultReplayBreadcrumbConverter; import io.sentry.rrweb.RRWebEvent; import io.sentry.rrweb.RRWebBreadcrumbEvent; +import java.util.ArrayList; +import java.util.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,9 +20,30 @@ public RNSentryReplayBreadcrumbConverter() { if (breadcrumb.getCategory().equals("touch")) { rrwebBreadcrumb.setCategory("ui.tap"); - Object target = breadcrumb.getData("target"); - if (target != null) { - rrwebBreadcrumb.setMessage(target.toString()); + ArrayList path = (ArrayList) breadcrumb.getData("path"); + if (path != null) { + StringBuilder message = new StringBuilder(); + for (int i = Math.min(3, path.size()); i >= 0; i--) { + HashMap item = (HashMap) path.get(i); + message.append(item.get("name")); + if (item.containsKey("element") || item.containsKey("file")) { + message.append('('); + if (item.containsKey("element")) { + message.append(item.get("element")); + if (item.containsKey("file")) { + message.append(", "); + message.append(item.get("file")); + } + } else if (item.containsKey("file")) { + message.append(item.get("file")); + } + message.append(')'); + } + if (i > 0) { + message.append(" > "); + } + } + rrwebBreadcrumb.setMessage(message.toString()); } rrwebBreadcrumb.setData(breadcrumb.getData()); } diff --git a/ios/RNSentryBreadcrumbConverter.m b/ios/RNSentryBreadcrumbConverter.m index a09ecdd651..4e1a1ee865 100644 --- a/ios/RNSentryBreadcrumbConverter.m +++ b/ios/RNSentryBreadcrumbConverter.m @@ -21,18 +21,42 @@ - (instancetype _Nonnull)init { assert(breadcrumb.timestamp != nil); if ([breadcrumb.category isEqualToString:@"touch"]) { + NSMutableString *message; + if (breadcrumb.data) { + NSMutableArray *path = [breadcrumb.data valueForKey:@"path"]; + if (path != nil) { + message = [[NSMutableString alloc] init]; + for (NSInteger i = MIN(3, [path count] - 1); i >= 0; i--) { + NSDictionary *item = [path objectAtIndex:i]; + [message appendString:[item objectForKey:@"name"]]; + if ([item objectForKey:@"element"] || [item objectForKey:@"file"]) { + [message appendString:@"("]; + if ([item objectForKey:@"element"]) { + [message appendString:[item objectForKey:@"element"]]; + if ([item objectForKey:@"file"]) { + [message appendString:@", "]; + [message appendString:[item objectForKey:@"file"]]; + } + } else if ([item objectForKey:@"file"]) { + [message appendString:[item objectForKey:@"file"]]; + } + [message appendString:@")"]; + } + if (i > 0) { + [message appendString:@" > "]; + } + } + } + } return [SentrySessionReplayIntegration createBreadcrumbwithTimestamp:breadcrumb.timestamp category:@"ui.tap" - message:breadcrumb.data - ? [breadcrumb.data - valueForKey:@"target"] - : nil + message:message level:breadcrumb.level data:breadcrumb.data]; } else if ([breadcrumb.category isEqualToString:@"navigation"]) { return [SentrySessionReplayIntegration - createBreadcrumbwithTimestamp:breadcrumb.timestamp ?: 0 + createBreadcrumbwithTimestamp:breadcrumb.timestamp category:breadcrumb.category message:nil level:breadcrumb.level diff --git a/src/js/touchevents.tsx b/src/js/touchevents.tsx index 1db4107b96..785dc2977c 100644 --- a/src/js/touchevents.tsx +++ b/src/js/touchevents.tsx @@ -130,10 +130,7 @@ class TouchEventBoundary extends React.Component { const crumb = { category: this.props.breadcrumbCategory, - data: { - path: touchPath, - target: detail, - }, + data: { path: touchPath }, level: level, message: `Touch event within element: ${detail}`, type: this.props.breadcrumbType, @@ -196,13 +193,25 @@ class TouchEventBoundary extends React.Component { const info: TouchedComponentInfo = {}; // provided by @sentry/babel-plugin-component-annotate - if (typeof props[SENTRY_COMPONENT_PROP_KEY] === 'string' && props[SENTRY_COMPONENT_PROP_KEY].length > 0 && props[SENTRY_COMPONENT_PROP_KEY] !== 'unknown') { + if ( + typeof props[SENTRY_COMPONENT_PROP_KEY] === 'string' && + props[SENTRY_COMPONENT_PROP_KEY].length > 0 && + props[SENTRY_COMPONENT_PROP_KEY] !== 'unknown' + ) { info.name = props[SENTRY_COMPONENT_PROP_KEY]; } - if (typeof props[SENTRY_ELEMENT_PROP_KEY] === 'string' && props[SENTRY_ELEMENT_PROP_KEY].length > 0 && props[SENTRY_ELEMENT_PROP_KEY] !== 'unknown') { + if ( + typeof props[SENTRY_ELEMENT_PROP_KEY] === 'string' && + props[SENTRY_ELEMENT_PROP_KEY].length > 0 && + props[SENTRY_ELEMENT_PROP_KEY] !== 'unknown' + ) { info.element = props[SENTRY_ELEMENT_PROP_KEY]; } - if (typeof props[SENTRY_FILE_PROP_KEY] === 'string' && props[SENTRY_FILE_PROP_KEY].length > 0 && props[SENTRY_FILE_PROP_KEY] !== 'unknown') { + if ( + typeof props[SENTRY_FILE_PROP_KEY] === 'string' && + props[SENTRY_FILE_PROP_KEY].length > 0 && + props[SENTRY_FILE_PROP_KEY] !== 'unknown' + ) { info.file = props[SENTRY_FILE_PROP_KEY]; } diff --git a/test/touchevents.test.tsx b/test/touchevents.test.tsx index 6eba8e2979..4d5dd1f3cd 100644 --- a/test/touchevents.test.tsx +++ b/test/touchevents.test.tsx @@ -102,7 +102,6 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { path: [{ name: 'View' }, { name: 'Connect(View)' }, { label: 'LABEL!' }], - target: "LABEL!", }, level: 'info' as SeverityLevel, message: 'Touch event within element: LABEL!', @@ -162,7 +161,6 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { path: [{ name: 'Styled(View)' }], - target: "Styled(View)", }, level: 'info' as SeverityLevel, message: 'Touch event within element: Styled(View)', @@ -213,7 +211,6 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { path: [{ label: 'Connect(View)' }, { name: 'Styled(View)' }], - target: "Connect(View)", }, level: 'info' as SeverityLevel, message: 'Touch event within element: Connect(View)', @@ -277,7 +274,6 @@ describe('TouchEventBoundary._onTouchStart', () => { { name: 'Styled(View)' }, { element: 'View', file: 'happyview.js', name: 'Happy' }, ], - target: "Screen (screen.tsx)", }, level: 'info' as SeverityLevel, message: 'Touch event within element: Screen (screen.tsx)', @@ -309,7 +305,6 @@ describe('TouchEventBoundary._onTouchStart', () => { category: defaultProps.breadcrumbCategory, data: { path: [{ name: 'Text' }], - target: "Text", }, level: 'info' as SeverityLevel, message: 'Touch event within element: Text',