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 // Improve errors thrown by invariant (ex: `Invariant Violation: "main" has not been registered`). 28 'node_modules/invariant/.+\\.js$', 29 // Remove babel runtime additions 30 'node_modules/regenerator-runtime/.+\\.js$', 31 // Remove react native setImmediate ponyfill 32 'node_modules/promise/setimmediate/.+\\.js$', 33 // Babel helpers that implement language features 34 'node_modules/@babel/runtime/.+\\.js$', 35 // Hide Hermes internal bytecode 36 '/InternalBytecode/InternalBytecode\\.js$', 37 // Block native code invocations 38 `\\[native code\\]`, 39 // Hide react-dom (web) 40 'node_modules/react-dom/.+\\.js$', 41 // Block expo's metro-runtime 42 '@expo/metro-runtime/build/.+\\.js$', 43 // Block upstream metro-runtime 44 '/metro-runtime/.+\\.js$', 45 ].join('|') 46); 47 48function isUrl(value: string): boolean { 49 try { 50 // eslint-disable-next-line no-new 51 new URL(value); 52 return true; 53 } catch { 54 return false; 55 } 56} 57 58/** 59 * The default frame processor. This is used to modify the stack traces. 60 * This method attempts to collapse all frames that aren't relevant to 61 * the user by default. 62 */ 63export function getDefaultCustomizeFrame(): CustomizeFrameFunc { 64 return (frame: Parameters<CustomizeFrameFunc>[0]) => { 65 if (frame.file && isUrl(frame.file)) { 66 return { 67 ...frame, 68 // HACK: This prevents Metro from attempting to read the invalid file URL it sent us. 69 lineNumber: null, 70 column: null, 71 // This prevents the invalid frame from being shown by default. 72 collapse: true, 73 }; 74 } 75 let collapse = Boolean(frame.file && INTERNAL_CALLSITES_REGEX.test(frame.file)); 76 77 if (!collapse) { 78 // This represents the first frame of the stacktrace. 79 // Often this looks like: `__r(0);`. 80 // The URL will also be unactionable in the app and therefore not very useful to the developer. 81 if ( 82 frame.column === 3 && 83 frame.methodName === 'global code' && 84 frame.file?.match(/^https?:\/\//g) 85 ) { 86 collapse = true; 87 } 88 } 89 90 return { ...(frame || {}), collapse }; 91 }; 92} 93