@sentry/react-native
Version:
Official Sentry SDK for react-native
150 lines • 7.11 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withSentryMiddleware = exports.createSentryMetroMiddleware = exports.createStackFramesContextMiddleware = void 0;
const core_1 = require("@sentry/core");
const fs_1 = require("fs");
const path = require("path");
const util_1 = require("util");
const constants_1 = require("../metro/constants");
const getRawBody_1 = require("../metro/getRawBody");
const openUrlMiddleware_1 = require("../metro/openUrlMiddleware");
const readFileAsync = (0, util_1.promisify)(fs_1.readFile);
const realpathAsync = (0, util_1.promisify)(fs_1.realpath);
/**
* Accepts Sentry formatted stack frames and
* adds source context to the in app frames.
*
* Relative filenames are resolved against the first entry in `allowedRoots`.
* Both the resolved filename and the allowed roots are canonicalized via
* `fs.realpath`, so a symlink inside an allowed root pointing outside of it
* cannot escape the containment check.
*/
const createStackFramesContextMiddleware = (allowedRoots) => {
const canonicalRoots = allowedRoots.map(root => {
const resolved = path.resolve(root);
try {
return (0, fs_1.realpathSync)(resolved);
}
catch (_a) {
return resolved;
}
});
return (request, response, _next) => __awaiter(void 0, void 0, void 0, function* () {
core_1.debug.log('[@sentry/react-native/metro] Received request for stack frames context.');
request.setEncoding('utf8');
const rawBody = yield (0, getRawBody_1.getRawBody)(request);
let body = {};
try {
body = JSON.parse(rawBody);
}
catch (e) {
core_1.debug.log('[@sentry/react-native/metro] Could not parse request body.', e);
badRequest(response, 'Invalid request body. Expected a JSON object.');
return;
}
const stack = body.stack;
if (!Array.isArray(stack)) {
core_1.debug.log('[@sentry/react-native/metro] Invalid stack frames.', stack);
badRequest(response, 'Invalid stack frames. Expected an array.');
return;
}
const stackWithSourceContext = yield Promise.all(stack.map(frame => addSourceContext(frame, canonicalRoots)));
response.setHeader('Content-Type', 'application/json');
response.statusCode = 200;
response.end(JSON.stringify({ stack: stackWithSourceContext }));
core_1.debug.log('[@sentry/react-native/metro] Sent stack frames context.');
});
};
exports.createStackFramesContextMiddleware = createStackFramesContextMiddleware;
function addSourceContext(frame, canonicalRoots) {
return __awaiter(this, void 0, void 0, function* () {
if (!frame.in_app) {
return frame;
}
try {
if (typeof frame.filename !== 'string') {
core_1.debug.warn('[@sentry/react-native/metro] Could not read source context for frame without filename.');
return frame;
}
if (canonicalRoots.length === 0) {
core_1.debug.warn('[@sentry/react-native/metro] Skipping frame: no allowed roots configured.');
return frame;
}
const resolvedPath = path.resolve(canonicalRoots[0], frame.filename);
let canonicalPath;
try {
canonicalPath = yield realpathAsync(resolvedPath);
}
catch (_a) {
core_1.debug.warn('[@sentry/react-native/metro] Skipping frame: could not canonicalize filename.');
return frame;
}
const isInside = canonicalRoots.some(root => {
const relative = path.relative(root, canonicalPath);
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
});
if (!isInside) {
core_1.debug.warn('[@sentry/react-native/metro] Skipping frame whose filename is outside the allowed roots.');
return frame;
}
const source = yield readFileAsync(canonicalPath, { encoding: 'utf8' });
const lines = source.split('\n');
(0, core_1.addContextToFrame)(lines, frame);
}
catch (error) {
core_1.debug.warn('[@sentry/react-native/metro] Could not read source context for frame.', error);
}
return frame;
});
}
function badRequest(response, message) {
response.statusCode = 400;
response.end(message);
}
/**
* Creates a middleware that adds source context to the Sentry formatted stack frames.
*/
const createSentryMetroMiddleware = (middleware, allowedRoots) => {
const stackFramesContextMiddleware = (0, exports.createStackFramesContextMiddleware)(allowedRoots);
return (request, response, next) => {
var _a, _b;
if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.startsWith(`/${constants_1.SENTRY_CONTEXT_REQUEST_PATH}`)) {
return stackFramesContextMiddleware(request, response, next);
}
else if ((_b = request.url) === null || _b === void 0 ? void 0 : _b.startsWith(`/${constants_1.SENTRY_OPEN_URL_REQUEST_PATH}`)) {
return (0, openUrlMiddleware_1.openURLMiddleware)(request, response);
}
return middleware(request, response, next);
};
};
exports.createSentryMetroMiddleware = createSentryMetroMiddleware;
/**
* Adds the Sentry middleware to the Metro server config.
*/
const withSentryMiddleware = (config) => {
if (!config.server) {
// @ts-expect-error server is typed read only
config.server = {};
}
const projectRoot = config.projectRoot || process.cwd();
const watchFolders = config.watchFolders || [];
const allowedRoots = [projectRoot, ...watchFolders];
const originalEnhanceMiddleware = config.server.enhanceMiddleware;
// @ts-expect-error enhanceMiddleware is typed read only in metro 0.84+
config.server.enhanceMiddleware = (middleware, server) => {
const sentryMiddleware = (0, exports.createSentryMetroMiddleware)(middleware, allowedRoots);
return originalEnhanceMiddleware ? originalEnhanceMiddleware(sentryMiddleware, server) : sentryMiddleware;
};
return config;
};
exports.withSentryMiddleware = withSentryMiddleware;
//# sourceMappingURL=metroMiddleware.js.map