UNPKG

@bacons/expo-metro-runtime

Version:

Tools for making experimental Metro bundler features work

226 lines 8.47 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * Copyright (c) Evan Bacon. * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const parseErrorStack_1 = __importDefault(require("../parseErrorStack")); class SyntheticError extends Error { constructor() { super(...arguments); this.name = ""; } } let userExceptionDecorator; let inUserExceptionDecorator = false; /** * Allows the app to add information to the exception report before it is sent * to native. This API is not final. */ function unstable_setExceptionDecorator(exceptionDecorator) { userExceptionDecorator = exceptionDecorator; } function preprocessException(data) { if (userExceptionDecorator && !inUserExceptionDecorator) { inUserExceptionDecorator = true; try { return userExceptionDecorator(data); } catch { // Fall through } finally { inUserExceptionDecorator = false; } } return data; } function parseException(e, isFatal) { const stack = (0, parseErrorStack_1.default)(e === null || e === void 0 ? void 0 : e.stack); const currentExceptionID = ++exceptionID; const originalMessage = e.message || ""; let message = originalMessage; if (e.componentStack != null) { message += `\n\nThis error is located at:${e.componentStack}`; } const namePrefix = e.name == null || e.name === "" ? "" : `${e.name}: `; if (!message.startsWith(namePrefix)) { message = namePrefix + message; } message = e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`; const data = preprocessException({ message, originalMessage: message === originalMessage ? null : originalMessage, name: e.name == null || e.name === "" ? null : e.name, componentStack: typeof e.componentStack === "string" ? e.componentStack : null, stack, id: currentExceptionID, isFatal, extraData: { jsEngine: e.jsEngine, rawStack: e.stack, }, }); return { ...data, isComponentError: !!e.isComponentError, }; } /** * Handles the developer-visible aspect of errors and exceptions */ let exceptionID = 0; function reportException(e, isFatal, reportToConsole // only true when coming from handleException; the error has not yet been logged ) { const data = parseException(e, isFatal); if (reportToConsole) { // we feed back into console.error, to make sure any methods that are // monkey patched on top of console.error are called when coming from // handleException console.error(data.message); } if (__DEV__) { const LogBox = require("../../LogBox"); LogBox.addException(data); } else if (isFatal || e.type !== "warn") { // const NativeExceptionsManager = // require("./NativeExceptionsManager").default; // if (NativeExceptionsManager) { // NativeExceptionsManager.reportException(data); // } } } // If we trigger console.error _from_ handleException, // we do want to make sure that console.error doesn't trigger error reporting again let inExceptionHandler = false; /** * Logs exceptions to the (native) console and displays them */ function handleException(e, isFatal) { let error; if (e instanceof Error) { error = e; } else { // Workaround for reporting errors caused by `throw 'some string'` // Unfortunately there is no way to figure out the stacktrace in this // case, so if you ended up here trying to trace an error, look for // `throw '<error message>'` somewhere in your codebase. error = new SyntheticError(e); } try { inExceptionHandler = true; /* $FlowFixMe[class-object-subtyping] added when improving typing for this * parameters */ reportException(error, isFatal, /*reportToConsole*/ true); } finally { inExceptionHandler = false; } } function reactConsoleErrorHandler(...args) { // bubble up to any original handlers // @ts-expect-error console._errorOriginal(...args); // @ts-expect-error if (!console.reportErrorsAsExceptions) { return; } if (inExceptionHandler) { // The fundamental trick here is that are multiple entry point to logging errors: // (see D19743075 for more background) // // 1. An uncaught exception being caught by the global handler // 2. An error being logged throw console.error // // However, console.error is monkey patched multiple times: by this module, and by the // DevTools setup that sends messages to Metro. // The patching order cannot be relied upon. // // So, some scenarios that are handled by this flag: // // Logging an error: // 1. console.error called from user code // 2. (possibly) arrives _first_ at DevTool handler, send to Metro // 3. Bubbles to here // 4. goes into report Exception. // 5. should not trigger console.error again, to avoid looping / logging twice // 6. should still bubble up to original console // (which might either be console.log, or the DevTools handler in case it patched _earlier_ and (2) didn't happen) // // Throwing an uncaught exception: // 1. exception thrown // 2. picked up by handleException // 3. should be send to console.error (not console._errorOriginal, as DevTools might have patched _later_ and it needs to send it to Metro) // 4. that _might_ bubble again to the `reactConsoleErrorHandle` defined here // -> should not handle exception _again_, to avoid looping / showing twice (this code branch) // 5. should still bubble up to original console (which might either be console.log, or the DevTools handler in case that one patched _earlier_) return; } let error; const firstArg = args[0]; if (firstArg === null || firstArg === void 0 ? void 0 : firstArg.stack) { // reportException will console.error this with high enough fidelity. error = firstArg; } else { const stringifySafe = require("../stringifySafe").default; if (typeof firstArg === "string" && firstArg.startsWith("Warning: ")) { // React warnings use console.error so that a stack trace is shown, but // we don't (currently) want these to show a redbox // (Note: Logic duplicated in polyfills/console.js.) return; } const message = args .map((arg) => (typeof arg === "string" ? arg : stringifySafe(arg))) .join(" "); error = new SyntheticError(message); error.name = "console.error"; } reportException( /* $FlowFixMe[class-object-subtyping] added when improving typing for this * parameters */ error, false, // isFatal false // reportToConsole ); } /** * Shows a redbox with stacktrace for all console.error messages. Disable by * setting `console.reportErrorsAsExceptions = false;` in your app. */ function installConsoleErrorReporter() { // Enable reportErrorsAsExceptions // @ts-expect-error if (console._errorOriginal) { return; // already installed } // Flow doesn't like it when you set arbitrary values on a global object // @ts-expect-error console._errorOriginal = console.error.bind(console); console.error = reactConsoleErrorHandler; // @ts-expect-error if (console.reportErrorsAsExceptions === undefined) { // Individual apps can disable this // Flow doesn't like it when you set arbitrary values on a global object // @ts-expect-error console.reportErrorsAsExceptions = true; } } const ErrorUtils = { parseException, handleException, installConsoleErrorReporter, SyntheticError, unstable_setExceptionDecorator, }; module.exports = ErrorUtils; exports.default = ErrorUtils; //# sourceMappingURL=index.js.map