in extensions/applicationinsights-dependencies-js/src/ajax.ts [199:1004]
constructor() {
super();
let strTrackDependencyDataInternal = "trackDependencyDataInternal"; // Using string to help with minification
let location = getLocation();
let _fetchInitialized:boolean = false; // fetch monitoring initialized
let _xhrInitialized:boolean = false; // XHR monitoring initialized
let _currentWindowHost:string = location && location.host && location.host.toLowerCase();
let _config: ICorrelationConfig = AjaxMonitor.getEmptyConfig();
let _enableRequestHeaderTracking = false;
let _enableAjaxErrorStatusText = false;
let _trackAjaxAttempts: number = 0;
let _context: ITelemetryContext;
let _isUsingW3CHeaders: boolean;
let _isUsingAIHeaders: boolean;
let _markPrefix: string;
let _enableAjaxPerfTracking:boolean = false;
let _maxAjaxCallsPerView:number = 0;
let _enableResponseHeaderTracking:boolean = false;
let _hooks:IInstrumentHook[] = [];
let _disabledUrls:any = {};
let _excludeRequestFromAutoTrackingPatterns: string[] | RegExp[];
let _addRequestContext: (requestContext?: IRequestContext) => ICustomProperties;
dynamicProto(AjaxMonitor, this, (_self, base) => {
_self.initialize = (config: IConfiguration & IConfig, core: IAppInsightsCore, extensions: IPlugin[], pluginChain?:ITelemetryPluginChain) => {
if (!_self.isInitialized()) {
base.initialize(config, core, extensions, pluginChain);
let ctx = _self._getTelCtx();
const defaultConfig = AjaxMonitor.getDefaultConfig();
objForEachKey(defaultConfig, (field, value) => {
_config[field] = ctx.getConfig(AjaxMonitor.identifier, field, value);
});
let distributedTracingMode = _config.distributedTracingMode;
_enableRequestHeaderTracking = _config.enableRequestHeaderTracking;
_enableAjaxErrorStatusText = _config.enableAjaxErrorStatusText;
_enableAjaxPerfTracking = _config.enableAjaxPerfTracking;
_maxAjaxCallsPerView = _config.maxAjaxCallsPerView;
_enableResponseHeaderTracking = _config.enableResponseHeaderTracking;
_excludeRequestFromAutoTrackingPatterns = _config.excludeRequestFromAutoTrackingPatterns;
_addRequestContext = _config.addRequestContext;
_isUsingAIHeaders = distributedTracingMode === DistributedTracingModes.AI || distributedTracingMode === DistributedTracingModes.AI_AND_W3C;
_isUsingW3CHeaders = distributedTracingMode === DistributedTracingModes.AI_AND_W3C || distributedTracingMode === DistributedTracingModes.W3C;
if (_enableAjaxPerfTracking) {
let iKey = config.instrumentationKey || "unkwn";
if (iKey.length > 5) {
_markPrefix = AJAX_MONITOR_PREFIX + iKey.substring(iKey.length - 5) + ".";
} else {
_markPrefix = AJAX_MONITOR_PREFIX + iKey + ".";
}
}
if (_config.disableAjaxTracking === false) {
_instrumentXhr();
}
_instrumentFetch();
if (extensions.length > 0 && extensions) {
let propExt: any, extIx = 0;
while (!propExt && extIx < extensions.length) {
if (extensions[extIx] && extensions[extIx].identifier === PropertiesPluginIdentifier) {
propExt = extensions[extIx];
}
extIx++;
}
if (propExt) {
_context = propExt.context; // we could move IPropertiesPlugin to common as well
}
}
}
};
_self.teardown = () => {
// Remove all instrumentation hooks
arrForEach(_hooks, (fn) => {
fn.rm();
});
_hooks = [];
_fetchInitialized = false;
_xhrInitialized = false;
_self.setInitialized(false);
}
_self.trackDependencyData = (dependency: IDependencyTelemetry, properties?: { [key: string]: any }) => {
_self[strTrackDependencyDataInternal](dependency, properties);
}
_self.includeCorrelationHeaders = (ajaxData: ajaxRecord, input?: Request | string, init?: RequestInit, xhr?: XMLHttpRequestInstrumented): any => {
// Test Hook to allow the overriding of the location host
let currentWindowHost = _self["_currentWindowHost"] || _currentWindowHost;
if (input) { // Fetch
if (CorrelationIdHelper.canIncludeCorrelationHeader(_config, ajaxData.getAbsoluteUrl(), currentWindowHost)) {
if (!init) {
init = {};
}
// init headers override original request headers
// so, if they exist use only them, otherwise use request's because they should have been applied in the first place
// not using original request headers will result in them being lost
init.headers = new Headers(init.headers || (input instanceof Request ? (input.headers || {}) : {}));
if (_isUsingAIHeaders) {
const id = "|" + ajaxData.traceID + "." + ajaxData.spanID;
init.headers.set(RequestHeaders.requestIdHeader, id);
if (_enableRequestHeaderTracking) {
ajaxData.requestHeaders[RequestHeaders.requestIdHeader] = id;
}
}
const appId: string = _config.appId ||(_context && _context.appId());
if (appId) {
init.headers.set(RequestHeaders.requestContextHeader, RequestHeaders.requestContextAppIdFormat + appId);
if (_enableRequestHeaderTracking) {
ajaxData.requestHeaders[RequestHeaders.requestContextHeader] = RequestHeaders.requestContextAppIdFormat + appId;
}
}
if (_isUsingW3CHeaders) {
const traceparent = new Traceparent(ajaxData.traceID, ajaxData.spanID);
init.headers.set(RequestHeaders.traceParentHeader, traceparent.toString());
if (_enableRequestHeaderTracking) {
ajaxData.requestHeaders[RequestHeaders.traceParentHeader] = traceparent.toString();
}
}
}
return init;
} else if (xhr) { // XHR
if (CorrelationIdHelper.canIncludeCorrelationHeader(_config, ajaxData.getAbsoluteUrl(), currentWindowHost)) {
if (_isUsingAIHeaders) {
const id = "|" + ajaxData.traceID + "." + ajaxData.spanID;
xhr.setRequestHeader(RequestHeaders.requestIdHeader, id);
if (_enableRequestHeaderTracking) {
ajaxData.requestHeaders[RequestHeaders.requestIdHeader] = id;
}
}
const appId = _config.appId || (_context && _context.appId());
if (appId) {
xhr.setRequestHeader(RequestHeaders.requestContextHeader, RequestHeaders.requestContextAppIdFormat + appId);
if (_enableRequestHeaderTracking) {
ajaxData.requestHeaders[RequestHeaders.requestContextHeader] = RequestHeaders.requestContextAppIdFormat + appId;
}
}
if (_isUsingW3CHeaders) {
const traceparent = new Traceparent(ajaxData.traceID, ajaxData.spanID);
xhr.setRequestHeader(RequestHeaders.traceParentHeader, traceparent.toString());
if (_enableRequestHeaderTracking) {
ajaxData.requestHeaders[RequestHeaders.traceParentHeader] = traceparent.toString();
}
}
}
return xhr;
}
return undefined;
}
_self[strTrackDependencyDataInternal] = (dependency: IDependencyTelemetry, properties?: { [key: string]: any }, systemProperties?: { [key: string]: any }) => {
if (_maxAjaxCallsPerView === -1 || _trackAjaxAttempts < _maxAjaxCallsPerView) {
// Hack since expected format in w3c mode is |abc.def.
// Non-w3c format is |abc.def
// @todo Remove if better solution is available, e.g. handle in portal
if ((_config.distributedTracingMode === DistributedTracingModes.W3C
|| _config.distributedTracingMode === DistributedTracingModes.AI_AND_W3C)
&& typeof dependency.id === "string" && dependency.id[dependency.id.length - 1] !== "."
) {
dependency.id += ".";
}
if (isNullOrUndefined(dependency.startTime)) {
dependency.startTime = new Date();
}
const item = TelemetryItemCreator.create<IDependencyTelemetry>(
dependency,
RemoteDependencyData.dataType,
RemoteDependencyData.envelopeType,
_self[strDiagLog](),
properties,
systemProperties);
_self.core.track(item);
} else if (_trackAjaxAttempts === _maxAjaxCallsPerView) {
_throwInternalCritical(_self,
_InternalMessageId.MaxAjaxPerPVExceeded,
"Maximum ajax per page view limit reached, ajax monitoring is paused until the next trackPageView(). In order to increase the limit set the maxAjaxCallsPerView configuration parameter.",
true);
}
++_trackAjaxAttempts;
}
// discard the header if it's defined as ignoreHeaders in ICorrelationConfig
function _canIncludeHeaders(header: string) {
let rlt = true;
if (header || _config.ignoreHeaders) {
arrForEach(_config.ignoreHeaders,(key => {
if (key.toLowerCase() === header.toLowerCase()) {
rlt = false;
return -1;
}
}))
}
return rlt;
}
// Fetch Stuff
function _instrumentFetch(): void {
let fetch = _supportsFetch();
if (!fetch) {
return;
}
let global = getGlobal();
let isPolyfill = (fetch as any).polyfill;
if (_config.disableFetchTracking === false) {
_hooks.push(InstrumentFunc(global, strFetch, {
// Add request hook
req: (callDetails: IInstrumentCallDetails, input, init) => {
let fetchData: ajaxRecord;
if (_fetchInitialized &&
!_isDisabledRequest(null, input, init) &&
// If we have a polyfil and XHR instrumented then let XHR report otherwise we get duplicates
!(isPolyfill && _xhrInitialized)) {
let ctx = callDetails.ctx();
fetchData = _createFetchRecord(input, init);
let newInit = _self.includeCorrelationHeaders(fetchData, input, init);
if (newInit !== init) {
callDetails.set(1, newInit);
}
ctx.data = fetchData;
}
},
rsp: (callDetails: IInstrumentCallDetails, input) => {
let fetchData = callDetails.ctx().data;
if (fetchData) {
// Replace the result with the new promise from this code
callDetails.rslt = callDetails.rslt.then((response: any) => {
_reportFetchMetrics(callDetails, (response||{}).status, input, response, fetchData, () => {
let ajaxResponse:IAjaxRecordResponse = {
statusText: response.statusText,
headerMap: null,
correlationContext: _getFetchCorrelationContext(response)
};
if (_enableResponseHeaderTracking) {
const responseHeaderMap = {};
response.headers.forEach((value: string, name: string) => {
if (_canIncludeHeaders(name)) { responseHeaderMap[name] = value; }
});
ajaxResponse.headerMap = responseHeaderMap;
}
return ajaxResponse;
});
return response;
})
.catch((reason: any) => {
_reportFetchMetrics(callDetails, 0, input, null, fetchData, null, { error: reason.message });
throw reason;
});
}
},
// Create an error callback to report any hook errors
hkErr: _createErrorCallbackFunc(_self, _InternalMessageId.FailedMonitorAjaxOpen,
"Failed to monitor Window.fetch, monitoring data for this fetch call may be incorrect.")
}));
_fetchInitialized = true;
} else if (isPolyfill) {
// If fetch is a polyfill we need to capture the request to ensure that we correctly track
// disabled request URLS (i.e. internal urls) to ensure we don't end up in a constant loop
// of reporting ourselves, for example React Native uses a polyfill for fetch
// Note: Polyfill implementations that don't support the "poyyfill" tag are not supported
// the workaround is to add a polyfill property to your fetch implementation before initializing
// App Insights
_hooks.push(InstrumentFunc(global, strFetch, {
req: (callDetails: IInstrumentCallDetails, input, init) => {
// Just call so that we record any disabled URL
_isDisabledRequest(null, input, init);
}
}));
}
if (isPolyfill) {
// retag the instrumented fetch with the same polyfill settings this is mostly for testing
// But also supports multiple App Insights usages
(global[strFetch] as any).polyfill = isPolyfill;
}
}
function _hookProto(target: any, funcName: string, callbacks: IInstrumentHooksCallbacks) {
_hooks.push(InstrumentProto(target, funcName, callbacks));
}
function _instrumentXhr():void {
if (_supportsAjaxMonitoring(_self) && !_xhrInitialized) {
// Instrument open
_hookProto(XMLHttpRequest, "open", {
req: (args:IInstrumentCallDetails, method:string, url:string, async?:boolean) => {
let xhr = args.inst as XMLHttpRequestInstrumented;
let ajaxData = xhr[strAjaxData];
if (!_isDisabledRequest(xhr, url) && _isMonitoredXhrInstance(xhr, true)) {
if (!ajaxData || !ajaxData.xhrMonitoringState.openDone) {
// Only create a single ajaxData (even when multiple AI instances are running)
_openHandler(xhr, method, url, async);
}
// always attach to the on ready state change (required for handling multiple instances)
_attachToOnReadyStateChange(xhr);
}
},
hkErr: _createErrorCallbackFunc(_self, _InternalMessageId.FailedMonitorAjaxOpen,
"Failed to monitor XMLHttpRequest.open, monitoring data for this ajax call may be incorrect.")
});
// Instrument send
_hookProto(XMLHttpRequest, "send", {
req: (args:IInstrumentCallDetails, context?: Document | BodyInit | null) => {
let xhr = args.inst as XMLHttpRequestInstrumented;
let ajaxData = xhr[strAjaxData];
if (_isMonitoredXhrInstance(xhr) && !ajaxData.xhrMonitoringState.sendDone) {
_createMarkId("xhr", ajaxData);
ajaxData.requestSentTime = dateTimeUtilsNow();
_self.includeCorrelationHeaders(ajaxData, undefined, undefined, xhr);
ajaxData.xhrMonitoringState.sendDone = true;
}
},
hkErr: _createErrorCallbackFunc(_self, _InternalMessageId.FailedMonitorAjaxSend,
"Failed to monitor XMLHttpRequest, monitoring data for this ajax call may be incorrect.")
});
// Instrument abort
_hookProto(XMLHttpRequest, "abort", {
req: (args:IInstrumentCallDetails) => {
let xhr = args.inst as XMLHttpRequestInstrumented;
let ajaxData = xhr[strAjaxData];
if (_isMonitoredXhrInstance(xhr) && !ajaxData.xhrMonitoringState.abortDone) {
ajaxData.aborted = 1;
ajaxData.xhrMonitoringState.abortDone = true;
}
},
hkErr: _createErrorCallbackFunc(_self, _InternalMessageId.FailedMonitorAjaxAbort,
"Failed to monitor XMLHttpRequest.abort, monitoring data for this ajax call may be incorrect.")
});
// Instrument setRequestHeader
if (_enableRequestHeaderTracking) {
_hookProto(XMLHttpRequest, "setRequestHeader", {
req: (args: IInstrumentCallDetails, header: string, value: string) => {
let xhr = args.inst as XMLHttpRequestInstrumented;
if (_isMonitoredXhrInstance(xhr) && _canIncludeHeaders(header)) {
xhr[strAjaxData].requestHeaders[header] = value;
}
},
hkErr: _createErrorCallbackFunc(_self, _InternalMessageId.FailedMonitorAjaxSetRequestHeader,
"Failed to monitor XMLHttpRequest.setRequestHeader, monitoring data for this ajax call may be incorrect.")
});
}
_xhrInitialized = true;
}
}
function _isDisabledRequest(xhr?: XMLHttpRequestInstrumented, request?: Request | string, init?: RequestInit) {
let isDisabled = false;
let theUrl:string = ((!isString(request) ? ((request ||{}) as Request).url || "" : request as string) ||"").toLowerCase();
// check excludeRequestFromAutoTrackingPatterns before stripping off any query string
arrForEach(_excludeRequestFromAutoTrackingPatterns, (regex: string | RegExp) => {
let theRegex = regex;
if (isString(regex)) {
theRegex = new RegExp(regex);
}
if (!isDisabled) {
isDisabled = (theRegex as RegExp).test(theUrl);
}
});
// if request url matches with exclude regex pattern, return true and no need to check for headers
if (isDisabled) {
return isDisabled;
}
let idx = _indexOf(theUrl, "?");
let idx2 = _indexOf(theUrl, "#");
if (idx === -1 || (idx2 !== -1 && idx2 < idx)) {
idx = idx2;
}
if (idx !== -1) {
// Strip off any Query string
theUrl = theUrl.substring(0, idx);
}
// check that this instance is not not used by ajax call performed inside client side monitoring to send data to collector
if (!isNullOrUndefined(xhr)) {
// Look on the XMLHttpRequest of the URL string value
isDisabled = xhr[DisabledPropertyName] === true || theUrl[DisabledPropertyName] === true;
} else if (!isNullOrUndefined(request)) { // fetch
// Look for DisabledPropertyName in either Request or RequestInit
isDisabled = (typeof request === "object" ? request[DisabledPropertyName] === true : false) ||
(init ? init[DisabledPropertyName] === true : false);
}
// Also add extra check just in case the XHR or fetch objects where not decorated with the DisableProperty due to sealing or freezing
if (!isDisabled && theUrl && isInternalApplicationInsightsEndpoint(theUrl)) {
isDisabled = true;
}
if (isDisabled) {
// Add the disabled url if not present
if (!_disabledUrls[theUrl]) {
_disabledUrls[theUrl] = 1;
}
} else {
// Check to see if the url is listed as disabled
if (_disabledUrls[theUrl]) {
isDisabled = true;
}
}
return isDisabled;
}
/// <summary>Verifies that particalar instance of XMLHttpRequest needs to be monitored</summary>
/// <param name="excludeAjaxDataValidation">Optional parameter. True if ajaxData must be excluded from verification</param>
/// <returns type="bool">True if instance needs to be monitored, otherwise false</returns>
function _isMonitoredXhrInstance(xhr: XMLHttpRequestInstrumented, excludeAjaxDataValidation?: boolean): boolean {
let ajaxValidation = true;
let initialized = _xhrInitialized;
if (!isNullOrUndefined(xhr)) {
ajaxValidation = excludeAjaxDataValidation === true || !isNullOrUndefined(xhr[strAjaxData]);
}
// checking to see that all interested functions on xhr were instrumented
return initialized
// checking on ajaxData to see that it was not removed in user code
&& ajaxValidation;
}
function _openHandler(xhr: XMLHttpRequestInstrumented, method: string, url: string, async: boolean) {
const traceID = (_context && _context.telemetryTrace && _context.telemetryTrace.traceID) || generateW3CId();
const spanID = generateW3CId().substr(0, 16);
const ajaxData = new ajaxRecord(traceID, spanID, _self[strDiagLog]());
ajaxData.method = method;
ajaxData.requestUrl = url;
ajaxData.xhrMonitoringState.openDone = true;
ajaxData.requestHeaders = {};
ajaxData.async = async;
ajaxData.errorStatusText = _enableAjaxErrorStatusText;
xhr[strAjaxData] = ajaxData;
}
function _attachToOnReadyStateChange(xhr: XMLHttpRequestInstrumented) {
xhr[strAjaxData].xhrMonitoringState.stateChangeAttached = attachEvent(xhr, "readystatechange", () => {
try {
if (xhr && xhr.readyState === 4 && _isMonitoredXhrInstance(xhr)) {
_onAjaxComplete(xhr);
}
} catch (e) {
const exceptionText = dumpObj(e);
// ignore messages with c00c023f, as this a known IE9 XHR abort issue
if (!exceptionText || _indexOf(exceptionText.toLowerCase(), "c00c023f") === -1) {
_throwInternalCritical(_self,
_InternalMessageId.FailedMonitorAjaxRSC,
"Failed to monitor XMLHttpRequest 'readystatechange' event handler, monitoring data for this ajax call may be incorrect.",
{
ajaxDiagnosticsMessage: _getFailedAjaxDiagnosticsMessage(xhr),
exception: exceptionText
});
}
}
});
}
function _getResponseText(xhr: XMLHttpRequestInstrumented) {
try {
const responseType = xhr.responseType;
if (responseType === "" || responseType === "text") {
// As per the specification responseText is only valid if the type is an empty string or "text"
return xhr.responseText;
}
} catch (e) {
// This shouldn't happen because of the above check -- but just in case, so just ignore
}
return null;
}
function _onAjaxComplete(xhr: XMLHttpRequestInstrumented) {
let ajaxData = xhr[strAjaxData];
ajaxData.responseFinishedTime = dateTimeUtilsNow();
ajaxData.status = xhr.status;
function _reportXhrError(e: any, failedProps?:Object) {
let errorProps = failedProps||{};
errorProps["ajaxDiagnosticsMessage"] = _getFailedAjaxDiagnosticsMessage(xhr);
if (e) {
errorProps["exception"] = dumpObj(e);
}
_throwInternalWarning(_self,
_InternalMessageId.FailedMonitorAjaxDur,
"Failed to calculate the duration of the ajax call, monitoring data for this ajax call won't be sent.",
errorProps
);
}
_findPerfResourceEntry("xmlhttprequest", ajaxData, () => {
try {
const dependency = ajaxData.CreateTrackItem("Ajax", _enableRequestHeaderTracking, () => {
let ajaxResponse:IAjaxRecordResponse = {
statusText: xhr.statusText,
headerMap: null,
correlationContext: _getAjaxCorrelationContext(xhr),
type: xhr.responseType,
responseText: _getResponseText(xhr),
response: xhr.response
};
if (_enableResponseHeaderTracking) {
const headers = xhr.getAllResponseHeaders();
if (headers) {
// xhr.getAllResponseHeaders() method returns all the response headers, separated by CRLF, as a string or null
// the regex converts the header string into an array of individual headers
const arr = strTrim(headers).split(/[\r\n]+/);
const responseHeaderMap = {};
arrForEach(arr, (line) => {
const parts = line.split(": ");
const header = parts.shift();
const value = parts.join(": ");
if(_canIncludeHeaders(header)) { responseHeaderMap[header] = value; }
});
ajaxResponse.headerMap = responseHeaderMap;
}
}
return ajaxResponse;
});
let properties;
try {
if (!!_addRequestContext) {
properties = _addRequestContext({status: xhr.status, xhr});
}
} catch (e) {
_throwInternalWarning(_self,
_InternalMessageId.FailedAddingCustomDefinedRequestContext,
"Failed to add custom defined request context as configured call back may missing a null check.")
}
if (dependency) {
if (properties !== undefined) {
dependency.properties = {...dependency.properties, ...properties};
}
_self[strTrackDependencyDataInternal](dependency);
} else {
_reportXhrError(null, {
requestSentTime: ajaxData.requestSentTime,
responseFinishedTime: ajaxData.responseFinishedTime
});
}
} finally {
// cleanup telemetry data
try {
xhr[strAjaxData] = null;
} catch (e) {
// May throw in environments that prevent extension or freeze xhr
}
}
}, (e) => {
_reportXhrError(e, null);
});
}
function _getAjaxCorrelationContext(xhr: XMLHttpRequestInstrumented) {
try {
const responseHeadersString = xhr.getAllResponseHeaders();
if (responseHeadersString !== null) {
const index = _indexOf(responseHeadersString.toLowerCase(), RequestHeaders.requestContextHeaderLowerCase);
if (index !== -1) {
const responseHeader = xhr.getResponseHeader(RequestHeaders.requestContextHeader);
return CorrelationIdHelper.getCorrelationContext(responseHeader);
}
}
} catch (e) {
_throwInternalWarning(_self,
_InternalMessageId.FailedMonitorAjaxGetCorrelationHeader,
"Failed to get Request-Context correlation header as it may be not included in the response or not accessible.",
{
ajaxDiagnosticsMessage: _getFailedAjaxDiagnosticsMessage(xhr),
exception: dumpObj(e)
});
}
}
function _createMarkId(type:string, ajaxData:ajaxRecord) {
if (ajaxData.requestUrl && _markPrefix && _enableAjaxPerfTracking) {
let performance = getPerformance();
if (performance && isFunction(performance.mark)) {
_markCount++;
let markId = _markPrefix + type + "#" + _markCount;
performance.mark(markId);
let entries = performance.getEntriesByName(markId);
if (entries && entries.length === 1) {
ajaxData.perfMark = entries[0] as any;
}
}
}
}
function _findPerfResourceEntry(initiatorType:string, ajaxData:ajaxRecord, trackCallback:() => void, reportError:(e:any) => void): void {
let perfMark = ajaxData.perfMark;
let performance = getPerformance();
let maxAttempts = _config.maxAjaxPerfLookupAttempts;
let retryDelay = _config.ajaxPerfLookupDelay;
let requestUrl = ajaxData.requestUrl;
let attempt = 0;
(function locateResourceTiming() {
try {
if (performance && perfMark) {
attempt++;
let perfTiming:PerformanceResourceTiming = null;
let entries = performance.getEntries();
for (let lp = entries.length - 1; lp >= 0; lp--) {
let entry:PerformanceEntry = entries[lp];
if (entry) {
if (entry.entryType === "resource") {
if ((entry as PerformanceResourceTiming).initiatorType === initiatorType &&
(_indexOf(entry.name, requestUrl) !== -1 || _indexOf(requestUrl, entry.name) !== -1)) {
perfTiming = entry as PerformanceResourceTiming;
}
} else if (entry.entryType === "mark" && entry.name === perfMark.name) {
// We hit the start event
ajaxData.perfTiming = perfTiming;
break;
}
if (entry.startTime < perfMark.startTime - 1000) {
// Fallback to try and reduce the time spent looking for the perf entry
break;
}
}
}
}
if (!perfMark || // - we don't have a perfMark or
ajaxData.perfTiming || // - we have not found the perf entry or
attempt >= maxAttempts || // - we have tried too many attempts or
ajaxData.async === false) { // - this is a sync request
if (perfMark && isFunction(performance.clearMarks)) {
// Remove the mark so we don't fill up the performance resources too much
performance.clearMarks(perfMark.name);
}
ajaxData.perfAttempts = attempt;
// just continue and report the track event
trackCallback();
} else {
// We need to wait for the browser to populate the window.performance entry
// This needs to be at least 1ms as waiting <= 1 (on firefox) is not enough time for fetch or xhr,
// this is a scheduling issue for the browser implementation
setTimeout(locateResourceTiming, retryDelay);
}
} catch (e) {
reportError(e);
}
})();
}
function _createFetchRecord(input?: Request | string, init?: RequestInit): ajaxRecord {
const traceID = (_context && _context.telemetryTrace && _context.telemetryTrace.traceID) || generateW3CId();
const spanID = generateW3CId().substr(0, 16);
const ajaxData = new ajaxRecord(traceID, spanID, _self[strDiagLog]());
ajaxData.requestSentTime = dateTimeUtilsNow();
ajaxData.errorStatusText = _enableAjaxErrorStatusText;
if (input instanceof Request) {
ajaxData.requestUrl = input ? input.url : "";
} else {
ajaxData.requestUrl = input;
}
let method = "GET";
if (init && init.method) {
method = init.method;
} else if (input && input instanceof Request) {
method = input.method;
}
ajaxData.method = method;
let requestHeaders = {};
if (_enableRequestHeaderTracking) {
let headers = new Headers((init ? init.headers : 0) || (input instanceof Request ? (input.headers || {}) : {}));
headers.forEach((value, key) => {
if (_canIncludeHeaders(key)) { requestHeaders[key] = value; }
});
}
ajaxData.requestHeaders = requestHeaders;
_createMarkId("fetch", ajaxData);
return ajaxData;
}
function _getFailedFetchDiagnosticsMessage(input: Request | Response | string): string {
let result: string = "";
try {
if (!isNullOrUndefined(input)) {
if (typeof (input) === "string") {
result += `(url: '${input}')`;
} else {
result += `(url: '${input.url}')`;
}
}
} catch (e) {
_throwInternalCritical(_self,
_InternalMessageId.FailedMonitorAjaxOpen,
"Failed to grab failed fetch diagnostics message",
{ exception: dumpObj(e) }
);
}
return result;
}
function _reportFetchMetrics(callDetails: IInstrumentCallDetails, status: number, input: Request, response: Response | string, ajaxData: ajaxRecord, getResponse:() => IAjaxRecordResponse, properties?: { [key: string]: any }): void {
if (!ajaxData) {
return;
}
function _reportFetchError(msgId: _InternalMessageId, e: any, failedProps?:Object) {
let errorProps = failedProps||{};
errorProps["fetchDiagnosticsMessage"] = _getFailedFetchDiagnosticsMessage(input);
if (e) {
errorProps["exception"] = dumpObj(e);
}
_throwInternalWarning(_self,
msgId,
"Failed to calculate the duration of the fetch call, monitoring data for this fetch call won't be sent.",
errorProps
);
}
ajaxData.responseFinishedTime = dateTimeUtilsNow();
ajaxData.status = status;
_findPerfResourceEntry("fetch", ajaxData, () => {
const dependency = ajaxData.CreateTrackItem("Fetch", _enableRequestHeaderTracking, getResponse);
let properties;
try {
if (!!_addRequestContext) {
properties = _addRequestContext({status, request: input, response});
}
} catch (e) {
_throwInternalWarning(_self,
_InternalMessageId.FailedAddingCustomDefinedRequestContext,
"Failed to add custom defined request context as configured call back may missing a null check.")
}
if (dependency) {
if (properties !== undefined) {
dependency.properties = {...dependency.properties, ...properties};
}
_self[strTrackDependencyDataInternal](dependency);
} else {
_reportFetchError(_InternalMessageId.FailedMonitorAjaxDur, null,
{
requestSentTime: ajaxData.requestSentTime,
responseFinishedTime: ajaxData.responseFinishedTime
});
}
}, (e) => {
_reportFetchError(_InternalMessageId.FailedMonitorAjaxGetCorrelationHeader, e, null);
});
}
function _getFetchCorrelationContext(response: Response): string {
if (response && response.headers) {
try {
const responseHeader: string = response.headers.get(RequestHeaders.requestContextHeader);
return CorrelationIdHelper.getCorrelationContext(responseHeader);
} catch (e) {
_throwInternalWarning(_self,
_InternalMessageId.FailedMonitorAjaxGetCorrelationHeader,
"Failed to get Request-Context correlation header as it may be not included in the response or not accessible.",
{
fetchDiagnosticsMessage: _getFailedFetchDiagnosticsMessage(response),
exception: dumpObj(e)
});
}
}
}
});
}