Skip to content

Commit efd0931

Browse files
feat(expo): Add support for Expo hash named bundles in RewriteFrames (#3115)
1 parent c4d59b2 commit efd0931

File tree

3 files changed

+118
-19
lines changed

3 files changed

+118
-19
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Overwrite Expo bundle names in stack frames ([#3115])(https://github.com/getsentry/sentry-react-native/pull/3115)
8+
- This enables source maps to resolve correctly without using `sentry-expo` package
9+
510
### Fixes
611

712
- Disable `enableNative` if Native SDK is not available ([#3099](https://github.com/getsentry/sentry-react-native/pull/3099))

src/js/integrations/rewriteframes.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
11
import { RewriteFrames } from '@sentry/integrations';
22
import type { StackFrame } from '@sentry/types';
3+
import { Platform } from 'react-native';
4+
5+
import { isExpo } from '../utils/environment';
6+
7+
const ANDROID_DEFAULT_BUNDLE_NAME = 'app:///index.android.bundle';
8+
const IOS_DEFAULT_BUNDLE_NAME = 'app:///main.jsbundle';
39

410
/**
511
* Creates React Native default rewrite frames integration
612
* which appends app:// to the beginning of the filename
7-
* and removes file://, 'address at' prefixes and CodePush postfix.
13+
* and removes file://, 'address at' prefixes, CodePush postfix,
14+
* and Expo bundle postfix.
815
*/
916
export function createReactNativeRewriteFrames(): RewriteFrames {
1017
return new RewriteFrames({
1118
iteratee: (frame: StackFrame) => {
12-
if (frame.filename) {
13-
frame.filename = frame.filename
14-
.replace(/^file:\/\//, '')
15-
.replace(/^address at /, '')
16-
.replace(/^.*\/[^.]+(\.app|CodePush|.*(?=\/))/, '');
17-
18-
if (frame.filename !== '[native code]' && frame.filename !== 'native') {
19-
const appPrefix = 'app://';
20-
// We always want to have a triple slash
21-
frame.filename =
22-
frame.filename.indexOf('/') === 0 ? `${appPrefix}${frame.filename}` : `${appPrefix}/${frame.filename}`;
23-
}
24-
delete frame.abs_path;
19+
if (!frame.filename) {
20+
return frame;
21+
}
22+
delete frame.abs_path;
23+
24+
frame.filename = frame.filename
25+
.replace(/^file:\/\//, '')
26+
.replace(/^address at /, '')
27+
.replace(/^.*\/[^.]+(\.app|CodePush|.*(?=\/))/, '');
28+
29+
if (frame.filename === '[native code]' || frame.filename === 'native') {
30+
return frame;
2531
}
32+
33+
// Expo adds hash to the end of bundle names
34+
if (isExpo() && Platform.OS === 'android') {
35+
frame.filename = ANDROID_DEFAULT_BUNDLE_NAME;
36+
return frame;
37+
}
38+
39+
if (isExpo() && Platform.OS === 'ios') {
40+
frame.filename = IOS_DEFAULT_BUNDLE_NAME;
41+
return frame;
42+
}
43+
44+
const appPrefix = 'app://';
45+
// We always want to have a triple slash
46+
frame.filename =
47+
frame.filename.indexOf('/') === 0 ? `${appPrefix}${frame.filename}` : `${appPrefix}/${frame.filename}`;
2648
return frame;
2749
},
2850
});

test/integrations/rewriteframes.test.ts

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import type { Exception } from '@sentry/browser';
22
import { defaultStackParser, eventFromException } from '@sentry/browser';
3+
import { Platform } from 'react-native';
34

45
import { createReactNativeRewriteFrames } from '../../src/js/integrations/rewriteframes';
6+
import { isExpo } from '../../src/js/utils/environment';
7+
import { mockFunction } from '../testutils';
8+
9+
jest.mock('../../src/js/utils/environment');
10+
jest.mock('react-native', () => ({ Platform: { OS: 'ios' } }));
511

612
describe('RewriteFrames', () => {
713
const HINT = {};
@@ -20,6 +26,11 @@ describe('RewriteFrames', () => {
2026
return exception;
2127
};
2228

29+
beforeEach(() => {
30+
mockFunction(isExpo).mockReturnValue(false);
31+
jest.resetAllMocks();
32+
});
33+
2334
it('should parse exceptions for react-native-v8', async () => {
2435
const REACT_NATIVE_V8_EXCEPTION = {
2536
message: 'Manually triggered crash to test Sentry reporting',
@@ -98,7 +109,10 @@ describe('RewriteFrames', () => {
98109
});
99110
});
100111

101-
it('should parse exceptions for react-native Expo bundles', async () => {
112+
it('should parse exceptions for react-native Expo bundles on ios', async () => {
113+
mockFunction(isExpo).mockReturnValue(true);
114+
Platform.OS = 'ios';
115+
102116
const REACT_NATIVE_EXPO_EXCEPTION = {
103117
message: 'Test Error Expo',
104118
name: 'Error',
@@ -121,28 +135,86 @@ describe('RewriteFrames', () => {
121135
frames: [
122136
{ filename: '[native code]', function: 'forEach', in_app: true },
123137
{
124-
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
138+
filename: 'app:///main.jsbundle',
125139
function: 'p',
126140
lineno: 96,
127141
colno: 385,
128142
in_app: true,
129143
},
130144
{
131-
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
145+
filename: 'app:///main.jsbundle',
132146
function: 'onResponderRelease',
133147
lineno: 221,
134148
colno: 5666,
135149
in_app: true,
136150
},
137151
{
138-
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
152+
filename: 'app:///main.jsbundle',
139153
function: 'value',
140154
lineno: 221,
141155
colno: 7656,
142156
in_app: true,
143157
},
144158
{
145-
filename: 'app:///bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3',
159+
filename: 'app:///main.jsbundle',
160+
function: 'onPress',
161+
lineno: 595,
162+
colno: 658,
163+
in_app: true,
164+
},
165+
],
166+
},
167+
});
168+
});
169+
170+
it('should parse exceptions for react-native Expo bundles on android', async () => {
171+
mockFunction(isExpo).mockReturnValue(true);
172+
Platform.OS = 'android';
173+
174+
const REACT_NATIVE_EXPO_EXCEPTION = {
175+
message: 'Test Error Expo',
176+
name: 'Error',
177+
stack: `onPress@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:595:658
178+
value@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:7656
179+
onResponderRelease@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:221:5666
180+
p@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:96:385
181+
forEach@[native code]`,
182+
};
183+
const exception = await exceptionFromError(REACT_NATIVE_EXPO_EXCEPTION);
184+
185+
expect(exception).toEqual({
186+
value: 'Test Error Expo',
187+
type: 'Error',
188+
mechanism: {
189+
handled: true,
190+
type: 'generic',
191+
},
192+
stacktrace: {
193+
frames: [
194+
{ filename: '[native code]', function: 'forEach', in_app: true },
195+
{
196+
filename: 'app:///index.android.bundle',
197+
function: 'p',
198+
lineno: 96,
199+
colno: 385,
200+
in_app: true,
201+
},
202+
{
203+
filename: 'app:///index.android.bundle',
204+
function: 'onResponderRelease',
205+
lineno: 221,
206+
colno: 5666,
207+
in_app: true,
208+
},
209+
{
210+
filename: 'app:///index.android.bundle',
211+
function: 'value',
212+
lineno: 221,
213+
colno: 7656,
214+
in_app: true,
215+
},
216+
{
217+
filename: 'app:///index.android.bundle',
146218
function: 'onPress',
147219
lineno: 595,
148220
colno: 658,

0 commit comments

Comments
 (0)