import * as Path from 'path'; import * as Fs from 'fs'; const fileUriToPath: (uri: string) => string = require('file-uri-to-path'); const sourceMapSupport = require('source-map-support'); /** * This array tells the source map logic which files that we can expect to * be able to resolve a source map for and they should reflect the chunks * entry names from our webpack config. * * Note that we explicitly don't enable source maps for the crash process * since it's possible that the error which caused us to spawn the crash * process was related to source maps. */ const knownFilesWithSourceMap = ['renderer.js', 'main.js']; function retrieveSourceMap(source: string) { // This is a happy path in case we know for certain that we won't be // able to resolve a source map for the given location. if (!knownFilesWithSourceMap.some(file => source.endsWith(file))) { return null; } // We get a file uri when we're inside a renderer, convert to a path if (source.startsWith('file://')) { source = fileUriToPath(source); } // We store our source maps right next to the bundle const path = `${source}.map`; if (__DEV__ && path.startsWith('http://')) { try { const xhr = new XMLHttpRequest(); xhr.open('GET', path, false); xhr.send(null); if (xhr.readyState === 4 && xhr.status === 200) { return { url: Path.basename(path), map: xhr.responseText }; } } catch (error) { return; } return; } // We don't have an option here, see // https://github.com/v8/v8/wiki/Stack-Trace-API#customizing-stack-traces // This happens on-demand when someone accesses the stack // property on an error object and has to be synchronous :/ // eslint-disable-next-line no-sync if (!Fs.existsSync(path)) { return; } try { // eslint-disable-next-line no-sync const map = Fs.readFileSync(path, 'utf8'); return { url: Path.basename(path), map }; } catch (error) { return; } } /** A map from errors to their stack frames. */ const stackFrameMap = new WeakMap>(); /** * The `prepareStackTrace` that comes from the `source-map-support` module. * We'll use this when the user explicitly wants the stack source mapped. */ let prepareStackTraceWithSourceMap: ( error: Error, frames: ReadonlyArray ) => string; /** * Capture the error's stack frames and return a standard, un-source mapped * stack trace. */ function prepareStackTrace(error: Error, frames: ReadonlyArray) { stackFrameMap.set(error, frames); // Ideally we'd use the default `Error.prepareStackTrace` here but it's // undefined so V8 must doing something fancy. Instead we'll do a decent // impression. return error + frames.map(frame => `\n at ${frame}`).join(''); } /** Enable source map support in the current process. */ export function enableSourceMaps() { sourceMapSupport.install({ environment: 'node', handleUncaughtExceptions: false, retrieveSourceMap, }); const AnyError = Error as any; // We want to keep `source-map-support`s `prepareStackTrace` around to use // later, but our cheaper `prepareStackTrace` should be the default. prepareStackTraceWithSourceMap = AnyError.prepareStackTrace; AnyError.prepareStackTrace = prepareStackTrace; } /** * Make a copy of the error with a source-mapped stack trace. If it couldn't * perform the source mapping, it'll use the original error stack. */ export function withSourceMappedStack(error: Error): Error { return { name: error.name, message: error.message, stack: sourceMappedStackTrace(error), }; } /** Get the source mapped stack trace for the error. */ function sourceMappedStackTrace(error: Error): string | undefined { let frames = stackFrameMap.get(error); if (!frames) { // At this point there's no guarantee that anyone has actually retrieved the // stack on this error which means that our custom prepareStackTrace handler // hasn't run and as a result of that we don't have the native frames stored // in our weak map. In order to get around that we'll eagerly access the // stack, forcing our handler to run which should ensure that the native // frames are stored in our weak map. (error.stack || '').toString(); frames = stackFrameMap.get(error); } if (!frames) { return error.stack; } return prepareStackTraceWithSourceMap(error, frames); }