Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

## Unreleased

### Changes

- Enables Spotlight in Android and iOS SDKs ([#4211](https://github.com/getsentry/sentry-react-native/pull/4211))

### Dependencies

- Bump JavaScript SDK from v8.34.0 to v8.35.0 ([#4196](https://github.com/getsentry/sentry-react-native/pull/4196))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry.react
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableMap
Expand Down Expand Up @@ -93,4 +94,11 @@ class RNSentryModuleImplTest {
val capturedMap = writableMapCaptor.value
assertEquals(false, capturedMap.getBoolean("has_fetched"))
}

@Test
fun `when the spotlight option is enabled, the spotlight SentryAndroidOption is set to true`() {
val options = JavaOnlyMap.of("spotlight", true)
val actualOptions = module.getSentryAndroidOptions(options, logger)
assert(actualOptions.isEnableSpotlight)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@ - (void)testCreateOptionsWithDictionaryAutoPerformanceTracingDisabled
XCTAssertEqual(actualOptions.enableAutoPerformanceTracing, false, @"Did not disable Auto Performance Tracing");
}

- (void)testCreateOptionsWithDictionarySpotlightEnabled
{
RNSentry * rnSentry = [[RNSentry alloc] init];
NSError* error = nil;

NSDictionary *_Nonnull mockedReactNativeDictionary = @{
@"dsn": @"https://[email protected]/123456",
@"spotlight": @YES,
};
SentryOptions* actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary error:&error];
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
XCTAssertNil(error, @"Should not pass no error");
XCTAssertTrue(actualOptions.enableSpotlight , @"Did not enable spotlight");
}

- (void)testCreateOptionsWithDictionarySpotlightDisabled
{
RNSentry * rnSentry = [[RNSentry alloc] init];
NSError* error = nil;

NSDictionary *_Nonnull mockedReactNativeDictionary = @{
@"dsn": @"https://[email protected]/123456",
@"spotlight": @NO,
};
SentryOptions* actualOptions = [rnSentry createOptionsWithDictionary:mockedReactNativeDictionary error:&error];
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
XCTAssertNil(error, @"Should not pass no error");
XCTAssertFalse(actualOptions.enableSpotlight, @"Did not disable spotlight");
}

- (void)testPassesErrorOnWrongDsn
{
RNSentry * rnSentry = [[RNSentry alloc] init];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,136 +173,139 @@ public void initNativeReactNavigationNewFrameTracking(Promise promise) {

public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
SentryAndroid.init(
this.getReactApplicationContext(),
options -> {
@Nullable SdkVersion sdkVersion = options.getSdkVersion();
if (sdkVersion == null) {
sdkVersion = new SdkVersion(ANDROID_SDK_NAME, BuildConfig.VERSION_NAME);
} else {
sdkVersion.setName(ANDROID_SDK_NAME);
}

options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion());
options.setNativeSdkName(NATIVE_SDK_NAME);
options.setSdkVersion(sdkVersion);
this.getReactApplicationContext(), options -> getSentryAndroidOptions(rnOptions, logger));

if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) {
options.setDebug(true);
}
if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) {
String dsn = rnOptions.getString("dsn");
logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn));
options.setDsn(dsn);
} else {
// SentryAndroid needs an empty string fallback for the dsn.
options.setDsn("");
}
if (rnOptions.hasKey("sampleRate")) {
options.setSampleRate(rnOptions.getDouble("sampleRate"));
}
if (rnOptions.hasKey("sendClientReports")) {
options.setSendClientReports(rnOptions.getBoolean("sendClientReports"));
}
if (rnOptions.hasKey("maxBreadcrumbs")) {
options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs"));
}
if (rnOptions.hasKey("maxCacheItems")) {
options.setMaxCacheItems(rnOptions.getInt("maxCacheItems"));
}
if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) {
options.setEnvironment(rnOptions.getString("environment"));
}
if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) {
options.setRelease(rnOptions.getString("release"));
}
if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) {
options.setDist(rnOptions.getString("dist"));
}
if (rnOptions.hasKey("enableAutoSessionTracking")) {
options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking"));
}
if (rnOptions.hasKey("sessionTrackingIntervalMillis")) {
options.setSessionTrackingIntervalMillis(
rnOptions.getInt("sessionTrackingIntervalMillis"));
}
if (rnOptions.hasKey("shutdownTimeout")) {
options.setShutdownTimeoutMillis(rnOptions.getInt("shutdownTimeout"));
}
if (rnOptions.hasKey("enableNdkScopeSync")) {
options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync"));
}
if (rnOptions.hasKey("attachStacktrace")) {
options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace"));
}
if (rnOptions.hasKey("attachThreads")) {
// JS use top level stacktrace and android attaches Threads which hides them so
// by default we hide.
options.setAttachThreads(rnOptions.getBoolean("attachThreads"));
}
if (rnOptions.hasKey("attachScreenshot")) {
options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot"));
}
if (rnOptions.hasKey("attachViewHierarchy")) {
options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy"));
}
if (rnOptions.hasKey("sendDefaultPii")) {
options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii"));
}
if (rnOptions.hasKey("maxQueueSize")) {
options.setMaxQueueSize(rnOptions.getInt("maxQueueSize"));
}
if (rnOptions.hasKey("enableNdk")) {
options.setEnableNdk(rnOptions.getBoolean("enableNdk"));
}
if (rnOptions.hasKey("_experiments")) {
options.getExperimental().setSessionReplay(getReplayOptions(rnOptions));
options
.getReplayController()
.setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter());
}
options.setBeforeSend(
(event, hint) -> {
// React native internally throws a JavascriptException
// Since we catch it before that, we don't want to send this one
// because we would send it twice
try {
SentryException ex = event.getExceptions().get(0);
if (null != ex && ex.getType().contains("JavascriptException")) {
return null;
}
} catch (Throwable ignored) { // NOPMD - We don't want to crash in any case
// We do nothing
}
promise.resolve(true);
}

setEventOriginTag(event);
addPackages(event, options.getSdkVersion());
protected SentryAndroidOptions getSentryAndroidOptions(
@NotNull ReadableMap rnOptions, ILogger logger) {
final SentryAndroidOptions options = new SentryAndroidOptions();
@Nullable SdkVersion sdkVersion = options.getSdkVersion();
if (sdkVersion == null) {
sdkVersion = new SdkVersion(ANDROID_SDK_NAME, BuildConfig.VERSION_NAME);
} else {
sdkVersion.setName(ANDROID_SDK_NAME);
}

return event;
});
options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion());
options.setNativeSdkName(NATIVE_SDK_NAME);
options.setSdkVersion(sdkVersion);

