diff --git a/README.md b/README.md index 42a27f182..8d1fa5329 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,8 @@ Most configuration fields are named such that they can be defaulted to falsey. A | featureOptIn
since 3.0.3 | IFeatureOptIn | undefined | [Optional] Set Feature opt in details. | | throttleMgrCfg
since 3.0.3 | `{[key: number]: IThrottleMgrConfig}` | undefined | [Optional] Set throttle mgr configuration by key. | | retryCodes | number[] | undefined | Identifies the status codes that will cause event batches to be resent, when `null` or `undefined` the SDK will use it's defaults `[401, 408, 429, 500, 502, 503, 504]`. `403` was removed in version 3.1.1. | +| expCfg | [`IExceptionConfig`](https://github.com/microsoft/ApplicationInsights-JS/blob/main/shared/AppInsightsCommon/src/Interfaces/IExceptionTelemetry.ts) | undefined | Set additional configuration for exceptions, such as more scripts to include in the exception telemetry. | + ### ExtensionConfig diff --git a/channels/1ds-post-js/src/DataModels.ts b/channels/1ds-post-js/src/DataModels.ts index 6ec624dfe..59483949c 100644 --- a/channels/1ds-post-js/src/DataModels.ts +++ b/channels/1ds-post-js/src/DataModels.ts @@ -256,7 +256,7 @@ export interface IChannelConfiguration { * - "same-origin": only send and include credentials for same-origin requests. * * If not set, the default value will be "include". - * + * * For more information, refer to: * - [Fetch API - Using Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#including_credentials) * @since 3.3.1 diff --git a/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts b/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts index 4528bf636..7c457d856 100644 --- a/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts +++ b/extensions/applicationinsights-analytics-js/Tests/Unit/src/AnalyticsPlugin.tests.ts @@ -7,7 +7,7 @@ import { Exception, SeverityLevel, Event, Trace, PageViewPerformance, IConfig, IExceptionInternal, AnalyticsPluginIdentifier, IAppInsights, Metric, PageView, RemoteDependencyData, utlCanUseLocalStorage, createDomEvent } from "@microsoft/applicationinsights-common"; -import { ITelemetryItem, AppInsightsCore, IPlugin, IConfiguration, IAppInsightsCore, setEnableEnvMocks, getLocation, dumpObj, __getRegisteredEvents, createCookieMgr } from "@microsoft/applicationinsights-core-js"; +import { ITelemetryItem, AppInsightsCore, IPlugin, IConfiguration, IAppInsightsCore, setEnableEnvMocks, getLocation, dumpObj, __getRegisteredEvents, createCookieMgr, findAllScripts } from "@microsoft/applicationinsights-core-js"; import { Sender } from "@microsoft/applicationinsights-channel-js" import { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js"; import { AnalyticsPlugin } from "../../../src/JavaScriptSDK/AnalyticsPlugin"; @@ -406,6 +406,76 @@ export class AnalyticsPluginTests extends AITestClass { this.addOnErrorTests(); this.addTrackMetricTests(); this.addTelemetryInitializerTests(); + this.addScriptInfoTests(); + } + + private addScriptInfoTests(): void { + this.testCase({ + name: "AppInsightsTests: findAllScripts function returns correct information", + useFakeTimers: true, + test: () => { + // Initialize Application Insights core with plugins + let script = window.document.createElement("script"); + script.src = "https://www.example.com/test.js"; + script.innerHTML = 'test script'; + window.document.body.appendChild(script); + + let doc = window.document; + let scriptsInfo = findAllScripts(doc); + Assert.deepEqual(true, JSON.stringify(scriptsInfo).indexOf("https://www.example.com/test.js") !== -1, "script info contains the correct url"); + } + }); + this.testCase({ + name: "AppInsightsTests: trackException would contain scrips info when config turns on", + useFakeTimers: true, + test: () => { + const appInsights = new AnalyticsPlugin(); + const core = new AppInsightsCore(); + const channel = new ChannelPlugin(); + const properties = new PropertiesPlugin(); + // Configuration + const config = { + instrumentationKey: 'ikey', + }; + this.onDone(() => { + core.unload(false); + }); + // Initialize Application Insights core with plugins + core.initialize(config, [appInsights, channel, properties]); + + // add test script + let script = window.document.createElement("script"); + script.src = "https://www.example1.com/test.js"; + script.setAttribute("referrerpolicy", "no-referrer"); + script.innerHTML = 'test script'; + let script2 = window.document.createElement("script"); + script2.src = "https://www.test.com/test.js"; + script2.innerHTML = "test tests"; + script2.setAttribute("async", ""); + window.document.body.appendChild(script); + window.document.body.appendChild(script2); + + + const trackStub = this.sandbox.stub(appInsights.core, "track"); + appInsights.trackException({error: new Error(), severityLevel: SeverityLevel.Critical}); + Assert.ok(trackStub.calledOnce, "single exception is tracked"); + const baseData = (trackStub.args[0][0] as ITelemetryItem).baseData as IExceptionInternal; + const prop = baseData.properties; + Assert.equal(-1, JSON.stringify(prop).indexOf("https://www.example.com/test.js"), "script info is not included"); + + appInsights.config.expCfg.inclScripts = true; + this.clock.tick(1); + appInsights.trackException({error: new Error(), severityLevel: SeverityLevel.Critical}); + Assert.ok(trackStub.calledTwice, "single exception is tracked"); + const baseData2 = (trackStub.args[1][0] as ITelemetryItem).baseData as IExceptionInternal; + const prop2 = baseData2.properties; + Assert.deepEqual(true, prop2["exceptionScripts"].includes('"url":"https://www.test.com/test.js","async":true}')) + Assert.deepEqual(true, prop2["exceptionScripts"].includes('"url":"https://www.example1.com/test.js","referrerPolicy":"no-referrer"')) + + } + }); + + } private addGenericTests(): void { diff --git a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts index 6edfb95ed..2530e01b5 100644 --- a/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts +++ b/extensions/applicationinsights-analytics-js/src/JavaScriptSDK/AnalyticsPlugin.ts @@ -6,7 +6,7 @@ import dynamicProto from "@microsoft/dynamicproto-js"; import { AnalyticsPluginIdentifier, Event as EventTelemetry, Exception, IAppInsights, IAutoExceptionTelemetry, IConfig, IDependencyTelemetry, - IEventTelemetry, IExceptionInternal, IExceptionTelemetry, IMetricTelemetry, IPageViewPerformanceTelemetry, + IEventTelemetry, IExceptionConfig, IExceptionInternal, IExceptionTelemetry, IMetricTelemetry, IPageViewPerformanceTelemetry, IPageViewPerformanceTelemetryInternal, IPageViewTelemetry, IPageViewTelemetryInternal, ITraceTelemetry, Metric, PageView, PageViewPerformance, PropertiesPluginIdentifier, RemoteDependencyData, Trace, createDistributedTraceContextFromTrace, createDomEvent, createTelemetryItem, dataSanitizeString, eSeverityLevel, isCrossOriginError, strNotSpecified, utlDisableStorage, utlEnableStorage, @@ -16,9 +16,10 @@ import { BaseTelemetryPlugin, IAppInsightsCore, IConfigDefaults, IConfiguration, ICookieMgr, ICustomProperties, IDistributedTraceContext, IInstrumentCallDetails, IPlugin, IProcessTelemetryContext, IProcessTelemetryUnloadContext, ITelemetryInitializerHandler, ITelemetryItem, ITelemetryPluginChain, ITelemetryUnloadState, InstrumentEvent, TelemetryInitializerFunction, _eInternalMessageId, arrForEach, - cfgDfBoolean, cfgDfSet, cfgDfString, cfgDfValidate, createProcessTelemetryContext, createUniqueNamespace, dumpObj, eLoggingSeverity, - eventOff, eventOn, generateW3CId, getDocument, getExceptionName, getHistory, getLocation, getWindow, hasHistory, hasWindow, isFunction, - isNullOrUndefined, isString, isUndefined, mergeEvtNamespace, onConfigChange, safeGetCookieMgr, strUndefined, throwError + cfgDfBoolean, cfgDfMerge, cfgDfSet, cfgDfString, cfgDfValidate, createProcessTelemetryContext, createUniqueNamespace, dumpObj, + eLoggingSeverity, eventOff, eventOn, findAllScripts, generateW3CId, getDocument, getExceptionName, getHistory, getLocation, getWindow, + hasHistory, hasWindow, isFunction, isNullOrUndefined, isString, isUndefined, mergeEvtNamespace, onConfigChange, safeGetCookieMgr, + strUndefined, throwError } from "@microsoft/applicationinsights-core-js"; import { PropertiesPlugin } from "@microsoft/applicationinsights-properties-js"; import { isError, objDeepFreeze, objDefine, scheduleTimeout, strIndexOf } from "@nevware21/ts-utils"; @@ -66,7 +67,8 @@ const defaultValues: IConfigDefaults = objDeepFreeze({ namePrefix: cfgDfString(), enableDebug: cfgDfBoolean(), disableFlushOnBeforeUnload: cfgDfBoolean(), - disableFlushOnUnload: cfgDfBoolean(false, "disableFlushOnBeforeUnload") + disableFlushOnUnload: cfgDfBoolean(false, "disableFlushOnBeforeUnload"), + expCfg: cfgDfMerge({inclScripts: false}) }); function _chkConfigMilliseconds(value: number, defValue: number): number { @@ -422,7 +424,11 @@ export class AnalyticsPlugin extends BaseTelemetryPlugin implements IAppInsights exception.severityLevel, exception.id ).toInterface(); - + var doc = getDocument(); + if (doc && _self.config.expCfg?.inclScripts) { + var scriptsInfo = findAllScripts(doc); + exceptionPartB.properties["exceptionScripts"] = JSON.stringify(scriptsInfo); + } let telemetryItem: ITelemetryItem = createTelemetryItem( exceptionPartB, Exception.dataType, diff --git a/shared/AppInsightsCommon/src/Interfaces/IConfig.ts b/shared/AppInsightsCommon/src/Interfaces/IConfig.ts index 878911921..7c63fe954 100644 --- a/shared/AppInsightsCommon/src/Interfaces/IConfig.ts +++ b/shared/AppInsightsCommon/src/Interfaces/IConfig.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { IConfiguration, ICustomProperties, isNullOrUndefined } from "@microsoft/applicationinsights-core-js"; import { DistributedTracingModes } from "../Enums"; +import { IExceptionConfig } from "./IExceptionTelemetry"; import { IRequestContext } from "./IRequestContext"; import { IStorageBuffer } from "./IStorageBuffer"; import { IThrottleMgrConfig } from "./IThrottleMgr"; @@ -389,6 +390,11 @@ export interface IConfig { * This URL takes precedence over the 'config.endpointUrl' and any endpoint in the connection string. */ userOverrideEndpointUrl?: string; + + /** + * [Optional] Set additional configuration for exceptions, such as more scripts to include in the exception telemetry. + */ + expCfg?: IExceptionConfig; } export class ConfigurationManager { diff --git a/shared/AppInsightsCommon/src/Interfaces/IExceptionTelemetry.ts b/shared/AppInsightsCommon/src/Interfaces/IExceptionTelemetry.ts index 6f0be410f..5207e515d 100644 --- a/shared/AppInsightsCommon/src/Interfaces/IExceptionTelemetry.ts +++ b/shared/AppInsightsCommon/src/Interfaces/IExceptionTelemetry.ts @@ -4,6 +4,14 @@ import { SeverityLevel } from "./Contracts/SeverityLevel"; import { IPartC } from "./IPartC"; +export interface IExceptionConfig{ + /** + * If set to true, when exception is sent out, the SDK will also send out all scripts basic info that are loaded on the page. + * Notice: This would increase the size of the exception telemetry. + */ + inclScripts?: boolean; +} + /** * @export * @interface IExceptionTelemetry diff --git a/shared/AppInsightsCommon/src/applicationinsights-common.ts b/shared/AppInsightsCommon/src/applicationinsights-common.ts index 32af7d137..dcc988c06 100644 --- a/shared/AppInsightsCommon/src/applicationinsights-common.ts +++ b/shared/AppInsightsCommon/src/applicationinsights-common.ts @@ -26,7 +26,7 @@ export { IEventTelemetry } from "./Interfaces/IEventTelemetry"; export { ITraceTelemetry } from "./Interfaces/ITraceTelemetry"; export { IMetricTelemetry } from "./Interfaces/IMetricTelemetry"; export { IDependencyTelemetry } from "./Interfaces/IDependencyTelemetry"; -export { IExceptionTelemetry, IAutoExceptionTelemetry, IExceptionInternal } from "./Interfaces/IExceptionTelemetry"; +export { IExceptionTelemetry, IAutoExceptionTelemetry, IExceptionInternal, IExceptionConfig } from "./Interfaces/IExceptionTelemetry"; export { IPageViewTelemetry, IPageViewTelemetryInternal } from "./Interfaces/IPageViewTelemetry"; export { IPageViewPerformanceTelemetry, IPageViewPerformanceTelemetryInternal } from "./Interfaces/IPageViewPerformanceTelemetry"; export { Trace } from "./Telemetry/Trace"; @@ -64,7 +64,7 @@ export { eDistributedTracingModes, DistributedTracingModes, EventPersistence } f export { stringToBoolOrDefault, msToTimeSpan, getExtensionByName, isCrossOriginError } from "./HelperFuncs"; export { isBeaconsSupported as isBeaconApiSupported, - ITraceParent, createTraceParent, parseTraceParent, isValidTraceId, isValidSpanId, isValidTraceParent, isSampledFlag, formatTraceParent, findW3cTraceParent + ITraceParent, createTraceParent, parseTraceParent, isValidTraceId, isValidSpanId, isValidTraceParent, isSampledFlag, formatTraceParent, findW3cTraceParent, findAllScripts } from "@microsoft/applicationinsights-core-js"; export { createDomEvent } from "./DomHelperFuncs"; export { diff --git a/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts b/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts index 1cdcf9337..8a22e9ae6 100644 --- a/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts +++ b/shared/AppInsightsCore/Tests/Unit/src/AppInsightsCoreSize.Tests.ts @@ -4,8 +4,8 @@ import * as pako from "pako"; export class AppInsightsCoreSizeCheck extends AITestClass { private readonly MAX_RAW_SIZE = 65; private readonly MAX_BUNDLE_SIZE = 65; - private readonly MAX_RAW_DEFLATE_SIZE = 27; - private readonly MAX_BUNDLE_DEFLATE_SIZE = 27; + private readonly MAX_RAW_DEFLATE_SIZE = 28; + private readonly MAX_BUNDLE_DEFLATE_SIZE = 28; private readonly rawFilePath = "../dist/es5/applicationinsights-core-js.min.js"; private readonly prodFilePath = "../browser/es5/applicationinsights-core-js.min.js"; diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/W3cTraceParent.ts b/shared/AppInsightsCore/src/JavaScriptSDK/W3cTraceParent.ts index e1264da83..ca62f1723 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/W3cTraceParent.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/W3cTraceParent.ts @@ -1,4 +1,4 @@ -import { isArray, isString, strLeft, strTrim } from "@nevware21/ts-utils"; +import { arrForEach, isArray, isString, strLeft, strTrim } from "@nevware21/ts-utils"; import { ITraceParent } from "../JavaScriptSDK.Interfaces/ITraceParent"; import { generateW3CId } from "./CoreUtils"; import { findMetaTag, findNamedServerTiming } from "./EnvUtils"; @@ -202,3 +202,45 @@ export function findW3cTraceParent(selectIdx?: number): ITraceParent { return traceParent; } + +export interface scriptsInfo { + url: string; + crossOrigin?: string; + async?: boolean; + defer?: boolean; + referrerPolicy?: string; +} + +/** + * Find all script tags in the provided document and return the information about them. + * @param doc + * @returns + */ +export function findAllScripts(doc: any) { + let scripts = doc.getElementsByTagName("script"); + let result: scriptsInfo[] = []; + arrForEach(scripts, (script: any) => { + let src = script.getAttribute("src"); + if (src) { + let crossOrigin = script.getAttribute("crossorigin"); + let async = script.hasAttribute("async") === true; + let defer = script.hasAttribute("defer") === true; + let referrerPolicy = script.getAttribute("referrerpolicy"); + let info: scriptsInfo = { url: src }; + if (crossOrigin) { + info.crossOrigin = crossOrigin; + } + if (async) { + info.async = async; + } + if (defer) { + info.defer = defer; + } + if (referrerPolicy) { + info.referrerPolicy = referrerPolicy; + } + result.push(info); + } + }); + return result; +} diff --git a/shared/AppInsightsCore/src/applicationinsights-core-js.ts b/shared/AppInsightsCore/src/applicationinsights-core-js.ts index 9eaf98bde..3a8551abc 100644 --- a/shared/AppInsightsCore/src/applicationinsights-core-js.ts +++ b/shared/AppInsightsCore/src/applicationinsights-core-js.ts @@ -93,7 +93,7 @@ export { ITelemetryUnloadState } from "./JavaScriptSDK.Interfaces/ITelemetryUnlo export { IDistributedTraceContext } from "./JavaScriptSDK.Interfaces/IDistributedTraceContext"; export { ITraceParent } from "./JavaScriptSDK.Interfaces/ITraceParent"; export { - createTraceParent, parseTraceParent, isValidTraceId, isValidSpanId, isValidTraceParent, isSampledFlag, formatTraceParent, findW3cTraceParent + createTraceParent, parseTraceParent, isValidTraceId, isValidSpanId, isValidTraceParent, isSampledFlag, formatTraceParent, findW3cTraceParent, findAllScripts } from "./JavaScriptSDK/W3cTraceParent"; // Dynamic Config definitions