1// Copyright 2023-present 650 Industries (Expo). All rights reserved. 2import { SymbolicatorConfigT } from 'metro-config'; 3import { URL } from 'url'; 4 5type CustomizeFrameFunc = SymbolicatorConfigT['customizeFrame']; 6 7// Import only the types here, the values will be imported from the project, at runtime. 8export const INTERNAL_CALLSITES_REGEX = new RegExp( 9 [ 10 '/Libraries/Renderer/implementations/.+\\.js$', 11 '/Libraries/BatchedBridge/MessageQueue\\.js$', 12 '/Libraries/YellowBox/.+\\.js$', 13 '/Libraries/LogBox/.+\\.js$', 14 '/Libraries/Core/Timers/.+\\.js$', 15 'node_modules/react-devtools-core/.+\\.js$', 16 'node_modules/react-refresh/.+\\.js$', 17 'node_modules/scheduler/.+\\.js$', 18 // Metro replaces `require()` with a different method, 19 // we want to omit this method from the stack trace. 20 // This is akin to most React tooling. 21 '/metro/.*/polyfills/require.js$', 22 // Hide frames related to a fast refresh. 23 '/metro/.*/lib/bundle-modules/.+\\.js$', 24 'node_modules/react-native/Libraries/Utilities/HMRClient.js$', 25 'node_modules/eventemitter3/index.js', 26 'node_modules/event-target-shim/dist/.+\\.js$', 27 // Ignore the log forwarder used in the expo package. 28 '/expo/build/logs/RemoteConsole.js$', 29 // Improve errors thrown by invariant (ex: `Invariant Violation: "main" has not been registered`). 30 'node_modules/invariant/.+\\.js$', 31 // Remove babel runtime additions 32 'node_modules/regenerator-runtime/.+\\.js$', 33 // Remove react native setImmediate ponyfill 34 'node_modules/promise/setimmediate/.+\\.js$', 35 // Babel helpers that implement language features 36 'node_modules/@babel/runtime/.+\\.js$', 37 // Hide Hermes internal bytecode 38 '/InternalBytecode/InternalBytecode\\.js$', 39 // Block native code invocations 40 `\\[native code\\]`, 41 // Hide react-dom (web) 42 'node_modules/react-dom/.+\\.js$', 43 '@expo/metro-runtime/build/.+\\.js$', 44 ].join('|') 45); 46 47function isUrl(value: string): boolean { 48 try { 49 // eslint-disable-next-line no-new 50 new URL(value); 51 return true; 52 } catch { 53 return false; 54 } 55} 56 57/** 58 * The default frame processor. This is used to modify the stack traces. 59 * This method attempts to collapse all frames that aren't relevant to 60 * the user by default. 61 */ 62export function getDefaultCustomizeFrame(): CustomizeFrameFunc { 63 return (frame: Parameters<CustomizeFrameFunc>[0]) => { 64 if (frame.file && isUrl(frame.file)) { 65 return { 66 ...frame, 67 // HACK: This prevents Metro from attempting to read the invalid file URL it sent us. 68 lineNumber: null, 69 column: null, 70 // This prevents the invalid frame from being shown by default. 71 collapse: true, 72 }; 73 } 74 let collapse = Boolean(frame.file && INTERNAL_CALLSITES_REGEX.test(frame.file)); 75 76 if (!collapse) { 77 // This represents the first frame of the stacktrace. 78 // Often this looks like: `__r(0);`. 79 // The URL will also be unactionable in the app and therefore not very useful to the developer. 80 if ( 81 frame.column === 3 && 82 frame.methodName === 'global code' && 83 frame.file?.match(/^https?:\/\//g) 84 ) { 85 collapse = true; 86 } 87 } 88 89 return { ...(frame || {}), collapse }; 90 }; 91} 92