-
-
Notifications
You must be signed in to change notification settings - Fork 360
Expand file tree
/
Copy pathdebugsymbolicatorutils.ts
More file actions
160 lines (138 loc) · 4.73 KB
/
debugsymbolicatorutils.ts
File metadata and controls
160 lines (138 loc) · 4.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import type { StackFrame as SentryStackFrame } from '@sentry/core';
import { debug, parseStackFrames } from '@sentry/core';
import { defaultStackParser } from '@sentry/react';
import type * as ReactNative from '../vendor/react-native';
import { ReactNativeLibraries } from '../utils/rnlibraries';
import { createStealthXhr, XHR_READYSTATE_DONE } from '../utils/xhr';
/**
* Fetches source context for the Sentry Middleware (/__sentry/context)
*
* @param frame StackFrame
* @param getDevServer function from RN to get DevServer URL
*/
export async function fetchSourceContext(frames: SentryStackFrame[]): Promise<SentryStackFrame[]> {
return new Promise(resolve => {
try {
const xhr = createStealthXhr();
if (!xhr) {
resolve(frames);
return;
}
const url = getSentryMetroSourceContextUrl();
if (!url) {
debug.error('Could not fetch source context. No dev server URL found.');
resolve(frames);
return;
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ stack: frames }));
xhr.onreadystatechange = (): void => {
if (xhr.readyState === XHR_READYSTATE_DONE) {
if (xhr.status !== 200) {
resolve(frames);
}
try {
const response: { stack?: SentryStackFrame[] } = JSON.parse(xhr.responseText);
if (Array.isArray(response.stack)) {
resolve(response.stack);
} else {
resolve(frames);
}
} catch (error) {
resolve(frames);
}
}
};
xhr.onerror = (): void => {
resolve(frames);
};
} catch (error) {
debug.error('Could not fetch source context.', error);
resolve(frames);
}
});
}
function getSentryMetroSourceContextUrl(): string | undefined {
const devServer = getDevServer();
if (!devServer) {
return undefined;
}
return `${devServer.url}__sentry/context`;
}
/**
* Converts Sentry StackFrames to React Native StackFrames.
* This is the reverse of convertReactNativeFramesToSentryFrames in debugsymbolicator.ts
*/
function convertSentryFramesToReactNativeFrames(frames: SentryStackFrame[]): Array<ReactNative.StackFrame> {
// Reverse the frames because Sentry parser returns them in reverse order compared to RN
return frames.reverse().map((frame): ReactNative.StackFrame => {
const rnFrame: ReactNative.StackFrame = {
methodName: frame.function || '?',
};
if (frame.filename !== undefined) {
rnFrame.file = frame.filename;
}
if (frame.lineno !== undefined) {
rnFrame.lineNumber = frame.lineno;
}
if (frame.colno !== undefined) {
rnFrame.column = frame.colno;
}
return rnFrame;
});
}
/**
* Parses an error stack string into React Native StackFrames.
* Uses RN Devtools parseErrorStack by default for compatibility.
* Falls back to Sentry's built-in stack parser if Devtools is not available.
*
* @param errorStack - Raw stack trace string from Error.stack
* @returns Array of React Native StackFrame objects
*/
export function parseErrorStack(errorStack: string): Array<ReactNative.StackFrame> {
// Try using RN Devtools first for maximum compatibility with existing tooling
if (ReactNativeLibraries.Devtools?.parseErrorStack) {
try {
return ReactNativeLibraries.Devtools.parseErrorStack(errorStack);
} catch (error) {
debug.warn('RN Devtools parseErrorStack failed, falling back to Sentry stack parser');
}
}
// Fallback: Use Sentry's stack parser (works without RN Devtools dependency)
try {
// Create a temporary Error object with the stack
const error = new Error();
error.stack = errorStack;
// Use Sentry's parser to parse the stack
const sentryFrames = parseStackFrames(defaultStackParser, error);
// Convert Sentry frames back to RN format
return convertSentryFramesToReactNativeFrames(sentryFrames);
} catch (error) {
debug.error('Failed to parse error stack:', error);
return [];
}
}
/**
* Loads and calls RN Core Devtools symbolicateStackTrace function.
*/
export function symbolicateStackTrace(
stack: Array<ReactNative.StackFrame>,
extraData?: Record<string, unknown>,
): Promise<ReactNative.SymbolicatedStackTrace> {
if (!ReactNativeLibraries.Devtools) {
throw new Error('React Native Devtools not available.');
}
return ReactNativeLibraries.Devtools.symbolicateStackTrace(stack, extraData);
}
/**
* Loads and returns the RN DevServer URL.
*/
export function getDevServer(): ReactNative.DevServerInfo | undefined {
try {
return ReactNativeLibraries.Devtools?.getDevServer();
} catch (_oO) {
// We can't load devserver URL
}
return undefined;
}