UNPKG

@sentry/utils

Version:
311 lines (278 loc) 8.82 kB
var { _optionalChain } = require('./buildPolyfills'); Object.defineProperty(exports, '__esModule', { value: true }); var is = require('./is.js'); var misc = require('./misc.js'); var normalize = require('./normalize.js'); var DEFAULT_INCLUDES = { ip: false, request: true, transaction: true, user: true, }; var DEFAULT_REQUEST_INCLUDES = ['cookies', 'data', 'headers', 'method', 'query_string', 'url']; var DEFAULT_USER_INCLUDES = ['id', 'username', 'email']; /** * Sets parameterized route as transaction name e.g.: `GET /users/:id` * Also adds more context data on the transaction from the request */ function addRequestDataToTransaction( transaction, req, deps, ) { if (!transaction) return; transaction.name = extractPathForTransaction(req, { path: true, method: true }); transaction.setData('url', req.originalUrl || req.url); if (req.baseUrl) { transaction.setData('baseUrl', req.baseUrl); } transaction.setData('query', extractQueryParams(req, deps)); } /** * Extracts complete generalized path from the request object and uses it to construct transaction name. * * eg. GET /mountpoint/user/:id * * @param req A request object * @param options What to include in the transaction name (method, path, or both) * * @returns The fully constructed transaction name */ function extractPathForTransaction( req, options = {}, ) { var method = req.method && req.method.toUpperCase(); let path = ''; // Check to see if there's a parameterized route we can use (as there is in Express) if (req.route) { path = `${req.baseUrl || ''}${req.route.path}`; } // Otherwise, just take the original URL else if (req.originalUrl || req.url) { path = misc.stripUrlQueryAndFragment(req.originalUrl || req.url || ''); } let info = ''; if (options.method && method) { info += method; } if (options.method && options.path) { info += ' '; } if (options.path && path) { info += path; } return info; } /** JSDoc */ function extractTransaction(req, type) { switch (type) { case 'path': { return extractPathForTransaction(req, { path: true }); } case 'handler': { return (req.route && req.route.stack && req.route.stack[0] && req.route.stack[0].name) || '<anonymous>'; } case 'methodPath': default: { return extractPathForTransaction(req, { path: true, method: true }); } } } /** JSDoc */ function extractUserData( user , keys, ) { var extractedUser = {}; var attributes = Array.isArray(keys) ? keys : DEFAULT_USER_INCLUDES; attributes.forEach(key => { if (user && key in user) { extractedUser[key] = user[key]; } }); return extractedUser; } /** * Normalize data from the request object, accounting for framework differences. * * @param req The request object from which to extract data * @param options.include An optional array of keys to include in the normalized data. Defaults to * DEFAULT_REQUEST_INCLUDES if not provided. * @param options.deps Injected, platform-specific dependencies * @returns An object containing normalized request data */ function extractRequestData( req, options , ) { const { include = DEFAULT_REQUEST_INCLUDES, deps } = options || {}; var requestData = {}; // headers: // node, express, koa, nextjs: req.headers var headers = (req.headers || {}) ; // method: // node, express, koa, nextjs: req.method var method = req.method; // host: // express: req.hostname in > 4 and req.host in < 4 // koa: req.host // node, nextjs: req.headers.host var host = req.hostname || req.host || headers.host || '<no host>'; // protocol: // node, nextjs: <n/a> // express, koa: req.protocol var protocol = req.protocol === 'https' || (req.socket && req.socket.encrypted) ? 'https' : 'http'; // url (including path and query string): // node, express: req.originalUrl // koa, nextjs: req.url var originalUrl = req.originalUrl || req.url || ''; // absolute url var absoluteUrl = `${protocol}://${host}${originalUrl}`; include.forEach(key => { switch (key) { case 'headers': { requestData.headers = headers; break; } case 'method': { requestData.method = method; break; } case 'url': { requestData.url = absoluteUrl; break; } case 'cookies': { // cookies: // node, express, koa: req.headers.cookie // vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies requestData.cookies = // TODO (v8 / #5257): We're only sending the empty object for backwards compatibility, so the last bit can // come off in v8 req.cookies || (headers.cookie && deps && deps.cookie && deps.cookie.parse(headers.cookie)) || {}; break; } case 'query_string': { // query string: // node: req.url (raw) // express, koa, nextjs: req.query requestData.query_string = extractQueryParams(req, deps); break; } case 'data': { if (method === 'GET' || method === 'HEAD') { break; } // body data: // express, koa, nextjs: req.body // // when using node by itself, you have to read the incoming stream(see // https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know // where they're going to store the final result, so they'll have to capture this data themselves if (req.body !== undefined) { requestData.data = is.isString(req.body) ? req.body : JSON.stringify(normalize.normalize(req.body)); } break; } default: { if ({}.hasOwnProperty.call(req, key)) { requestData[key] = (req )[key]; } } } }); return requestData; } /** * Options deciding what parts of the request to use when enhancing an event */ /** * Add data from the given request to the given event * * @param event The event to which the request data will be added * @param req Request object * @param options.include Flags to control what data is included * @param options.deps Injected platform-specific dependencies * @hidden */ function addRequestDataToEvent( event, req, options, ) { var include = { ...DEFAULT_INCLUDES, ..._optionalChain([options, 'optionalAccess', _ => _.include]), }; if (include.request) { var extractedRequestData = Array.isArray(include.request) ? extractRequestData(req, { include: include.request, deps: _optionalChain([options, 'optionalAccess', _2 => _2.deps]) }) : extractRequestData(req, { deps: _optionalChain([options, 'optionalAccess', _3 => _3.deps]) }); event.request = { ...event.request, ...extractedRequestData, }; } if (include.user) { var extractedUser = req.user && is.isPlainObject(req.user) ? extractUserData(req.user, include.user) : {}; if (Object.keys(extractedUser).length) { event.user = { ...event.user, ...extractedUser, }; } } // client ip: // node, nextjs: req.socket.remoteAddress // express, koa: req.ip if (include.ip) { var ip = req.ip || (req.socket && req.socket.remoteAddress); if (ip) { event.user = { ...event.user, ip_address: ip, }; } } if (include.transaction && !event.transaction) { // TODO do we even need this anymore? // TODO make this work for nextjs event.transaction = extractTransaction(req, include.transaction); } return event; } function extractQueryParams( req, deps, ) { // url (including path and query string): // node, express: req.originalUrl // koa, nextjs: req.url let originalUrl = req.originalUrl || req.url || ''; if (!originalUrl) { return; } // The `URL` constructor can't handle internal URLs of the form `/some/path/here`, so stick a dummy protocol and // hostname on the beginning. Since the point here is just to grab the query string, it doesn't matter what we use. if (originalUrl.startsWith('/')) { originalUrl = `http://dogs.are.great${originalUrl}`; } return ( req.query || (typeof URL !== undefined && new URL(originalUrl).search.replace('?', '')) || // In Node 8, `URL` isn't in the global scope, so we have to use the built-in module from Node (deps && deps.url && deps.url.parse(originalUrl).query) || undefined ); } exports.addRequestDataToEvent = addRequestDataToEvent; exports.addRequestDataToTransaction = addRequestDataToTransaction; exports.extractPathForTransaction = extractPathForTransaction; exports.extractRequestData = extractRequestData; //# sourceMappingURL=requestdata.js.map