@datadog/mobile-react-native
Version:
A client-side React Native module to interact with Datadog
177 lines (173 loc) • 6.77 kB
JavaScript
/*
* 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