123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- 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
|