if (rnOptions.hasKey("enableNativeCrashHandling")
&& !rnOptions.getBoolean("enableNativeCrashHandling")) {
final List<Integration> integrations = options.getIntegrations();
for (final Integration integration : integrations) {
if (integration instanceof UncaughtExceptionHandlerIntegration
|| integration instanceof AnrIntegration
|| integration instanceof NdkIntegration) {
integrations.remove(integration);
}
if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) {
options.setDebug(true);
}
if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) {
String dsn = rnOptions.getString("dsn");
logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn));
options.setDsn(dsn);
} else {
// SentryAndroid needs an empty string fallback for the dsn.
options.setDsn("");
}
if (rnOptions.hasKey("sampleRate")) {
options.setSampleRate(rnOptions.getDouble("sampleRate"));
}
if (rnOptions.hasKey("sendClientReports")) {
options.setSendClientReports(rnOptions.getBoolean("sendClientReports"));
}
if (rnOptions.hasKey("maxBreadcrumbs")) {
options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs"));
}
if (rnOptions.hasKey("maxCacheItems")) {
options.setMaxCacheItems(rnOptions.getInt("maxCacheItems"));
}
if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) {
options.setEnvironment(rnOptions.getString("environment"));
}
if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) {
options.setRelease(rnOptions.getString("release"));
}
if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) {
options.setDist(rnOptions.getString("dist"));
}
if (rnOptions.hasKey("enableAutoSessionTracking")) {
options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking"));
}
if (rnOptions.hasKey("sessionTrackingIntervalMillis")) {
options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis"));
}
if (rnOptions.hasKey("shutdownTimeout")) {
options.setShutdownTimeoutMillis(rnOptions.getInt("shutdownTimeout"));
}
if (rnOptions.hasKey("enableNdkScopeSync")) {
options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync"));
}
if (rnOptions.hasKey("attachStacktrace")) {
options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace"));
}
if (rnOptions.hasKey("attachThreads")) {
// JS use top level stacktrace and android attaches Threads which hides them so
// by default we hide.
options.setAttachThreads(rnOptions.getBoolean("attachThreads"));
}
if (rnOptions.hasKey("attachScreenshot")) {
options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot"));
}
if (rnOptions.hasKey("attachViewHierarchy")) {
options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy"));
}
if (rnOptions.hasKey("sendDefaultPii")) {
options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii"));
}
if (rnOptions.hasKey("maxQueueSize")) {
options.setMaxQueueSize(rnOptions.getInt("maxQueueSize"));
}
if (rnOptions.hasKey("enableNdk")) {
options.setEnableNdk(rnOptions.getBoolean("enableNdk"));
}
if (rnOptions.hasKey("spotlight")) {
options.setEnableSpotlight(rnOptions.getBoolean("spotlight"));
}
if (rnOptions.hasKey("_experiments")) {
options.getExperimental().setSessionReplay(getReplayOptions(rnOptions));
options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter());
}
options.setBeforeSend(
(event, hint) -> {
// React native internally throws a JavascriptException
// Since we catch it before that, we don't want to send this one
// because we would send it twice
try {
SentryException ex = event.getExceptions().get(0);
if (null != ex && ex.getType().contains("JavascriptException")) {
return null;
}
} catch (Throwable ignored) { // NOPMD - We don't want to crash in any case
// We do nothing
}
logger.log(
SentryLevel.INFO,
String.format("Native Integrations '%s'", options.getIntegrations()));

final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder.getInstance();
final Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
currentActivityHolder.setActivity(currentActivity);
}

setEventOriginTag(event);
addPackages(event, options.getSdkVersion());

return event;
});

promise.resolve(true);
if (rnOptions.hasKey("enableNativeCrashHandling")
&& !rnOptions.getBoolean("enableNativeCrashHandling")) {
final List<Integration> integrations = options.getIntegrations();
for (final Integration integration : integrations) {
if (integration instanceof UncaughtExceptionHandlerIntegration
|| integration instanceof AnrIntegration
|| integration instanceof NdkIntegration) {
integrations.remove(integration);
}
}
}
logger.log(
SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations()));

final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder.getInstance();
final Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
currentActivityHolder.setActivity(currentActivity);
}
return options;
}

private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
Expand Down
5 changes: 5 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
sentryOptions.integrations = integrations;
}
}

// Set spotlight option
if ([mutableOptions valueForKey:@"spotlight"] != nil) {
sentryOptions.enableSpotlight = [mutableOptions[@"spotlight"] boolValue];
}

// Enable the App start and Frames tracking measurements
if ([mutableOptions valueForKey:@"enableAutoPerformanceTracing"] != nil) {
Expand Down
Loading