123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- Object.defineProperty(exports, '__esModule', { value: true });
- const core = require('@sentry/core');
- const utils = require('@sentry/utils');
- const fetch = require('../common/fetch.js');
- const instrument = require('./instrument.js');
- /* eslint-disable max-lines */
- const DEFAULT_TRACE_PROPAGATION_TARGETS = ['localhost', /^\/(?!\/)/];
- /** Options for Request Instrumentation */
- const defaultRequestInstrumentationOptions = {
- traceFetch: true,
- traceXHR: true,
- enableHTTPTimings: true,
- // TODO (v8): Remove this property
- tracingOrigins: DEFAULT_TRACE_PROPAGATION_TARGETS,
- tracePropagationTargets: DEFAULT_TRACE_PROPAGATION_TARGETS,
- };
- /** Registers span creators for xhr and fetch requests */
- function instrumentOutgoingRequests(_options) {
- const {
- traceFetch,
- traceXHR,
- // eslint-disable-next-line deprecation/deprecation
- tracePropagationTargets,
- // eslint-disable-next-line deprecation/deprecation
- tracingOrigins,
- shouldCreateSpanForRequest,
- enableHTTPTimings,
- } = {
- 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) {
- utils.addFetchInstrumentationHandler(handlerData => {
- const createdSpan = fetch.instrumentFetchRequest(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans);
- if (enableHTTPTimings && createdSpan) {
- addHTTPTimings(createdSpan);
- }
- });
- }
- if (traceXHR) {
- utils.addXhrInstrumentationHandler(handlerData => {
- const createdSpan = xhrCallback(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans);
- if (enableHTTPTimings && createdSpan) {
- addHTTPTimings(createdSpan);
- }
- });
- }
- }
- function isPerformanceResourceTiming(entry) {
- return (
- entry.entryType === 'resource' &&
- 'initiatorType' in entry &&
- typeof (entry ).nextHopProtocol === 'string' &&
- (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest')
- );
- }
- /**
- * 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 } = core.spanToJSON(span).data || {};
- if (!url || typeof url !== 'string') {
- return;
- }
- const cleanup = instrument.addPerformanceInstrumentationHandler('resource', ({ entries }) => {
- entries.forEach(entry => {
- if (isPerformanceResourceTiming(entry) && entry.name.endsWith(url)) {
- const spanData = resourceTimingEntryToSpanData(entry);
- spanData.forEach(data => span.setAttribute(...data));
- // In the next tick, clean this handler up
- // We have to wait here because otherwise this cleans itself up before it is fully done
- setTimeout(cleanup);
- }
- });
- });
- }
- /**
- * Converts ALPN protocol ids to name and version.
- *
- * (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
- * @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol
- */
- function extractNetworkProtocol(nextHopProtocol) {
- let name = 'unknown';
- let version = 'unknown';
- let _name = '';
- for (const char of nextHopProtocol) {
- // http/1.1 etc.
- if (char === '/') {
- [name, version] = nextHopProtocol.split('/');
- break;
- }
- // h2, h3 etc.
- if (!isNaN(Number(char))) {
- name = _name === 'h' ? 'http' : _name;
- version = nextHopProtocol.split(_name)[1];
- break;
- }
- _name += char;
- }
- if (_name === nextHopProtocol) {
- // webrtc, ftp, etc.
- name = _name;
- }
- return { name, version };
- }
- function getAbsoluteTime(time = 0) {
- return ((utils.browserPerformanceTimeOrigin || performance.timeOrigin) + time) / 1000;
- }
- function resourceTimingEntryToSpanData(resourceTiming) {
- const { name, version } = extractNetworkProtocol(resourceTiming.nextHopProtocol);
- const timingSpanData = [];
- timingSpanData.push(['network.protocol.version', version], ['network.protocol.name', name]);
- if (!utils.browserPerformanceTimeOrigin) {
- return timingSpanData;
- }
- return [
- ...timingSpanData,
- ['http.request.redirect_start', getAbsoluteTime(resourceTiming.redirectStart)],
- ['http.request.fetch_start', getAbsoluteTime(resourceTiming.fetchStart)],
- ['http.request.domain_lookup_start', getAbsoluteTime(resourceTiming.domainLookupStart)],
- ['http.request.domain_lookup_end', getAbsoluteTime(resourceTiming.domainLookupEnd)],
- ['http.request.connect_start', getAbsoluteTime(resourceTiming.connectStart)],
- ['http.request.secure_connection_start', getAbsoluteTime(resourceTiming.secureConnectionStart)],
- ['http.request.connection_end', getAbsoluteTime(resourceTiming.connectEnd)],
- ['http.request.request_start', getAbsoluteTime(resourceTiming.requestStart)],
- ['http.request.response_start', getAbsoluteTime(resourceTiming.responseStart)],
- ['http.request.response_end', getAbsoluteTime(resourceTiming.responseEnd)],
- ];
- }
- /**
- * 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 utils.stringMatchesSomePattern(url, tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS);
- }
- /**
- * Create and track xhr request spans
- *
- * @returns Span if a span was created, otherwise void.
- */
- // eslint-disable-next-line complexity
- function xhrCallback(
- handlerData,
- shouldCreateSpan,
- shouldAttachHeaders,
- spans,
- ) {
- const xhr = handlerData.xhr;
- const sentryXhrData = xhr && xhr[utils.SENTRY_XHR_DATA_KEY];
- if (!core.hasTracingEnabled() || !xhr || xhr.__sentry_own_request__ || !sentryXhrData) {
- return undefined;
- }
- const shouldCreateSpanResult = shouldCreateSpan(sentryXhrData.url);
- // check first if the request has finished and is tracked by an existing span which should now end
- if (handlerData.endTimestamp && shouldCreateSpanResult) {
- const spanId = xhr.__sentry_xhr_span_id__;
- if (!spanId) return;
- const span = spans[spanId];
- if (span && sentryXhrData.status_code !== undefined) {
- core.setHttpStatus(span, sentryXhrData.status_code);
- span.end();
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
- delete spans[spanId];
- }
- return undefined;
- }
- const scope = core.getCurrentScope();
- const isolationScope = core.getIsolationScope();
- const span = shouldCreateSpanResult
- ? core.startInactiveSpan({
- name: `${sentryXhrData.method} ${sentryXhrData.url}`,
- onlyIfParent: true,
- attributes: {
- type: 'xhr',
- 'http.method': sentryXhrData.method,
- url: sentryXhrData.url,
- [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser',
- },
- op: 'http.client',
- })
- : undefined;
- if (span) {
- xhr.__sentry_xhr_span_id__ = span.spanContext().spanId;
- spans[xhr.__sentry_xhr_span_id__] = span;
- }
- const client = core.getClient();
- if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && client) {
- const { traceId, spanId, sampled, dsc } = {
- ...isolationScope.getPropagationContext(),
- ...scope.getPropagationContext(),
- };
- const sentryTraceHeader = span ? core.spanToTraceHeader(span) : utils.generateSentryTraceHeader(traceId, spanId, sampled);
- const sentryBaggageHeader = utils.dynamicSamplingContextToSentryBaggageHeader(
- dsc ||
- (span ? core.getDynamicSamplingContextFromSpan(span) : core.getDynamicSamplingContextFromClient(traceId, client, scope)),
- );
- setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader);
- }
- return span;
- }
- function setHeaderOnXhr(
- xhr,
- sentryTraceHeader,
- sentryBaggageHeader,
- ) {
- try {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- xhr.setRequestHeader('sentry-trace', sentryTraceHeader);
- 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
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- xhr.setRequestHeader(utils.BAGGAGE_HEADER_NAME, sentryBaggageHeader);
- }
- } catch (_) {
- // Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
- }
- }
- exports.DEFAULT_TRACE_PROPAGATION_TARGETS = DEFAULT_TRACE_PROPAGATION_TARGETS;
- exports.defaultRequestInstrumentationOptions = defaultRequestInstrumentationOptions;
- exports.extractNetworkProtocol = extractNetworkProtocol;
- exports.instrumentOutgoingRequests = instrumentOutgoingRequests;
- exports.shouldAttachHeaders = shouldAttachHeaders;
- exports.xhrCallback = xhrCallback;
- //# sourceMappingURL=request.js.map
|