Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-zimerman committed Oct 17, 2024
1 parent fe13591 commit 3b300d9
Show file tree
Hide file tree
Showing 18 changed files with 649 additions and 84 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
<!-- prettier-ignore-end -->

## Unreleased

### Fixes

- Enhanced accuracy of time-to-display spans. ([#4042](https://github.com/getsentry/sentry-react-native/pull/4042))

## 6.0.0

This is a new major version 6.0.0 of the Sentry React Native SDK.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.sentry.react;

import com.facebook.react.bridge.Promise;

import android.os.Handler;
import android.os.Looper;
import android.view.Choreographer;

import org.jetbrains.annotations.NotNull;
import io.sentry.SentryDate;
import io.sentry.SentryDateProvider;
import io.sentry.android.core.SentryAndroidDateProvider;

public class RNSentryTimeToDisplay {
public static void GetTimeToDisplay(Promise promise, SentryDateProvider dateProvider) {
Looper mainLooper = Looper.getMainLooper();

if (mainLooper == null) {
promise.reject("GetTimeToDisplay is not able to measure the time to display: Main looper not available.");
return;
}

// Ensure the code runs on the main thread
new Handler(mainLooper)
.post(() -> {
try {
Choreographer choreographer = Choreographer.getInstance();

// Invoke the callback after the frame is rendered
choreographer.postFrameCallback(frameTimeNanos -> {
final SentryDate endDate = dateProvider.now();
promise.resolve(endDate.nanoTimestamp() / 1e9);
});
} catch (Exception exception) {
promise.reject("Failed to receive the instance of Choreographer", exception);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,13 @@ public class RNSentryModuleImpl {
/** Max trace file size in bytes. */
private long maxTraceFileSize = 5 * 1024 * 1024;

private final @NotNull SentryDateProvider dateProvider;

public RNSentryModuleImpl(ReactApplicationContext reactApplicationContext) {
packageInfo = getPackageInfo(reactApplicationContext);
this.reactApplicationContext = reactApplicationContext;
this.emitNewFrameEvent = createEmitNewFrameEvent();
this.dateProvider = new SentryAndroidDateProvider();
}

private ReactApplicationContext getReactApplicationContext() {
Expand All @@ -141,8 +144,6 @@ private ReactApplicationContext getReactApplicationContext() {
}

private @NotNull Runnable createEmitNewFrameEvent() {
final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider();

return () -> {
final SentryDate endDate = dateProvider.now();
WritableMap event = Arguments.createMap();
Expand Down Expand Up @@ -172,6 +173,7 @@ public void initNativeReactNavigationNewFrameTracking(Promise promise) {
}

public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
<<<<<<< HEAD
SentryAndroid.init(
this.getReactApplicationContext(),
options -> {
Expand Down Expand Up @@ -272,36 +274,142 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
}
} catch (Throwable ignored) { // NOPMD - We don't want to crash in any case
// We do nothing
=======
try {
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);
>>>>>>> 4ef0e98c (port navigation tracker #4042 to V6 / Todo: Fix tests)
}

setEventOriginTag(event);
addPackages(event, options.getSdkVersion());
options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion());
options.setNativeSdkName(NATIVE_SDK_NAME);
options.setSdkVersion(sdkVersion);

return event;
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) {
// We do nothing
}

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

return event;
});

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);
}
});

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);
}
});

}
catch (Exception ex){
promise.reject(ex);
}
promise.resolve(true);
}

Expand Down Expand Up @@ -745,6 +853,10 @@ public void disableNativeFramesTracking() {
}
}

public void getNewScreenTimeToDisplay(Promise promise) {
RNSentryTimeToDisplay.GetTimeToDisplay(promise, dateProvider);
}

private String getProfilingTracesDirPath() {
if (cacheDirPath == null) {
cacheDirPath =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,9 @@ public String getCurrentReplayId() {
public void crashedLastRun(Promise promise) {
this.impl.crashedLastRun(promise);
}

@Override
public void getNewScreenTimeToDisplay(Promise promise) {
this.impl.getNewScreenTimeToDisplay(promise);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,9 @@ public String getCurrentReplayId() {
public void crashedLastRun(Promise promise) {
this.impl.crashedLastRun(promise);
}

@ReactMethod()
public void getNewScreenTimeToDisplay(Promise promise) {
this.impl.getNewScreenTimeToDisplay(promise);
}
}
9 changes: 9 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import <dlfcn.h>
#import "RNSentry.h"
#import "RNSentryTimeToDisplay.h"

#if __has_include(<React/RCTConvert.h>)
#import <React/RCTConvert.h>
Expand Down Expand Up @@ -62,6 +63,7 @@ + (void)storeEnvelope:(SentryEnvelope *)envelope;
@implementation RNSentry {
bool sentHybridSdkDidBecomeActive;
bool hasListeners;
RNSentryTimeToDisplay *_timeToDisplay;
}

- (dispatch_queue_t)methodQueue
Expand Down Expand Up @@ -139,6 +141,8 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
[mutableOptions removeObjectForKey:@"tracesSampler"];
[mutableOptions removeObjectForKey:@"enableTracing"];

_timeToDisplay = [[RNSentryTimeToDisplay alloc] init];

#if SENTRY_TARGET_REPLAY_SUPPORTED
[RNSentryReplay updateOptions:mutableOptions];
#endif
Expand Down Expand Up @@ -786,4 +790,9 @@ - (NSDictionary*) fetchNativeStackFramesBy: (NSArray<NSNumber*>*)instructionsAdd
}
#endif

RCT_EXPORT_METHOD(getNewScreenTimeToDisplay:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
[_timeToDisplay getTimeToDisplay:resolve];
}

@end
7 changes: 7 additions & 0 deletions packages/core/ios/RNSentryTimeToDisplay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <React/RCTBridgeModule.h>

@interface RNSentryTimeToDisplay : NSObject

- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback;

@end
43 changes: 43 additions & 0 deletions packages/core/ios/RNSentryTimeToDisplay.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#import "RNSentryTimeToDisplay.h"
#import <QuartzCore/QuartzCore.h>
#import <React/RCTLog.h>

@implementation RNSentryTimeToDisplay
{
CADisplayLink *displayLink;
RCTResponseSenderBlock resolveBlock;
}

// Rename requestAnimationFrame to getTimeToDisplay
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback
{
// Store the resolve block to use in the callback.
resolveBlock = callback;

#if TARGET_OS_IOS
// Create and add a display link to get the callback after the screen is rendered.
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
#else
resolveBlock(@[]); // Return nothing if not iOS.
#endif
}

#if TARGET_OS_IOS
- (void)handleDisplayLink:(CADisplayLink *)link {
// Get the current time
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970] * 1000.0; // Convert to milliseconds

// Ensure the callback is valid and pass the current time back
if (resolveBlock) {
resolveBlock(@[@(currentTime)]); // Call the callback with the current time
resolveBlock = nil; // Clear the block after it's called
}

// Invalidate the display link to stop future callbacks
[displayLink invalidate];
displayLink = nil;
}
#endif

@end
1 change: 1 addition & 0 deletions packages/core/src/js/NativeRNSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { UnsafeObject } from './utils/rnlibrariesinterface';
export interface Spec extends TurboModule {
addListener: (eventType: string) => void;
removeListeners: (id: number) => void;
getNewScreenTimeToDisplay(): Promise<number | undefined | null>;
addBreadcrumb(breadcrumb: UnsafeObject): void;
captureEnvelope(
bytes: string,
Expand Down
Loading

0 comments on commit 3b300d9

Please sign in to comment.