Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- Add first-class `expoRouterIntegration()` with auto-registration ([#6189](https://github.com/getsentry/sentry-react-native/pull/6189))
- Expose `addConsoleInstrumentationFilter` from `@sentry/core` ([#6180](https://github.com/getsentry/sentry-react-native/pull/6180))
- Expose experimental `captureSurfaceViews` option for Android Session Replay ([#6175](https://github.com/getsentry/sentry-react-native/pull/6175))
- Expose `pauseAppHangTracking` and `resumeAppHangTracking` APIs on iOS ([#6192](https://github.com/getsentry/sentry-react-native/pull/6192))
Comment thread
antonis marked this conversation as resolved.
Outdated
- Add OTA SDK version to native `sdk.packages` when JS bundle version differs from built-in version ([#6191](https://github.com/getsentry/sentry-react-native/pull/6191))

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ public void disableShakeDetection() {
stopShakeDetection();
}

public void pauseAppHangTracking() {
// No-op: App hang tracking is iOS-only
}

public void resumeAppHangTracking() {
// No-op: App hang tracking is iOS-only
}

public void fetchModules(Promise promise) {
final AssetManager assets = this.getReactApplicationContext().getResources().getAssets();
try (InputStream stream = new BufferedInputStream(assets.open(modulesPath))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ public void disableShakeDetection() {
this.impl.disableShakeDetection();
}

@Override
public void pauseAppHangTracking() {
this.impl.pauseAppHangTracking();
}

@Override
public void resumeAppHangTracking() {
this.impl.resumeAppHangTracking();
}

@Override
public void invalidate() {
this.impl.invalidate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ public void disableShakeDetection() {
this.impl.disableShakeDetection();
}

@ReactMethod
public void pauseAppHangTracking() {
this.impl.pauseAppHangTracking();
}

@ReactMethod
public void resumeAppHangTracking() {
this.impl.resumeAppHangTracking();
}

@Override
public void invalidate() {
this.impl.invalidate();
Expand Down
6 changes: 6 additions & 0 deletions packages/core/etc/sentry-react-native.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,9 @@ export { OpenAiClient }

export { OpenAiOptions }

// @public
Comment thread
sentry-warden[bot] marked this conversation as resolved.
export function pauseAppHangTracking(): void;

// @public
export const primitiveTagIntegration: () => Integration;

Expand Down Expand Up @@ -559,6 +562,9 @@ export const reactNavigationIntegration: (input?: Partial<ReactNavigationIntegra
options: ReactNavigationIntegrationOptions;
};

// @public
export function resumeAppHangTracking(): void;

export { rewriteFramesIntegration }

export { Scope }
Expand Down
4 changes: 4 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,10 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys
// the 'tracesSampleRate' or 'tracesSampler' option.
}

RCT_EXPORT_METHOD(pauseAppHangTracking) { [SentrySDKWrapper pauseAppHangTracking]; }

RCT_EXPORT_METHOD(resumeAppHangTracking) { [SentrySDKWrapper resumeAppHangTracking]; }

/**
* Calls captureReplay on the native replay integration and returns
* the BOOL result indicating whether the capture succeeded.
Expand Down
4 changes: 4 additions & 0 deletions packages/core/ios/SentrySDKWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

+ (BOOL)crashedLastRun;

+ (void)pauseAppHangTracking;

+ (void)resumeAppHangTracking;

+ (BOOL)debug;

+ (NSString *)releaseName;
Expand Down
10 changes: 10 additions & 0 deletions packages/core/ios/SentrySDKWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ + (BOOL)crashedLastRun
return [SentrySDK crashedLastRun];
}

+ (void)pauseAppHangTracking
{
[SentrySDK pauseAppHangTracking];
}

+ (void)resumeAppHangTracking
{
[SentrySDK resumeAppHangTracking];
}

+ (void)configureScope:(void (^)(SentryScope *scope))callback
{
[SentrySDK configureScope:callback];
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/js/NativeRNSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export interface Spec extends TurboModule {
encodeToBase64(data: number[]): Promise<string | undefined | null>;
enableShakeDetection(): void;
disableShakeDetection(): void;
pauseAppHangTracking(): void;
resumeAppHangTracking(): void;
}

export type NativeStackFrame = {
Expand Down
13 changes: 12 additions & 1 deletion packages/core/src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,18 @@ export { SDK_NAME, SDK_VERSION } from './version';
export type { ReactNativeOptions, NativeLogEntry } from './options';
export { ReactNativeClient } from './client';

export { init, wrap, nativeCrash, flush, close, withScope, crashedLastRun, appLoaded } from './sdk';
export {
init,
wrap,
nativeCrash,
flush,
close,
withScope,
crashedLastRun,
appLoaded,
pauseAppHangTracking,
resumeAppHangTracking,
} from './sdk';
export { TouchEventBoundary, withTouchEventBoundary } from './touchevents';
export { GlobalErrorBoundary, withGlobalErrorBoundary } from './GlobalErrorBoundary';
export type { GlobalErrorBoundaryProps } from './GlobalErrorBoundary';
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/js/sdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,31 @@ export function withScope<T>(callback: (scope: Scope) => T): T | undefined {
export async function crashedLastRun(): Promise<boolean | null> {
return NATIVE.crashedLastRun();
}

/**
* Pauses app hang tracking on iOS.
*
* App hang detection will ignore detected app hangs until
* `resumeAppHangTracking` is called.
*
* Use this when showing system dialogs (e.g., permission prompts)
* that block the main thread but are not real hangs.
*
* No-op on Android and when native is not available.
*
* @platform ios
*/
export function pauseAppHangTracking(): void {
NATIVE.pauseAppHangTracking();
}

/**
* Resumes app hang tracking on iOS after it was paused.
*
* No-op on Android and when native is not available.
*
* @platform ios
*/
export function resumeAppHangTracking(): void {
NATIVE.resumeAppHangTracking();
}
24 changes: 24 additions & 0 deletions packages/core/src/js/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ interface SentryNativeWrapper {

disableNativeFramesTracking(): void;
enableNativeFramesTracking(): void;
pauseAppHangTracking(): void;
resumeAppHangTracking(): void;

addBreadcrumb(breadcrumb: Breadcrumb): void;
// oxlint-disable-next-line typescript-eslint(no-explicit-any)
Expand Down Expand Up @@ -673,6 +675,28 @@ export const NATIVE: SentryNativeWrapper = {
RNSentry.enableNativeFramesTracking();
},

pauseAppHangTracking(): void {
if (!this.enableNative) {
return;
}
if (!this._isModuleLoaded(RNSentry)) {
return;
}

RNSentry.pauseAppHangTracking();
},

resumeAppHangTracking(): void {
if (!this.enableNative) {
return;
}
if (!this._isModuleLoaded(RNSentry)) {
return;
}

RNSentry.resumeAppHangTracking();
},

isNativeAvailable(): boolean {
if (!RNSentry) {
RNSentry = getRNSentryModule();
Expand Down
2 changes: 2 additions & 0 deletions packages/core/test/mockWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const NATIVE: MockInterface<NativeType> = {

disableNativeFramesTracking: jest.fn(),
enableNativeFramesTracking: jest.fn(),
pauseAppHangTracking: jest.fn(),
resumeAppHangTracking: jest.fn(),

addBreadcrumb: jest.fn(),
setContext: jest.fn(),
Expand Down
54 changes: 54 additions & 0 deletions packages/core/test/wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jest.mock('react-native', () => {
_getLastPayload: () => ({ initPayload }),
startProfiling: jest.fn(),
stopProfiling: jest.fn(),
pauseAppHangTracking: jest.fn(),
resumeAppHangTracking: jest.fn(),
};

return {
Expand Down Expand Up @@ -1208,6 +1210,58 @@ describe('Tests Native Wrapper', () => {
});
});

describe('pauseAppHangTracking', () => {
test('calls native pauseAppHangTracking', async () => {
await NATIVE.initNativeSdk({
dsn: VALID_DSN,
enableNative: true,
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});
NATIVE.pauseAppHangTracking();
expect(RNSentry.pauseAppHangTracking).toHaveBeenCalled();
});

test('does not call native when enableNative is false', async () => {
await NATIVE.initNativeSdk({
dsn: VALID_DSN,
enableNative: false,
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});
NATIVE.pauseAppHangTracking();
expect(RNSentry.pauseAppHangTracking).not.toHaveBeenCalled();
});
});

describe('resumeAppHangTracking', () => {
test('calls native resumeAppHangTracking', async () => {
await NATIVE.initNativeSdk({
dsn: VALID_DSN,
enableNative: true,
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});
NATIVE.resumeAppHangTracking();
expect(RNSentry.resumeAppHangTracking).toHaveBeenCalled();
});

test('does not call native when enableNative is false', async () => {
await NATIVE.initNativeSdk({
dsn: VALID_DSN,
enableNative: false,
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});
NATIVE.resumeAppHangTracking();
expect(RNSentry.resumeAppHangTracking).not.toHaveBeenCalled();
Comment thread
sentry-warden[bot] marked this conversation as resolved.
});
});

describe('primitiveProcessor and _setPrimitiveProcessor', () => {
describe('primitiveProcessor', () => {
it('default primitiveProcessor returns value as string', () => {
Expand Down
Loading