@sentry/browser
Version:
Official Sentry SDK for browsers
234 lines (231 loc) • 7.16 kB
JavaScript
import { defineIntegration, supportsNativeFetch, addFetchInstrumentationHandler, getClient, GLOBAL_OBJ, debug, captureEvent, isSentryRequestUrl, addExceptionMechanism } from '@sentry/core/browser';
import { addXhrInstrumentationHandler, SENTRY_XHR_DATA_KEY } from '@sentry-internal/browser-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,
setup(client) {
_wrapFetch(client, _options);
_wrapXHR(client, _options);
}
};
});
const httpClientIntegration = defineIntegration(_httpClientIntegration);
function _fetchResponseHandler(options, requestInfo, response, requestInit, error) {
if (_shouldCaptureResponse(options, response.status, response.url)) {
const request = _getRequest(requestInfo, requestInit);
let requestHeaders, responseHeaders, requestCookies, responseCookies;
if (_shouldSendDefaultPii()) {
[requestHeaders, requestCookies] = _parseCookieHeaders("Cookie", request);
[responseHeaders, responseCookies] = _parseCookieHeaders("Set-Cookie", response);
}
const event = _createEvent({
url: request.url,
method: request.method,
status: response.status,
requestHeaders,
responseHeaders,
requestCookies,
responseCookies,
error,
type: "fetch"
});
captureEvent(event);
}
}
function _parseCookieHeaders(cookieHeader, obj) {
const headers = _extractFetchHeaders(obj.headers);
let cookies;
try {
const cookieString = headers[cookieHeader] || headers[cookieHeader.toLowerCase()] || void 0;
if (cookieString) {
cookies = _parseCookieString(cookieString);
}
} catch {
}
return [headers, cookies];
}
function _xhrResponseHandler(options, xhr, method, headers, error) {
if (_shouldCaptureResponse(options, xhr.status, xhr.responseURL)) {
let requestHeaders, responseCookies, responseHeaders;
if (_shouldSendDefaultPii()) {
try {
const cookieString = xhr.getResponseHeader("Set-Cookie") || xhr.getResponseHeader("set-cookie") || void 0;
if (cookieString) {
responseCookies = _parseCookieString(cookieString);
}
} catch {
}
try {
responseHeaders = _getXHRResponseHeaders(xhr);
} catch {
}
requestHeaders = headers;
}
const event = _createEvent({
url: xhr.responseURL,
method,
status: xhr.status,
requestHeaders,
// Can't access request cookies from XHR
responseHeaders,
responseCookies,
error,
type: "xhr"
});
captureEvent(event);
}
}
function _getResponseSizeFromHeaders(headers) {
if (headers) {
const contentLength = headers["Content-Length"] || headers["content-length"];
if (contentLength) {
return parseInt(contentLength, 10);
}
}
return void 0;
}
function _parseCookieString(cookieString) {
return cookieString.split("; ").reduce((acc, cookie) => {
const [key, value] = cookie.split("=");
if (key && value) {
acc[key] = value;
}
return acc;
}, {});
}
function _extractFetchHeaders(headers) {
const result = {};
headers.forEach((value, key) => {
result[key] = value;
});
return result;
}
function _getXHRResponseHeaders(xhr) {
const headers = xhr.getAllResponseHeaders();
if (!headers) {
return {};
}
return headers.split("\r\n").reduce((acc, line) => {
const [key, value] = line.split(": ");
if (key && value) {
acc[key] = value;
}
return acc;
}, {});
}
function _isInGivenRequestTargets(failedRequestTargets, target) {
return failedRequestTargets.some((givenRequestTarget) => {
if (typeof givenRequestTarget === "string") {
return target.includes(givenRequestTarget);
}
return givenRequestTarget.test(target);
});
}
function _isInGivenStatusRanges(failedRequestStatusCodes, status) {
return failedRequestStatusCodes.some((range) => {
if (typeof range === "number") {
return range === status;
}
return status >= range[0] && status <= range[1];
});
}
function _wrapFetch(client, options) {
if (!supportsNativeFetch()) {
return;
}
addFetchInstrumentationHandler((handlerData) => {
if (getClient() !== client) {
return;
}
const { response, args, error, virtualError } = handlerData;
const [requestInfo, requestInit] = args;
if (!response) {
return;
}
_fetchResponseHandler(options, requestInfo, response, requestInit, error || virtualError);
}, false);
}
function _wrapXHR(client, options) {
if (!("XMLHttpRequest" in GLOBAL_OBJ)) {
return;
}
addXhrInstrumentationHandler((handlerData) => {
if (getClient() !== client) {
return;
}
const { error, virtualError } = handlerData;
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, error || virtualError);
} catch (e) {
DEBUG_BUILD && debug.warn("Error while extracting response event form XHR response", e);
}
});
}
function _shouldCaptureResponse(options, status, url) {
return _isInGivenStatusRanges(options.failedRequestStatusCodes, status) && _isInGivenRequestTargets(options.failedRequestTargets, url) && !isSentryRequestUrl(url, getClient());
}
function _createEvent(data) {
const client = getClient();
const virtualStackTrace = client && data.error && data.error instanceof Error ? data.error.stack : void 0;
const stack = virtualStackTrace && client ? client.getOptions().stackParser(virtualStackTrace, 0, 1) : void 0;
const message = `HTTP Client Error with status code: ${data.status}`;
const event = {
message,
exception: {
values: [
{
type: "Error",
value: message,
stacktrace: stack ? { frames: stack } : void 0
}
]
},
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: `auto.http.client.${data.type}`,
handled: false
});
return event;
}
function _getRequest(requestInfo, requestInit) {
if (!requestInit && requestInfo instanceof Request) {
return requestInfo;
}
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 { httpClientIntegration };
//# sourceMappingURL=httpclient.js.map