UNPKG

@microsoft/applicationinsights-core-js

Version:

Microsoft Application Insights Core Javascript SDK

540 lines (537 loc) • 28.6 kB
/* * Application Insights JavaScript SDK - Core, 3.3.6 * Copyright (c) Microsoft and contributors. All rights reserved. */ import dynamicProto from "@microsoft/dynamicproto-js"; import { createPromise, doAwaitResponse } from "@nevware21/ts-async"; import { arrForEach, dumpObj, getNavigator, getWindow, isFunction, objKeys } from "@nevware21/ts-utils"; import { _DYN_DATA, _DYN_HEADERS, _DYN_INITIALIZE, _DYN_LENGTH, _DYN_MESSAGE, _DYN_REPLACE, _DYN_STATUS, _DYN_TIMEOUT, _DYN_TO_LOWER_CASE, _DYN_URL_STRING, _DYN_VALUE, _DYN__DO_TEARDOWN } from "../__DynamicConstants"; import { DisabledPropertyName } from "./Constants"; import { _throwInternal, _warnToConsole } from "./DiagnosticLogger"; import { getLocation, isBeaconsSupported, isFetchSupported, isXhrSupported, useXDomainRequest } from "./EnvUtils"; import { _getAllResponseHeaders, formatErrorMessageXdr, formatErrorMessageXhr, getResponseText, openXhr } from "./HelperFuncs"; var STR_EMPTY = ""; var STR_NO_RESPONSE_BODY = "NoResponseBody"; var _noResponseQs = "&" + STR_NO_RESPONSE_BODY + "=true"; var STR_POST_METHOD = "POST"; /** * This Internal component * Manager SendPost functions * SendPostManger * @internal for internal use only */ var SenderPostManager = /** @class */ (function () { function SenderPostManager() { var _syncFetchPayload = 0; // Keep track of the outstanding sync fetch payload total (as sync fetch has limits) var _enableSendPromise; var _isInitialized; var _diagLog; var _isOneDs; var _onCompleteFuncs; var _disableCredentials; var _fetchCredentials; var _fallbackInst; var _disableXhr; var _disableBeacon; var _disableBeaconSync; var _disableFetchKeepAlive; var _addNoResponse; var _timeoutWrapper; dynamicProto(SenderPostManager, this, function (_self, _base) { var _sendCredentials = true; // for 1ds _initDefaults(); _self[_DYN_INITIALIZE /* @min:%2einitialize */] = function (config, diagLog) { _diagLog = diagLog; if (_isInitialized) { _throwInternal(_diagLog, 1 /* eLoggingSeverity.CRITICAL */, 28 /* _eInternalMessageId.SenderNotInitialized */, "Sender is already initialized"); } _self.SetConfig(config); _isInitialized = true; }; _self["_getDbgPlgTargets"] = function () { return [_isInitialized, _isOneDs, _disableCredentials, _enableSendPromise]; }; // This componet might get its config from sender, offline sender, 1ds post // so set this function to mock dynamic changes _self.SetConfig = function (config) { try { _onCompleteFuncs = config.senderOnCompleteCallBack || {}; _disableCredentials = !!config.disableCredentials; _fetchCredentials = config.fetchCredentials; _isOneDs = !!config.isOneDs; _enableSendPromise = !!config.enableSendPromise; _disableXhr = !!config.disableXhr; _disableBeacon = !!config.disableBeacon; _disableBeaconSync = !!config.disableBeaconSync; _timeoutWrapper = config.timeWrapper; _addNoResponse = !!config.addNoResponse; _disableFetchKeepAlive = !!config.disableFetchKeepAlive; _fallbackInst = { sendPOST: _xhrSender }; if (!_isOneDs) { _sendCredentials = false; // for appInsights, set it to false always } if (_disableCredentials) { var location_1 = getLocation(); if (location_1 && location_1.protocol && location_1.protocol[_DYN_TO_LOWER_CASE /* @min:%2etoLowerCase */]() === "file:") { // Special case where a local html file fails with a CORS error on Chromium browsers _sendCredentials = false; } } return true; } catch (e) { // eslint-disable-next-line no-empty } return false; }; _self.getSyncFetchPayload = function () { return _syncFetchPayload; }; _self.getSenderInst = function (transports, sync) { if (transports && transports[_DYN_LENGTH /* @min:%2elength */]) { return _getSenderInterface(transports, sync); } return null; }; _self.getFallbackInst = function () { return _fallbackInst; }; _self[_DYN__DO_TEARDOWN /* @min:%2e_doTeardown */] = function (unloadCtx, unloadState) { _initDefaults(); }; /** * success handler */ function _onSuccess(res, onComplete) { _doOnComplete(onComplete, 200, {}, res); } /** * error handler */ function _onError(message, onComplete) { _throwInternal(_diagLog, 2 /* eLoggingSeverity.WARNING */, 26 /* _eInternalMessageId.OnError */, "Failed to send telemetry.", { message: message }); _doOnComplete(onComplete, 400, {}); } function _onNoPayloadUrl(onComplete) { _onError("No endpoint url is provided for the batch", onComplete); } function _getSenderInterface(transports, syncSupport) { var transportType = 0 /* TransportType.NotSet */; var sendPostFunc = null; var lp = 0; while (sendPostFunc == null && lp < transports[_DYN_LENGTH /* @min:%2elength */]) { transportType = transports[lp]; if (!_disableXhr && transportType === 1 /* TransportType.Xhr */) { if (useXDomainRequest()) { // IE 8 and 9 sendPostFunc = _xdrSender; } else if (isXhrSupported()) { sendPostFunc = _xhrSender; } } else if (transportType === 2 /* TransportType.Fetch */ && isFetchSupported(syncSupport) && (!syncSupport || !_disableFetchKeepAlive)) { sendPostFunc = _doFetchSender; } else if (transportType === 3 /* TransportType.Beacon */ && isBeaconsSupported() && (syncSupport ? !_disableBeaconSync : !_disableBeacon)) { sendPostFunc = _beaconSender; } lp++; } if (sendPostFunc) { return { _transport: transportType, _isSync: syncSupport, sendPOST: sendPostFunc }; } return null; } function _doOnComplete(oncomplete, status, headers, response) { try { oncomplete && oncomplete(status, headers, response); } catch (e) { // eslint-disable-next-line no-empty } } function _doBeaconSend(payload, oncomplete) { var nav = getNavigator(); var url = payload[_DYN_URL_STRING /* @min:%2eurlString */]; if (!url) { _onNoPayloadUrl(oncomplete); // return true here, because we don't want to retry it with fallback sender return true; } url = payload[_DYN_URL_STRING /* @min:%2eurlString */] + (_addNoResponse ? _noResponseQs : STR_EMPTY); var data = payload[_DYN_DATA /* @min:%2edata */]; // Chrome only allows CORS-safelisted values for the sendBeacon data argument // see: https://bugs.chromium.org/p/chromium/issues/detail?id=720283 // Chrome only allows CORS-safelisted values for the sendBeacon data argument // see: https://bugs.chromium.org/p/chromium/issues/detail?id=720283 var plainTextBatch = _isOneDs ? data : new Blob([data], { type: "text/plain;charset=UTF-8" }); // The sendBeacon method returns true if the user agent is able to successfully queue the data for transfer. Otherwise it returns false. var queued = nav.sendBeacon(url, plainTextBatch); return queued; } /** * Send Beacon API request * @param payload - The data payload to be sent. * @param sync - not used * Note: Beacon API does not support custom headers and we are not able to get * appId from the backend for the correct correlation. */ function _beaconSender(payload, oncomplete, sync) { var data = payload[_DYN_DATA /* @min:%2edata */]; try { if (data) { // The sendBeacon method returns true if the user agent is able to successfully queue the data for transfer. Otherwise it returns false. if (!_doBeaconSend(payload, oncomplete)) { var onRetry = _onCompleteFuncs && _onCompleteFuncs.beaconOnRetry; if (onRetry && isFunction(onRetry)) { onRetry(payload, oncomplete, _doBeaconSend); } else { _fallbackInst && _fallbackInst.sendPOST(payload, oncomplete, true); _throwInternal(_diagLog, 2 /* eLoggingSeverity.WARNING */, 40 /* _eInternalMessageId.TransmissionFailed */, ". " + "Failed to send telemetry with Beacon API, retried with normal sender."); } } else { // if can send _onSuccess(STR_EMPTY, oncomplete); } } } catch (e) { _isOneDs && _warnToConsole(_diagLog, "Failed to send telemetry using sendBeacon API. Ex:" + dumpObj(e)); _doOnComplete(oncomplete, _isOneDs ? 0 : 400, {}, STR_EMPTY); } return; } /** * Send XMLHttpRequest * @param payload - The data payload to be sent. * @param sync - Indicates if the request should be sent synchronously */ function _xhrSender(payload, oncomplete, sync) { //let internalPayload = payload as IInternalPayloadData; var thePromise; var resolveFunc; var rejectFunc; var headers = payload[_DYN_HEADERS /* @min:%2eheaders */] || {}; if (!sync && _enableSendPromise) { thePromise = createPromise(function (resolve, reject) { resolveFunc = resolve; rejectFunc = reject; }); } if (_isOneDs && sync && payload.disableXhrSync) { sync = false; } //const xhr = new XMLHttpRequest(); var endPointUrl = payload[_DYN_URL_STRING /* @min:%2eurlString */]; if (!endPointUrl) { _onNoPayloadUrl(oncomplete); resolveFunc && resolveFunc(false); return; } var xhr = openXhr(STR_POST_METHOD, endPointUrl, _sendCredentials, true, sync, payload[_DYN_TIMEOUT /* @min:%2etimeout */]); if (!_isOneDs) { // application/json should NOT add to 1ds post by default xhr.setRequestHeader("Content-type", "application/json"); } arrForEach(objKeys(headers), function (headerName) { xhr.setRequestHeader(headerName, headers[headerName]); }); xhr.onreadystatechange = function () { if (!_isOneDs) { _doOnReadyFunc(xhr); if (xhr.readyState === 4) { resolveFunc && resolveFunc(true); } } }; xhr.onload = function () { if (_isOneDs) { _doOnReadyFunc(xhr); } }; function _doOnReadyFunc(xhr) { var onReadyFunc = _onCompleteFuncs && _onCompleteFuncs.xhrOnComplete; var onReadyFuncExist = onReadyFunc && isFunction(onReadyFunc); if (onReadyFuncExist) { onReadyFunc(xhr, oncomplete, payload); } else { var response = getResponseText(xhr); _doOnComplete(oncomplete, xhr[_DYN_STATUS /* @min:%2estatus */], _getAllResponseHeaders(xhr, _isOneDs), response); } } xhr.onerror = function (event) { _doOnComplete(oncomplete, _isOneDs ? xhr[_DYN_STATUS /* @min:%2estatus */] : 400, _getAllResponseHeaders(xhr, _isOneDs), _isOneDs ? STR_EMPTY : formatErrorMessageXhr(xhr)); rejectFunc && rejectFunc(event); }; xhr.ontimeout = function () { _doOnComplete(oncomplete, _isOneDs ? xhr[_DYN_STATUS /* @min:%2estatus */] : 500, _getAllResponseHeaders(xhr, _isOneDs), _isOneDs ? STR_EMPTY : formatErrorMessageXhr(xhr)); resolveFunc && resolveFunc(false); }; xhr.send(payload[_DYN_DATA /* @min:%2edata */]); return thePromise; } /** * Send fetch API request * @param payload - The data payload to be sent. * @param sync - For fetch this identifies whether we are "unloading" (false) or a normal request */ function _doFetchSender(payload, oncomplete, sync) { var _a; var endPointUrl = payload[_DYN_URL_STRING /* @min:%2eurlString */]; var batch = payload[_DYN_DATA /* @min:%2edata */]; var plainTextBatch = _isOneDs ? batch : new Blob([batch], { type: "application/json" }); var thePromise; var resolveFunc; var rejectFunc; var requestHeaders = new Headers(); var batchLength = batch[_DYN_LENGTH /* @min:%2elength */]; var ignoreResponse = false; var responseHandled = false; var headers = payload[_DYN_HEADERS /* @min:%2eheaders */] || {}; //TODO: handle time out for 1ds var init = (_a = { method: STR_POST_METHOD, body: plainTextBatch }, _a[DisabledPropertyName] = true // Mark so we don't attempt to track this request , _a); // Only add headers if there are headers to add, due to issue with some polyfills if (payload.headers && objKeys(payload.headers)[_DYN_LENGTH /* @min:%2elength */] > 0) { arrForEach(objKeys(headers), function (headerName) { requestHeaders.append(headerName, headers[headerName]); }); init[_DYN_HEADERS /* @min:%2eheaders */] = requestHeaders; } if (_fetchCredentials) { // if user passed in this value via post channel (1ds), then use it init.credentials = _fetchCredentials; } else if (_sendCredentials && _isOneDs) { // for 1ds, Don't send credentials when URL is file:// init.credentials = "include"; } if (sync) { init.keepalive = true; _syncFetchPayload += batchLength; if (_isOneDs) { if (payload["_sendReason"] === 2 /* SendRequestReason.Unload */) { // As a sync request (during unload), it is unlikely that we will get a chance to process the response so // just like beacon send assume that the events have been accepted and processed ignoreResponse = true; if (_addNoResponse) { endPointUrl += _noResponseQs; } } } else { // for appinsights, set to true for all sync request ignoreResponse = true; } } var request = new Request(endPointUrl, init); try { // Also try and tag the request (just in case the value in init is not copied over) request[DisabledPropertyName] = true; } catch (e) { // If the environment has locked down the XMLHttpRequest (preventExtensions and/or freeze), this would // cause the request to fail and we no telemetry would be sent } if (!sync && _enableSendPromise) { thePromise = createPromise(function (resolve, reject) { resolveFunc = resolve; rejectFunc = reject; }); } if (!endPointUrl) { _onNoPayloadUrl(oncomplete); resolveFunc && resolveFunc(false); return; } function _handleError(res) { // In case there is an error in the request. Set the status to 0 for 1ds and 400 for appInsights // so that the events can be retried later. _doOnComplete(oncomplete, _isOneDs ? 0 : 400, {}, _isOneDs ? STR_EMPTY : res); } function _onFetchComplete(response, payload, value) { var status = response[_DYN_STATUS /* @min:%2estatus */]; var onCompleteFunc = _onCompleteFuncs.fetchOnComplete; if (onCompleteFunc && isFunction(onCompleteFunc)) { onCompleteFunc(response, oncomplete, value || STR_EMPTY, payload); } else { _doOnComplete(oncomplete, status, {}, value || STR_EMPTY); } } try { doAwaitResponse(fetch(_isOneDs ? endPointUrl : request, _isOneDs ? init : null), function (result) { if (sync) { _syncFetchPayload -= batchLength; batchLength = 0; } if (!responseHandled) { responseHandled = true; if (!result.rejected) { var response_1 = result[_DYN_VALUE /* @min:%2evalue */]; try { /** * The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. * Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure * or if anything prevented the request from completing. */ if (!_isOneDs && !response_1.ok) { // this is for appInsights only _handleError(response_1.statusText); resolveFunc && resolveFunc(false); } else { if (_isOneDs && !response_1.body) { _onFetchComplete(response_1, null, STR_EMPTY); resolveFunc && resolveFunc(true); } else { doAwaitResponse(response_1.text(), function (resp) { _onFetchComplete(response_1, payload, resp[_DYN_VALUE /* @min:%2evalue */]); resolveFunc && resolveFunc(true); }); } } } catch (e) { _handleError(dumpObj(e)); rejectFunc && rejectFunc(e); } } else { _handleError(result.reason && result.reason[_DYN_MESSAGE /* @min:%2emessage */]); rejectFunc && rejectFunc(result.reason); } } }); } catch (e) { if (!responseHandled) { _handleError(dumpObj(e)); rejectFunc && rejectFunc(e); } } if (ignoreResponse && !responseHandled) { // Assume success during unload processing as we most likely won't get the response responseHandled = true; _doOnComplete(oncomplete, 200, {}); resolveFunc && resolveFunc(true); } if (_isOneDs && !responseHandled && payload[_DYN_TIMEOUT /* @min:%2etimeout */] > 0) { // Simulate timeout _timeoutWrapper && _timeoutWrapper.set(function () { if (!responseHandled) { // Assume a 500 response (which will cause a retry) responseHandled = true; _doOnComplete(oncomplete, 500, {}); resolveFunc && resolveFunc(true); } }, payload[_DYN_TIMEOUT /* @min:%2etimeout */]); } return thePromise; } /** * Send XDomainRequest * @param payload - The data payload to be sent. * @param sync - Indicates if the request should be sent synchronously * * Note: XDomainRequest does not support sync requests. This 'isAsync' parameter is added * to maintain consistency with the xhrSender's contract * Note: XDomainRequest does not support custom headers and we are not able to get * appId from the backend for the correct correlation. */ function _xdrSender(payload, oncomplete, sync) { // It doesn't support custom headers, so no action is taken with current requestHeaders var _window = getWindow(); var xdr = new XDomainRequest(); var data = payload[_DYN_DATA /* @min:%2edata */]; xdr.onload = function () { var response = getResponseText(xdr); var onloadFunc = _onCompleteFuncs && _onCompleteFuncs.xdrOnComplete; if (onloadFunc && isFunction(onloadFunc)) { onloadFunc(xdr, oncomplete, payload); } else { _doOnComplete(oncomplete, 200, {}, response); } }; xdr.onerror = function () { _doOnComplete(oncomplete, 400, {}, _isOneDs ? STR_EMPTY : formatErrorMessageXdr(xdr)); }; xdr.ontimeout = function () { _doOnComplete(oncomplete, 500, {}); }; xdr.onprogress = function () { }; // XDomainRequest requires the same protocol as the hosting page. // If the protocol doesn't match, we can't send the telemetry :(. var hostingProtocol = _window && _window.location && _window.location.protocol || ""; var endpoint = payload[_DYN_URL_STRING /* @min:%2eurlString */]; if (!endpoint) { _onNoPayloadUrl(oncomplete); return; } if (!_isOneDs && endpoint.lastIndexOf(hostingProtocol, 0) !== 0) { var msg = "Cannot send XDomain request. The endpoint URL protocol doesn't match the hosting page protocol."; _throwInternal(_diagLog, 2 /* eLoggingSeverity.WARNING */, 40 /* _eInternalMessageId.TransmissionFailed */, ". " + msg); _onError(msg, oncomplete); return; } var endpointUrl = _isOneDs ? endpoint : endpoint[_DYN_REPLACE /* @min:%2ereplace */](/^(https?:)/, ""); xdr.open(STR_POST_METHOD, endpointUrl); if (payload[_DYN_TIMEOUT /* @min:%2etimeout */]) { xdr[_DYN_TIMEOUT /* @min:%2etimeout */] = payload[_DYN_TIMEOUT /* @min:%2etimeout */]; } xdr.send(data); if (_isOneDs && sync) { _timeoutWrapper && _timeoutWrapper.set(function () { xdr.send(data); }, 0); } else { xdr.send(data); } } function _initDefaults() { _syncFetchPayload = 0; _isInitialized = false; _enableSendPromise = false; _diagLog = null; _isOneDs = null; _onCompleteFuncs = null; _disableCredentials = null; _fetchCredentials = null; _fallbackInst = null; _disableXhr = false; _disableBeacon = false; _disableBeaconSync = false; _disableFetchKeepAlive = false; _addNoResponse = false; _timeoutWrapper = null; } }); } // Removed Stub for SenderPostManager.prototype.initialize. // Removed Stub for SenderPostManager.prototype.getSyncFetchPayload. // Removed Stub for SenderPostManager.prototype.SetConfig. // Removed Stub for SenderPostManager.prototype.getSenderInst. // Removed Stub for SenderPostManager.prototype.getFallbackInst. // Removed Stub for SenderPostManager.prototype._doTeardown. // This is a workaround for an IE bug when using dynamicProto() with classes that don't have any // non-dynamic functions or static properties/functions when using uglify-js to minify the resulting code. SenderPostManager.__ieDyn=1; return SenderPostManager; }()); export { SenderPostManager }; //# sourceMappingURL=SenderPostManager.js.map