rittenhop-dev/versions/5.94.2/node_modules/@sentry/integrations/esm/httpclient.js
2024-09-23 19:40:12 -04:00

385 lines
9.4 KiB
JavaScript

import { defineIntegration, convertIntegrationFnToClass, getClient, captureEvent, isSentryRequestUrl } from '@sentry/core';
import { supportsNativeFetch, addFetchInstrumentationHandler, GLOBAL_OBJ, addXhrInstrumentationHandler, SENTRY_XHR_DATA_KEY, logger, addExceptionMechanism } from '@sentry/utils';
import { DEBUG_BUILD } from './debug-build.js';
const INTEGRATION_NAME = 'HttpClient';
const _httpClientIntegration = ((options = {}) => {
const _options = {
failedRequestStatusCodes: [[500, 599]],
failedRequestTargets: [/.*/],
...options,
};
return {
name: INTEGRATION_NAME,
// TODO v8: Remove this
setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
setup(client) {
_wrapFetch(client, _options);
_wrapXHR(client, _options);
},
};
}) ;
const httpClientIntegration = defineIntegration(_httpClientIntegration);
/**
* Create events for failed client side HTTP requests.
* @deprecated Use `httpClientIntegration()` instead.
*/
// eslint-disable-next-line deprecation/deprecation
const HttpClient = convertIntegrationFnToClass(INTEGRATION_NAME, httpClientIntegration)
;
/**
* Interceptor function for fetch requests
*
* @param requestInfo The Fetch API request info
* @param response The Fetch API response
* @param requestInit The request init object
*/
function _fetchResponseHandler(
options,
requestInfo,
response,
requestInit,
) {
if (_shouldCaptureResponse(options, response.status, response.url)) {
const request = _getRequest(requestInfo, requestInit);
let requestHeaders, responseHeaders, requestCookies, responseCookies;
if (_shouldSendDefaultPii()) {
[{ headers: requestHeaders, cookies: requestCookies }, { headers: responseHeaders, cookies: responseCookies }] = [
{ cookieHeader: 'Cookie', obj: request },
{ cookieHeader: 'Set-Cookie', obj: response },
].map(({ cookieHeader, obj }) => {
const headers = _extractFetchHeaders(obj.headers);
let cookies;
try {
const cookieString = headers[cookieHeader] || headers[cookieHeader.toLowerCase()] || undefined;
if (cookieString) {
cookies = _parseCookieString(cookieString);
}
} catch (e) {
DEBUG_BUILD && logger.log(`Could not extract cookies from header ${cookieHeader}`);
}
return {
headers,
cookies,
};
});
}
const event = _createEvent({
url: request.url,
method: request.method,
status: response.status,
requestHeaders,
responseHeaders,
requestCookies,
responseCookies,
});
captureEvent(event);
}
}
/**
* Interceptor function for XHR requests
*
* @param xhr The XHR request
* @param method The HTTP method
* @param headers The HTTP headers
*/
function _xhrResponseHandler(
options,
xhr,
method,
headers,
) {
if (_shouldCaptureResponse(options, xhr.status, xhr.responseURL)) {
let requestHeaders, responseCookies, responseHeaders;
if (_shouldSendDefaultPii()) {
try {
const cookieString = xhr.getResponseHeader('Set-Cookie') || xhr.getResponseHeader('set-cookie') || undefined;
if (cookieString) {
responseCookies = _parseCookieString(cookieString);
}
} catch (e) {
DEBUG_BUILD && logger.log('Could not extract cookies from response headers');
}
try {
responseHeaders = _getXHRResponseHeaders(xhr);
} catch (e) {
DEBUG_BUILD && logger.log('Could not extract headers from response');
}
requestHeaders = headers;
}
const event = _createEvent({
url: xhr.responseURL,
method,
status: xhr.status,
requestHeaders,
// Can't access request cookies from XHR
responseHeaders,
responseCookies,
});
captureEvent(event);
}
}
/**
* Extracts response size from `Content-Length` header when possible
*
* @param headers
* @returns The response size in bytes or undefined
*/
function _getResponseSizeFromHeaders(headers) {
if (headers) {
const contentLength = headers['Content-Length'] || headers['content-length'];
if (contentLength) {
return parseInt(contentLength, 10);
}
}
return undefined;
}
/**
* Creates an object containing cookies from the given cookie string
*
* @param cookieString The cookie string to parse
* @returns The parsed cookies
*/
function _parseCookieString(cookieString) {
return cookieString.split('; ').reduce((acc, cookie) => {
const [key, value] = cookie.split('=');
acc[key] = value;
return acc;
}, {});
}
/**
* Extracts the headers as an object from the given Fetch API request or response object
*
* @param headers The headers to extract
* @returns The extracted headers as an object
*/
function _extractFetchHeaders(headers) {
const result = {};
headers.forEach((value, key) => {
result[key] = value;
});
return result;
}
/**
* Extracts the response headers as an object from the given XHR object
*
* @param xhr The XHR object to extract the response headers from
* @returns The response headers as an object
*/
function _getXHRResponseHeaders(xhr) {
const headers = xhr.getAllResponseHeaders();
if (!headers) {
return {};
}
return headers.split('\r\n').reduce((acc, line) => {
const [key, value] = line.split(': ');
acc[key] = value;
return acc;
}, {});
}
/**
* Checks if the given target url is in the given list of targets
*
* @param target The target url to check
* @returns true if the target url is in the given list of targets, false otherwise
*/
function _isInGivenRequestTargets(
failedRequestTargets,
target,
) {
return failedRequestTargets.some((givenRequestTarget) => {
if (typeof givenRequestTarget === 'string') {
return target.includes(givenRequestTarget);
}
return givenRequestTarget.test(target);
});
}
/**
* Checks if the given status code is in the given range
*
* @param status The status code to check
* @returns true if the status code is in the given range, false otherwise
*/
function _isInGivenStatusRanges(
failedRequestStatusCodes,
status,
) {
return failedRequestStatusCodes.some((range) => {
if (typeof range === 'number') {
return range === status;
}
return status >= range[0] && status <= range[1];
});
}
/**
* Wraps `fetch` function to capture request and response data
*/
function _wrapFetch(client, options) {
if (!supportsNativeFetch()) {
return;
}
addFetchInstrumentationHandler(handlerData => {
if (getClient() !== client) {
return;
}
const { response, args } = handlerData;
const [requestInfo, requestInit] = args ;
if (!response) {
return;
}
_fetchResponseHandler(options, requestInfo, response , requestInit);
});
}
/**
* Wraps XMLHttpRequest to capture request and response data
*/
function _wrapXHR(client, options) {
if (!('XMLHttpRequest' in GLOBAL_OBJ)) {
return;
}
addXhrInstrumentationHandler(handlerData => {
if (getClient() !== client) {
return;
}
const xhr = handlerData.xhr ;
const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY];
if (!sentryXhrData) {
return;
}
const { method, request_headers: headers } = sentryXhrData;
try {
_xhrResponseHandler(options, xhr, method, headers);
} catch (e) {
DEBUG_BUILD && logger.warn('Error while extracting response event form XHR response', e);
}
});
}
/**
* Checks whether to capture given response as an event
*
* @param status response status code
* @param url response url
*/
function _shouldCaptureResponse(options, status, url) {
return (
_isInGivenStatusRanges(options.failedRequestStatusCodes, status) &&
_isInGivenRequestTargets(options.failedRequestTargets, url) &&
!isSentryRequestUrl(url, getClient())
);
}
/**
* Creates a synthetic Sentry event from given response data
*
* @param data response data
* @returns event
*/
function _createEvent(data
) {
const message = `HTTP Client Error with status code: ${data.status}`;
const event = {
message,
exception: {
values: [
{
type: 'Error',
value: message,
},
],
},
request: {
url: data.url,
method: data.method,
headers: data.requestHeaders,
cookies: data.requestCookies,
},
contexts: {
response: {
status_code: data.status,
headers: data.responseHeaders,
cookies: data.responseCookies,
body_size: _getResponseSizeFromHeaders(data.responseHeaders),
},
},
};
addExceptionMechanism(event, {
type: 'http.client',
handled: false,
});
return event;
}
function _getRequest(requestInfo, requestInit) {
if (!requestInit && requestInfo instanceof Request) {
return requestInfo;
}
// If both are set, we try to construct a new Request with the given arguments
// However, if e.g. the original request has a `body`, this will throw an error because it was already accessed
// In this case, as a fallback, we just use the original request - using both is rather an edge case
if (requestInfo instanceof Request && requestInfo.bodyUsed) {
return requestInfo;
}
return new Request(requestInfo, requestInit);
}
function _shouldSendDefaultPii() {
const client = getClient();
return client ? Boolean(client.getOptions().sendDefaultPii) : false;
}
export { HttpClient, httpClientIntegration };
//# sourceMappingURL=httpclient.js.map