UNPKG

spinjs

Version:

[![Join the chat at https://gitter.im/sysgears/spinjs](https://badges.gitter.im/sysgears/spinjs.svg)](https://gitter.im/sysgears/spinjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![npm version](https://badge.fury.io/js/spi

196 lines (169 loc) 6.04 kB
/** * Copyright 2017-present, Callstack. * All rights reserved. * * MIT License * * Copyright (c) 2017 Mike Grabowski, SysGears INC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * @flow * * --- OVERVIEW --- * * When running in dev mode, React Native handles source map lookups by * asking the packager to do it. * * It does a POST to /symbolicate and passes the call stack and expects * back the same structure, but with the appropriate lines, columns & files. * * This is the express middleware which will handle that endpoint by reading * the source map that is tucked away inside webpack's in-memory filesystem. * */ import * as fetch from 'node-fetch'; import * as path from 'path'; import { SourceMapConsumer } from 'source-map'; /** * Creates a SourceMapConsumer so we can query it. */ async function createSourceMapConsumer(compiler: any, url: string, logger: any) { const response = await fetch(url); const sourceMap = await response.text(); // we stop here if we couldn't find that map if (!sourceMap) { logger.warn('Unable to find source map.'); return null; } // feed the raw source map into our consumer try { return new SourceMapConsumer(sourceMap); } catch (err) { logger.error('There was a problem reading the source map. :('); return null; } } /** * Gets the stack frames that React Native wants us to convert. */ function getRequestedFrames(req) { if (typeof req.rawBody !== 'string') { return null; } let stack; try { const payload = JSON.parse(req.rawBody); stack = payload.stack; } catch (err) { // should happen, but at least we won't die stack = null; } if (!stack) { return null; } const newStack = stack.filter(stackLine => { const { methodName } = stackLine; const unwantedStackRegExp = new RegExp(/(__webpack_require__|haul|eval(JS){0,1})/); if (unwantedStackRegExp.test(methodName)) { return false; } // we don't need those const evalLine = methodName.indexOf('Object../'); if (evalLine > -1) { const newMethodName = methodName.slice(evalLine + 9); // removing this prefix in method names stackLine.methodName = newMethodName; // eslint-disable-line } return true; }); return newStack; } /** * Create an Express middleware for handling React Native symbolication requests */ export default function create(compiler, logger) { /** * The Express middleware for symbolicating'. */ async function symbolicateMiddleware(req, res, next) { if (req.path !== '/symbolicate') { return next(); } // grab the source stack frames const unconvertedFrames = getRequestedFrames(req); if (!unconvertedFrames || unconvertedFrames.length === 0) { return next(); } // grab the platform and filename from the first frame (e.g. index.ios.bundle?platform=ios&dev=true&minify=false:69825:16) const filenameMatch = unconvertedFrames[0].file.match(/\/(\D+)\?/); const platformMatch = unconvertedFrames[0].file.match(/platform=([a-zA-Z]*)/); const filename: string = filenameMatch && filenameMatch[1]; const platform = platformMatch && platformMatch[1]; if (!filename || !platform) { return next(); } const [name, ...rest] = filename.split('.'); const bundleName = `${name}.mobile.${rest[rest.length - 1]}`; // grab our source map consumer const consumer = await createSourceMapConsumer( compiler, `http://localhost:${req.headers.host.split(':')[1]}/${bundleName}.map`, logger ); // console.log('C', consumer); if (!consumer) { return next(); } // the base directory const root = compiler.options.context; // error error on the wall, who's the fairest stack of all? const convertedFrames = unconvertedFrames.map(originalFrame => { // find the original home of this line of code. const lookup = consumer.originalPositionFor({ line: originalFrame.lineNumber, column: originalFrame.column }); // If lookup fails, we get the same shape object, but with // all values set to null if (lookup.source == null) { // It is better to gracefully return the original frame // than to throw an exception return originalFrame; } // convert the original source into an absolute path const mappedFile = lookup.source .replace('webpack:///~', path.resolve(root, 'node_modules')) .replace('webpack://', root); // convert these to a format which React Native wants return { lineNumber: lookup.line, column: lookup.column, file: mappedFile, methodName: originalFrame.methodName }; }); // send it back to React Native const responseObject = { stack: convertedFrames }; const response = JSON.stringify(responseObject); res.end(response); return null; } return symbolicateMiddleware; }