summaryrefslogtreecommitdiff
path: root/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js
diff options
context:
space:
mode:
Diffstat (limited to 'shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js')
-rw-r--r--shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js335
1 files changed, 335 insertions, 0 deletions
diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js
new file mode 100644
index 0000000..d505546
--- /dev/null
+++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js
@@ -0,0 +1,335 @@
+import { _optionalChain } from '@sentry/utils/esm/buildPolyfills';
+import { hasTracingEnabled, getCurrentHub } from '@sentry/core';
+import { addInstrumentationHandler, browserPerformanceTimeOrigin, dynamicSamplingContextToSentryBaggageHeader, isInstanceOf, BAGGAGE_HEADER_NAME, SENTRY_XHR_DATA_KEY, stringMatchesSomePattern } from '@sentry/utils';
+
+/* eslint-disable max-lines */
+
+const DEFAULT_TRACE_PROPAGATION_TARGETS = ['localhost', /^\/(?!\/)/];
+
+/** Options for Request Instrumentation */
+
+const defaultRequestInstrumentationOptions = {
+ traceFetch: true,
+ traceXHR: true,
+ // TODO (v8): Remove this property
+ tracingOrigins: DEFAULT_TRACE_PROPAGATION_TARGETS,
+ tracePropagationTargets: DEFAULT_TRACE_PROPAGATION_TARGETS,
+ _experiments: {},
+};
+
+/** Registers span creators for xhr and fetch requests */
+function instrumentOutgoingRequests(_options) {
+ // eslint-disable-next-line deprecation/deprecation
+ const { traceFetch, traceXHR, tracePropagationTargets, tracingOrigins, shouldCreateSpanForRequest, _experiments } = {
+ traceFetch: defaultRequestInstrumentationOptions.traceFetch,
+ traceXHR: defaultRequestInstrumentationOptions.traceXHR,
+ ..._options,
+ };
+
+ const shouldCreateSpan =
+ typeof shouldCreateSpanForRequest === 'function' ? shouldCreateSpanForRequest : (_) => true;
+
+ // TODO(v8) Remove tracingOrigins here
+ // The only reason we're passing it in here is because this instrumentOutgoingRequests function is publicly exported
+ // and we don't want to break the API. We can remove it in v8.
+ const shouldAttachHeadersWithTargets = (url) =>
+ shouldAttachHeaders(url, tracePropagationTargets || tracingOrigins);
+
+ const spans = {};
+
+ if (traceFetch) {
+ addInstrumentationHandler('fetch', (handlerData) => {
+ const createdSpan = fetchCallback(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans);
+ if (_optionalChain([_experiments, 'optionalAccess', _2 => _2.enableHTTPTimings]) && createdSpan) {
+ addHTTPTimings(createdSpan);
+ }
+ });
+ }
+
+ if (traceXHR) {
+ addInstrumentationHandler('xhr', (handlerData) => {
+ const createdSpan = xhrCallback(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans);
+ if (_optionalChain([_experiments, 'optionalAccess', _3 => _3.enableHTTPTimings]) && createdSpan) {
+ addHTTPTimings(createdSpan);
+ }
+ });
+ }
+}
+
+/**
+ * Creates a temporary observer to listen to the next fetch/xhr resourcing timings,
+ * so that when timings hit their per-browser limit they don't need to be removed.
+ *
+ * @param span A span that has yet to be finished, must contain `url` on data.
+ */
+function addHTTPTimings(span) {
+ const url = span.data.url;
+ const observer = new PerformanceObserver(list => {
+ const entries = list.getEntries() ;
+ entries.forEach(entry => {
+ if ((entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') && entry.name.endsWith(url)) {
+ const spanData = resourceTimingEntryToSpanData(entry);
+ spanData.forEach(data => span.setData(...data));
+ observer.disconnect();
+ }
+ });
+ });
+ observer.observe({
+ entryTypes: ['resource'],
+ });
+}
+
+function resourceTimingEntryToSpanData(resourceTiming) {
+ const version = resourceTiming.nextHopProtocol.split('/')[1] || 'none';
+
+ const timingSpanData = [];
+ if (version) {
+ timingSpanData.push(['network.protocol.version', version]);
+ }
+
+ if (!browserPerformanceTimeOrigin) {
+ return timingSpanData;
+ }
+ return [
+ ...timingSpanData,
+ ['http.request.connect_start', (browserPerformanceTimeOrigin + resourceTiming.connectStart) / 1000],
+ ['http.request.request_start', (browserPerformanceTimeOrigin + resourceTiming.requestStart) / 1000],
+ ['http.request.response_start', (browserPerformanceTimeOrigin + resourceTiming.responseStart) / 1000],
+ ];
+}
+
+/**
+ * A function that determines whether to attach tracing headers to a request.
+ * This was extracted from `instrumentOutgoingRequests` to make it easier to test shouldAttachHeaders.
+ * We only export this fuction for testing purposes.
+ */
+function shouldAttachHeaders(url, tracePropagationTargets) {
+ return stringMatchesSomePattern(url, tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS);
+}
+
+/**
+ * Create and track fetch request spans
+ *
+ * @returns Span if a span was created, otherwise void.
+ */
+function fetchCallback(
+ handlerData,
+ shouldCreateSpan,
+ shouldAttachHeaders,
+ spans,
+) {
+ if (!hasTracingEnabled() || !(handlerData.fetchData && shouldCreateSpan(handlerData.fetchData.url))) {
+ return;
+ }
+
+ if (handlerData.endTimestamp) {
+ const spanId = handlerData.fetchData.__span;
+ if (!spanId) return;
+
+ const span = spans[spanId];
+ if (span) {
+ if (handlerData.response) {
+ // TODO (kmclb) remove this once types PR goes through
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ span.setHttpStatus(handlerData.response.status);
+
+ const contentLength =
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ handlerData.response && handlerData.response.headers && handlerData.response.headers.get('content-length');
+
+ const contentLengthNum = parseInt(contentLength);
+ if (contentLengthNum > 0) {
+ span.setData('http.response_content_length', contentLengthNum);
+ }
+ } else if (handlerData.error) {
+ span.setStatus('internal_error');
+ }
+ span.finish();
+
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete spans[spanId];
+ }
+ return;
+ }
+
+ const currentSpan = getCurrentHub().getScope().getSpan();
+ const activeTransaction = currentSpan && currentSpan.transaction;
+
+ if (currentSpan && activeTransaction) {
+ const { method, url } = handlerData.fetchData;
+ const span = currentSpan.startChild({
+ data: {
+ url,
+ type: 'fetch',
+ 'http.method': method,
+ },
+ description: `${method} ${url}`,
+ op: 'http.client',
+ });
+
+ handlerData.fetchData.__span = span.spanId;
+ spans[span.spanId] = span;
+
+ const request = handlerData.args[0];
+
+ // In case the user hasn't set the second argument of a fetch call we default it to `{}`.
+ handlerData.args[1] = handlerData.args[1] || {};
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const options = handlerData.args[1];
+
+ if (shouldAttachHeaders(handlerData.fetchData.url)) {
+ options.headers = addTracingHeadersToFetchRequest(
+ request,
+ activeTransaction.getDynamicSamplingContext(),
+ span,
+ options,
+ );
+ }
+ return span;
+ }
+}
+
+/**
+ * Adds sentry-trace and baggage headers to the various forms of fetch headers
+ */
+function addTracingHeadersToFetchRequest(
+ request, // unknown is actually type Request but we can't export DOM types from this package,
+ dynamicSamplingContext,
+ span,
+ options
+
+,
+) {
+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
+ const sentryTraceHeader = span.toTraceparent();
+
+ const headers =
+ typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request ).headers : options.headers;
+
+ if (!headers) {
+ return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader };
+ } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) {
+ const newHeaders = new Headers(headers );
+
+ newHeaders.append('sentry-trace', sentryTraceHeader);
+
+ if (sentryBaggageHeader) {
+ // If the same header is appended multiple times the browser will merge the values into a single request header.
+ // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
+ newHeaders.append(BAGGAGE_HEADER_NAME, sentryBaggageHeader);
+ }
+
+ return newHeaders ;
+ } else if (Array.isArray(headers)) {
+ const newHeaders = [...headers, ['sentry-trace', sentryTraceHeader]];
+
+ if (sentryBaggageHeader) {
+ // If there are multiple entries with the same key, the browser will merge the values into a single request header.
+ // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
+ newHeaders.push([BAGGAGE_HEADER_NAME, sentryBaggageHeader]);
+ }
+
+ return newHeaders ;
+ } else {
+ const existingBaggageHeader = 'baggage' in headers ? headers.baggage : undefined;
+ const newBaggageHeaders = [];
+
+ if (Array.isArray(existingBaggageHeader)) {
+ newBaggageHeaders.push(...existingBaggageHeader);
+ } else if (existingBaggageHeader) {
+ newBaggageHeaders.push(existingBaggageHeader);
+ }
+
+ if (sentryBaggageHeader) {
+ newBaggageHeaders.push(sentryBaggageHeader);
+ }
+
+ return {
+ ...(headers ),
+ 'sentry-trace': sentryTraceHeader,
+ baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined,
+ };
+ }
+}
+
+/**
+ * Create and track xhr request spans
+ *
+ * @returns Span if a span was created, otherwise void.
+ */
+function xhrCallback(
+ handlerData,
+ shouldCreateSpan,
+ shouldAttachHeaders,
+ spans,
+) {
+ const xhr = handlerData.xhr;
+ const sentryXhrData = xhr && xhr[SENTRY_XHR_DATA_KEY];
+
+ if (
+ !hasTracingEnabled() ||
+ (xhr && xhr.__sentry_own_request__) ||
+ !(xhr && sentryXhrData && shouldCreateSpan(sentryXhrData.url))
+ ) {
+ return;
+ }
+
+ // check first if the request has finished and is tracked by an existing span which should now end
+ if (handlerData.endTimestamp) {
+ const spanId = xhr.__sentry_xhr_span_id__;
+ if (!spanId) return;
+
+ const span = spans[spanId];
+ if (span) {
+ span.setHttpStatus(sentryXhrData.status_code);
+ span.finish();
+
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete spans[spanId];
+ }
+ return;
+ }
+
+ const currentSpan = getCurrentHub().getScope().getSpan();
+ const activeTransaction = currentSpan && currentSpan.transaction;
+
+ if (currentSpan && activeTransaction) {
+ const span = currentSpan.startChild({
+ data: {
+ ...sentryXhrData.data,
+ type: 'xhr',
+ 'http.method': sentryXhrData.method,
+ url: sentryXhrData.url,
+ },
+ description: `${sentryXhrData.method} ${sentryXhrData.url}`,
+ op: 'http.client',
+ });
+
+ xhr.__sentry_xhr_span_id__ = span.spanId;
+ spans[xhr.__sentry_xhr_span_id__] = span;
+
+ if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
+ try {
+ xhr.setRequestHeader('sentry-trace', span.toTraceparent());
+
+ const dynamicSamplingContext = activeTransaction.getDynamicSamplingContext();
+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
+
+ if (sentryBaggageHeader) {
+ // From MDN: "If this method is called several times with the same header, the values are merged into one single request header."
+ // We can therefore simply set a baggage header without checking what was there before
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
+ xhr.setRequestHeader(BAGGAGE_HEADER_NAME, sentryBaggageHeader);
+ }
+ } catch (_) {
+ // Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
+ }
+ }
+
+ return span;
+ }
+}
+
+export { DEFAULT_TRACE_PROPAGATION_TARGETS, addTracingHeadersToFetchRequest, defaultRequestInstrumentationOptions, instrumentOutgoingRequests, shouldAttachHeaders };
+//# sourceMappingURL=request.js.map