UNPKG

4.69 kBPlain TextView Raw
1/**
2 * Provides a global error handler to report errors..
3 */
4import {
5 InferFeatures,
6 LoggerPlugin,
7 ReactotronCore,
8 assertHasLoggerPlugin,
9 Plugin,
10} from "reactotron-core-client"
11import _LogBox, {
12 LogBoxStatic as LogBoxStaticPublic,
13 // eslint-disable-next-line import/default, import/namespace
14} from "react-native/Libraries/LogBox/LogBox"
15// eslint-disable-next-line import/namespace
16import type { ExtendedExceptionData } from "react-native/Libraries/LogBox/Data/parseLogBoxLog"
17import type { SymbolicateStackTraceFn } from "../helpers/symbolicateStackTrace"
18import type { ParseErrorStackFn } from "../helpers/parseErrorStack"
19
20interface LogBoxStaticPrivate extends LogBoxStaticPublic {
21 /**
22 * @see https://github.com/facebook/react-native/blob/v0.72.1/packages/react-native/Libraries/LogBox/LogBox.js#L29
23 */
24 addException: (error: ExtendedExceptionData) => void
25}
26
27const LogBox = _LogBox as unknown as LogBoxStaticPrivate
28
29// a few functions to help source map errors -- these seem to be not available immediately
30// so we're lazy loading.
31let parseErrorStack: ParseErrorStackFn
32let symbolicateStackTrace: SymbolicateStackTraceFn
33
34export interface ErrorStackFrame {
35 fileName: string
36 functionName: string
37 lineNumber: number
38 columnNumber?: number | null
39}
40
41export interface TrackGlobalErrorsOptions {
42 veto?: (frame: ErrorStackFrame) => boolean
43}
44
45// defaults
46const PLUGIN_DEFAULTS: TrackGlobalErrorsOptions = {
47 veto: null,
48}
49
50const objectifyError = (error: Error) => {
51 const objectifiedError = {} as Record<string, unknown>
52 Object.getOwnPropertyNames(error).forEach((key) => {
53 objectifiedError[key] = error[key]
54 })
55 return objectifiedError
56}
57
58// const reactNativeFrameFinder = frame => contains('/node_modules/react-native/', frame.fileName)
59
60/**
61 * Track global errors and send them to Reactotron logger.
62 */
63const trackGlobalErrors = (options?: TrackGlobalErrorsOptions) => (reactotron: ReactotronCore) => {
64 // make sure we have the logger plugin
65 assertHasLoggerPlugin(reactotron)
66 const client = reactotron as ReactotronCore & InferFeatures<ReactotronCore, LoggerPlugin>
67
68 // setup configuration
69 const config = Object.assign({}, PLUGIN_DEFAULTS, options || {})
70
71 // manually fire an error
72 function reportError(error: Parameters<typeof LogBox.addException>[0]) {
73 try {
74 parseErrorStack =
75 parseErrorStack || require("react-native/Libraries/Core/Devtools/parseErrorStack")
76 symbolicateStackTrace =
77 symbolicateStackTrace ||
78 require("react-native/Libraries/Core/Devtools/symbolicateStackTrace")
79 } catch (e) {
80 client.error(
81 'Unable to load "react-native/Libraries/Core/Devtools/parseErrorStack" or "react-native/Libraries/Core/Devtools/symbolicateStackTrace"',
82 []
83 )
84 client.debug(objectifyError(e))
85 return
86 }
87
88 if (!parseErrorStack || !symbolicateStackTrace) {
89 return
90 }
91
92 let parsedStacktrace: ReturnType<typeof parseErrorStack>
93
94 try {
95 // parseErrorStack arg type is wrong, it's expecting an array, a string, or a hermes error data, https://github.com/facebook/react-native/blob/v0.72.1/packages/react-native/Libraries/Core/Devtools/parseErrorStack.js#L41
96 parsedStacktrace = parseErrorStack(error.stack)
97 } catch (e) {
98 client.error("Unable to parse stack trace from error object", [])
99 client.debug(objectifyError(e))
100 return
101 }
102
103 symbolicateStackTrace(parsedStacktrace)
104 .then((symbolicatedStackTrace) => {
105 let prettyStackFrames = symbolicatedStackTrace.stack.map((stackFrame) => ({
106 fileName: stackFrame.file,
107 functionName: stackFrame.methodName,
108 lineNumber: stackFrame.lineNumber,
109 }))
110 // does the dev want us to keep each frame?
111 if (config.veto) {
112 prettyStackFrames = prettyStackFrames.filter((frame) => config?.veto(frame))
113 }
114 client.error(error.message, prettyStackFrames) // TODO: Fix this.
115 })
116 .catch((e) => {
117 client.error("Unable to symbolicate stack trace from error object", [])
118 client.debug(objectifyError(e))
119 })
120 }
121
122 // the reactotron plugin interface
123 return {
124 onConnect: () => {
125 LogBox.addException = new Proxy(LogBox.addException, {
126 apply: function (target, thisArg, argumentsList: Parameters<typeof LogBox.addException>) {
127 const error = argumentsList[0]
128 reportError(error)
129 return target.apply(thisArg, argumentsList)
130 },
131 })
132 },
133
134 // attach these functions to the Reactotron
135 features: {
136 reportError,
137 },
138 } satisfies Plugin<ReactotronCore>
139}
140
141export default trackGlobalErrors