Skip to content

Commit

Permalink
feat(expo): Add support for Expo hash named bundles in RewriteFrames (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Jun 13, 2023
1 parent c4d59b2 commit efd0931
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 19 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Features

- Overwrite Expo bundle names in stack frames ([#3115])(https://github.com/getsentry/sentry-react-native/pull/3115)
- This enables source maps to resolve correctly without using `sentry-expo` package

### Fixes

- Disable `enableNative` if Native SDK is not available ([#3099](https://github.com/getsentry/sentry-react-native/pull/3099))
Expand Down
50 changes: 36 additions & 14 deletions src/js/integrations/rewriteframes.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
import { RewriteFrames } from '@sentry/integrations';
import type { StackFrame } from '@sentry/types';
import { Platform } from 'react-native';

import { isExpo } from '../utils/environment';

const ANDROID_DEFAULT_BUNDLE_NAME = 'app:///index.android.bundle';
const IOS_DEFAULT_BUNDLE_NAME = 'app:///main.jsbundle';

/**
* Creates React Native default rewrite frames integration
* which appends app:// to the beginning of the filename
* and removes file://, 'address at' prefixes and CodePush postfix.
* and removes file://, 'address at' prefixes, CodePush postfix,
* and Expo bundle postfix.
*/
export function createReactNativeRewriteFrames(): RewriteFrames {
return new RewriteFrames({
iteratee: (frame: StackFrame) => {
if (frame.filename) {
frame.filename = frame.filename
.replace(/^file:\/\//, '')
.replace(/^address at /, '')
.replace(/^.*\/[^.]+(\.app|CodePush|.*(?=\/))/, '');

if (frame.filename !== '[native code]' && frame.filename !== 'native') {
const appPrefix = 'app://';
// We always want to have a triple slash
frame.filename =
frame.filename.indexOf('/') === 0 ? `${appPrefix}${frame.filename}` : `${appPrefix}/${frame.filename}`;
}
delete frame.abs_path;
if (!frame.filename) {
return frame;
}
delete frame.abs_path;

frame.filename = frame.filename
.replace(/^file:\/\//, '')
.replace(/^address at /, '')
.replace(/^.*\/[^.]+(\.app|CodePush|.*(?=\/))/, '');

if (frame.filename === '[native code]' || frame.filename === 'native') {
return frame;
}

// Expo adds hash to the end of bundle names
if (isExpo() && Platform.OS === 'android') {
frame.filename = ANDROID_DEFAULT_BUNDLE_NAME;
return frame;
}

if (isExpo() && Platform.OS === 'ios') {
frame.filename = IOS_DEFAULT_BUNDLE_NAME;
return frame;
}

const appPrefix = 'app://';
// We always want to have a triple slash
frame.filename =
frame.filename.indexOf('/') === 0 ? `${appPrefix}${frame.filename}` : `${appPrefix}/${frame.filename}`;
return frame;
},
});
Expand Down
82 changes: 77 additions & 5 deletions test/integrations/rewriteframes.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { Exception } from '@sentry/browser';
import { defaultStackParser, eventFromException } from '@sentry/browser';
import { Platform } from 'react-native';

import { createReactNativeRewriteFrames } from '../../src/js/integrations/rewriteframes';
import { isExpo } from '../../src/js/utils/environment';
import { mockFunction } from '../testutils';

jest.mock('../../src/js/utils/environment');
jest.mock('react-native', () => ({ Platform: { OS: 'ios' } }));

describe('RewriteFrames', () => {
const HINT = {};
Expand All @@ -20,6 +26,11 @@ describe('RewriteFrames', () => {
return exception;
};

beforeEach(() => {
mockFunction(isExpo).mockReturnValue(false);
jest.resetAllMocks();
});

it('should parse exceptions for react-native-v8', async () => {
const REACT_NATIVE_V8_EXCEPTION = {
message: 'Manually triggered crash to test Sentry reporting',
Expand Down Expand Up @@ -98,7 +109,10 @@ describe('RewriteFrames', () => {
});
});

it('should parse exceptions for react-native Expo bundles', async () => {
it('should parse exceptions for react-native Expo bundles on ios', async () => {
mockFunction(isExpo).mockReturnValue(true);
Platform.OS = 'ios';

const REACT_NATIVE_EXPO_EXCEPTION = {
message: 'Test Error Expo',
name: 'Error',
Expand All @@ -121,28 +135,86 @@ describe('RewriteFrames', () => {
frames: [
{ filename: '[native code]', function: 'forEach', in_app: true },
{
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
filename: 'app:///main.jsbundle',
function: 'p',
lineno: 96,
colno: 385,
in_app: true,
},
{
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
filename: 'app:///main.jsbundle',
function: 'onResponderRelease',
lineno: 221,
colno: 5666,
in_app: true,
},
{
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
filename: 'app:///main.jsbundle',
function: 'value',
lineno: 221,
colno: 7656,
in_app: true,
},
{
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
filename: 'app:///main.jsbundle',
function: 'onPress',
lineno: 595,
colno: 658,
in_app: true,
},
],
},
});
});

it('should parse exceptions for react-native Expo bundles on android', async () => {
mockFunction(isExpo).mockReturnValue(true);
Platform.OS = 'android';

const REACT_NATIVE_EXPO_EXCEPTION = {
message: 'Test Error Expo',
name: 'Error',
stack: `onPress@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:595:658
value@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:7656
onResponderRelease@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:5666
p@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:96:385
forEach@[native code]`,
};
const exception = await exceptionFromError(REACT_NATIVE_EXPO_EXCEPTION);

expect(exception).toEqual({
value: 'Test Error Expo',
type: 'Error',
mechanism: {
handled: true,
type: 'generic',
},
stacktrace: {
frames: [
{ filename: '[native code]', function: 'forEach', in_app: true },
{
filename: 'app:///index.android.bundle',
function: 'p',
lineno: 96,
colno: 385,
in_app: true,
},
{
filename: 'app:///index.android.bundle',
function: 'onResponderRelease',
lineno: 221,
colno: 5666,
in_app: true,
},
{
filename: 'app:///index.android.bundle',
function: 'value',
lineno: 221,
colno: 7656,
in_app: true,
},
{
filename: 'app:///index.android.bundle',
function: 'onPress',
lineno: 595,
colno: 658,
Expand Down

0 comments on commit efd0931

Please sign in to comment.