Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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

### Features

- Expose `pauseAppHangTracking` and `resumeAppHangTracking` APIs on iOS ([#6192](https://github.com/getsentry/sentry-react-native/pull/6192))

## 8.12.0

### Features
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