UNPKG

@datadog/mobile-react-native

Version:

A client-side React Native module to interact with Datadog

177 lines (173 loc) 6.77 kB
/* * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ import { Timer } from '../../../../../utils/Timer'; import { getCachedSessionId } from '../../../../sessionId/sessionIdHelper'; import { BAGGAGE_HEADER_KEY, getTracingHeadersFromAttributes } from '../../distributedTracing/distributedTracingHeaders'; import { getTracingAttributes } from '../../distributedTracing/distributedTracing'; import { DATADOG_GRAPH_QL_OPERATION_NAME_HEADER, DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER, DATADOG_GRAPH_QL_VARIABLES_HEADER } from '../../graphql/graphqlHeaders'; import { DATADOG_BAGGAGE_HEADER, isDatadogCustomHeader } from '../../headers'; import { RequestProxy } from '../interfaces/RequestProxy'; import { URLHostParser } from './URLHostParser'; import { formatBaggageHeader } from './baggageHeaderUtils'; import { calculateResponseSize } from './responseSize'; const RESPONSE_START_LABEL = 'response_start'; /** * Proxies XMLHttpRequest to track resources. */ export class XHRProxy extends RequestProxy { constructor(providers) { super(); this.providers = providers; } onTrackingStart = context => { XHRProxy.originalXhrOpen = this.providers.xhrType.prototype.open; XHRProxy.originalXhrSend = this.providers.xhrType.prototype.send; XHRProxy.originalXhrSetRequestHeader = this.providers.xhrType.prototype.setRequestHeader; proxyRequests(this.providers, context); }; onTrackingStop = () => { this.providers.xhrType.prototype.open = XHRProxy.originalXhrOpen; this.providers.xhrType.prototype.send = XHRProxy.originalXhrSend; this.providers.xhrType.prototype.setRequestHeader = XHRProxy.originalXhrSetRequestHeader; }; } const proxyRequests = (providers, context) => { proxyOpen(providers, context); proxySend(providers); proxySetRequestHeader(providers); }; const proxyOpen = ({ xhrType }, context) => { const originalXhrOpen = xhrType.prototype.open; const firstPartyHostsRegexMap = context.firstPartyHostsRegexMap; const tracingSamplingRate = context.tracingSamplingRate; xhrType.prototype.open = function open(method, url) { const hostname = URLHostParser(url); // Keep track of the method and url // start time is tracked by the `send` method this._datadog_xhr = { method, url, reported: false, timer: new Timer(), graphql: {}, tracingAttributes: getTracingAttributes({ hostname, firstPartyHostsRegexMap, tracingSamplingRate, rumSessionId: getCachedSessionId() }), baggageHeaderEntries: new Set() }; // eslint-disable-next-line prefer-rest-params return originalXhrOpen.apply(this, arguments); }; }; const proxySend = providers => { const xhrType = providers.xhrType; const originalXhrSend = xhrType.prototype.send; xhrType.prototype.send = function send() { if (this._datadog_xhr) { // keep track of start time this._datadog_xhr.timer.start(); // Tracing Headers const tracingHeaders = getTracingHeadersFromAttributes(this._datadog_xhr.tracingAttributes); tracingHeaders.forEach(({ header, value }) => { this.setRequestHeader(header, value); }); // Join all baggage header entries const baggageHeader = formatBaggageHeader(this._datadog_xhr.baggageHeaderEntries); if (baggageHeader) { this.setRequestHeader(DATADOG_BAGGAGE_HEADER, baggageHeader); } } proxyOnReadyStateChange(this, providers); // eslint-disable-next-line prefer-rest-params return originalXhrSend.apply(this, arguments); }; }; const proxyOnReadyStateChange = (xhrProxy, providers) => { const xhrType = providers.xhrType; const originalOnreadystatechange = xhrProxy.onreadystatechange; xhrProxy.onreadystatechange = function onreadystatechange() { if (xhrProxy.readyState === xhrType.DONE) { if (!xhrProxy._datadog_xhr.reported) { reportXhr(xhrProxy, providers.resourceReporter); xhrProxy._datadog_xhr.reported = true; } } else if (xhrProxy.readyState === xhrType.HEADERS_RECEIVED) { xhrProxy._datadog_xhr.timer.recordTick(RESPONSE_START_LABEL); } if (originalOnreadystatechange) { // eslint-disable-next-line prefer-rest-params originalOnreadystatechange.apply(xhrProxy, arguments); } }; }; const reportXhr = async (xhrProxy, resourceReporter) => { const responseSize = calculateResponseSize(xhrProxy); const context = xhrProxy._datadog_xhr; const key = `${context.timer.startTime}/${context.method}`; context.timer.stop(); resourceReporter.reportResource({ key, request: { method: context.method, url: context.url, kind: 'xhr' }, graphqlAttributes: context.graphql, tracingAttributes: context.tracingAttributes, response: { statusCode: xhrProxy.status, size: responseSize }, timings: { startTime: context.timer.startTime, stopTime: context.timer.stopTime, responseStartTime: context.timer.hasTickFor(RESPONSE_START_LABEL) ? context.timer.timeAt(RESPONSE_START_LABEL) : undefined }, resourceContext: xhrProxy }); }; const proxySetRequestHeader = providers => { const xhrType = providers.xhrType; const originalXhrSetRequestHeader = xhrType.prototype.setRequestHeader; xhrType.prototype.setRequestHeader = function sendRequestHeader(header, value) { const key = header.toLowerCase(); if (isDatadogCustomHeader(key)) { switch (key) { case DATADOG_GRAPH_QL_OPERATION_NAME_HEADER: this._datadog_xhr.graphql.operationName = value; break; case DATADOG_GRAPH_QL_OPERATION_TYPE_HEADER: this._datadog_xhr.graphql.operationType = value; break; case DATADOG_GRAPH_QL_VARIABLES_HEADER: this._datadog_xhr.graphql.variables = value; break; case DATADOG_BAGGAGE_HEADER: // Apply Baggage Header only if pre-processed by Datadog return originalXhrSetRequestHeader.apply(this, [BAGGAGE_HEADER_KEY, value]); default: return originalXhrSetRequestHeader.apply(this, // eslint-disable-next-line prefer-rest-params arguments); } } else if (key === BAGGAGE_HEADER_KEY) { // Intercept User Baggage Header entries to apply them later this._datadog_xhr.baggageHeaderEntries?.add(value); } else { // eslint-disable-next-line prefer-rest-params return originalXhrSetRequestHeader.apply(this, arguments); } }; }; //# sourceMappingURL=XHRProxy.js.map