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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,10 @@

## Unreleased

### Features

- Add `Sentry.crashedLastRun()` ([#4014](https://github.com/getsentry/sentry-react-native/pull/4014))

### Fixes

- Use `install_modules_dependencies` for React iOS dependencies ([#4040](https://github.com/getsentry/sentry-react-native/pull/4040))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -852,6 +852,10 @@ public String fetchNativePackageName() {
return packageInfo.packageName;
}

public void crashedLastRun(Promise promise) {
promise.resolve(Sentry.isCrashedLastRun());
}

private void setEventOriginTag(SentryEvent event) {
SdkVersion sdk = event.getSdk();
if (sdk != null) {
Expand Down
5 changes: 5 additions & 0 deletions android/src/newarch/java/io/sentry/react/RNSentryModule.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,4 +168,9 @@ public void captureReplay(boolean isHardCrash, Promise promise) {
public String getCurrentReplayId() {
return this.impl.getCurrentReplayId();
}

@Override
public void crashedLastRun(Promise promise) {
this.impl.crashedLastRun(promise);
}
}
5 changes: 5 additions & 0 deletions android/src/oldarch/java/io/sentry/react/RNSentryModule.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,4 +168,9 @@ public void captureReplay(boolean isHardCrash, Promise promise) {
public String getCurrentReplayId() {
return this.impl.getCurrentReplayId();
}

@ReactMethod
public void crashedLastRun(Promise promise) {
this.impl.crashedLastRun(promise);
}
}
6 changes: 6 additions & 0 deletions ios/RNSentry.mm
Original file line numberDiff line numberDiff line change
Expand Up@@ -757,6 +757,12 @@ - (NSDictionary*) fetchNativeStackFramesBy: (NSArray<NSNumber*>*)instructionsAdd
#endif
}

RCT_EXPORT_METHOD(crashedLastRun:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
resolve(@([SentrySDK crashedLastRun]));
}

// Thanks to this guard, we won't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
Expand Down
7 changes: 7 additions & 0 deletions samples/react-native/src/Screens/ErrorsScreen.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,13 @@ const ErrorsScreen = (_props: Props) => {
Sentry.nativeCrash();
}}
/>
<Button
title="Get Crashed Last Run"
onPress={async () => {
const crashed = await Sentry.crashedLastRun();
console.log('Crashed last run:', crashed);
}}
/>
<Button
title="Set Scope Properties"
onPress={() => {
Expand Down
1 change: 1 addition & 0 deletions src/js/NativeRNSentry.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,7 @@ export interface Spec extends TurboModule {
initNativeReactNavigationNewFrameTracking(): Promise<void>;
captureReplay(isHardCrash: boolean): Promise<string | undefined | null>;
getCurrentReplayId(): string | undefined | null;
crashedLastRun(): Promise<boolean | undefined | null>;
}

export type NativeStackFrame = {
Expand Down
1 change: 1 addition & 0 deletions src/js/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,6 +84,7 @@ export {
captureUserFeedback,
withScope,
configureScope,
crashedLastRun,
} from './sdk';
export { TouchEventBoundary, withTouchEventBoundary } from './touchevents';

Expand Down
7 changes: 7 additions & 0 deletions src/js/sdk.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -246,3 +246,10 @@ export function configureScope(callback: (scope: Scope) => void): ReturnType<Hub
};
getCurrentHub().configureScope(safeCallback);
}

/**
* Returns if the app crashed in the last run.
*/
export async function crashedLastRun(): Promise<boolean | null> {
return NATIVE.crashedLastRun();
}
1 change: 1 addition & 0 deletions src/js/vendor/react-native/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,6 +67,7 @@ export type TurboModuleRegistry = {

// Adapted from https://github.com/facebook/react-native/blob/3f8340975b35767b192e3118f05d2b039676052e/packages/react-native/types/public/ReactNativeTypes.d.ts#L137
export interface HostComponent<P> extends Pick<React.ComponentClass<P>, Exclude<keyof React.ComponentClass<P>, 'new'>> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new (props: P, context?: any): React.Component<P> & Readonly<unknown>;
}

Expand Down
14 changes: 14 additions & 0 deletions src/js/wrapper.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,8 @@ interface SentryNativeWrapper {

captureReplay(isHardCrash: boolean): Promise<string | null>;
getCurrentReplayId(): string | null;

crashedLastRun(): Promise<boolean | null>;
}

const EOL = utf8ToBytes('\n');
Expand DownExpand Up@@ -642,6 +644,18 @@ export const NATIVE: SentryNativeWrapper = {
return RNSentry.getCurrentReplayId() || null;
},

async crashedLastRun(): Promise<boolean | null> {
if (!this.enableNative) {
return null;
}
if (!this._isModuleLoaded(RNSentry)) {
return null;
}

const result = RNSentry.crashedLastRun();
return typeof result === 'boolean' ? result : null;
},

/**
* Gets the event from envelopeItem and applies the level filter to the selected event.
* @param data An envelope item containing the event.
Expand Down
3 changes: 3 additions & 0 deletions test/mockWrapper.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,8 @@ const NATIVE: MockInterface<NativeType> = {
fetchNativeStackFramesBy: jest.fn(),

initNativeReactNavigationNewFrameTracking: jest.fn(),

crashedLastRun: jest.fn(),
};

NATIVE.isNativeAvailable.mockReturnValue(true);
Expand All@@ -74,6 +76,7 @@ NATIVE.stopProfiling.mockReturnValue(null);
NATIVE.fetchNativePackageName.mockReturnValue('mock-native-package-name');
NATIVE.fetchNativeStackFramesBy.mockReturnValue(null);
NATIVE.initNativeReactNavigationNewFrameTracking.mockReturnValue(Promise.resolve());
NATIVE.crashedLastRun.mockResolvedValue(false);

export const getRNSentryModule = jest.fn();

Expand Down
20 changes: 19 additions & 1 deletion test/sdk.withclient.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
jest.spyOn(logger, 'error');
jest.mock('../src/js/wrapper', () => jest.requireActual('./mockWrapper'));

import { setCurrentClient } from '@sentry/core';
import { logger } from '@sentry/utils';

import { configureScope, flush } from '../src/js/sdk';
import { configureScope, crashedLastRun, flush } from '../src/js/sdk';
import { getDefaultTestClientOptions, TestClient } from './mocks/client';
import { NATIVE } from './mockWrapper';

describe('Tests the SDK functionality', () => {
let client: TestClient;
Expand DownExpand Up@@ -46,4 +48,20 @@ describe('Tests the SDK functionality', () => {
expect(mockScopeCallback).toBeCalledTimes(1);
});
});

describe('crashedLastRun', () => {
it('Returns Native crashedLastRun', async () => {
NATIVE.crashedLastRun.mockClear().mockResolvedValue(true);
expect(await crashedLastRun()).toBe(true);
expect(NATIVE.crashedLastRun).toBeCalled();

NATIVE.crashedLastRun.mockClear().mockResolvedValue(false);
expect(await crashedLastRun()).toBe(false);
expect(NATIVE.crashedLastRun).toBeCalled();

NATIVE.crashedLastRun.mockClear().mockResolvedValue(null);
expect(await crashedLastRun()).toBe(null);
expect(NATIVE.crashedLastRun).toBeCalled();
});
});
});