diff --git a/AISKU/package.json b/AISKU/package.json index b0999df92..47ceb9200 100644 --- a/AISKU/package.json +++ b/AISKU/package.json @@ -13,7 +13,8 @@ }, "scripts": { "clean": "rm -rfv browser types dist-esm", - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt aisku", "build:browser": "rollup -c rollup.config.js", "build:snippet": "grunt snippetvnext", diff --git a/AISKULight/package.json b/AISKULight/package.json index f46610aef..340cf6161 100644 --- a/AISKULight/package.json +++ b/AISKULight/package.json @@ -8,7 +8,8 @@ "types": "types/index.d.ts", "sideEffects": false, "scripts": { - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt aiskulite", "build:browser": "rollup -c rollup.config.js", "test": "echo 'No tests'", diff --git a/channels/applicationinsights-channel-js/package.json b/channels/applicationinsights-channel-js/package.json index a5df6f4f5..d627b61be 100644 --- a/channels/applicationinsights-channel-js/package.json +++ b/channels/applicationinsights-channel-js/package.json @@ -9,7 +9,8 @@ "repository": "github:Microsoft/applicationinsights-js", "scripts": { "clean": "rm -rf browser dist dist-esm types", - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt aichannel", "build:browser": "rollup -c", "test": "grunt aichanneltest", diff --git a/extensions/applicationinsights-analytics-js/package.json b/extensions/applicationinsights-analytics-js/package.json index 54e328a24..b3fde404c 100644 --- a/extensions/applicationinsights-analytics-js/package.json +++ b/extensions/applicationinsights-analytics-js/package.json @@ -11,7 +11,8 @@ "url": "https://github.com/microsoft/ApplicationInsights-JS/tree/master/extensions/applicationinsights-analytics-js" }, "scripts": { - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt ai", "build:browser": "rollup -c", "test": "grunt aitests", diff --git a/extensions/applicationinsights-clickanalytics-js/package.json b/extensions/applicationinsights-clickanalytics-js/package.json index be97bfc4a..49d297604 100644 --- a/extensions/applicationinsights-clickanalytics-js/package.json +++ b/extensions/applicationinsights-clickanalytics-js/package.json @@ -7,7 +7,8 @@ "types": "types/applicationinsights-clickanalytics-js.d.ts", "sideEffects": false, "scripts": { - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt clickanalytics", "build:browser": "rollup -c", "test": "grunt clickanalyticstests", diff --git a/extensions/applicationinsights-debugplugin-js/package.json b/extensions/applicationinsights-debugplugin-js/package.json index b1fcc2fa7..4b6062fc9 100644 --- a/extensions/applicationinsights-debugplugin-js/package.json +++ b/extensions/applicationinsights-debugplugin-js/package.json @@ -11,7 +11,8 @@ "url": "https://github.com/microsoft/ApplicationInsights-JS/tree/master/extensions/applicationinsights-debugplugin-js" }, "scripts": { - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt debugplugin", "build:browser": "rollup -c", "test": "", diff --git a/extensions/applicationinsights-dependencies-js/package.json b/extensions/applicationinsights-dependencies-js/package.json index 22cf24252..4cc88bcd7 100644 --- a/extensions/applicationinsights-dependencies-js/package.json +++ b/extensions/applicationinsights-dependencies-js/package.json @@ -11,7 +11,8 @@ "url": "https://github.com/microsoft/ApplicationInsights-JS/tree/master/extensions/applicationinsights-dependencies-js" }, "scripts": { - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt deps", "build:browser": "rollup -c", "test": "grunt depstest", diff --git a/extensions/applicationinsights-properties-js/package.json b/extensions/applicationinsights-properties-js/package.json index d9733a752..3122402a7 100644 --- a/extensions/applicationinsights-properties-js/package.json +++ b/extensions/applicationinsights-properties-js/package.json @@ -12,6 +12,7 @@ }, "scripts": { "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt properties", "build:browser": "rollup -c", "test": "grunt propertiestests", diff --git a/shared/AppInsightsCommon/package.json b/shared/AppInsightsCommon/package.json index 70435d51d..82d3d1692 100644 --- a/shared/AppInsightsCommon/package.json +++ b/shared/AppInsightsCommon/package.json @@ -11,7 +11,8 @@ "url": "https://github.com/microsoft/ApplicationInsights-JS/tree/master/shared/AppInsightsCommon" }, "scripts": { - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt common", "build:browser": "rollup -c", "test": "grunt commontest", diff --git a/shared/AppInsightsCore/package.json b/shared/AppInsightsCore/package.json index c8fb323fd..fcaf8e013 100644 --- a/shared/AppInsightsCore/package.json +++ b/shared/AppInsightsCore/package.json @@ -17,7 +17,8 @@ "types": "types/applicationinsights-core-js.d.ts", "scripts": { "clean": "grunt clean", - "build": "npm run build:esm && npm run build:browser && npm run dtsgen", + "build": "npm run build:esm && npm run build:browser", + "build-gen": "npm run build:esm && npm run build:browser && npm run dtsgen", "build:esm": "grunt core", "build:browser": "rollup -c rollup.config.js", "test": "grunt coretest", diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts b/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts index 94f87f9bf..4f0dd13e0 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/AppInsightsCore.ts @@ -8,12 +8,12 @@ import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; import { INotificationListener } from "../JavaScriptSDK.Interfaces/INotificationListener"; import { EventsDiscardedReason } from "../JavaScriptSDK.Enums/EventsDiscardedReason"; import { NotificationManager } from "./NotificationManager"; -import { CoreUtils } from "./CoreUtils"; import { doPerf } from "./PerfManager"; import { INotificationManager } from '../JavaScriptSDK.Interfaces/INotificationManager'; import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger"; import { _InternalLogMessage, DiagnosticLogger } from "./DiagnosticLogger"; import dynamicProto from '@microsoft/dynamicproto-js'; +import { arrForEach, isNullOrUndefined, toISOString, throwError } from "./HelperFuncs"; "use strict"; @@ -32,7 +32,7 @@ export class AppInsightsCore extends BaseCore implements IAppInsightsCore { if (telemetryItem === null) { _notifyInvalidEvent(telemetryItem); // throw error - throw Error("Invalid telemetry item"); + throwError("Invalid telemetry item"); } // do basic validation before sending it through the pipeline @@ -78,11 +78,11 @@ export class AppInsightsCore extends BaseCore implements IAppInsightsCore { return setInterval(() => { const queue: _InternalLogMessage[] = _self.logger ? _self.logger.queue : []; - CoreUtils.arrForEach(queue, (logMessage: _InternalLogMessage) => { + arrForEach(queue, (logMessage: _InternalLogMessage) => { const item: ITelemetryItem = { name: eventName ? eventName : "InternalMessageId: " + logMessage.messageId, iKey: _self.config.instrumentationKey, - time: CoreUtils.toISOString(new Date()), + time: toISOString(new Date()), baseType: _InternalLogMessage.dataType, baseData: { message: logMessage.message } }; @@ -94,7 +94,7 @@ export class AppInsightsCore extends BaseCore implements IAppInsightsCore { } function _validateTelemetryItem(telemetryItem: ITelemetryItem) { - if (CoreUtils.isNullOrUndefined(telemetryItem.name)) { + if (isNullOrUndefined(telemetryItem.name)) { _notifyInvalidEvent(telemetryItem); throw Error("telemetry name required"); } diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/BaseCore.ts b/shared/AppInsightsCore/src/JavaScriptSDK/BaseCore.ts index 6d408dbda..1115692dd 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/BaseCore.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/BaseCore.ts @@ -2,12 +2,13 @@ // Licensed under the MIT License. "use strict"; +import { objCreateFn } from "@microsoft/applicationinsights-shims"; +import dynamicProto from '@microsoft/dynamicproto-js'; import { IAppInsightsCore } from "../JavaScriptSDK.Interfaces/IAppInsightsCore" import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration"; import { IPlugin, ITelemetryPlugin } from "../JavaScriptSDK.Interfaces/ITelemetryPlugin"; import { IChannelControls } from "../JavaScriptSDK.Interfaces/IChannelControls"; import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; -import { CoreUtils } from "./CoreUtils"; import { INotificationManager } from '../JavaScriptSDK.Interfaces/INotificationManager'; import { INotificationListener } from "../JavaScriptSDK.Interfaces/INotificationListener"; import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger"; @@ -16,14 +17,13 @@ import { IProcessTelemetryContext } from '../JavaScriptSDK.Interfaces/IProcessTe import { ProcessTelemetryContext } from './ProcessTelemetryContext'; import { initializePlugins, sortPlugins } from './TelemetryHelpers'; import { _InternalMessageId, LoggingSeverity } from "../JavaScriptSDK.Enums/LoggingEnums"; -import dynamicProto from '@microsoft/dynamicproto-js'; import { IPerfManager } from "../JavaScriptSDK.Interfaces/IPerfManager"; import { PerfManager } from "./PerfManager"; +import { arrForEach, isNullOrUndefined, isUndefined, toISOString, getSetValue, setValue, throwError, isNotTruthy } from "./HelperFuncs"; +import { strExtensionConfig, strIKey } from "./Constants"; const validationError = "Extensions must provide callback to initialize"; -const _arrForEach = CoreUtils.arrForEach; -const _isNullOrUndefined = CoreUtils.isNullOrUndefined; const strNotificationManager = "_notificationManager"; export class BaseCore implements IAppInsightsCore { @@ -44,7 +44,7 @@ export class BaseCore implements IAppInsightsCore { dynamicProto(BaseCore, this, (_self) => { _self._extensions = new Array(); _channelController = new ChannelController(); - _self.logger = CoreUtils.objCreate({ + _self.logger = objCreateFn({ throwInternal: (severity: LoggingSeverity, msgId: _InternalMessageId, msg: string, properties?: Object, isUserAct = false) => { }, warnToConsole: (message: string) => { }, resetInternalMessageCount: () => { } @@ -56,11 +56,11 @@ export class BaseCore implements IAppInsightsCore { _self.initialize = (config: IConfiguration, extensions: IPlugin[], logger?: IDiagnosticLogger, notificationManager?: INotificationManager): void => { // Make sure core is only initialized once if (_self.isInitialized()) { - throw Error("Core should not be initialized more than once"); + throwError("Core should not be initialized more than once"); } - if (!config || _isNullOrUndefined(config.instrumentationKey)) { - throw Error("Please provide instrumentation key"); + if (!config || isNullOrUndefined(config.instrumentationKey)) { + throwError("Please provide instrumentation key"); } _notificationManager = notificationManager; @@ -69,11 +69,11 @@ export class BaseCore implements IAppInsightsCore { _self[strNotificationManager] = notificationManager; _self.config = config || {}; - - config.extensions = _isNullOrUndefined(config.extensions) ? [] : config.extensions; + + config.extensions = isNullOrUndefined(config.extensions) ? [] : config.extensions; // add notification to the extensions in the config so other plugins can access it - let extConfig = config.extensionConfig = _isNullOrUndefined(config.extensionConfig) ? {} : config.extensionConfig; + let extConfig = getSetValue(config, strExtensionConfig); extConfig.NotificationManager = notificationManager; if (logger) { @@ -93,16 +93,16 @@ export class BaseCore implements IAppInsightsCore { const extPriorities = {}; // Extension validation - _arrForEach(allExtensions, (ext: ITelemetryPlugin) => { - if (_isNullOrUndefined(ext) || _isNullOrUndefined(ext.initialize)) { - throw Error(validationError); + arrForEach(allExtensions, (ext: ITelemetryPlugin) => { + if (isNullOrUndefined(ext) || isNullOrUndefined(ext.initialize)) { + throwError(validationError); } const extPriority = ext.priority; const identifier = ext.identifier; if (ext && extPriority) { - if (!_isNullOrUndefined(extPriorities[extPriority])) { + if (!isNullOrUndefined(extPriorities[extPriority])) { logger.warnToConsole("Two extensions have same priority #" + extPriority + " - " + extPriorities[extPriority] + ", " + identifier); } else { // set a value @@ -138,7 +138,7 @@ export class BaseCore implements IAppInsightsCore { _self._extensions = coreExtensions; if (_self.getTransmissionControls().length === 0) { - throw new Error("No channels available"); + throwError("No channels available"); } _isInitialized = true; @@ -150,18 +150,14 @@ export class BaseCore implements IAppInsightsCore { }; _self.track = (telemetryItem: ITelemetryItem) => { - if (!telemetryItem.iKey) { - // setup default iKey if not passed in - telemetryItem.iKey = _self.config.instrumentationKey; - } - if (!telemetryItem.time) { - // add default timestamp if not passed in - telemetryItem.time = CoreUtils.toISOString(new Date()); - } - if (_isNullOrUndefined(telemetryItem.ver)) { - // CommonSchema 4.0 - telemetryItem.ver = "4.0"; - } + // setup default iKey if not passed in + setValue(telemetryItem, strIKey, _self.config.instrumentationKey, null, isNotTruthy); + + // add default timestamp if not passed in + setValue(telemetryItem, "time", toISOString(new Date()), null, isNotTruthy); + + // Common Schema 4.0 + setValue(telemetryItem, "ver", "4.0", null, isNullOrUndefined); if (_self.isInitialized()) { // Process the telemetry plugin chain @@ -188,7 +184,7 @@ export class BaseCore implements IAppInsightsCore { _self.getNotifyMgr = (): INotificationManager => { if (!_notificationManager) { // Create Dummy notification manager - _notificationManager = CoreUtils.objCreate({ + _notificationManager = objCreateFn({ addNotificationListener: (listener: INotificationListener) => { }, removeNotificationListener: (listener: INotificationListener) => { }, eventsSent: (events: ITelemetryItem[]) => { }, @@ -223,7 +219,7 @@ export class BaseCore implements IAppInsightsCore { _self.releaseQueue = () => { if (_eventQueue.length > 0) { - _arrForEach(_eventQueue, (event: ITelemetryItem) => { + arrForEach(_eventQueue, (event: ITelemetryItem) => { _self.getProcessTelContext().processNext(event); }); diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/BaseTelemetryPlugin.ts b/shared/AppInsightsCore/src/JavaScriptSDK/BaseTelemetryPlugin.ts index 12f7f244f..49b274200 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/BaseTelemetryPlugin.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/BaseTelemetryPlugin.ts @@ -10,11 +10,11 @@ import { IPlugin, ITelemetryPlugin } from "../JavaScriptSDK.Interfaces/ITelemetr import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; import { IProcessTelemetryContext } from "../JavaScriptSDK.Interfaces/IProcessTelemetryContext"; import { ITelemetryPluginChain } from "../JavaScriptSDK.Interfaces/ITelemetryPluginChain"; -import { CoreUtils } from "./CoreUtils"; import { ProcessTelemetryContext } from './ProcessTelemetryContext'; +import { isFunction, isNullOrUndefined, setValue } from "./HelperFuncs"; +import { strExtensionConfig } from "./Constants"; -let _isFunction = CoreUtils.isFunction; -let getPlugin = "getPlugin"; +let strGetPlugin = "getPlugin"; /** * BaseTelemetryPlugin provides a basic implementation of the ITelemetryPlugin interface so that plugins @@ -109,7 +109,7 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin { if (itemCtx) { // Normal core execution sequence itemCtx.processNext(env); - } else if (_nextPlugin && _isFunction(_nextPlugin.processTelemetry)) { + } else if (_nextPlugin && isFunction(_nextPlugin.processTelemetry)) { // Looks like backward compatibility or out of band processing. And as it looks // like a ITelemetryPlugin or ITelemetryPluginChain, just call processTelemetry _nextPlugin.processTelemetry(env, null); @@ -121,9 +121,9 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin { if (!itemCtx) { let rootCtx = _rootCtx || new ProcessTelemetryContext(null, {}, _self.core); // tslint:disable-next-line: prefer-conditional-expression - if (_nextPlugin && _nextPlugin[getPlugin]) { + if (_nextPlugin && _nextPlugin[strGetPlugin]) { // Looks like a chain object - itemCtx = rootCtx.createNew(null, _nextPlugin[getPlugin]); + itemCtx = rootCtx.createNew(null, _nextPlugin[strGetPlugin]); } else { itemCtx = rootCtx.createNew(null, _nextPlugin as ITelemetryPlugin); } @@ -135,7 +135,7 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin { _self._baseTelInit = (config: IConfiguration, core: IAppInsightsCore, extensions: IPlugin[], pluginChain?:ITelemetryPluginChain) => { if (config) { // Make sure the extensionConfig exists - config.extensionConfig = config.extensionConfig || []; + setValue(config, strExtensionConfig, [], null, isNullOrUndefined); } if (!pluginChain && core) { @@ -144,9 +144,9 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin { } let nextPlugin:IPlugin = _nextPlugin as IPlugin; - if (_nextPlugin && _nextPlugin[getPlugin]) { + if (_nextPlugin && _nextPlugin[strGetPlugin]) { // If it looks like a proxy/chain then get the plugin - nextPlugin = _nextPlugin[getPlugin](); + nextPlugin = _nextPlugin[strGetPlugin](); } // Support legacy plugins where core was defined as a property diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/ChannelController.ts b/shared/AppInsightsCore/src/JavaScriptSDK/ChannelController.ts index f269061b6..0559820ef 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/ChannelController.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/ChannelController.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. "use strict"; +import dynamicProto from '@microsoft/dynamicproto-js'; import { IAppInsightsCore } from "../JavaScriptSDK.Interfaces/IAppInsightsCore" import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration"; import { IChannelControls } from "../JavaScriptSDK.Interfaces/IChannelControls"; @@ -9,18 +10,16 @@ import { IPlugin, ITelemetryPlugin, } from "../JavaScriptSDK.Interfaces/ITeleme import { ITelemetryPluginChain } from "../JavaScriptSDK.Interfaces/ITelemetryPluginChain"; import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; import { IProcessTelemetryContext } from "../JavaScriptSDK.Interfaces/IProcessTelemetryContext"; -import { CoreUtils } from "./CoreUtils"; import { _InternalLogMessage } from "./DiagnosticLogger"; import { BaseTelemetryPlugin } from './BaseTelemetryPlugin'; import { ProcessTelemetryContext } from './ProcessTelemetryContext'; import { initializePlugins } from './TelemetryHelpers'; -import dynamicProto from '@microsoft/dynamicproto-js'; +import { arrForEach, objDefineAccessors, throwError } from "./HelperFuncs"; +import { disableCookies } from './CoreUtils'; const ChannelControllerPriority = 500; const ChannelValidationMessage = "Channel has invalid priority"; -let _objDefineAccessors = CoreUtils.objDefineAccessors; - export class ChannelController extends BaseTelemetryPlugin { identifier: string = "ChannelControllerPlugin"; @@ -30,7 +29,6 @@ export class ChannelController extends BaseTelemetryPlugin { constructor() { super(); - let _arrForEach = CoreUtils.arrForEach; let _channelQueue: IChannelControls[][]; dynamicProto(ChannelController, this, (_self, _base) => { @@ -40,7 +38,7 @@ export class ChannelController extends BaseTelemetryPlugin { _self.processTelemetry = (item: ITelemetryItem, itemCtx: IProcessTelemetryContext) => { if (_channelQueue) { - _arrForEach(_channelQueue, queues => { + arrForEach(_channelQueue, queues => { // pass on to first item in queue if (queues.length > 0) { // Copying the item context as we could have mutiple chains that are executing asynchronously @@ -65,20 +63,20 @@ export class ChannelController extends BaseTelemetryPlugin { _base.initialize(config, core, extensions); if ((config as any).isCookieUseDisabled) { - CoreUtils.disableCookies(); + disableCookies(); } _createChannelQueues((config||{}).channels, extensions); // Initialize the Queues - _arrForEach(_channelQueue, queue => initializePlugins(new ProcessTelemetryContext(queue, config, core), extensions)); + arrForEach(_channelQueue, queue => initializePlugins(new ProcessTelemetryContext(queue, config, core), extensions)); } }); function _checkQueuePriority(queue:IChannelControls[]) { - _arrForEach(queue, queueItem => { + arrForEach(queue, queueItem => { if (queueItem.priority < ChannelControllerPriority) { - throw Error(ChannelValidationMessage + queueItem.identifier); + throwError(ChannelValidationMessage + queueItem.identifier); } }); } @@ -99,13 +97,13 @@ export class ChannelController extends BaseTelemetryPlugin { if (channels) { // Add and sort the configuration channel queues - _arrForEach(channels, queue => _addChannelQueue(queue)); + arrForEach(channels, queue => _addChannelQueue(queue)); } if (extensions) { // Create a new channel queue for any extensions with a priority > the ChannelControllerPriority let extensionQueue:IChannelControls[] = []; - _arrForEach(extensions as IChannelControls[], plugin => { + arrForEach(extensions as IChannelControls[], plugin => { if (plugin.priority > ChannelControllerPriority) { extensionQueue.push(plugin); } @@ -135,7 +133,7 @@ export class ChannelController extends BaseTelemetryPlugin { // tslint:disable-next-line private static _staticInit = (() => { // Dynamically create get/set property accessors - _objDefineAccessors(ChannelController.prototype, "ChannelControls", ChannelController.prototype.getChannelControls); - _objDefineAccessors(ChannelController.prototype, "channelQueue", ChannelController.prototype.getChannelControls); + objDefineAccessors(ChannelController.prototype, "ChannelControls", ChannelController.prototype.getChannelControls); + objDefineAccessors(ChannelController.prototype, "channelQueue", ChannelController.prototype.getChannelControls); })(); } \ No newline at end of file diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/Constants.ts b/shared/AppInsightsCore/src/JavaScriptSDK/Constants.ts new file mode 100644 index 000000000..2a66c3427 --- /dev/null +++ b/shared/AppInsightsCore/src/JavaScriptSDK/Constants.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export const strIKey = "iKey"; +export const strExtensionConfig = "extensionConfig"; \ No newline at end of file diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/CoreUtils.ts b/shared/AppInsightsCore/src/JavaScriptSDK/CoreUtils.ts index 71de5200d..e3700f78c 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/CoreUtils.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/CoreUtils.ts @@ -1,379 +1,223 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. "use strict"; -import { objCreateFn, strShimObject, strShimUndefined, strShimFunction, strShimPrototype } from "@microsoft/applicationinsights-shims"; -import { getWindow, getDocument, getCrypto, getPerformance, getMsCrypto, getNavigator } from './EnvUtils'; +import { objCreateFn, strShimUndefined } from "@microsoft/applicationinsights-shims"; +import { getWindow, getDocument, getPerformance, isIE } from "./EnvUtils"; +import { + arrForEach, arrIndexOf, arrMap, arrReduce, attachEvent, dateNow, detachEvent, hasOwnProperty, + isArray, isBoolean, isDate, isError, isFunction, isNullOrUndefined, isNumber, isObject, isString, isTypeof, + isUndefined, objDefineAccessors, objKeys, strTrim, toISOString +} from "./HelperFuncs"; +import { randomValue, random32, mwcRandomSeed, mwcRandom32 } from "./RandomHelper"; // Added to help with minfication export const Undefined = strShimUndefined; -const strOnPrefix = "on"; -const strAttachEvent = "attachEvent"; -const strAddEventHelper = "addEventListener"; -const strDetachEvent = "detachEvent"; -const strRemoveEventListener = "removeEventListener"; -const UInt32Mask = 0x100000000; -const MaxUInt32 = 0xffffffff; - -let _isTrident: boolean = null; - -// MWC based Random generator (for IE) -let _mwcSeeded = false; -let _mwcW = 123456789; -var _mwcZ = 987654321; - -// Takes any integer -function _mwcSeed(seedValue: number) { - if (seedValue < 0) { - // Make sure we end up with a positive number and not -ve one. - seedValue >>>= 0; + +/** + * Trys to add an event handler for the specified event to the window, body and document + * @param eventName {string} - The name of the event + * @param callback {any} - The callback function that needs to be executed for the given event + * @return {boolean} - true if the handler was successfully added + */ +export function addEventHandler(eventName: string, callback: any): boolean { + let result = false; + let w = getWindow(); + if (w) { + result = attachEvent(w, eventName, callback); + result = attachEvent(w["body"], eventName, callback) || result; } - _mwcW = (123456789 + seedValue) & MaxUInt32; - _mwcZ = (987654321 - seedValue) & MaxUInt32; - _mwcSeeded = true; -} + let doc = getDocument(); + if (doc) { + result = EventHelper.Attach(doc, eventName, callback) || result; + } -function _autoSeedMwc() { - // Simple initialization using default Math.random() - So we inherit any entropy from the browser - // and bitwise XOR with the current milliseconds - _mwcSeed((Math.random() * UInt32Mask) ^ new Date().getTime()); + return result; } -function _isTypeof(value: any, theType: string): boolean { - return typeof value === theType; -}; - -function _isUndefined(value: any): boolean { - return _isTypeof(value, strShimUndefined) || value === undefined; -}; - -function _isNullOrUndefined(value: any): boolean { - return (_isUndefined(value) || value === null); +export function disableCookies() { + CoreUtils._canUseCookies = false; } -function _hasOwnProperty(obj: any, prop: string): boolean { - return obj && Object[strShimPrototype].hasOwnProperty.call(obj, prop); -}; - -function _isObject(value: any): boolean { - return _isTypeof(value, strShimObject); -}; - -function _isFunction(value: any): value is Function { - return _isTypeof(value, strShimFunction); -}; - -/** - * Binds the specified function to an event, so that the function gets called whenever the event fires on the object - * @param obj Object to add the event too. - * @param eventNameWithoutOn String that specifies any of the standard DHTML Events without "on" prefix - * @param handlerRef Pointer that specifies the function to call when event fires - * @param useCapture [Optional] Defaults to false - * @returns True if the function was bound successfully to the event, otherwise false - */ -function _attachEvent(obj: any, eventNameWithoutOn: string, handlerRef: any, useCapture: boolean = false) { - let result = false; - if (!_isNullOrUndefined(obj)) { - try { - if (!_isNullOrUndefined(obj[strAddEventHelper])) { - // all browsers except IE before version 9 - obj[strAddEventHelper](eventNameWithoutOn, handlerRef, useCapture); - result = true; - } else if (!_isNullOrUndefined(obj[strAttachEvent])) { - // IE before version 9 - obj[strAttachEvent](strOnPrefix + eventNameWithoutOn, handlerRef); - result = true; - } - } catch (e) { - // Just Ignore any error so that we don't break any execution path - } +export function newGuid(): string { + function randomHexDigit() { + return randomValue(15); // Get a random value from 0..15 } - return result; + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(GuidRegex, (c) => { + const r = (randomHexDigit() | 0), v = (c === 'x' ? r : r & 0x3 | 0x8); + return v.toString(16); + }); } /** - * Removes an event handler for the specified event - * @param Object to remove the event from - * @param eventNameWithoutOn {string} - The name of the event - * @param handlerRef {any} - The callback function that needs to be executed for the given event - * @param useCapture [Optional] Defaults to false + * Return the current value of the Performance Api now() function (if available) and fallback to dateNow() if it is unavailable (IE9 or less) + * https://caniuse.com/#search=performance.now */ -function _detachEvent(obj: any, eventNameWithoutOn: string, handlerRef: any, useCapture: boolean = false) { - if (!_isNullOrUndefined(obj)) { - try { - if (!_isNullOrUndefined(obj[strRemoveEventListener])) { - obj[strRemoveEventListener](eventNameWithoutOn, handlerRef, useCapture); - } else if (!_isNullOrUndefined(obj[strDetachEvent])) { - obj[strDetachEvent](strOnPrefix + eventNameWithoutOn, handlerRef); - } - } catch (e) { - // Just Ignore any error so that we don't break any execution path - } +export function perfNow(): number { + let perf = getPerformance(); + if (perf && perf.now) { + return perf.now(); } + + return dateNow(); } /** - * Try to define get/set object property accessors for the target object/prototype, this will provide compatibility with - * existing API definition when run within an ES5+ container that supports accessors but still enable the code to be loaded - * and executed in an ES3 container, providing basic IE8 compatibility. - * @param target The object on which to define the property. - * @param prop The name of the property to be defined or modified. - * @param getProp The getter function to wire against the getter. - * @param setProp The setter function to wire against the setter. - * @returns True if it was able to create the accessors otherwise false + * Generate random base64 id string. + * The default length is 22 which is 132-bits so almost the same as a GUID but as base64 (the previous default was 5) + * @param maxLength - Optional value to specify the length of the id to be generated, defaults to 22 */ -export function objDefineAccessors(target: any, prop: string, getProp?: () => T, setProp?: (v: T) => void): boolean { - let defineProp = Object["defineProperty"]; - if (defineProp) { - try { - let descriptor: PropertyDescriptor = { - enumerable: true, - configurable: true - } - - if (getProp) { - descriptor.get = getProp; - } - if (setProp) { - descriptor.set = setProp; - } - - defineProp(target, prop, descriptor); - return true; - } catch (e) { - // IE8 Defines a defineProperty on Object but it's only supported for DOM elements so it will throw - // We will just ignore this here. +export function newId(maxLength = 22): string { + const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + // Start with an initial random number, consuming the value in reverse byte order + let number = random32() >>> 0; // Make sure it's a +ve number + let chars = 0; + let result = ""; + while (result.length < maxLength) { + chars ++; + result += base64chars.charAt(number & 0x3F); + number >>>= 6; // Zero fill with right shift + if (chars === 5) { + // 5 base64 characters === 30 bits so we don't have enough bits for another base64 char + // So add on another 30 bits and make sure it's +ve + number = (((random32() << 2) & 0xFFFFFFFF) | (number & 0x03)) >>> 0; + chars = 0; // We need to reset the number every 5 chars (30 bits) } } - return false; + return result; } /** - * Validates that the string name conforms to the JS IdentifierName specification and if not - * normalizes the name so that it would. This method does not identify or change any keywords - * meaning that if you pass in a known keyword the same value will be returned. - * This is a simplified version - * @param name The name to validate + * The strEndsWith() method determines whether a string ends with the characters of a specified string, returning true or false as appropriate. + * @param value - The value to check whether it ends with the search value. + * @param search - The characters to be searched for at the end of the value. + * @returns true if the given search value is found at the end of the string, otherwise false. */ -export function normalizeJsName(name: string): string { - let value = name; - let match = /([^\w\d_$])/g; - if (match.test(name)) { - value = name.replace(match, "_"); +export function strEndsWith(value: string, search: string) { + if (value && search) { + let len = value.length; + let start = len - search.length; + return value.substring(start >= 0 ? start : 0, len) === search; } - return value; -} + return false; +} /** - * This is a helper function for the equivalent of arForEach(objKeys(target), callbackFn), this is a - * performance optimization to avoid the creation of a new array for large objects - * @param target The target object to find and process the keys - * @param callbackfn The function to call with the details + * generate W3C trace id */ -export function objForEachKey(target: any, callbackfn: (name: string, value: any) => void) { - if (target && _isObject(target)) { - for (let prop in target) { - if (_hasOwnProperty(target, prop)) { - callbackfn.call(target, prop, target[prop]); - } - } +export function generateW3CId(): string { + const hexValues = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]; + + // rfc4122 version 4 UUID without dashes and with lowercase letters + let oct = "", tmp; + for (let a = 0; a < 4; a++) { + tmp = random32(); + oct += + hexValues[tmp & 0xF] + + hexValues[tmp >> 4 & 0xF] + + hexValues[tmp >> 8 & 0xF] + + hexValues[tmp >> 12 & 0xF] + + hexValues[tmp >> 16 & 0xF] + + hexValues[tmp >> 20 & 0xF] + + hexValues[tmp >> 24 & 0xF] + + hexValues[tmp >> 28 & 0xF]; } + + // "Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively" + const clockSequenceHi = hexValues[8 + (random32() & 0x03) | 0]; + return oct.substr(0, 8) + oct.substr(9, 4) + "4" + oct.substr(13, 3) + clockSequenceHi + oct.substr(16, 3) + oct.substr(19, 12); } /** - * Effectively assigns all enumerable properties (not just own properties) and functions (including inherited prototype) from - * the source object to the target, it attempts to use proxy getters / setters (if possible) and proxy functions to avoid potential - * implementation issues by assigning prototype functions as instance ones - * - * This method is the primary method used to "update" the snippet proxy with the ultimate implementations. - * - * Special ES3 Notes: - * Updates (setting) of direct property values on the target or indirectly on the source object WILL NOT WORK PROPERLY, updates to the - * properties of "referenced" object will work (target.context.newValue = 10 => will be reflected in the source.context as it's the - * same object). ES3 Failures: assigning target.myProp = 3 -> Won't change source.myProp = 3, likewise the reverse would also fail. - * @param target - The target object to be assigned with the source properties and functions - * @param source - The source object which will be assigned / called by setting / calling the targets proxies - * @param chkSet - An optional callback to determine whether a specific property/function should be proxied - * @memberof Initialization + * Provides a collection of utility functions, included for backward compatibility with previous releases. + * @deprecated Marking this interface and instance as deprecated in favor of direct usage of the helper functions + * as direct usage provides better tree-shaking and minification by avoiding the inclusion of the unused items + * in your resulting code. */ -export function proxyAssign(target: any, source: any, chkSet?: (name: string, isFunc?: boolean, source?: any, target?: any) => boolean) { - if (target && source && target !== source && _isObject(target) && _isObject(source)) { - // effectively apply/proxy full source to the target instance - for (const field in source) { - if (CoreUtils.isString(field)) { - let value = source[field] as any; - if (_isFunction(value)) { - if (!chkSet || chkSet(field, true, source, target)) { - // Create a proxy function rather than just copying the (possible) prototype to the new object as an instance function - target[field as string] = (function(funcName: string) { - return function() { - // Capture the original arguments passed to the method - var originalArguments = arguments; - return source[funcName].apply(source, originalArguments); - } - })(field); - } - } else if (!chkSet || chkSet(field, false, source, target)) { - if (_hasOwnProperty(target, field)) { - // Remove any previous instance property - delete target[field]; - } - - if (!objDefineAccessors(target, field, () => { - return source[field]; - }, (theValue) => { - source[field] = theValue; - })) { - // Unable to create an accessor, so just assign the values as a fallback - // -- this will (mostly) work for objects - // -- but will fail for accessing primitives (if the source changes it) and all types of "setters" as the source won't be modified - target[field as string] = value; - } - } - } - } - } +export interface ICoreUtils { - return target; -} - -export class CoreUtils { - public static _canUseCookies: boolean; + /** + * Internal - Do not use directly. + * @deprecated Direct usage of this property is not recommend + */ + _canUseCookies: boolean; - public static isTypeof: (value: any, theType: string) => boolean = _isTypeof; + isTypeof: (value: any, theType: string) => boolean; - public static isUndefined: (value: any) => boolean = _isUndefined; + isUndefined: (value: any) => boolean; - public static isNullOrUndefined: (value: any) => boolean = _isNullOrUndefined; + isNullOrUndefined: (value: any) => boolean; - public static hasOwnProperty: (obj: any, prop: string) => boolean = _hasOwnProperty; + hasOwnProperty: (obj: any, prop: string) => boolean; /** * Checks if the passed of value is a function. * @param {any} value - Value to be checked. * @return {boolean} True if the value is a boolean, false otherwise. */ - public static isFunction: (value: any) => value is Function = _isFunction; + isFunction: (value: any) => value is Function; /** * Checks if the passed of value is a function. * @param {any} value - Value to be checked. * @return {boolean} True if the value is a boolean, false otherwise. */ - public static isObject: (value: any) => boolean = _isObject; + isObject: (value: any) => boolean; /** * Check if an object is of type Date */ - public static isDate(obj: any): obj is Date { - return Object[strShimPrototype].toString.call(obj) === "[object Date]"; - } + isDate: (obj: any) => obj is Date; /** * Check if an object is of type Array */ - public static isArray(obj: any): boolean { - return Object[strShimPrototype].toString.call(obj) === "[object Array]"; - } + isArray: (obj: any) => boolean; /** * Check if an object is of type Error */ - public static isError(obj: any): boolean { - return Object[strShimPrototype].toString.call(obj) === "[object Error]"; - } + isError: (obj: any) => boolean; /** * Checks if the type of value is a string. * @param {any} value - Value to be checked. * @return {boolean} True if the value is a string, false otherwise. */ - public static isString(value: any): value is string { - return _isTypeof(value, "string"); - } + isString: (value: any) => value is string; /** * Checks if the type of value is a number. * @param {any} value - Value to be checked. * @return {boolean} True if the value is a number, false otherwise. */ - public static isNumber(value: any): value is number { - return _isTypeof(value, "number"); - } + isNumber: (value: any) => value is number; /** * Checks if the type of value is a boolean. * @param {any} value - Value to be checked. * @return {boolean} True if the value is a boolean, false otherwise. */ - public static isBoolean(value: any): value is boolean { - return _isTypeof(value, "boolean"); - } - - /** - * Creates a new GUID. - * @return {string} A GUID. - */ - - public static disableCookies() { - CoreUtils._canUseCookies = false; - } - - public static newGuid(): string { - function randomHexDigit() { - return CoreUtils.randomValue(15); // Get a random value from 0..15 - } - - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(GuidRegex, (c) => { - const r = (randomHexDigit() | 0), v = (c === 'x' ? r : r & 0x3 | 0x8); - return v.toString(16); - }); - } + isBoolean: (value: any) => value is boolean; /** * Convert a date to I.S.O. format in IE8 */ - public static toISOString(date: Date) { - if (CoreUtils.isDate(date)) { - const pad = (num: number) => { - let r = String(num); - if (r.length === 1) { - r = "0" + r; - } - - return r; - } - - return date.getUTCFullYear() - + "-" + pad(date.getUTCMonth() + 1) - + "-" + pad(date.getUTCDate()) - + "T" + pad(date.getUTCHours()) - + ":" + pad(date.getUTCMinutes()) - + ":" + pad(date.getUTCSeconds()) - + "." + String((date.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) - + "Z"; - } - } + toISOString: (date: Date) => string; /** * Performs the specified action for each element in an array. This helper exists to avoid adding a polyfil for older browsers * that do not define Array.prototype.xxxx (eg. ES3 only, IE8) just in case any page checks for presence/absence of the prototype * implementation. Note: For consistency this will not use the Array.prototype.xxxx implementation if it exists as this would * cause a testing requirement to test with and without the implementations - * @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. It can return -1 to break out of the loop * @param thisArg [Optional] An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ - public static arrForEach(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => void, thisArg?: any): void { - let len = arr.length; - for (let idx = 0; idx < len; idx++) { - if (idx in arr) { - callbackfn.call(thisArg || arr, arr[idx], idx, arr); - } - } - } + arrForEach: (arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => void|number, thisArg?: any) => void; /** * Returns the index of the first occurrence of a value in an array. This helper exists to avoid adding a polyfil for older browsers @@ -383,17 +227,7 @@ export class CoreUtils { * @param searchElement The value to locate in the array. * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0. */ - public static arrIndexOf(arr: T[], searchElement: T, fromIndex?: number): number { - let len = arr.length; - let from = fromIndex || 0; - for (let lp = Math.max(from >= 0 ? from : len - Math.abs(from), 0); lp < len; lp++) { - if (lp in arr && arr[lp] === searchElement) { - return lp; - } - } - - return -1; - } + arrIndexOf: (arr: T[], searchElement: T, fromIndex?: number) => number; /** * Calls a defined callback function on each element of an array, and returns an array that contains the results. This helper exists @@ -403,19 +237,7 @@ export class CoreUtils { * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array. * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ - public static arrMap(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => R, thisArg?: any): R[] { - let len = arr.length; - let _this = thisArg || arr; - let results = new Array(len); - - for (let lp = 0; lp < len; lp++) { - if (lp in arr) { - results[lp] = callbackfn.call(_this, arr[lp], arr); - } - } - - return results; - } + arrMap: (arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => R, thisArg?: any) => R[]; /** * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is @@ -425,42 +247,12 @@ export class CoreUtils { * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array. * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value. */ - public static arrReduce(arr: T[], callbackfn: (previousValue: T | R, currentValue?: T, currentIndex?: number, array?: T[]) => R, initialValue?: R): R { - let len = arr.length; - let lp = 0; - let value; - - // Specifically checking the number of passed arguments as the value could be anything - if (arguments.length >= 3) { - value = arguments[2]; - } else { - while (lp < len && !(lp in arr)) { - lp++; - } - - value = arr[lp++]; - } - - while (lp < len) { - if (lp in arr) { - value = callbackfn(value, arr[lp], lp, arr); - } - lp++; - } - - return value; - } + arrReduce: (arr: T[], callbackfn: (previousValue: T | R, currentValue?: T, currentIndex?: number, array?: T[]) => R, initialValue?: R) => R; /** * helper method to trim strings (IE8 does not implement String.prototype.trim) */ - public static strTrim(str: any): string { - if (!CoreUtils.isString(str)) { - return str; - } - - return str.replace(/^\s+|\s+$/g, ""); - } + strTrim: (str: any) => string; /** * Creates an object that has the specified prototype, and that optionally contains specified properties. This helper exists to avoid adding a polyfil @@ -469,7 +261,7 @@ export class CoreUtils { * @param obj Object to use as a prototype. May be null */ // tslint:disable-next-line: member-ordering - public static objCreate:(obj: object) => any = objCreateFn; + objCreate:(obj: object) => any; /** * Returns the names of the enumerable string properties and methods of an object. This helper exists to avoid adding a polyfil for older browsers @@ -477,42 +269,7 @@ export class CoreUtils { * Note: For consistency this will not use the Object.keys implementation if it exists as this would cause a testing requirement to test with and without the implementations * @param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. */ - public static objKeys(obj: {}): string[] { - var hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'); - - if (!_isFunction(obj) && (!_isObject(obj) || obj === null)) { - throw new TypeError('objKeys called on non-object'); - } - - let result: string[] = []; - - for (let prop in obj) { - if (_hasOwnProperty(obj, prop)) { - result.push(prop); - } - } - - if (hasDontEnumBug) { - let dontEnums = [ - 'toString', - 'toLocaleString', - 'valueOf', - 'hasOwnProperty', - 'isPrototypeOf', - 'propertyIsEnumerable', - 'constructor' - ]; - let dontEnumsLength = dontEnums.length; - - for (let lp = 0; lp < dontEnumsLength; lp++) { - if (_hasOwnProperty(obj, dontEnums[lp])) { - result.push(dontEnums[lp]); - } - } - } - - return result; - } + objKeys: (obj: {}) => string[]; /** * Try to define get/set object property accessors for the target object/prototype, this will provide compatibility with @@ -524,7 +281,7 @@ export class CoreUtils { * @param setProp The setter function to wire against the setter. * @returns True if it was able to create the accessors otherwise false */ - public static objDefineAccessors = objDefineAccessors; + objDefineAccessors: (target: any, prop: string, getProp?: () => T, setProp?: (v: T) => void) => boolean; /** * Trys to add an event handler for the specified event to the window, body and document @@ -532,146 +289,58 @@ export class CoreUtils { * @param callback {any} - The callback function that needs to be executed for the given event * @return {boolean} - true if the handler was successfully added */ - public static addEventHandler(eventName: string, callback: any): boolean { - let result = false; - let w = getWindow(); - if (w) { - result = _attachEvent(w, eventName, callback); - result = _attachEvent(w["body"], eventName, callback) || result; - } - - let doc = getDocument(); - if (doc) { - result = EventHelper.Attach(doc, eventName, callback) || result; - } - - return result; - } + addEventHandler: (eventName: string, callback: any) => boolean; /** * Return the current time via the Date now() function (if available) and falls back to (new Date()).getTime() if now() is unavailable (IE8 or less) * https://caniuse.com/#search=Date.now */ - public static dateNow() { - let dt = Date; - if (dt.now) { - return dt.now(); - } + dateNow: () => number; - return new dt().getTime(); - } + /** + * Identifies whether the current environment appears to be IE + */ + isIE: () => boolean; /** - * Return the current value of the Performance Api now() function (if available) and fallback to CoreUtils.dateNow() if it is unavailable (IE9 or less) - * https://caniuse.com/#search=performance.now + * @deprecated - Use the core.getCookieMgr().disable() + * Force the SDK not to store and read any data from cookies. */ - public static perfNow() { - let perf = getPerformance(); - if (perf && perf.now) { - return perf.now(); - } + disableCookies: () => void; - return CoreUtils.dateNow(); - } + newGuid: () => string; /** - * Generate random base64 id string. - * The default length is 22 which is 132-bits so almost the same as a GUID but as base64 (the previous default was 5) - * @param maxLength - Optional value to specify the length of the id to be generated, defaults to 22 + * Return the current value of the Performance Api now() function (if available) and fallback to dateNow() if it is unavailable (IE9 or less) + * https://caniuse.com/#search=performance.now */ - public static newId(maxLength = 22): string { - const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - - // Start with an initial random number, consuming the value in reverse byte order - let number = CoreUtils.random32() >>> 0; // Make sure it's a +ve number - let chars = 0; - let result = ""; - while (result.length < maxLength) { - chars ++; - result += base64chars.charAt(number & 0x3F); - number >>>= 6; // Zero fill with right shift - if (chars === 5) { - // 5 base64 characters === 30 bits so we don't have enough bits for another base64 char - // So add on another 30 bits and make sure it's +ve - number = (((CoreUtils.random32() << 2) & 0xFFFFFFFF) | (number & 0x03)) >>> 0; - chars = 0; // We need to reset the number every 5 chars (30 bits) - } - } - - return result; - } + perfNow: () => number; /** - * Identifies whether the current environment appears to be IE + * Generate random base64 id string. + * The default length is 22 which is 132-bits so almost the same as a GUID but as base64 (the previous default was 5) + * @param maxLength - Optional value to specify the length of the id to be generated, defaults to 22 */ - public static isIE() { - if (_isTrident === null) { - let navigator = getNavigator() || ({} as Navigator); - let userAgent = (navigator.userAgent || "").toLowerCase(); - _isTrident = (userAgent.indexOf("msie") !== -1 || userAgent.indexOf("trident/") !== -1); - } - - return _isTrident; - } + newId: (maxLength?: number) => string; /** * Generate a random value between 0 and maxValue, max value should be limited to a 32-bit maximum. * So maxValue(16) will produce a number from 0..16 (range of 17) * @param maxValue */ - public static randomValue(maxValue: number) { - if (maxValue > 0) { - return Math.floor((CoreUtils.random32() / MaxUInt32) * (maxValue + 1)) >>> 0; - } - - return 0; - } + randomValue: (maxValue: number) => number; /** * generate a random 32-bit number (0x000000..0xFFFFFFFF) or (-0x80000000..0x7FFFFFFF), defaults un-unsigned. * @param signed - True to return a signed 32-bit number (-0x80000000..0x7FFFFFFF) otherwise an unsigned one (0x000000..0xFFFFFFFF) */ - public static random32(signed?: boolean) { - let value; - let c = getCrypto() || getMsCrypto(); - if (c && c.getRandomValues) { - // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) - value = c.getRandomValues(new Uint32Array(1))[0] & MaxUInt32; - } else if (CoreUtils.isIE()) { - // For IE 6, 7, 8 (especially on XP) Math.random is not very random - if (!_mwcSeeded) { - // Set the seed for the Mwc algorithm - _autoSeedMwc(); - } - - // Don't use Math.random for IE - // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) - value = CoreUtils.mwcRandom32() & MaxUInt32; - } else { - // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) - value = Math.floor((UInt32Mask * Math.random()) | 0); - } - - if (!signed) { - // Make sure we end up with a positive number and not -ve one. - value >>>= 0; - } - - return value; - } + random32: (signed?: boolean) => number; /** * Seed the MWC random number generator with the specified seed or a random value * @param value - optional the number to used as the seed, if undefined, null or zero a random value will be chosen */ - public static mwcRandomSeed(value?: number) - { - if (!value) { - _autoSeedMwc(); - } else { - _mwcSeed(value); - } - } + mwcRandomSeed: (value?: number) => void; /** * Generate a random 32-bit number between (0x000000..0xFFFFFFFF) or (-0x80000000..0x7FFFFFFF), using MWC (Multiply with carry) @@ -679,50 +348,65 @@ export class CoreUtils { * Used as a replacement random generator for IE to avoid issues with older IE instances. * @param signed - True to return a signed 32-bit number (-0x80000000..0x7FFFFFFF) otherwise an unsigned one (0x000000..0xFFFFFFFF) */ - public static mwcRandom32(signed?: boolean) { - _mwcZ = (36969 * (_mwcZ & 0xFFFF) + (_mwcZ >> 16)) & MaxUInt32; - _mwcW = (18000 * (_mwcW & 0xFFFF) + (_mwcW >> 16)) & MaxUInt32; - - let value = (((_mwcZ << 16) + (_mwcW & 0xFFFF)) >>> 0) & MaxUInt32 | 0; - - if (!signed) { - // Make sure we end up with a positive number and not -ve one. - value >>>= 0; - } - - return value; - } + mwcRandom32: (signed?: boolean) => number; /** * generate W3C trace id */ - public static generateW3CId() { - const hexValues = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]; - - // rfc4122 version 4 UUID without dashes and with lowercase letters - let oct = "", tmp; - for (let a = 0; a < 4; a++) { - tmp = CoreUtils.random32(); - oct += - hexValues[tmp & 0xF] + - hexValues[tmp >> 4 & 0xF] + - hexValues[tmp >> 8 & 0xF] + - hexValues[tmp >> 12 & 0xF] + - hexValues[tmp >> 16 & 0xF] + - hexValues[tmp >> 20 & 0xF] + - hexValues[tmp >> 24 & 0xF] + - hexValues[tmp >> 28 & 0xF]; - } - - // "Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively" - const clockSequenceHi = hexValues[8 + (CoreUtils.random32() & 0x03) | 0]; - return oct.substr(0, 8) + oct.substr(9, 4) + "4" + oct.substr(13, 3) + clockSequenceHi + oct.substr(16, 3) + oct.substr(19, 12); - } + generateW3CId: () => string; } +/** + * Provides a collection of utility functions, included for backward compatibility with previous releases. + * @deprecated Marking this instance as deprecated in favor of direct usage of the helper functions + * as direct usage provides better tree-shaking and minification by avoiding the inclusion of the unused items + * in your resulting code. + */ +export const CoreUtils: ICoreUtils = (function() { + + const coreUtils: ICoreUtils = { + _canUseCookies: undefined, + isTypeof: isTypeof, + isUndefined: isUndefined, + isNullOrUndefined: isNullOrUndefined, + hasOwnProperty: hasOwnProperty, + isFunction: isFunction, + isObject: isObject, + isDate: isDate, + isArray: isArray, + isError: isError, + isString: isString, + isNumber: isNumber, + isBoolean: isBoolean, + toISOString: toISOString, + arrForEach: arrForEach, + arrIndexOf: arrIndexOf, + arrMap: arrMap, + arrReduce: arrReduce, + strTrim: strTrim, + objCreate: objCreateFn, + objKeys: objKeys, + objDefineAccessors: objDefineAccessors, + addEventHandler: addEventHandler, + dateNow: dateNow, + isIE: isIE, + disableCookies: disableCookies, + newGuid: newGuid, + perfNow: perfNow, + newId: newId, + randomValue: randomValue, + random32: random32, + mwcRandomSeed: mwcRandomSeed, + mwcRandom32: mwcRandom32, + generateW3CId: generateW3CId + }; + + return coreUtils; +})(); + const GuidRegex = /[xy]/g; -export class EventHelper { +export interface IEventHelper { /** * Binds the specified function to an event, so that the function gets called whenever the event fires on the object * @param obj Object to add the event too. @@ -730,7 +414,7 @@ export class EventHelper { * @param handlerRef Pointer that specifies the function to call when event fires * @returns True if the function was bound successfully to the event, otherwise false */ - public static Attach: (obj: any, eventNameWithoutOn: string, handlerRef: any) => boolean = _attachEvent; + Attach: (obj: any, eventNameWithoutOn: string, handlerRef: any) => boolean; /** * Binds the specified function to an event, so that the function gets called whenever the event fires on the object @@ -740,7 +424,7 @@ export class EventHelper { * @param handlerRef Pointer that specifies the function to call when event fires * @returns True if the function was bound successfully to the event, otherwise false */ - public static AttachEvent: (obj: any, eventNameWithoutOn: string, handlerRef: any) => boolean = _attachEvent; + AttachEvent: (obj: any, eventNameWithoutOn: string, handlerRef: any) => boolean; /** * Removes an event handler for the specified event @@ -748,7 +432,7 @@ export class EventHelper { * @param callback {any} - The callback function that needs to be executed for the given event * @return {boolean} - true if the handler was successfully added */ - public static Detach: (obj: any, eventNameWithoutOn: string, handlerRef: any) => void = _detachEvent; + Detach: (obj: any, eventNameWithoutOn: string, handlerRef: any) => void; /** * Removes an event handler for the specified event @@ -757,5 +441,15 @@ export class EventHelper { * @param callback {any} - The callback function that needs to be executed for the given event * @return {boolean} - true if the handler was successfully added */ - public static DetachEvent: (obj: any, eventNameWithoutOn: string, handlerRef: any) => void = _detachEvent; + DetachEvent: (obj: any, eventNameWithoutOn: string, handlerRef: any) => void; } + +export const EventHelper: IEventHelper = (function() { + return { + Attach: attachEvent, + AttachEvent: attachEvent, + Detach: detachEvent, + DetachEvent: detachEvent + }; +})(); + diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/DiagnosticLogger.ts b/shared/AppInsightsCore/src/JavaScriptSDK/DiagnosticLogger.ts index d82cae304..38f057e3d 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/DiagnosticLogger.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/DiagnosticLogger.ts @@ -4,9 +4,9 @@ import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration" import { _InternalMessageId, LoggingSeverity } from "../JavaScriptSDK.Enums/LoggingEnums"; import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger"; -import { CoreUtils } from "./CoreUtils"; import { hasJSON, getJSON, getConsole } from "./EnvUtils"; import dynamicProto from '@microsoft/dynamicproto-js'; +import { isFunction, isNullOrUndefined, isUndefined } from "./HelperFuncs"; /** * For user non actionable traces use AI Internal prefix. @@ -79,10 +79,6 @@ export class DiagnosticLogger implements IDiagnosticLogger { let _messageLogged: { [msg: number]: boolean } = {}; dynamicProto(DiagnosticLogger, this, (_self) => { - const isNullOrUndefined = CoreUtils.isNullOrUndefined; - const isUndefined = CoreUtils.isUndefined; - const isFunction = CoreUtils.isFunction; - if (isNullOrUndefined(config)) { config = {}; } @@ -108,17 +104,18 @@ export class DiagnosticLogger implements IDiagnosticLogger { } else { if (!isUndefined(message) && !!message) { if (!isUndefined(message.message)) { + const logLevel = _self.consoleLoggingLevel(); if (isUserAct) { // check if this message type was already logged to console for this page view and if so, don't log it again const messageKey: number = +message.messageId; - if (!_messageLogged[messageKey] && _self.consoleLoggingLevel() >= LoggingSeverity.WARNING) { + if (!_messageLogged[messageKey] && logLevel >= LoggingSeverity.WARNING) { _self.warnToConsole(message.message); _messageLogged[messageKey] = true; } } else { // don't log internal AI traces in the console, unless the verbose logging is enabled - if (_self.consoleLoggingLevel() >= LoggingSeverity.WARNING) { + if (logLevel >= LoggingSeverity.WARNING) { _self.warnToConsole(message.message); } } diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/EnvUtils.ts b/shared/AppInsightsCore/src/JavaScriptSDK/EnvUtils.ts index 68617a478..5ce773941 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/EnvUtils.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/EnvUtils.ts @@ -5,9 +5,10 @@ import { getGlobal as shimsGetGlobal, strShimUndefined, strShimObject, strShimPrototype, strShimFunction } from "@microsoft/applicationinsights-shims"; +import { strContains } from "./HelperFuncs"; /** - * This file exists to hold environment utilities that are requied to check and + * This file exists to hold environment utilities that are required to check and * validate the current operating environment. Unless otherwise required, please * only defined methods (functions) in this class so that users of these * functions/properties only need to include those that are used within their own modules. @@ -29,6 +30,11 @@ const strJSON = "JSON"; const strCrypto = "crypto"; const strMsCrypto = "msCrypto"; const strReactNative = "ReactNative"; +const strMsie = "msie"; +const strTrident = "trident/"; + +let _isTrident: boolean = null; +let _navUserAgentCheck: string = null; /** * Returns the current global scope object, for a normal web page this will be the current @@ -248,3 +254,54 @@ export function isReactNative(): boolean { return false; } + +/** + * Identifies whether the current environment appears to be IE + */ +export function isIE() { + let nav = getNavigator(); + if (nav && nav.userAgent !== _navUserAgentCheck && _isTrident === null) { + // Added to support test mocking of the user agent + _navUserAgentCheck = nav.userAgent; + let userAgent = (_navUserAgentCheck || "").toLowerCase(); + _isTrident = (strContains(userAgent, strMsie) || strContains(userAgent, strTrident)); + } + + return _isTrident; +} + +/** + * Gets IE version returning the document emulation mode if we are running on IE, or null otherwise + */ +export function getIEVersion(userAgentStr: string = null): number { + let myNav = userAgentStr ? userAgentStr.toLowerCase() : ""; + if (!userAgentStr) { + let navigator = getNavigator() || ({} as Navigator); + myNav = navigator ? (navigator.userAgent || "").toLowerCase() : ""; + } + if (strContains(myNav, strMsie)) { + return parseInt(myNav.split(strMsie)[1]); + } else if (strContains(myNav, strTrident)) { + let tridentVer = parseInt(myNav.split(strTrident)[1]); + if (tridentVer) { + return tridentVer + 4; + } + } + + return null; +} + +/** + * Returns string representation of an object suitable for diagnostics logging. + */ +export function dumpObj(object: any): string { + const objectTypeDump: string = Object[strShimPrototype].toString.call(object); + let propertyValueDump: string = ""; + if (objectTypeDump === "[object Error]") { + propertyValueDump = "{ stack: '" + object.stack + "', message: '" + object.message + "', name: '" + object.name + "'"; + } else if (hasJSON()) { + propertyValueDump = getJSON().stringify(object); + } + + return objectTypeDump + propertyValueDump; +} diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/HelperFuncs.ts b/shared/AppInsightsCore/src/JavaScriptSDK/HelperFuncs.ts new file mode 100644 index 000000000..cbfbdbdaa --- /dev/null +++ b/shared/AppInsightsCore/src/JavaScriptSDK/HelperFuncs.ts @@ -0,0 +1,537 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { strShimUndefined, strShimObject, strShimPrototype, strShimFunction } from "@microsoft/applicationinsights-shims"; + +// RESTRICT and AVOID circular dependencies you should not import other contained modules or export the contents of this file directly + +// Added to help with minfication +const strOnPrefix = "on"; +const strAttachEvent = "attachEvent"; +const strAddEventHelper = "addEventListener"; +const strDetachEvent = "detachEvent"; +const strRemoveEventListener = "removeEventListener"; + +export function objToString(obj: any) { + return Object[strShimPrototype].toString.call(obj); +} + +export function isTypeof(value: any, theType: string): boolean { + return typeof value === theType; +}; + +export function isUndefined(value: any): boolean { + return isTypeof(value, strShimUndefined) || value === undefined; +}; + +export function isNullOrUndefined(value: any): boolean { + return (isUndefined(value) || value === null); +} + +export function hasOwnProperty(obj: any, prop: string): boolean { + return obj && Object[strShimPrototype].hasOwnProperty.call(obj, prop); +}; + +export function isObject(value: any): boolean { + return isTypeof(value, strShimObject); +}; + +export function isFunction(value: any): value is Function { + return isTypeof(value, strShimFunction); +}; + +/** + * Binds the specified function to an event, so that the function gets called whenever the event fires on the object + * @param obj Object to add the event too. + * @param eventNameWithoutOn String that specifies any of the standard DHTML Events without "on" prefix + * @param handlerRef Pointer that specifies the function to call when event fires + * @param useCapture [Optional] Defaults to false + * @returns True if the function was bound successfully to the event, otherwise false + */ +export function attachEvent(obj: any, eventNameWithoutOn: string, handlerRef: any, useCapture: boolean = false) { + let result = false; + if (!isNullOrUndefined(obj)) { + try { + if (!isNullOrUndefined(obj[strAddEventHelper])) { + // all browsers except IE before version 9 + obj[strAddEventHelper](eventNameWithoutOn, handlerRef, useCapture); + result = true; + } else if (!isNullOrUndefined(obj[strAttachEvent])) { + // IE before version 9 + obj[strAttachEvent](strOnPrefix + eventNameWithoutOn, handlerRef); + result = true; + } + } catch (e) { + // Just Ignore any error so that we don't break any execution path + } + } + + return result; +} + +/** + * Removes an event handler for the specified event + * @param Object to remove the event from + * @param eventNameWithoutOn {string} - The name of the event + * @param handlerRef {any} - The callback function that needs to be executed for the given event + * @param useCapture [Optional] Defaults to false + */ +export function detachEvent(obj: any, eventNameWithoutOn: string, handlerRef: any, useCapture: boolean = false) { + if (!isNullOrUndefined(obj)) { + try { + if (!isNullOrUndefined(obj[strRemoveEventListener])) { + obj[strRemoveEventListener](eventNameWithoutOn, handlerRef, useCapture); + } else if (!isNullOrUndefined(obj[strDetachEvent])) { + obj[strDetachEvent](strOnPrefix + eventNameWithoutOn, handlerRef); + } + } catch (e) { + // Just Ignore any error so that we don't break any execution path + } + } +} + +/** + * Validates that the string name conforms to the JS IdentifierName specification and if not + * normalizes the name so that it would. This method does not identify or change any keywords + * meaning that if you pass in a known keyword the same value will be returned. + * This is a simplified version + * @param name The name to validate + */ +export function normalizeJsName(name: string): string { + let value = name; + let match = /([^\w\d_$])/g; + if (match.test(name)) { + value = name.replace(match, "_"); + } + + return value; +} + +/** + * This is a helper function for the equivalent of arForEach(objKeys(target), callbackFn), this is a + * performance optimization to avoid the creation of a new array for large objects + * @param target The target object to find and process the keys + * @param callbackfn The function to call with the details + */ +export function objForEachKey(target: any, callbackfn: (name: string, value: any) => void) { + if (target && isObject(target)) { + for (let prop in target) { + if (hasOwnProperty(target, prop)) { + callbackfn.call(target, prop, target[prop]); + } + } + } +} + +/** + * The strEndsWith() method determines whether a string ends with the characters of a specified string, returning true or false as appropriate. + * @param value - The value to check whether it ends with the search value. + * @param search - The characters to be searched for at the end of the value. + * @returns true if the given search value is found at the end of the string, otherwise false. + */ +export function strEndsWith(value: string, search: string) { + if (value && search) { + let len = value.length; + let start = len - search.length; + return value.substring(start >= 0 ? start : 0, len) === search; + } + + return false; +} + +/** + * A simple wrapper (for minification support) to check if the value contains the search string. + * @param value - The string value to check for the existence of the search value + * @param search - The value search within the value + */ +export function strContains(value: string, search: string) { + if (value && search) { + return value.indexOf(search) !== -1; + } + + return false; +} + +/** + * Check if an object is of type Date + */ +export function isDate(obj: any): obj is Date { + return objToString(obj) === "[object Date]"; +} + +/** + * Check if an object is of type Array + */ +export function isArray(obj: any): boolean { + return objToString(obj) === "[object Array]"; +} + +/** + * Check if an object is of type Error + */ +export function isError(obj: any): boolean { + return objToString(obj) === "[object Error]"; +} + +/** + * Checks if the type of value is a string. + * @param {any} value - Value to be checked. + * @return {boolean} True if the value is a string, false otherwise. + */ +export function isString(value: any): value is string { + return isTypeof(value, "string"); +} + +/** + * Checks if the type of value is a number. + * @param {any} value - Value to be checked. + * @return {boolean} True if the value is a number, false otherwise. + */ +export function isNumber(value: any): value is number { + return isTypeof(value, "number"); +} + +/** + * Checks if the type of value is a boolean. + * @param {any} value - Value to be checked. + * @return {boolean} True if the value is a boolean, false otherwise. + */ +export function isBoolean(value: any): value is boolean { + return isTypeof(value, "boolean"); +} + +/** + * Convert a date to I.S.O. format in IE8 + */ +export function toISOString(date: Date) { + if (isDate(date)) { + const pad = (num: number) => { + let r = String(num); + if (r.length === 1) { + r = "0" + r; + } + + return r; + } + + return date.getUTCFullYear() + + "-" + pad(date.getUTCMonth() + 1) + + "-" + pad(date.getUTCDate()) + + "T" + pad(date.getUTCHours()) + + ":" + pad(date.getUTCMinutes()) + + ":" + pad(date.getUTCSeconds()) + + "." + String((date.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) + + "Z"; + } +} + +/** + * Performs the specified action for each element in an array. This helper exists to avoid adding a polyfil for older browsers + * that do not define Array.prototype.xxxx (eg. ES3 only, IE8) just in case any page checks for presence/absence of the prototype + * implementation. Note: For consistency this will not use the Array.prototype.xxxx implementation if it exists as this would + * cause a testing requirement to test with and without the implementations + * @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. It can return -1 to break out of the loop + * @param thisArg [Optional] An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. + */ +export function arrForEach(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => void|number, thisArg?: any): void { + let len = arr.length; + for (let idx = 0; idx < len; idx++) { + if (idx in arr) { + if (callbackfn.call(thisArg || arr, arr[idx], idx, arr) === -1) { + break; + } + } + } +} + +/** + * Returns the index of the first occurrence of a value in an array. This helper exists to avoid adding a polyfil for older browsers + * that do not define Array.prototype.xxxx (eg. ES3 only, IE8) just in case any page checks for presence/absence of the prototype + * implementation. Note: For consistency this will not use the Array.prototype.xxxx implementation if it exists as this would + * cause a testing requirement to test with and without the implementations + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0. + */ +export function arrIndexOf(arr: T[], searchElement: T, fromIndex?: number): number { + let len = arr.length; + let from = fromIndex || 0; + for (let lp = Math.max(from >= 0 ? from : len - Math.abs(from), 0); lp < len; lp++) { + if (lp in arr && arr[lp] === searchElement) { + return lp; + } + } + + return -1; +} + +/** + * Calls a defined callback function on each element of an array, and returns an array that contains the results. This helper exists + * to avoid adding a polyfil for older browsers that do not define Array.prototype.xxxx (eg. ES3 only, IE8) just in case any page + * checks for presence/absence of the prototype implementation. Note: For consistency this will not use the Array.prototype.xxxx + * implementation if it exists as this would cause a testing requirement to test with and without the implementations + * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. + */ +export function arrMap(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => R, thisArg?: any): R[] { + let len = arr.length; + let _this = thisArg || arr; + let results = new Array(len); + + for (let lp = 0; lp < len; lp++) { + if (lp in arr) { + results[lp] = callbackfn.call(_this, arr[lp], arr); + } + } + + return results; +} + +/** + * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is + * provided as an argument in the next call to the callback function. This helper exists to avoid adding a polyfil for older browsers that do not define + * Array.prototype.xxxx (eg. ES3 only, IE8) just in case any page checks for presence/absence of the prototype implementation. Note: For consistency + * this will not use the Array.prototype.xxxx implementation if it exists as this would cause a testing requirement to test with and without the implementations + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value. + */ +export function arrReduce(arr: T[], callbackfn: (previousValue: T | R, currentValue?: T, currentIndex?: number, array?: T[]) => R, initialValue?: R): R { + let len = arr.length; + let lp = 0; + let value; + + // Specifically checking the number of passed arguments as the value could be anything + if (arguments.length >= 3) { + value = arguments[2]; + } else { + while (lp < len && !(lp in arr)) { + lp++; + } + + value = arr[lp++]; + } + + while (lp < len) { + if (lp in arr) { + value = callbackfn(value, arr[lp], lp, arr); + } + lp++; + } + + return value; +} + +/** + * helper method to trim strings (IE8 does not implement String.prototype.trim) + */ +export function strTrim(str: any): string { + if (!isString(str)) { + return str; + } + + return str.replace(/^\s+|\s+$/g, ""); +} + +/** + * Returns the names of the enumerable string properties and methods of an object. This helper exists to avoid adding a polyfil for older browsers + * that do not define Object.keys eg. ES3 only, IE8 just in case any page checks for presence/absence of the prototype implementation. + * Note: For consistency this will not use the Object.keys implementation if it exists as this would cause a testing requirement to test with and without the implementations + * @param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. + */ +export function objKeys(obj: {}): string[] { + var hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'); + + if (!isFunction(obj) && (!isObject(obj) || obj === null)) { + throw new TypeError('objKeys called on non-object'); + } + + let result: string[] = []; + + for (let prop in obj) { + if (hasOwnProperty(obj, prop)) { + result.push(prop); + } + } + + if (hasDontEnumBug) { + let dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ]; + let dontEnumsLength = dontEnums.length; + + for (let lp = 0; lp < dontEnumsLength; lp++) { + if (hasOwnProperty(obj, dontEnums[lp])) { + result.push(dontEnums[lp]); + } + } + } + + return result; +} + +/** + * Try to define get/set object property accessors for the target object/prototype, this will provide compatibility with + * existing API definition when run within an ES5+ container that supports accessors but still enable the code to be loaded + * and executed in an ES3 container, providing basic IE8 compatibility. + * @param target The object on which to define the property. + * @param prop The name of the property to be defined or modified. + * @param getProp The getter function to wire against the getter. + * @param setProp The setter function to wire against the setter. + * @returns True if it was able to create the accessors otherwise false + */ +export function objDefineAccessors(target: any, prop: string, getProp?: () => T, setProp?: (v: T) => void): boolean { + let defineProp = Object["defineProperty"]; + if (defineProp) { + try { + let descriptor: PropertyDescriptor = { + enumerable: true, + configurable: true + } + + if (getProp) { + descriptor.get = getProp; + } + if (setProp) { + descriptor.set = setProp; + } + + defineProp(target, prop, descriptor); + return true; + } catch (e) { + // IE8 Defines a defineProperty on Object but it's only supported for DOM elements so it will throw + // We will just ignore this here. + } + } + + return false; +} + +/** + * Return the current time via the Date now() function (if available) and falls back to (new Date()).getTime() if now() is unavailable (IE8 or less) + * https://caniuse.com/#search=Date.now + */ +export function dateNow() { + let dt = Date; + if (dt.now) { + return dt.now(); + } + + return new dt().getTime(); +} + +/** + * Returns the name of object if it's an Error. Otherwise, returns empty string. + */ +export function getExceptionName(object: any): string { + if (isError(object)) { + return object.name; + } + + return ""; +} + +/** + * Sets the provided value on the target instance using the field name when the provided chk function returns true, the chk + * function will only be called if the new value is no equal to the original value. + * @param target - The target object + * @param field - The key of the target + * @param value - The value to set + * @param valChk - [Optional] Callback to check the value that if supplied will be called check if the new value can be set + * @param srcChk - [Optional] Callback to check to original value that if supplied will be called if the new value should be set (if allowed) + * @returns The existing or new value, depending what was set + */ +export function setValue(target: T, field: K, value: T[K], valChk?: (value: T[K]) => boolean, srcChk?: (value: T[K]) => boolean) { + let theValue = value; + if (target) { + theValue = target[field]; + if (theValue !== value && (!srcChk || srcChk(theValue)) && (!valChk || valChk(value))) { + theValue = value; + target[field] = theValue; + } + } + + return theValue; +} + +/** + * Returns the current value from the target object if not null or undefined otherwise sets the new value and returns it + * @param target - The target object to return or set the default value + * @param field - The key for the field to set on the target + * @param defValue - [Optional] The value to set if not already present, when not provided a empty object will be added + */ +export function getSetValue(target: T, field: K, defValue?: T[K]): T[K] { + return setValue(target, field, !isUndefined(defValue) ? defValue : {} as any, null, isNullOrUndefined); +} + +export function isNotTruthy(value: any) { + return !value; +} + +export function isTruthy(value: any) { + return !!value; +} + +export function throwError(message: string) { + throw new Error(message); +} + +/** + * Effectively assigns all enumerable properties (not just own properties) and functions (including inherited prototype) from + * the source object to the target, it attempts to use proxy getters / setters (if possible) and proxy functions to avoid potential + * implementation issues by assigning prototype functions as instance ones + * + * This method is the primary method used to "update" the snippet proxy with the ultimate implementations. + * + * Special ES3 Notes: + * Updates (setting) of direct property values on the target or indirectly on the source object WILL NOT WORK PROPERLY, updates to the + * properties of "referenced" object will work (target.context.newValue = 10 => will be reflected in the source.context as it's the + * same object). ES3 Failures: assigning target.myProp = 3 -> Won't change source.myProp = 3, likewise the reverse would also fail. + * @param target - The target object to be assigned with the source properties and functions + * @param source - The source object which will be assigned / called by setting / calling the targets proxies + * @param chkSet - An optional callback to determine whether a specific property/function should be proxied + * @memberof Initialization + */ +export function proxyAssign(target: any, source: any, chkSet?: (name: string, isFunc?: boolean, source?: any, target?: any) => boolean) { + if (target && source && target !== source && isObject(target) && isObject(source)) { + // effectively apply/proxy full source to the target instance + for (const field in source) { + if (isString(field)) { + let value = source[field] as any; + if (isFunction(value)) { + if (!chkSet || chkSet(field, true, source, target)) { + // Create a proxy function rather than just copying the (possible) prototype to the new object as an instance function + target[field as string] = (function(funcName: string) { + return function() { + // Capture the original arguments passed to the method + var originalArguments = arguments; + return source[funcName].apply(source, originalArguments); + } + })(field); + } + } else if (!chkSet || chkSet(field, false, source, target)) { + if (hasOwnProperty(target, field)) { + // Remove any previous instance property + delete target[field]; + } + + if (!objDefineAccessors(target, field, () => { + return source[field]; + }, (theValue) => { + source[field] = theValue; + })) { + // Unable to create an accessor, so just assign the values as a fallback + // -- this will (mostly) work for objects + // -- but will fail for accessing primitives (if the source changes it) and all types of "setters" as the source won't be modified + target[field as string] = value; + } + } + } + } + } + + return target; +} diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/InstrumentHooks.ts b/shared/AppInsightsCore/src/JavaScriptSDK/InstrumentHooks.ts index 292ff63a8..7e7872f0b 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/InstrumentHooks.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/InstrumentHooks.ts @@ -7,7 +7,7 @@ import { import { strFunction, strPrototype } from "./EnvUtils" -import { CoreUtils } from './CoreUtils'; +import { hasOwnProperty } from "./HelperFuncs"; const aiInstrumentHooks = "_aiHooks"; @@ -172,7 +172,7 @@ function _getObjProto(target:any) { function _getOwner(target:any, name:string, checkPrototype:boolean): any { let owner = null; if (target) { - if (CoreUtils.hasOwnProperty(target, name)) { + if (hasOwnProperty(target, name)) { owner = target; } else if (checkPrototype) { owner = _getOwner(_getObjProto(target), name, false); diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/NotificationManager.ts b/shared/AppInsightsCore/src/JavaScriptSDK/NotificationManager.ts index 352c9b593..b26ad0b9b 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/NotificationManager.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/NotificationManager.ts @@ -5,8 +5,8 @@ import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem"; import { INotificationListener } from "../JavaScriptSDK.Interfaces/INotificationListener"; import { INotificationManager } from '../JavaScriptSDK.Interfaces/INotificationManager'; import { IPerfEvent } from "../JavaScriptSDK.Interfaces/IPerfEvent"; -import { CoreUtils, } from "./CoreUtils"; import dynamicProto from "@microsoft/dynamicproto-js"; +import { arrForEach, arrIndexOf } from './HelperFuncs'; /** * Class to manage sending notifications to all the listeners. @@ -15,7 +15,6 @@ export class NotificationManager implements INotificationManager { listeners: INotificationListener[] = []; constructor(config?: IConfiguration) { - let arrForEach = CoreUtils.arrForEach; let perfEvtsSendAll = !!(config ||{}).perfEvtsSendAll; dynamicProto(NotificationManager, this, (_self) => { @@ -28,10 +27,10 @@ export class NotificationManager implements INotificationManager { * @param {INotificationListener} listener - AWTNotificationListener to remove. */ _self.removeNotificationListener = (listener: INotificationListener): void => { - let index: number = CoreUtils.arrIndexOf(_self.listeners, listener); + let index: number = arrIndexOf(_self.listeners, listener); while (index > -1) { _self.listeners.splice(index, 1); - index = CoreUtils.arrIndexOf(_self.listeners, listener); + index = arrIndexOf(_self.listeners, listener); } }; diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/PerfManager.ts b/shared/AppInsightsCore/src/JavaScriptSDK/PerfManager.ts index a7e89e055..27d8452ae 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/PerfManager.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/PerfManager.ts @@ -5,7 +5,7 @@ import { IPerfEvent } from '../JavaScriptSDK.Interfaces/IPerfEvent'; import { IPerfManager, IPerfManagerProvider } from '../JavaScriptSDK.Interfaces/IPerfManager'; import dynamicProto from "@microsoft/dynamicproto-js"; -import { CoreUtils } from "./CoreUtils"; +import { dateNow, isArray, isFunction, objDefineAccessors } from './HelperFuncs'; const strExecutionContextKey = "ctx"; @@ -60,17 +60,17 @@ export class PerfEvent implements IPerfEvent { constructor(name: string, payloadDetails: () => any, isAsync: boolean) { let _self = this; let accessorDefined = false; - _self.start = CoreUtils.dateNow(); + _self.start = dateNow(); _self.name = name; _self.isAsync = isAsync; _self.isChildEvt = (): boolean => false; - if (CoreUtils.isFunction(payloadDetails)) { + if (isFunction(payloadDetails)) { // Create an accessor to minimize the potential performance impact of executing the payloadDetails callback let theDetails:any; - accessorDefined = CoreUtils.objDefineAccessors(_self, 'payload', () => { + accessorDefined = objDefineAccessors(_self, 'payload', () => { // Delay the execution of the payloadDetails until needed - if (!theDetails && CoreUtils.isFunction(payloadDetails)) { + if (!theDetails && isFunction(payloadDetails)) { theDetails = payloadDetails(); // clear it out now so the referenced objects can be garbage collected payloadDetails = null; @@ -117,7 +117,7 @@ export class PerfEvent implements IPerfEvent { _self.complete = () => { let childTime = 0; let childEvts = _self.getCtx(PerfEvent.ChildrenContextKey); - if (CoreUtils.isArray(childEvts)) { + if (isArray(childEvts)) { for (let lp = 0; lp < childEvts.length; lp++) { let childEvt: IPerfEvent = childEvts[lp]; if (childEvt) { @@ -126,10 +126,10 @@ export class PerfEvent implements IPerfEvent { } } - _self.time = CoreUtils.dateNow() - _self.start; + _self.time = dateNow() - _self.start; _self.exTime = _self.time - childTime; _self.complete = () => {}; - if (!accessorDefined && CoreUtils.isFunction(payloadDetails)) { + if (!accessorDefined && isFunction(payloadDetails)) { // If we couldn't define the property set during complete -- to minimize the perf impact until after the time _self.payload = payloadDetails(); } @@ -228,7 +228,7 @@ const doPerfActiveKey = "CoreUtils.doPerf"; export function doPerf(mgrSource: IPerfManagerProvider | IPerfManager, getSource: () => string, func: (perfEvt?: IPerfEvent) => T, details?: () => any, isAsync?: boolean) { if (mgrSource) { let perfMgr: IPerfManager = mgrSource as IPerfManager; - if (perfMgr && CoreUtils.isFunction(perfMgr["getPerfMgr"])) { + if (perfMgr && isFunction(perfMgr["getPerfMgr"])) { // Looks like a perf manager provider object perfMgr = perfMgr["getPerfMgr"]() } diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/ProcessTelemetryContext.ts b/shared/AppInsightsCore/src/JavaScriptSDK/ProcessTelemetryContext.ts index 2249d03ae..a80320b72 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/ProcessTelemetryContext.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/ProcessTelemetryContext.ts @@ -9,11 +9,9 @@ import { ITelemetryItem } from '../JavaScriptSDK.Interfaces/ITelemetryItem'; import { IPlugin, ITelemetryPlugin } from '../JavaScriptSDK.Interfaces/ITelemetryPlugin'; import { IProcessTelemetryContext } from "../JavaScriptSDK.Interfaces/IProcessTelemetryContext"; import { ITelemetryPluginChain } from '../JavaScriptSDK.Interfaces/ITelemetryPluginChain'; -import { CoreUtils } from "./CoreUtils"; import { DiagnosticLogger } from "./DiagnosticLogger"; import { TelemetryPluginChain } from "./TelemetryPluginChain"; - -let _isNullOrUndefined = CoreUtils.isNullOrUndefined; +import { arrForEach, isFunction, isNullOrUndefined, isUndefined } from "./HelperFuncs"; /** * Creates the instance execution chain for the plugins @@ -26,7 +24,7 @@ function _createProxyChain(plugins:IPlugin[], itemCtx:IProcessTelemetryContext) let lastProxy:TelemetryPluginChain = null; for (let idx = 0; idx < plugins.length; idx++) { let thePlugin = plugins[idx] as ITelemetryPlugin; - if (thePlugin && CoreUtils.isFunction(thePlugin.processTelemetry)) { + if (thePlugin && isFunction(thePlugin.processTelemetry)) { // Only add plugins that are processors let newProxy = new TelemetryPluginChain(thePlugin, itemCtx); proxies.push(newProxy); @@ -71,7 +69,7 @@ function _copyPluginChain(srcPlugins:IPlugin[], itemCtx:IProcessTelemetryContext if (startAt && srcPlugins) { plugins = []; - CoreUtils.arrForEach(srcPlugins, thePlugin => { + arrForEach(srcPlugins, thePlugin => { if (add || thePlugin === startAt) { add = true; plugins.push(thePlugin); @@ -151,14 +149,14 @@ export class ProcessTelemetryContext implements IProcessTelemetryContext { // There is no next element (null) vs not defined (undefined) if (startAt !== null) { - if (plugins && CoreUtils.isFunction((plugins as ITelemetryPluginChain).getPlugin)) { + if (plugins && isFunction((plugins as ITelemetryPluginChain).getPlugin)) { // We have a proxy chain object _nextProxy = _copyProxyChain(plugins as ITelemetryPluginChain, _self, startAt||(plugins as ITelemetryPluginChain).getPlugin()); } else { // We just have an array if (startAt) { _nextProxy = _copyPluginChain(plugins as IPlugin[], _self, startAt); - } else if (CoreUtils.isUndefined(startAt)) { + } else if (isUndefined(startAt)) { // Undefined means copy the existing chain _nextProxy = _createProxyChain(plugins as IPlugin[], _self) } @@ -198,13 +196,13 @@ export class ProcessTelemetryContext implements IProcessTelemetryContext { _self.getConfig = (identifier:string, field: string, defaultValue: number | string | boolean = false) => { let theValue; let extConfig = _self.getExtCfg(identifier, null); - if (extConfig && !_isNullOrUndefined(extConfig[field])) { + if (extConfig && !isNullOrUndefined(extConfig[field])) { theValue = extConfig[field]; - } else if (config && !_isNullOrUndefined(config[field])) { + } else if (config && !isNullOrUndefined(config[field])) { theValue = config[field]; } - return !_isNullOrUndefined(theValue) ? theValue : defaultValue; + return !isNullOrUndefined(theValue) ? theValue : defaultValue; }; _self.hasNext = () => { diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/RandomHelper.ts b/shared/AppInsightsCore/src/JavaScriptSDK/RandomHelper.ts new file mode 100644 index 000000000..b00e71384 --- /dev/null +++ b/shared/AppInsightsCore/src/JavaScriptSDK/RandomHelper.ts @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { getCrypto, getMsCrypto, isIE } from "./EnvUtils"; + +const UInt32Mask = 0x100000000; +const MaxUInt32 = 0xffffffff; + +// MWC based Random generator (for IE) +let _mwcSeeded = false; +let _mwcW = 123456789; +var _mwcZ = 987654321; + +// Takes any integer +function _mwcSeed(seedValue: number) { + if (seedValue < 0) { + // Make sure we end up with a positive number and not -ve one. + seedValue >>>= 0; + } + + _mwcW = (123456789 + seedValue) & MaxUInt32; + _mwcZ = (987654321 - seedValue) & MaxUInt32; + _mwcSeeded = true; +} + +function _autoSeedMwc() { + // Simple initialization using default Math.random() - So we inherit any entropy from the browser + // and bitwise XOR with the current milliseconds + _mwcSeed((Math.random() * UInt32Mask) ^ new Date().getTime()); +} + +/** + * Generate a random value between 0 and maxValue, max value should be limited to a 32-bit maximum. + * So maxValue(16) will produce a number from 0..16 (range of 17) + * @param maxValue + */ +export function randomValue(maxValue: number) { + if (maxValue > 0) { + return Math.floor((random32() / MaxUInt32) * (maxValue + 1)) >>> 0; + } + + return 0; +} + +/** + * generate a random 32-bit number (0x000000..0xFFFFFFFF) or (-0x80000000..0x7FFFFFFF), defaults un-unsigned. + * @param signed - True to return a signed 32-bit number (-0x80000000..0x7FFFFFFF) otherwise an unsigned one (0x000000..0xFFFFFFFF) + */ +export function random32(signed?: boolean) { + let value; + let c = getCrypto() || getMsCrypto(); + if (c && c.getRandomValues) { + // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) + value = c.getRandomValues(new Uint32Array(1))[0] & MaxUInt32; + } else if (isIE()) { + // For IE 6, 7, 8 (especially on XP) Math.random is not very random + if (!_mwcSeeded) { + // Set the seed for the Mwc algorithm + _autoSeedMwc(); + } + + // Don't use Math.random for IE + // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) + value = mwcRandom32() & MaxUInt32; + } else { + // Make sure the number is converted into the specified range (-0x80000000..0x7FFFFFFF) + value = Math.floor((UInt32Mask * Math.random()) | 0); + } + + if (!signed) { + // Make sure we end up with a positive number and not -ve one. + value >>>= 0; + } + + return value; +} + +/** + * Seed the MWC random number generator with the specified seed or a random value + * @param value - optional the number to used as the seed, if undefined, null or zero a random value will be chosen + */ +export function mwcRandomSeed(value?: number) { + if (!value) { + _autoSeedMwc(); + } else { + _mwcSeed(value); + } +} + +/** + * Generate a random 32-bit number between (0x000000..0xFFFFFFFF) or (-0x80000000..0x7FFFFFFF), using MWC (Multiply with carry) + * instead of Math.random() defaults to un-signed. + * Used as a replacement random generator for IE to avoid issues with older IE instances. + * @param signed - True to return a signed 32-bit number (-0x80000000..0x7FFFFFFF) otherwise an unsigned one (0x000000..0xFFFFFFFF) + */ +export function mwcRandom32(signed?: boolean) { + _mwcZ = (36969 * (_mwcZ & 0xFFFF) + (_mwcZ >> 16)) & MaxUInt32; + _mwcW = (18000 * (_mwcW & 0xFFFF) + (_mwcW >> 16)) & MaxUInt32; + + let value = (((_mwcZ << 16) + (_mwcW & 0xFFFF)) >>> 0) & MaxUInt32 | 0; + + if (!signed) { + // Make sure we end up with a positive number and not -ve one. + value >>>= 0; + } + + return value; +} + diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryHelpers.ts b/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryHelpers.ts index 3b8201ca3..597ca2cda 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryHelpers.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryHelpers.ts @@ -3,13 +3,12 @@ "use strict"; import { IPlugin, ITelemetryPlugin } from '../JavaScriptSDK.Interfaces/ITelemetryPlugin'; -import { CoreUtils } from "./CoreUtils"; import { _InternalLogMessage } from "./DiagnosticLogger"; import { _InternalMessageId } from '../JavaScriptSDK.Enums/LoggingEnums'; import { ProcessTelemetryContext } from './ProcessTelemetryContext'; import { ITelemetryPluginChain } from '../JavaScriptSDK.Interfaces/ITelemetryPluginChain'; +import { arrForEach, isFunction } from './HelperFuncs'; -let _isFunction = CoreUtils.isFunction; let processTelemetry = "processTelemetry"; let priority = "priority"; let setNextPlugin = "setNextPlugin"; @@ -32,13 +31,13 @@ export function initializePlugins(processContext:ProcessTelemetryContext, extens let thePlugin = proxy.getPlugin(); if (thePlugin) { if (lastPlugin && - _isFunction(lastPlugin[setNextPlugin]) && - _isFunction(thePlugin[processTelemetry])) { + isFunction(lastPlugin[setNextPlugin]) && + isFunction(thePlugin[processTelemetry])) { // Set this plugin as the next for the previous one lastPlugin[setNextPlugin](thePlugin); } - if (!_isFunction(thePlugin[isInitialized]) || !thePlugin[isInitialized]()) { + if (!isFunction(thePlugin[isInitialized]) || !thePlugin[isInitialized]()) { initPlugins.push(thePlugin); } @@ -48,7 +47,7 @@ export function initializePlugins(processContext:ProcessTelemetryContext, extens } // Now initiatilize the plugins - CoreUtils.arrForEach(initPlugins, thePlugin => { + arrForEach(initPlugins, thePlugin => { thePlugin.initialize( processContext.getCfg(), processContext.core(), @@ -61,8 +60,8 @@ export function sortPlugins(plugins:IPlugin[]) { // Sort by priority return plugins.sort((extA, extB) => { let result = 0; - let bHasProcess = _isFunction(extB[processTelemetry]); - if (_isFunction(extA[processTelemetry])) { + let bHasProcess = isFunction(extB[processTelemetry]); + if (isFunction(extA[processTelemetry])) { result = bHasProcess ? extA[priority] - extB[priority] : 1; } else if (bHasProcess) { result = -1; diff --git a/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryPluginChain.ts b/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryPluginChain.ts index dbd2aa891..926d527e7 100644 --- a/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryPluginChain.ts +++ b/shared/AppInsightsCore/src/JavaScriptSDK/TelemetryPluginChain.ts @@ -6,12 +6,10 @@ import { ITelemetryItem } from '../JavaScriptSDK.Interfaces/ITelemetryItem'; import { IProcessTelemetryContext } from "../JavaScriptSDK.Interfaces/IProcessTelemetryContext"; import { ITelemetryPluginChain } from "../JavaScriptSDK.Interfaces/ITelemetryPluginChain"; import { ITelemetryPlugin } from '../JavaScriptSDK.Interfaces/ITelemetryPlugin'; -import { CoreUtils } from "./CoreUtils"; import { _InternalLogMessage } from "./DiagnosticLogger"; import { doPerf } from "./PerfManager"; import { LoggingSeverity, _InternalMessageId } from '../JavaScriptSDK.Enums/LoggingEnums'; - -let _isFunction = CoreUtils.isFunction; +import { isFunction } from './HelperFuncs'; export class TelemetryPluginChain implements ITelemetryPluginChain { @@ -48,8 +46,8 @@ export class TelemetryPluginChain implements ITelemetryPluginChain { constructor(plugin:ITelemetryPlugin, defItemCtx:IProcessTelemetryContext) { let _self = this; let _nextProxy:ITelemetryPluginChain = null; - let _hasProcessTelemetry = _isFunction(plugin.processTelemetry); - let _hasSetNext = _isFunction(plugin.setNextPlugin); + let _hasProcessTelemetry = isFunction(plugin.processTelemetry); + let _hasSetNext = isFunction(plugin.setNextPlugin); _self._hasRun = false; diff --git a/shared/AppInsightsCore/src/applicationinsights-core-js.ts b/shared/AppInsightsCore/src/applicationinsights-core-js.ts index 98ebc1ce9..31bbf29c8 100644 --- a/shared/AppInsightsCore/src/applicationinsights-core-js.ts +++ b/shared/AppInsightsCore/src/applicationinsights-core-js.ts @@ -16,12 +16,22 @@ export { SendRequestReason } from "./JavaScriptSDK.Enums/SendRequestReason"; export { AppInsightsCore } from "./JavaScriptSDK/AppInsightsCore"; export { BaseCore } from './JavaScriptSDK/BaseCore'; export { BaseTelemetryPlugin } from './JavaScriptSDK/BaseTelemetryPlugin'; -export { CoreUtils, EventHelper, Undefined, normalizeJsName, objForEachKey, proxyAssign } from "./JavaScriptSDK/CoreUtils"; +export { randomValue, random32, mwcRandomSeed, mwcRandom32 } from './JavaScriptSDK/RandomHelper'; +export { CoreUtils, ICoreUtils, EventHelper, IEventHelper, Undefined, addEventHandler, disableCookies, newGuid, perfNow, newId, generateW3CId } from "./JavaScriptSDK/CoreUtils"; +export { + isTypeof, isUndefined, isNullOrUndefined, hasOwnProperty, isObject, isFunction, attachEvent, detachEvent, normalizeJsName, + objForEachKey, strEndsWith, isDate, isArray, isError, isString, isNumber, isBoolean, toISOString, arrForEach, arrIndexOf, + arrMap, arrReduce, strTrim, objKeys, objDefineAccessors, dateNow, getExceptionName, throwError, strContains, + setValue, getSetValue, isNotTruthy, isTruthy, proxyAssign +} from './JavaScriptSDK/HelperFuncs'; export { getGlobal, getGlobalInst, hasWindow, getWindow, hasDocument, getDocument, getCrypto, getMsCrypto, hasNavigator, getNavigator, hasHistory, getHistory, getLocation, getPerformance, hasJSON, getJSON, - isReactNative, getConsole, strUndefined, strObject, strPrototype, strFunction + isReactNative, getConsole, dumpObj, isIE, getIEVersion, strUndefined, strObject, strPrototype, strFunction } from "./JavaScriptSDK/EnvUtils"; +export { + objCreateFn as objCreate +} from '@microsoft/applicationinsights-shims'; export { NotificationManager } from "./JavaScriptSDK/NotificationManager"; export { INotificationManager } from "./JavaScriptSDK.Interfaces/INotificationManager"; export { IPerfEvent } from './JavaScriptSDK.Interfaces/IPerfEvent'; @@ -31,4 +41,5 @@ export { DiagnosticLogger, _InternalLogMessage } from './JavaScriptSDK/Diagnosti export { ProcessTelemetryContext } from './JavaScriptSDK/ProcessTelemetryContext'; export { initializePlugins, sortPlugins } from "./JavaScriptSDK/TelemetryHelpers"; export { _InternalMessageId, LoggingSeverity } from './JavaScriptSDK.Enums/LoggingEnums'; -export { InstrumentProto, InstrumentProtos, InstrumentFunc, InstrumentFuncs } from "./JavaScriptSDK/InstrumentHooks"; \ No newline at end of file +export { InstrumentProto, InstrumentProtos, InstrumentFunc, InstrumentFuncs } from "./JavaScriptSDK/InstrumentHooks"; +export { strIKey, strExtensionConfig } from './JavaScriptSDK/Constants';