/** * Provides a global error handler to report errors.. */ import { InferFeatures, LoggerPlugin, DReactionCore, assertHasLoggerPlugin, Plugin, } from 'dreaction-client-core'; import _LogBox, { LogBoxStatic as LogBoxStaticPublic, // eslint-disable-next-line import/default, import/namespace } from 'react-native/Libraries/LogBox/LogBox'; // eslint-disable-next-line import/namespace // import type { ExtendedExceptionData } from 'react-native/Libraries/LogBox/Data/parseLogBoxLog'; import type { SymbolicateStackTraceFn } from '../helpers/symbolicateStackTrace'; import type { ParseErrorStackFn } from '../helpers/parseErrorStack'; interface LogBoxStaticPrivate extends LogBoxStaticPublic { /** * @see https://github.com/facebook/react-native/blob/v0.72.1/packages/react-native/Libraries/LogBox/LogBox.js#L29 */ // addException: (error: ExtendedExceptionData) => void; addException: (error: any) => void; } const LogBox = _LogBox as unknown as LogBoxStaticPrivate; // a few functions to help source map errors -- these seem to be not available immediately // so we're lazy loading. let parseErrorStack: ParseErrorStackFn; let symbolicateStackTrace: SymbolicateStackTraceFn; export interface ErrorStackFrame { fileName: string; functionName: string; lineNumber: number; columnNumber?: number | null; } export interface TrackGlobalErrorsOptions { veto?: (frame: ErrorStackFrame) => boolean; } // defaults const PLUGIN_DEFAULTS: TrackGlobalErrorsOptions = { veto: undefined, }; const objectifyError = (error: Error) => { const objectifiedError = {} as Record; Object.getOwnPropertyNames(error).forEach((key) => { objectifiedError[key] = (error as any)[key]; }); return objectifiedError; }; // const reactNativeFrameFinder = frame => contains('/node_modules/react-native/', frame.fileName) /** * Track global errors and send them to DReaction logger. */ const trackGlobalErrors = (options?: TrackGlobalErrorsOptions) => (dreaction: DReactionCore) => { // make sure we have the logger plugin assertHasLoggerPlugin(dreaction); const client = dreaction as DReactionCore & InferFeatures; // setup configuration const config = Object.assign({}, PLUGIN_DEFAULTS, options || {}); // manually fire an error function reportError(error: Parameters[0]) { try { parseErrorStack = parseErrorStack || require('react-native/Libraries/Core/Devtools/parseErrorStack'); symbolicateStackTrace = symbolicateStackTrace || require('react-native/Libraries/Core/Devtools/symbolicateStackTrace'); } catch (e) { client.error( 'Unable to load "react-native/Libraries/Core/Devtools/parseErrorStack" or "react-native/Libraries/Core/Devtools/symbolicateStackTrace"', [] ); client.debug(objectifyError(e as Error)); return; } if (!parseErrorStack || !symbolicateStackTrace) { return; } let parsedStacktrace: ReturnType; try { // 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 parsedStacktrace = parseErrorStack(error.stack); } catch (e) { client.error('Unable to parse stack trace from error object', []); client.debug(objectifyError(e as Error)); return; } symbolicateStackTrace(parsedStacktrace) .then((symbolicatedStackTrace) => { let prettyStackFrames = symbolicatedStackTrace.stack.map( (stackFrame) => ({ fileName: stackFrame.file, functionName: stackFrame.methodName, lineNumber: stackFrame.lineNumber, }) ); // does the dev want us to keep each frame? if (config.veto) { prettyStackFrames = prettyStackFrames.filter((frame) => config?.veto?.(frame) ); } client.error(error.message, prettyStackFrames); // TODO: Fix this. }) .catch((e) => { client.error( 'Unable to symbolicate stack trace from error object', [] ); client.debug(objectifyError(e)); }); } // the dreaction plugin interface return { onConnect: () => { LogBox.addException = new Proxy(LogBox.addException, { apply: function ( target, thisArg, argumentsList: Parameters ) { const error = argumentsList[0]; reportError(error); return target.apply(thisArg, argumentsList); }, }); }, // attach these functions to the DReaction features: { reportError, }, } satisfies Plugin; }; export default trackGlobalErrors;