@sentry/react-native
Version:
Official Sentry SDK for react-native
123 lines • 5 kB
JavaScript
import { dropUndefinedKeys } from '@sentry/core';
import { filterHeaders, getBodySize, getBodyString, parseAllResponseHeaders, parseContentLengthHeader, shouldCaptureNetworkDetails, } from './networkUtils';
const DEFAULT_NETWORK_OPTIONS = {
allowUrls: [],
denyUrls: [],
captureBodies: false,
requestHeaders: [],
responseHeaders: [],
};
/**
* Build a `beforeAddBreadcrumb` handler that enriches XHR breadcrumbs with
* network details (sizes always; headers/bodies for URLs matching the allow
* list and not the deny list) for the Mobile Replay network tab.
*/
export function makeEnrichXhrBreadcrumbsForMobileReplay(networkOptions) {
return (breadcrumb, hint) => enrichXhrBreadcrumb(breadcrumb, hint, networkOptions);
}
/**
* Enrich an XHR breadcrumb with additional data for Mobile Replay network tab.
* Preserves the legacy behaviour: sizes only, no headers/bodies.
*/
export function enrichXhrBreadcrumbsForMobileReplay(breadcrumb, hint) {
enrichXhrBreadcrumb(breadcrumb, hint, DEFAULT_NETWORK_OPTIONS);
}
function enrichXhrBreadcrumb(breadcrumb, hint, networkOptions) {
var _a;
if (breadcrumb.category !== 'xhr' || !hint) {
return;
}
const xhrHint = hint;
if (!xhrHint.xhr) {
return;
}
const now = Date.now();
const { startTimestamp = now, endTimestamp = now, input, xhr } = xhrHint;
const reqSize = getBodySize(input);
const resSize = xhr.getResponseHeader('content-length')
? parseContentLengthHeader(xhr.getResponseHeader('content-length'))
: _getBodySize(xhr.response, xhr.responseType);
let request;
let response;
const url = typeof ((_a = breadcrumb.data) === null || _a === void 0 ? void 0 : _a.url) === 'string' ? breadcrumb.data.url : undefined;
if (shouldCaptureNetworkDetails(url, networkOptions)) {
request = _buildRequestDetails(input, xhr, networkOptions);
response = _buildResponseDetails(xhr, networkOptions);
}
breadcrumb.data = dropUndefinedKeys(Object.assign(Object.assign({ start_timestamp: startTimestamp, end_timestamp: endTimestamp, request_body_size: reqSize, response_body_size: resSize }, breadcrumb.data), { request,
response }));
}
function _buildRequestDetails(input, xhr, networkOptions) {
const sentryXhr = xhr.__sentry_xhr_v3__;
const headers = filterHeaders(sentryXhr === null || sentryXhr === void 0 ? void 0 : sentryXhr.request_headers, networkOptions.requestHeaders);
let body;
if (networkOptions.captureBodies) {
body = getBodyString(input);
}
return _toBreadcrumbSide(headers, body);
}
function _buildResponseDetails(xhr, networkOptions) {
let rawHeaders = null;
try {
rawHeaders = xhr.getAllResponseHeaders();
}
catch (_a) {
// ignore — some environments may throw before the request is complete
}
const headers = filterHeaders(parseAllResponseHeaders(rawHeaders), networkOptions.responseHeaders);
let body;
if (networkOptions.captureBodies) {
body = _getResponseBodyString(xhr);
}
return _toBreadcrumbSide(headers, body);
}
function _toBreadcrumbSide(headers, body) {
const side = {};
if (headers) {
side.headers = headers;
}
if ((body === null || body === void 0 ? void 0 : body.body) !== undefined) {
side.body = body.body;
}
if (body === null || body === void 0 ? void 0 : body._meta) {
side._meta = body._meta;
// Native converters strip `_meta` before forwarding the side to the rrweb
// span (the native replay SDKs don't know about it). Materialize a
// placeholder `body` whenever we have a warning but no concrete body so
// the signal (e.g. UNPARSEABLE_BODY_TYPE) still surfaces in Session
// Replay instead of being silently dropped natively.
if (side.body === undefined) {
side.body = `[${body._meta.warnings.join(', ')}]`;
}
}
return Object.keys(side).length > 0 ? side : undefined;
}
function _getResponseBodyString(xhr) {
try {
if (xhr.responseType === '' || xhr.responseType === 'text') {
// responseText only exists for text/empty responseType
return getBodyString(xhr.responseText);
}
if (xhr.responseType === 'json') {
return getBodyString(xhr.response);
}
if (xhr.response == null) {
return undefined;
}
// For 'blob' / 'arraybuffer' / 'document' we don't attempt to inline binary data.
return { _meta: { warnings: ['UNPARSEABLE_BODY_TYPE'] } };
}
catch (_a) {
return { _meta: { warnings: ['UNPARSEABLE_BODY_TYPE'] } };
}
}
function _getBodySize(body, responseType) {
try {
const bodyStr = responseType === 'json' && body && typeof body === 'object' ? JSON.stringify(body) : body;
return getBodySize(bodyStr);
}
catch (_a) {
return undefined;
}
}
//# sourceMappingURL=xhrUtils.js.map