summaryrefslogtreecommitdiff
path: root/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private
diff options
context:
space:
mode:
Diffstat (limited to 'shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private')
-rw-r--r--shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private/dist/mt-metricskit-utils-private.esm.js2428
1 files changed, 2428 insertions, 0 deletions
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private/dist/mt-metricskit-utils-private.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private/dist/mt-metricskit-utils-private.esm.js
new file mode 100644
index 0000000..435731d
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private/dist/mt-metricskit-utils-private.esm.js
@@ -0,0 +1,2428 @@
+/*
+ * src/reflect.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PRIVATE METHODS/IVARS ************************************
+ */
+var _nonOverrideableFunctions = { setDelegate: true };
+
+/**
+ ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+function _utResetNonOverridableFunctions() {
+ _nonOverrideableFunctions = { setDelegate: true };
+}
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Simple shallow clone which just copies over top-level keys and values (without "hasOwnProperty" checks).
+ * Useful for using a passed-in map without having that parameter data be corrupted by the function it's passed to.
+ * @param source
+ * @returns {object} will never return null... worst case will return an empty object.
+ */
+function shallowClone(source) {
+ var dest = {};
+ var sourceHasGetterAndSetterMethods = hasGetterAndSetterMethods(source);
+ var aGetter;
+ var aSetter;
+
+ for (var key in source) {
+ aGetter = null;
+ aSetter = null;
+
+ if (sourceHasGetterAndSetterMethods) {
+ // Be careful to copy aGetter and setter methods properly, per: http://ejohn.org/blog/javascript-getters-and-setters/
+ aGetter = source.__lookupGetter__(key);
+ aSetter = source.__lookupSetter__(key);
+ }
+
+ if (aGetter || aSetter) {
+ if (aGetter) {
+ dest.__defineGetter__(key, aGetter);
+ }
+ if (aSetter) {
+ dest.__defineSetter__(key, aSetter);
+ }
+ } else {
+ dest[key] = source[key];
+ }
+ }
+ return dest;
+}
+
+/**
+ * Returns true if the specified obj is not undefined
+ * NOTE: Does not work for global variables (because the variable gets defined by virtue of passing it in)... use "typeof()" directly
+ */
+function isDefined(anObject) {
+ return typeof anObject !== 'undefined';
+}
+
+/**
+ * Returns true if the specified obj is not undefined and not null
+ * NOTE: Does not work for global variables... in that case, use "typeof()" directly, because the act of passing them to here will make them appear to be defined
+ */
+function isDefinedNonNull(anObject) {
+ return isDefined(anObject) && anObject !== null;
+}
+
+/**
+ * Returns true if the specified obj is not undefined and not null and not empty
+ * NOTE: Does not work for global variables... in that case, use "typeof()" directly, because the act of passing them to here will make them appear to be defined
+ */
+function isDefinedNonNullNonEmpty(anObject) {
+ return (
+ isDefinedNonNull(anObject) && !isEmptyString(anObject) && !isEmptyArray(anObject) && !isEmptyObject(anObject)
+ );
+}
+
+function isEmptyString(anObject) {
+ return isString(anObject) && anObject.length === 0;
+}
+
+function isEmptyArray(anObject) {
+ return isArray(anObject) && anObject.length === 0;
+}
+
+function isEmptyObject(anObject) {
+ return isObject(anObject) && Object.keys(anObject).length === 0;
+}
+
+function isFunction(anObject) {
+ // the following works for regular functions and native functions, e.g. iTunes.buy
+ return typeof anObject === 'function';
+}
+
+function isNumber(anObject) {
+ return typeof anObject == 'number';
+}
+
+function isInteger(anObject) {
+ return isNumber(anObject) && anObject % 1 === 0;
+}
+
+function isString(anObject) {
+ return typeof anObject === 'string' || anObject instanceof String;
+}
+
+function isElement(anObject) {
+ return !!anObject && anObject.nodeType == 1;
+}
+
+function isArray(anObject) {
+ return !!anObject && anObject.constructor === Array;
+}
+
+function isObject(anObject) {
+ return !!anObject && anObject.constructor === Object;
+}
+
+/*
+ * NOTE: this method skips object properties that are functions.
+ */
+function values(anObject) {
+ var values = [];
+ for (var property in anObject) {
+ var aValue = anObject[property];
+ if (anObject.hasOwnProperty(property) && !isFunction(aValue)) {
+ values.push(aValue);
+ }
+ }
+ return values;
+}
+
+function keys(anObject) {
+ var keys = [];
+ for (var property in anObject) {
+ if (anObject.hasOwnProperty(property) && !isFunction(anObject[property])) {
+ keys.push(property);
+ }
+ }
+ return keys;
+}
+
+/**
+ * Returns "true" if the passed-in object has any values at all on it.
+ * NOTE: this method skips object properties that are functions.
+ */
+function hasAnyKeys(anObject) {
+ for (var aKey in anObject) {
+ if (anObject.hasOwnProperty(aKey)) {
+ return true;
+ }
+ }
+}
+
+/**
+ * Returns "true" if the passed-in object has any values on it at all *and* at least one of those values is non-null.
+ * NOTE: this method skips object properties that are functions.
+ */
+function hasAnyNonNullKeys(anObject) {
+ for (var aKey in anObject) {
+ if (anObject.hasOwnProperty(aKey) && anObject[aKey]) {
+ return true;
+ }
+ }
+}
+
+/**
+ * @return {boolean} true if the object has the default object getter and setter methods (e.g. __lookupGetter__())
+ * see <rdar://problem/33045481> MetricsKit: Protect against JS errors when __lookupGetter__ is not on the object prototype
+ */
+function hasGetterAndSetterMethods(anObject) {
+ return (
+ isObject(anObject) &&
+ isFunction(anObject.__lookupGetter__) &&
+ isFunction(anObject.__lookupSetter__) &&
+ isFunction(anObject.__defineGetter__) &&
+ isFunction(anObject.__defineSetter__)
+ );
+}
+
+/*
+ * NOTE: this method returns *only* object properties that are functions.
+ */
+function methods(anObject) {
+ var methods = [];
+ for (var property in anObject) {
+ var aValue = anObject[property];
+ if (anObject.hasOwnProperty(property) && isFunction(aValue)) {
+ methods.push(aValue);
+ }
+ }
+ return methods;
+}
+
+/*
+ * NOTE: this method skips object properties that are functions.
+ */
+function invert(anObject) {
+ var invertedMap = {};
+ for (var property in anObject) {
+ if (anObject.hasOwnProperty(property) && !isFunction(anObject[property])) {
+ invertedMap[anObject[property]] = property;
+ }
+ }
+ return invertedMap;
+}
+
+/**
+ * Adds all the fields of the objects in the varargs to the fields in the first parameter, "obj".
+ * *All* "hasOwnProperty" fields will be added, including functions and fields with no values.
+ * @param {Object} targetObject an object with keys and values. If only one parameter is provided, the return value will be the non-null values of that single object.
+ * @param {varargs} sourceObjectN a variable number of Object arguments from 0-N. Each object's fields will be copied into targetObject. Later objects take precedence over earlier ones.
+ * @return targetObject (*not* a clone) with the additional fields added..
+ */
+function extend(targetObject /* , ...sourceObjectN(varargs) */) {
+ var argumentsArray = [true, true, true].concat(Array.prototype.slice.call(arguments));
+ return copyKeysAndValues.apply(null, argumentsArray);
+}
+
+/**
+ * Takes one or more objects, [possibly] cleans them (removes keys that are typeof 'function', keys with 'null' values, keys with 'undefined' values),
+ * merges them (later objects take precedence), and returns a single object with the union of all remaining fields.
+ * @param keepNullsAndUndefined a boolean if true fields with a "null" or "undefined" value will still be copied.
+ * Otherwise, if false, any field with a "null" or "undefined" value will not be copied.
+ * @param keepFunctions a boolean if true fields whose value is typeof 'function' will still be copied.
+ * Otherwise, if false, any field with a typeof 'function' value will not be copied.
+ * @param inPlace a boolean if true will copy all results to the "targetObject" object, rather than copying them all to a new object.
+ * Otherwise if "inPlace" is false, a new object will be returned and all passed in objects are treated as immutable and so will never be modified.
+ * @param {Object} targetObject an object with keys and values. If only one parameter is provided, the return value will be the non-null values of that single object.
+ * @param {varargs} sourceObjectN a variable number of Object arguments from 0-N. Each object's fields will be copied into targetObject. Later objects take precedence over earlier ones.
+ * @return either targetObject (if "inPlace" is true) or a new object (if "inPlace" is false or "targetObject" is null) with the
+ * union of all fields, filtered based on the values of the keepNullsAndUndefined and keepFunctions parameters
+ * @example copyKeysAndValues(true, {}) ===> {}
+ * @example copyKeysAndValues(true, null) ===> {}
+ * @example copyKeysAndValues({true, "foo":10}) ===> {"foo":10}
+ * @example copyKeysAndValues({true, "foo":10, "bar":null}) ===> {"foo":10}
+ * @example copyKeysAndValues({false, "foo":10, "bar":null}) ===> {"foo":10, "bar":null}
+ * @example copyKeysAndValues({true, "foo":10, "bar":null}, {"cat":null}) ===> {"foo":10}
+ * @example copyKeysAndValues({true, "foo":10, "bar":null}, {"cat":null, "mouse":"gray"}) ===> {"foo":10, "mouse":"gray"}
+ * @example copyKeysAndValues({true, "foo":10, "bar":null}, {"cat":null, "mouse":"gray", "dog":"bark"}) ===> {"foo":10, "mouse":"gray", "dog":"bark"}
+ * @example copyKeysAndValues({true, "foo":10, "bar":null}, {"cat":null, "mouse":"gray", "dog":"bark", "foo":11}) ===> {"foo":11, "mouse":"gray", "dog":"bark"}
+ * @example copyKeysAndValues({true, "foo":10, "bar":null}, {"cat":null, "mouse":"gray", "dog":"bark", "foo":11}, {"foo":12}) ===> {"foo":12, "mouse":"gray", "dog":"bark"}
+ */
+function copyKeysAndValues(keepNullsAndUndefined, keepFunctions, inPlace, targetObject /*, sourceObjectN(varargs)*/) {
+ var returnValue = inPlace ? targetObject || {} : {};
+ var sourceObject;
+
+ for (var ii = 3; ii < arguments.length; ii++) {
+ sourceObject = arguments[ii];
+
+ for (var key in sourceObject) {
+ if (Object.prototype.hasOwnProperty.call(sourceObject, key)) {
+ var value = sourceObject[key];
+
+ if (keepNullsAndUndefined || (value !== null && value !== undefined)) {
+ if (keepFunctions || typeof value !== 'function') {
+ returnValue[key] = value;
+ }
+ }
+ }
+ }
+ }
+ return returnValue;
+}
+
+/**
+ * Add one or more function names to the list of non overrideable functions
+ * attachDelegate will check this list and not override any functions with matching names
+ * @param {Array} functionNames
+ * @returns undefined
+ */
+function addNonOverrideableFunctions(functionNames) {
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ _nonOverrideableFunctions[functionName] = true;
+ }
+}
+
+/**
+ * Replace any "target" methods found on "delegate" with the delegate's version of the method.
+ * The replacement function will actually be our own wrapper function with the original function attached as a property called origFunction
+ * in case the delegate's replacement method wants to, essentially, call "super"
+ * We do delegation this way, vs. checking each time a "target" function is called, because this way we don't pollute the implementation
+ * of all the target's functions.
+ * Subsequent calls to "attachDelegate" will then replace whatever methods *they* match, including methods that have already been replaced.
+ * This allows callers to use "canned" delegates to get most of their functionality, but still replace some number of methods that need custom implementations.
+ * If a replaced method is overridden again with a subsequent "setDelegate" call, the "origFunction" parameter will be the previous delegate's function.
+ * NOTE: Only methods present on "target" will be replaced
+ * @param {Object} target
+ * @param {Object} delegate
+ * @param {Boolean} useOriginalContext whether to use the execution context of the original method for the delegate method. Default to false
+ * @returns {Boolean} true if one or more methods on the delegate object were attached to the target object
+ */
+function attachDelegate(target, delegate, useOriginalContext) {
+ var returnValue = false;
+ useOriginalContext = useOriginalContext || false;
+
+ if (target && delegate && target !== delegate) {
+ // only attach methods that exist on the target
+ var methodsToOmitMap = {};
+ Object.keys(delegate).forEach(function (methodName) {
+ if (!target[methodName]) {
+ methodsToOmitMap[methodName] = true;
+ }
+ });
+
+ // ignore nonOverrideableFunctions
+ extend(methodsToOmitMap, _nonOverrideableFunctions);
+
+ returnValue = attachMethods(target, delegate, useOriginalContext ? target : null, methodsToOmitMap);
+ }
+
+ return returnValue;
+}
+
+/**
+ * Replace any "target" methods found on "source" with the source's version of the method.
+ * The replacement function will actually be our own wrapper function with the original function attached as a property called origFunction
+ * in case the source's replacement method wants to, essentially, call "super"
+ * We do delegation this way, vs. checking each time a "target" function is called, because this way we don't pollute the implementation
+ * of all the target's functions.
+ * Subsequent calls to "attachMethods" will then replace whatever methods *they* match, including methods that have already been replaced.
+ * This allows callers to use "canned" sources to get most of their functionality, but still replace some number of methods that need custom implementations.
+ * If a replaced method is overridden again with a subsequent "attachMethod" call, the "origFunction" parameter will be the previous source's function.
+ * @param {Object} target
+ * @param {Object} source
+ * @param {Object} (optional) methodContext 'this' context to apply to any bound methods; defaults to the source object
+ * @param {Object} (optional) methodsToOmitMap a dictionary of method names to ignore when copying
+ * @returns {Boolean} true if one or more methods on the source object were attached to the target object
+ */
+function attachMethods(target, source, methodContext, methodsToOmitMap) {
+ var returnValue = false;
+
+ if (target && source) {
+ methodsToOmitMap = methodsToOmitMap || {};
+ methodContext = methodContext || source;
+
+ var captureFunction = function captureFunction(
+ methodContext,
+ capturedOrigFunction,
+ source,
+ origFunctionContext,
+ functionName
+ ) {
+ var returnValue = function () {
+ // dereference the source so delegate chaining (source object delegating to another object) works properly
+ return source[functionName].apply(methodContext, arguments);
+ };
+ // Attach the origFunction in case its needed later by the caller for delegate chaining.
+ // Do that in here too, so it doesn't get overwritten in the loop...
+ if (capturedOrigFunction) {
+ returnValue.origFunction = capturedOrigFunction;
+ }
+ // we need to add a symbol to the attached function that doesn't have an original function, in order to remove them by "detachMethods()".
+ returnValue.attachedMethod = true;
+
+ return returnValue;
+ };
+
+ for (var functionName in source) {
+ if (!(functionName in methodsToOmitMap)) {
+ if (source[functionName] && isFunction(source[functionName])) {
+ var origFunction = target[functionName];
+ var origFunctionExists = origFunction && isFunction(origFunction);
+ var capturedOrigFunction = null;
+ if (origFunctionExists) {
+ // Avoid binding the delegate function repeatedly
+ // We need to avoid binding the delegated function in order to the "detachMethods" function could reset the delegated function to the original function by the "origFunction" chain.
+ // And the delegated function's context was already bound in captureFunction.
+ if (origFunction.attachedMethod === true) {
+ capturedOrigFunction = origFunction;
+ } else {
+ capturedOrigFunction = origFunction.bind(target);
+ }
+ }
+
+ // Careful! This is that tough "closure in a loop" case where the local variables captured by the closure
+ // will change even for previously created closure functions on each iteration of this loop!
+ // So, we need to capture it in an additional, invoked, closure...
+ // See (and cited links): http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example
+ target[functionName] = captureFunction(
+ methodContext,
+ capturedOrigFunction,
+ source,
+ target,
+ functionName
+ );
+ returnValue = true;
+ }
+ }
+ }
+ }
+ return returnValue;
+}
+
+/**
+ * detach the any of delegated methods(the functions which have 'attachedMethod' symbol) from the provided object
+ * @param target
+ * @returns {boolean} true if one or more delegate methods were detached from the target object
+ */
+function detachMethods(target) {
+ var returnValue = false;
+ for (var propKey in target) {
+ if (isFunction(target[propKey]) && target[propKey].attachedMethod === true) {
+ var methodIsDelegated = target[propKey].origFunction;
+ if (methodIsDelegated) {
+ while (target[propKey].origFunction) {
+ target[propKey] = target[propKey].origFunction;
+ returnValue = true;
+ }
+ } else {
+ delete target[propKey];
+ }
+ }
+ }
+ return returnValue;
+}
+
+/**
+ * Convenience method to loop over a set of delegatable targets and apply appropriate delegates
+ * @param {Object} targetMap containing one or more delegatable target objects
+ * @param {Object} delegateMap containing one or more delegates
+ * @return {Object} contains the results of all setDelegate calls
+ * @example usage:
+ * var targetMap = { environment: environmentObject, eventRecorder: eventRecorderObject, foo: fooObject };
+ * var delegateMap = { environment: environmentDelegate, eventRecorder: eventRecorderDelegate };
+ * setDelegates(targetMap, delegateMap); // returns { environment: true, eventRecorder: true }
+ * // targetMap.environment's delegate is now environmentDelegate and targetMap.eventRecorder's delegate is now eventRecorderDelegate
+ */
+function setDelegates(targetMap, delegateMap) {
+ var resultObject = {};
+
+ for (var targetName in targetMap) {
+ if (delegateMap[targetName] && isFunction(targetMap[targetName].setDelegate)) {
+ resultObject[targetName] = targetMap[targetName].setDelegate.apply(targetMap[targetName], [delegateMap[targetName]].concat(Array.prototype.slice.call(arguments, 2)));
+ }
+ }
+
+ return resultObject;
+}
+
+/**
+ * Reset the delegate functions from the objects of provided target map.
+ * @param {Object} targetMap containing one or more delegatable target objects
+ * @returns {Boolean} true if one or more delegate methods were reset from the target object
+ */
+var resetDelegates = function resetDelegates(targetMap) {
+ var returnValue = false;
+
+ for (var targetName in targetMap) {
+ var delegateObject = targetMap[targetName];
+ if (delegateObject && typeof delegateObject === 'object' && isFunction(delegateObject.setDelegate)) {
+ returnValue |= detachMethods(delegateObject);
+ }
+ }
+ return !!returnValue;
+};
+
+/**
+ * @param {Object} sourceObject
+ * @param {Object} targetObject
+ * @returns {Boolean} true if one or more methods on the target was delegated, false otherwise
+ * If one (or more) of the source object's methods has been delegated to a function,
+ * the same method on the target object will also be delegated to that function
+ */
+function copyDelegatedFunctions(sourceObject, targetObject) {
+ var returnValue = null;
+
+ if (sourceObject && targetObject && targetObject.setDelegate) {
+ var delegatedFunctions = {};
+ var functionName;
+
+ for (functionName in sourceObject) {
+ if (isFunction(sourceObject[functionName]) && sourceObject[functionName].origFunction) {
+ delegatedFunctions[functionName] = sourceObject[functionName];
+ }
+ }
+
+ returnValue = targetObject.setDelegate(delegatedFunctions);
+ }
+
+ return returnValue;
+}
+
+/**
+ * Returns a deduped array (similar to a Set object) using the contents from both arrayA and arrayB
+ * @param {Array} arrayA the first array to dedupe
+ * @param {Array} arrayB the other array to dedupe
+ * @return {Array} The deduped array
+ */
+function dedupedArray(arrayA, arrayB) {
+ var tempDict = {}; // necessary for returning array of unique values and not having access to Set object in ES5
+ if (arrayA) {
+ for (var i = 0; i < arrayA.length; i++) {
+ tempDict[arrayA[i]] = 0; // value for key doesn't matter; we want a list of unique values
+ }
+ }
+ if (arrayB) {
+ for (var j = 0; j < arrayB.length; j++) {
+ tempDict[arrayB[j]] = 0; // value for key doesn't matter; we want a list of unique values
+ }
+ }
+ return Object.keys(tempDict); // equivalent to a Set of unique values
+}
+
+/**
+ * Returns the global scope object associated with the platform
+ */
+function globalScope() {
+ if (typeof globalThis !== 'undefined') {
+ return globalThis;
+ } else if (typeof global !== 'undefined') {
+ return global;
+ } else if (typeof window !== 'undefined') {
+ return window;
+ } else {
+ return {};
+ }
+}
+
+var reflect = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ _utResetNonOverridableFunctions: _utResetNonOverridableFunctions,
+ shallowClone: shallowClone,
+ isDefined: isDefined,
+ isDefinedNonNull: isDefinedNonNull,
+ isDefinedNonNullNonEmpty: isDefinedNonNullNonEmpty,
+ isEmptyString: isEmptyString,
+ isEmptyArray: isEmptyArray,
+ isEmptyObject: isEmptyObject,
+ isFunction: isFunction,
+ isNumber: isNumber,
+ isInteger: isInteger,
+ isString: isString,
+ isElement: isElement,
+ isArray: isArray,
+ isObject: isObject,
+ values: values,
+ keys: keys,
+ hasAnyKeys: hasAnyKeys,
+ hasAnyNonNullKeys: hasAnyNonNullKeys,
+ hasGetterAndSetterMethods: hasGetterAndSetterMethods,
+ methods: methods,
+ invert: invert,
+ extend: extend,
+ copyKeysAndValues: copyKeysAndValues,
+ addNonOverrideableFunctions: addNonOverrideableFunctions,
+ attachMethods: attachMethods,
+ detachMethods: detachMethods,
+ attachDelegate: attachDelegate,
+ setDelegates: setDelegates,
+ resetDelegates: resetDelegates,
+ copyDelegatedFunctions: copyDelegatedFunctions,
+ dedupedArray: dedupedArray,
+ globalScope: globalScope
+});
+
+/*
+ * src/backoff.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+var DEFAULTS = {
+ exponential: {
+ maxWait: 1500,
+ initialDelay: 100,
+ factor: 2
+ }
+};
+
+/**
+ * @param {int} (optional) initialDelay - time in ms before the first reattempt (each subsequent reattempt will wait exponentially longer than the previous one)
+ * @param {int} (optional) maxWait - max cumulative time in ms to wait before giving up (does not include the time taken by the function to execute)
+ setting this to 0 will cause the strategy to retry indefinitely
+ * @param {int} (optional) factor - multiplier to apply when delaying subsequent reattempts. Defaults to DEFAULT_EXPONENT_FACTOR
+ */
+var ExponentialStrategy = function (initialDelay, maxWait, factor) {
+ this.delay = initialDelay || DEFAULTS.exponential.initialDelay;
+ this.maxWait = isNumber(maxWait) ? maxWait : DEFAULTS.exponential.maxWait;
+ this.factor = factor || DEFAULTS.exponential.factor;
+
+ this.timeWaited = 0;
+};
+
+ExponentialStrategy.prototype.nextDelay = function nextDelay() {
+ var returnValue = null;
+
+ var timeRemaining = this.maxWait - this.timeWaited;
+
+ if (timeRemaining > 0) {
+ this.delay = Math.min(this.delay, timeRemaining);
+ this.timeWaited += this.delay;
+ }
+
+ if (this.maxWait === 0 || timeRemaining > 0) {
+ returnValue = this.delay;
+ this.delay = this.delay * this.factor; // increase the delay for next time
+ }
+
+ return returnValue;
+};
+
+/**
+ * Execute a function according to a given backoff failure strategy
+ * @param {Object} strategy is an object representing a failure stategy. It has a nextDelay() method that returns the time in ms to wait until reattempting
+ * @param {Function} networkRequestor - the function to execute. It should accept an onSuccessHandler and an onFailureHandler as its final arguments
+ * @param {Function} onSuccessHandler - callback to execute on success
+ * @param {Function} onFailureHandler - callback to execute on failure
+ */
+function _backoff(strategy, networkRequestor, onSuccessHandler, onFailureHandler) {
+ var onBackoff = function onBackoff() {
+ var delay = strategy.nextDelay();
+ if (delay) {
+ setTimeout(_backoff.bind(null, strategy, networkRequestor, onSuccessHandler, onFailureHandler), delay);
+ } else {
+ onFailureHandler.apply(onFailureHandler, arguments);
+ }
+ };
+
+ networkRequestor.call(networkRequestor, onSuccessHandler, onBackoff);
+}
+
+/**
+ * Execute a function according to an exponential backoff failure strategy
+ * @param {Function} networkRequestor - the function to execute. It should accept an onSuccessHandler and an onFailureHandler as its final arguments
+ * @param {Function} onSuccessHandler - callback to execute on success
+ * @param {Function} onFailureHandler - callback to execute on failure
+ * @param {int} (optional) initialDelay - time in ms before the first reattempt (each subsequent reattempt will wait exponentially longer than the previous one)
+ * @param {int} (optional) maxWait - max cumulative time in ms to wait before giving up (does not include the time taken by the function to execute)
+ setting this to 0 will cause the strategy to retry indefinitely
+ * @param {int} (optional) factor - multiplier to apply when delaying subsequent reattempts. Defaults to DEFAULT_EXPONENT_FACTOR
+ */
+function exponentialBackoff(networkRequestor, onSuccessHandler, onFailureHandler, initialDelay, maxWait, factor) {
+ var strategy = new ExponentialStrategy(initialDelay, maxWait, factor);
+ _backoff(strategy, networkRequestor, onSuccessHandler, onFailureHandler);
+}
+
+var backoff = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ exponentialBackoff: exponentialBackoff
+});
+
+/*
+ * src/number.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * "De-res" a number (lower the resolution of the number) per the Privacy Team and these radars:
+ * <rdar://problem/17423020> Add "capacityXXX" fields to UserXP Figaro reporting.
+ * <rdar://problem/23571925> Privacy: De-res capacityXXX fields
+ * Default behavior will de-res numbers by a magnitude of 1024^2 ie. bytes to megabytes and remove the last two significant digits
+ * For example, a raw number of bytes "de-res"'d to MB, but without the "floor" filter, would look like these examples:
+ * 31708938240/1024/1024 ==> 30240
+ * 15854469120/1024/1024 ==> 15120
+ * 63417876480/1024/1024 ==> 60480
+ *
+ * With the "floor" formula we replace the two least significant digits with "00"
+ * Doing so will convert values like so:
+ *
+ * 31708938240/1024/1024 ==> 30200
+ * 15854469120/1024/1024 ==> 15100
+ * 63417876480/1024/1024 ==> 60400
+ *
+ * @param {number} aNumber
+ * @param {number} (optional) magnitude, must be greater than 0. default 1024^2
+ * @param {number} (optional) significantDigits to remove, must be a positive integer or 0. default 2
+ * @returns {number} if the "aNumber" parameter is absent, the return value will be undefined.
+ * If any of the arguments are disallowed values, the value "NaN" will be returned.
+ * @overridable
+ */
+function deResNumber(aNumber, magnitude, significantDigits) {
+ var returnValue = undefined;
+
+ if (isDefined(aNumber)) {
+ if (!isDefined(magnitude)) {
+ magnitude = 1024 * 1024;
+ }
+ if (!isDefined(significantDigits)) {
+ significantDigits = 2;
+ }
+
+ if (
+ isNumber(aNumber) &&
+ isNumber(magnitude) &&
+ magnitude > 0 &&
+ isInteger(significantDigits) &&
+ significantDigits >= 0
+ ) {
+ var roundFactor = Math.pow(10, significantDigits);
+ var roundOperation = aNumber > 0 ? 'floor' : 'ceil';
+
+ returnValue = Math[roundOperation](aNumber / magnitude / roundFactor) * roundFactor;
+ } else {
+ returnValue = NaN;
+ }
+ }
+
+ return returnValue;
+}
+
+var number = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ deResNumber: deResNumber
+});
+
+/*
+ * src/config.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * @deprecated This class will be removed in the next Major release because the methods of this class have been moved to the mt-client-config/src/config/metrics_config.js
+ * Config utility methods
+ * IMPORTANT: These methods should be called within the context of a Config instance that implements a value() method,
+ * such as @amp/mt-client-config (the singleton or any instance created by it).
+ * They can be attached via reflect.attachMethods, passing the config instance as the methodContext
+ * @example:
+ * var Config = require('@amp/mt-client-config');
+ * var configUtils = ( ... ); // this file
+ * reflect.attachMethods(Config, configUtils, Config);
+ * Failing to use the correct context will result in thrown errors ("this.value is not a function").
+ */
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * @deprecated
+ * Boolean config value which, when "true", tells clients to avoid all metrics code paths (different than simply not sending metrics).
+ * Useful for avoiding discovered client bugs.
+ * NOTE1: This will cause unrecoverable event loss, as the clients will not be recording events at all.
+ * NOTE2: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded.
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {boolean}
+ */
+function disabled(topic) {
+ return this.value('disabled', topic) ? true : false;
+}
+
+/**
+ * @deprecated - Deprecated Language, use denylist instead
+ * Array config value which instructs clients to avoid sending particular event types.
+ * Useful for reducing server processing in emergencies by abandoning less-critical events.
+ * Useful for dealing with urgent privacy concerns, etc., around specific events.
+ * NOTE1: This will cause unrecoverable event loss, as the clients will not be recording events at all.
+ * NOTE2: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Array} Guaranteed to always return a valid array, though it may be empty if the value was unset in config
+ */
+function blacklistedEvents(topic) {
+ return denylistedEvents.call(this, topic);
+}
+
+/**
+ * @deprecated
+ * Array config value which instructs clients to avoid sending particular event types.
+ * Useful for reducing server processing in emergencies by abandoning less-critical events.
+ * Useful for dealing with urgent privacy concerns, etc., around specific events.
+ * NOTE1: This will cause unrecoverable event loss, as the clients will not be recording events at all.
+ * NOTE2: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * NOTE3: To honor both old blacklistedEvents configs and new denylistedEvents Configs, we'll merge the blacklistedEvents config with the denylistedEvents config.
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Array} Guaranteed to always return a valid array, though it may be empty if the value was unset in config
+ */
+function denylistedEvents(topic) {
+ var denylistedEventsArray = this.value('denylistedEvents', topic);
+ var blacklistedEventsArray = this.value('blacklistedEvents', topic);
+ return dedupedArray(blacklistedEventsArray, denylistedEventsArray);
+}
+
+/**
+ * @deprecated
+ * Array config value which instructs clients to avoid sending particular event fields.
+ * Useful for dealing with urgent privacy concerns, etc., around specific event fields (e.g. dsid)
+ * NOTE: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Array} Guaranteed to always return a valid array, though it may be empty if the value was unset in config
+ */
+function blacklistedFields(topic) {
+ return denylistedFields.call(this, topic);
+}
+
+/**
+ * @deprecated
+ * Array config value which instructs clients to avoid sending particular event fields.
+ * Useful for dealing with urgent privacy concerns, etc., around specific event fields (e.g. dsid)
+ * NOTE: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * NOTE2: To honor both old blacklistedFields configs and new denylistedFields configs, we'll merge the blacklistedEvents config with the denylistedEvents config.
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Array} Guaranteed to always return a valid array, though it may be empty if the value was unset in config
+ */
+function denylistedFields(topic) {
+ var denylistedFieldsArray = this.value('denylistedFields', topic);
+ var blacklistedFieldsArray = this.value('blacklistedFields', topic);
+ return dedupedArray(blacklistedFieldsArray, denylistedFieldsArray);
+}
+
+/**
+ * @deprecated
+ * Remove all blacklisted fields from the passed-in object.
+ * IMPORTANT: This action is performed in-place for performance of not having to create new objects each time.
+ * NOTE: Typically all event_handlers will call this in addition to "recordEvent()" calling it because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {Object} eventFields a dictionary of event data
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Object} the passed-in object with any blacklisted fields removed
+ */
+function removeBlacklistedFields(eventFields, topic) {
+ return removeDenylistedFields.call(this, eventFields, topic);
+}
+
+/**
+ * @deprecated
+ * Remove all denylisted fields from the passed-in object.
+ * IMPORTANT: This action is performed in-place for performance of not having to create new objects each time.
+ * NOTE: Typically all event_handlers will call this in addition to "recordEvent()" calling it because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {Object} eventFields a dictionary of event data
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Object} the passed-in object with any denylisted fields removed
+ */
+function removeDenylistedFields(eventFields, topic) {
+ if (eventFields) {
+ var denylistedFieldsArray = denylistedFields.call(this, topic);
+
+ for (var ii = 0; ii < denylistedFieldsArray.length; ii++) {
+ var aDenylistedField = denylistedFieldsArray[ii];
+ // Double check this is not null (or empty string), or "delete" will blow up...
+ if (aDenylistedField) {
+ if (aDenylistedField in eventFields) {
+ delete eventFields[aDenylistedField];
+ }
+ }
+ }
+ }
+ return eventFields;
+}
+
+/**
+ * @deprecated
+ * Convenience function used by event handlers to determine if they should build and return metricsData.
+ * NOTE: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {String} anEventType
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Boolean} returns "true" if <b>either</b> "disabled()" is true or "denylistedEvents()" contains this eventType
+ */
+function metricsDisabledOrBlacklistedEvent(anEventType, topic) {
+ return metricsDisabledOrDenylistedEvent.call(this, anEventType, topic);
+}
+
+/**
+ * @deprecated
+ * Convenience function used by event handlers to determine if they should build and return metricsData.
+ * NOTE: Typically all event_handlers will check for this in addition to "recordEvent()" checking because that way
+ * if a client overrides "recordEvent", these checks will still take effect.
+ * We also test it in recordEvent() in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ * @param {String} anEventType
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Boolean} returns "true" if <b>either</b> "disabled()" is true or "denylistedEvents()" contains this eventType
+ */
+function metricsDisabledOrDenylistedEvent(anEventType, topic) {
+ var returnValue =
+ disabled.call(this, topic) ||
+ (anEventType ? denylistedEvents.call(this, topic).indexOf(anEventType) > -1 : false);
+
+ return returnValue;
+}
+
+/**
+ * @deprecated
+ * Config map which instructs clients to de-res (lower the resolution of) particular event fields.
+ * The Privacy team typically requires device capacity information to be de-resed.
+ * @param {String} (optional) topic the Figaro topic to use to look up config values
+ * @returns {Array} An array of config objects { fieldName, (optional) magnitude, (optional) significantDigits }
+ * Guaranteed to always return a valid array, though it may be empty if the value was unset in config
+ */
+function deResFields(topic) {
+ var returnArray = this.value('deResFields', topic);
+
+ return returnArray || [];
+}
+
+/**
+ * @deprecated
+ * De-res appropriate fields in the passed-in object by lowering the resolution of those field values.
+ * For example, a raw number of bytes "de-res"'d to MB, but without the "floor" filter, would look like these examples:
+ * 31708938240/1024/1024 ==> 30240
+ * 15854469120/1024/1024 ==> 15120
+ * 63417876480/1024/1024 ==> 60480
+ *
+ * With the "floor" formula we replace the two least significant digits with "00"
+ * Doing so will convert values like so:
+ *
+ * 31708938240/1024/1024 ==> 30200
+ * 15854469120/1024/1024 ==> 15100
+ * 63417876480/1024/1024 ==> 60400
+ *
+ * IMPORTANT: This action is performed in-place for performance of not having to create new objects each time.
+ * NOTE: Be careful not to call this method more than once for a given event, as de-resing a number more than
+ * once can lead to inaccurate reporting (numbers will likely be smaller than their real values)
+ * @param {Object} eventFields a dictionary of event data
+ * @param {String} (optional) topic the Figaro topic to use to look up de-res config values
+ * @returns {Object} the passed-in object with any fields de-resed
+ */
+function applyDeRes(eventFields, topic) {
+ if (eventFields) {
+ var deResFieldsConfigArray = deResFields.call(this, topic);
+ var fieldName;
+
+ deResFieldsConfigArray.forEach(function (deResFieldConfig) {
+ fieldName = deResFieldConfig.fieldName;
+ if (fieldName in eventFields) {
+ eventFields[fieldName] = deResNumber(
+ eventFields[fieldName],
+ deResFieldConfig.magnitude,
+ deResFieldConfig.significantDigits
+ );
+ }
+ });
+ }
+ return eventFields;
+}
+
+var config = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ disabled: disabled,
+ blacklistedEvents: blacklistedEvents,
+ denylistedEvents: denylistedEvents,
+ blacklistedFields: blacklistedFields,
+ denylistedFields: denylistedFields,
+ removeBlacklistedFields: removeBlacklistedFields,
+ removeDenylistedFields: removeDenylistedFields,
+ metricsDisabledOrBlacklistedEvent: metricsDisabledOrBlacklistedEvent,
+ metricsDisabledOrDenylistedEvent: metricsDisabledOrDenylistedEvent,
+ deResFields: deResFields,
+ applyDeRes: applyDeRes
+});
+
+/*
+ * src/string.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ **************************** PUBLIC METHODS/IVARS ****************************
+ */
+
+/** Canned alphabets for use with convertNumberToBaseAlphabet
+ * Users can create their own alphabets/bases, e.g. "base61Alphabet",
+ * by truncating characters from the below, pre-defined, alphabets)
+ */
+var base10Alphabet = '0123456789';
+var base16Alphabet = base10Alphabet + 'ABCDEF';
+var base36Alphabet = base10Alphabet + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+var base61Alphabet = base36Alphabet + 'abcdefghijklmnopqrstuvwxy';
+var base62Alphabet = base61Alphabet + 'z';
+
+/**
+ * Test if mainString starts with subString.
+ * Optionally specify boolean "ignoreCase".
+ * @param mainString
+ * @param subString
+ * @param ignoreCase
+ * @returns {boolean} "false" if "mainString" or "subString" are null.
+ */
+function startsWith(mainString, subString, ignoreCase) {
+ var returnValue = false;
+
+ if (mainString && subString) {
+ mainString = mainString.substr(0, subString.length);
+ if (ignoreCase) {
+ mainString = mainString.toLowerCase();
+ subString = subString.toLowerCase();
+ }
+ returnValue = mainString.indexOf(subString) === 0;
+ }
+ return returnValue;
+}
+
+/**
+ * Test if one string ends with another.
+ * @param mainString
+ * @param subString
+ * @param ignoreCase
+ * @returns {boolean} "false" if "mainString" or "subString" are null.
+ */
+function endsWith(mainString, subString, ignoreCase) {
+ var returnValue = false;
+ if (mainString && subString) {
+ if (ignoreCase) {
+ mainString = mainString.toLowerCase();
+ subString = subString.toLowerCase();
+ }
+ // These two lines of logic (the guts) are the implementation from Prototype.js, which is well-optimized and well-tested.
+ var endIndex = mainString.length - subString.length;
+ returnValue = endIndex >= 0 && mainString.lastIndexOf(subString) === endIndex;
+ }
+ return returnValue;
+}
+
+/**
+ * Removes characters in the passed-in charString from the front and back of baseString
+ *
+ * If no "charsString" is passed in (or it's non-null but identical to stringWhitespace), it tries to use the browser-platform-native "trim()" if found, otherwise trims the stringWhitespace characters (which are the same set trimmed by the built-in function):
+ * If a non-null "charsString" string is passed in, it will try to remove all characters within that string, regardless of their order.
+ *
+ * (NOTE: These come from WebKit's built-in "trim()" method, who's testcase lives here:
+ * http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/third_party/string-trim.js?spec=svn3842&r=3052
+ * '\u0009' (HORIZONTAL TAB)
+ * '\u000A' (LINE FEED OR NEW LINE)
+ * '\u000B' (VERTICAL TAB)
+ * '\u000C' (FORMFEED)
+ * '\u000D' (CARRIAGE RETURN)
+ * '\u0020' (SPACE)
+ * '\u00A0' (NO-BREAK SPACE)
+ * '\u2000' (EN QUAD)
+ * '\u2001' (EM QUAD)
+ * '\u2002' (EN SPACE)
+ * '\u2003' (EM SPACE)
+ * '\u2004' (THREE-PER-EM SPACE)
+ * '\u2005' (FOUR-PER-EM SPACE)
+ * '\u2006' (SIX-PER-EM SPACE)
+ * '\u2007' (FIGURE SPACE)
+ * '\u2008' (PUNCTUATION SPACE)
+ * '\u2009' (THIN SPACE)
+ * '\u200A' (HAIR SPACE)
+ * '\u3000' (IDEOGRAPHIC SPACE)
+ * '\u2028' (LINE SEPARATOR)
+ * '\u2029' (PARAGRAPH SEPARATOR)
+ * '\u200B' (ZERO WIDTH SPACE (category Cf)'}
+ * NOTE: If you pass a custom "charString" and want whitespace removed as well, be sure to include the whitespace string as well
+ * Examples: " hello world ".trim() -> "hello world" -- " e hello world f".trim(stringWhitespace+"ef") -> "hello world"
+ * @param basestring is the string to trim
+ * @param If no "charsString" is passed in (or it's non-null but identical to stringWhitespace), it tries to use the browser-platform-native "trim()" if found, otherwise trims the stringWhitespace characters (which are the same set trimmed by the built-in function):
+ * If a non-null "charsString" string is passed in, it will try to remove all characters within that string, regardless of their order.
+ * @param forceNonNativeTrim is mostly used for testing purposes, but does what it says.
+ */
+function trim(baseString, charString, forceNonNativeTrim) {
+ var returnValue = null;
+ var stringWhitespace =
+ '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u3000\u2028\u2029\u200B';
+ var whitespaceTrimStartRegex = new RegExp('^[' + stringWhitespace + ']+'); // No need to create a new one of these objects on each call!
+ var whitespaceTrimEndRegex = new RegExp('[' + stringWhitespace + ']+$'); // No need to create a new one of these objects on each call!
+
+ if (baseString) {
+ if (!forceNonNativeTrim && (!charString || charString == stringWhitespace) && baseString.trim) {
+ // Use browser-built-in trim, if it exists...
+ returnValue = baseString.trim();
+ } else {
+ // NOTE: IF YOU MODIFY THIS METHOD, COPY AND TEST THE MODIFICATION TO itmsCheck.js WHICH HAS A COPY/PASTED VERSION (SANS COMMENTS)
+ var trimChars = null;
+ var startRegex = null;
+ var endRegex = null;
+
+ if (charString && typeof charString !== 'undefined') {
+ // This is bits and pieces combined together from here: http://stackoverflow.com/questions/494035/how-do-you-pass-a-variable-to-a-regular-expression-javascript
+ charString = charString.replace(/([.?*+^$[\]\\(){}-])/g, '\\$1'); // If we don't do this, then if "mainString" has .'s, or other regex chars in it, the regex interprets them as part of the regex!
+ trimChars = '[' + charString + ']';
+ startRegex = new RegExp('^' + trimChars + '+');
+ endRegex = new RegExp(trimChars + '+$');
+ } else {
+ trimChars = stringWhitespace;
+ startRegex = whitespaceTrimStartRegex;
+ endRegex = whitespaceTrimEndRegex;
+ }
+ var str = baseString.replace(startRegex, '');
+ returnValue = str.replace(endRegex, '');
+ }
+ }
+ return returnValue;
+}
+
+/**
+ * Changes snake_case "source" string to lowerCamelCase or UpperCamelCase
+ * @param {String} source underscore separated sentence/list of words
+ * @param {Boolean} upperCamelCase - optional parameter specifying whether to capitalize the first letter, defaults to false
+ * @return {String} result the source parameter in lower or upper camel case
+ */
+function snakeCaseToCamelCase(source, upperCamelCase) {
+ var result = '';
+ if (source) {
+ var words = source.toLowerCase().split('_');
+ var firstChar;
+
+ for (var i = 0; i < words.length; i++) {
+ firstChar = words[i][0];
+ if (i !== 0 || upperCamelCase) {
+ firstChar = firstChar.toUpperCase();
+ }
+ result += firstChar + words[i].slice(1);
+ }
+ }
+ return result;
+}
+
+/**
+ * Changes snake_case "source" string to UpperCamelCase
+ * @param {String} source Underscore separated sentence/list of words
+ * @return {String} result The source parameter in upper camel case
+ */
+function snakeCaseToUpperCamelCase(source) {
+ return snakeCaseToCamelCase(source, true);
+}
+
+/**
+ * Turns an object into a query param string
+ * @param {Object} params is the set of key-value pairs to turn into a query param string.
+ * @returns {String} a query param string with URI-encoded values created using the key-value pairs in the passed in object. e.g. "app=com.apple.Safari&testValue=test&eventTime=14927450"
+ * NOTE: The first key of the returned string is never prefaced, not with an ampersand (&) or a question mark (?)
+ * @example
+ * var paramString = _utils.string.paramString({
+ * app: 'com.apple.Safari',
+ * testValue: 'test',
+ * eventTime: 14927450
+ * });
+ */
+function paramString(params) {
+ var paramString = '';
+ var delimiter = '';
+ var firstKey = true;
+
+ for (var key in params) {
+ var value = params[key];
+ if (value || value === 0 || value === false) {
+ paramString += delimiter + key + '=' + encodeURIComponent(value);
+ if (firstKey) {
+ delimiter = '&';
+ firstKey = false;
+ }
+ }
+ }
+ return paramString;
+}
+
+function exceptionString(className, methodName) {
+ return (
+ 'The function ' +
+ className +
+ '.' +
+ methodName +
+ '() must be overridden with a platform-specific delegate function.' +
+ 'If you have no data for this function, have your delegate return null ' +
+ "or undefined (no 'return')"
+ );
+}
+
+/**
+ * Parses a user agent string for a particular product name and returns its version
+ * @param {String} userAgent that conforms with RFC 7231 section 5.5.3 regarding User-Agents
+ * @param {String} (optional) productName the name of a product identifier to search for e.g. 'iTunes'; if omitted, defaults to the first identifier
+ * @return {String} the version of the product, or null if none found
+ * @example
+ * versionStringFromUserAgent('iTunes/12.6 (Macintosh; OS X 10.12.4) AppleWebKit/603.1.30.0.34') returns '12.6'
+ * versionStringFromUserAgent('iTunes/12.6 (Macintosh; OS X 10.12.4) AppleWebKit/603.1.30.0.34', 'AppleWebKit') returns '603.1.30.0.34'
+ * versionStringFromUserAgent('iTunes/12.6 (Macintosh; OS X 10.12.4) AppleWebKit/603.1.30.0.34', 'Macintosh') returns null
+ * (strings contained in parentheses are counted as comments, not product identifiers)
+ */
+function versionStringFromUserAgent(userAgent, productName) {
+ var returnValue = null;
+
+ productName = productName || '\\S+'; // default to the first product name
+
+ var re = new RegExp('\\b' + productName + '/(\\S+)\\b', 'i');
+ var match = re.exec(userAgent);
+
+ if (match && match[1]) {
+ returnValue = match[1];
+ }
+
+ return returnValue;
+}
+
+/**
+ * Takes a client ID (universally unique per device) and generates another UUID that is unique per request
+ * TODO: have a fallback in case clientId is unavailable
+ * @param {string} clientId, a base-61 UUID that uses 'z' as a delimiter
+ * @return {string} A generated UUID, to be used in visit stitching
+ */
+function requestId(clientId) {
+ // NOTE: The reason we integrate "clientId" into this requestId (uuid) is because there is no itms.crypto functionality in ITMLKit for creating robust UUIDs, so we
+ // leverage off the fact that "clientId" was created cryptographically strong in Java.
+ var delimiter = 'z';
+ var epochTime = Date.now();
+ var randomNum = Math.floor(Math.random() * 100000);
+
+ // convert to base 36, and use uppercase since 'z' is a delimiter in clientId
+ epochTime = epochTime.toString(36).toUpperCase();
+ randomNum = randomNum.toString(36).toUpperCase();
+
+ return clientId + delimiter + epochTime + delimiter + randomNum;
+}
+
+/**
+ * Generates a RFC4122-compliant UUID (v4)
+ * See https://tools.ietf.org/html/rfc4122
+ * For a discussion on the probability of collisions of version 4 UUIDs, see:
+ * https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions
+ * @param {Function} (optional) pseudoRNG a function that returns a pseudo random number between 0 and 1
+ * defaults to a cryptographically strong PRNG when available or Math.random()
+ * which is not cryptographically strong, but can be used where a small number of collisions are acceptable
+ * @return {String}
+ * TODO: consider optimizing to use fewer calls to randomHexCharacter (i.e. switch to a randomHexString method)
+ */
+function uuid(pseudoRNG) {
+ var template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
+ var uuid = '';
+ var character;
+
+ for (var i = 0, len = template.length; i < len; i++) {
+ character = template.charAt(i);
+
+ if (character === 'x') {
+ uuid += randomHexCharacter(pseudoRNG);
+ } else if (character === 'y') {
+ uuid += randomHexCharacter(pseudoRNG, '8', 'b');
+ } else {
+ uuid += character;
+ }
+ }
+
+ return uuid;
+}
+
+/**
+ * Generates a random hexdecimal character between 0 and f
+ * @param {Function} (optional) a pseudo random number generator that returns a value between 0 and 1
+ * defaults to a cryptographically strong PRNG when available or Math.random()
+ * which is not cryptographically strong, but can be used where a small number of collisions are acceptable
+ * @param {String} (optional) min the lowest character (0-f) to include, inclusive
+ * @param {String} (optional) max the highest character (0-f) to include, inclusive
+ * @return {String}
+ */
+function randomHexCharacter(pseudoRNG, min, max) {
+ var globalObject = globalScope();
+ var cryptoObject = globalObject.crypto || globalObject.msCrypto;
+ var randomCharacter;
+
+ if (pseudoRNG) {
+ randomCharacter = ((pseudoRNG() * 16) | 0).toString(16);
+ } else if (cryptoObject && cryptoObject.getRandomValues) {
+ randomCharacter = (cryptoObject.getRandomValues(new Uint8Array(1))[0] & 15).toString(16);
+ } else if (cryptoObject && cryptoObject.randomBytes) {
+ randomCharacter = cryptoObject.randomBytes(1).toString('hex')[0];
+ } else {
+ randomCharacter = ((Math.random() * 16) | 0).toString(16);
+ }
+
+ // rejection sampling: if character not in desired range, generate another one
+ if (min && max && (randomCharacter < min || randomCharacter > max)) {
+ randomCharacter = randomHexCharacter(pseudoRNG, min, max);
+ }
+
+ return randomCharacter;
+}
+
+/**
+ * Adapted from MTStringUtil.java, which copied from MZStringUtil.java.
+ * Base-2 to base-62 target alphabets are accepted.
+ * Alphabet order must be 0-9, then "A" stands for 10, "Z" for 35, "a" (lower-case) for 36 and "z" (lower-case) for 61.
+ * Not sure if there is any standard for displaying numbers higher than base-36. Lowercase letters are used to go up
+ * to base-62.
+ *
+ * @param {Number} number a POSITIVE base 10 number to convert
+ * @param {String} targetAlphabet indicates the base of the target value (by virtue of the length of the alphabet)
+ * as well as the characters to use during conversion. "Canned" alphabets are provided,
+ * but homegrown alphabets may be used by truncating values from canned alphabets.
+ * @return {String} a string that has been converted to targetAlphabet
+ */
+function convertNumberToBaseAlphabet(number, targetAlphabet) {
+ var returnValue = '';
+ var targetRadix = targetAlphabet.length;
+
+ if (targetRadix <= 36) {
+ returnValue = number.toString(targetRadix).toUpperCase();
+ } else {
+ var remainder;
+ var charForRemainder;
+ var charArray = [];
+
+ while (number > 0) {
+ remainder = number % targetRadix;
+ charForRemainder = targetAlphabet.charAt(remainder);
+ charArray.push(charForRemainder);
+ number = (number - remainder) / targetRadix;
+ }
+
+ returnValue = charArray.reverse().join('');
+ }
+
+ if (returnValue === '') {
+ returnValue = '0';
+ }
+
+ return returnValue;
+}
+
+/**
+ * Generates a random base62 string. If strong crypto is available, this will try to use it first.
+ * See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#Browser_compatibility
+ * for availability.
+ * @param {Boolean} hasPrefix - optional parameter specifying whether to add version/crypto indicator prefix to the string.
+ * @return {string} A random 24 character base62 random string if successful, or null if no strong crypto is available.
+ */
+function cryptoRandomBase62String(hasPrefix) {
+ var base62String;
+ // Test to make sure unsigned numbers with full 32 bits are supported
+ if (Math.floor(0xffffffff / 0x100) == 0xffffff) {
+ var globalObject = globalScope();
+ var cryptoObject = globalObject.crypto || globalObject.msCrypto;
+ var arr;
+ var i;
+ var j;
+ var num;
+ var isCrypto;
+
+ // UUID has 16 bytes
+ if (cryptoObject && cryptoObject.getRandomValues) {
+ arr = cryptoObject.getRandomValues(new Uint32Array(16 / Uint32Array.BYTES_PER_ELEMENT));
+ isCrypto = true;
+ } else if (cryptoObject && cryptoObject.randomBytes) {
+ var b = cryptoObject.randomBytes(16);
+ arr = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / Uint32Array.BYTES_PER_ELEMENT);
+ isCrypto = true;
+ } else {
+ arr = new Uint32Array(16 / Uint32Array.BYTES_PER_ELEMENT);
+ for (i = 0; i < arr.length; i++) {
+ arr[i] = Math.floor(Math.random() * Math.floor(0xffffffff));
+ }
+ }
+
+ if (arr) {
+ base62String = '';
+ for (i = 0; i < arr.length; i++) {
+ num = arr[i];
+ for (j = 0; j < 6; j++) {
+ // 4-byte block encoded to 6 byte base62
+ base62String += base62Alphabet[num % 62];
+ num = Math.floor(num / 62);
+ }
+ }
+ if (hasPrefix) {
+ // 1st char: version (currently 1)
+ // 2nd char: separator _
+ // 3rd char: encryption type, 0 = unknown, 1 = yes, 2 = no
+ // 4th char: separator _
+ base62String = '1_' + (isCrypto ? '1' : '2') + '_' + base62String;
+ }
+ }
+ }
+ return base62String;
+}
+
+var string = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ base10Alphabet: base10Alphabet,
+ base16Alphabet: base16Alphabet,
+ base36Alphabet: base36Alphabet,
+ base61Alphabet: base61Alphabet,
+ base62Alphabet: base62Alphabet,
+ startsWith: startsWith,
+ endsWith: endsWith,
+ trim: trim,
+ snakeCaseToCamelCase: snakeCaseToCamelCase,
+ snakeCaseToUpperCamelCase: snakeCaseToUpperCamelCase,
+ exceptionString: exceptionString,
+ paramString: paramString,
+ versionStringFromUserAgent: versionStringFromUserAgent,
+ requestId: requestId,
+ uuid: uuid,
+ randomHexCharacter: randomHexCharacter,
+ convertNumberToBaseAlphabet: convertNumberToBaseAlphabet,
+ cryptoRandomBase62String: cryptoRandomBase62String
+});
+
+/*
+ * src/cookies.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ *
+ * Cookie related util methods
+ * @constructor
+ *
+ * Packaging note: tree-shaking does not remove unused functions from this class object
+ * It might be more efficient to separate the cookie delegate from the utility functions
+ */
+var cookies = {
+ /**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+ /**
+ * Allows replacement of one or more of this class' functions
+ * Any method on the passed-in object which matches a method that this class has will be called instead of the built-in class method.
+ * To replace *all* methods of his class, simply have your delegate implement all the methods of this class
+ * Your delegate can be a true object instance, an anonymous object, or a class object.
+ * Your delegate is free to have as many additional non-matching methods as it likes.
+ * It can even act as a delegate for multiple MetricsKit objects, though that is not recommended.
+ *
+ * NOTE: when the delegate function is called, it will include an additional final parameter representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * @example:
+ * To override one or more methods, in place:
+ * eventRecorder.setDelegate({recordEvent: itms.recordEvent});
+ * To override one or more methods with a separate object:
+ * eventRecorder.setDelegate(eventRecorderDelegate);
+ * (where "eventRecorderDelegate" might be defined elsewhere as, e.g.:
+ * var eventRecorderDelegate = {recordEvent: itms.recordEvent,
+ * sendMethod: 'itms'};
+ * To override one or more methods with an instantiated object from a class definition:
+ * eventRecorder.setDelegate(new EventRecorderDelegate());
+ * (where "EventRecorderDelegate" might be defined elsewhere as, e.g.:
+ * function EventRecorderDelegate() {
+ * }
+ * EventRecorderDelegate.prototype.recordEvent = itms.recordEvent;
+ * EventRecorderDelegate.prototype.sendMethod = function sendMethod() {
+ * return 'itms';
+ * };
+ * To override one or more methods with a class object (with "static" methods):
+ * eventRecorder.setDelegate(EventRecorderDelegate);
+ * (where "EventRecorderDelegate" might be defined elsewhere as, e.g.:
+ * function EventRecorderDelegate() {
+ * }
+ * EventRecorderDelegate.recordEvent = itms.recordEvent;
+ * EventRecorderDelegate.sendMethod = function sendMethod() {
+ * return 'itms';
+ * };
+ * @param {Object} Object or Class with delegate method(s) to be called instead of default (built-in) methods.
+ * @returns {Boolean} true if one or more methods on the delegate object match one or more methods on the default object,
+ * otherwise returns false.
+ */
+ setDelegate: function setDelegate(delegate) {
+ return attachDelegate(this, delegate);
+ },
+
+ /**
+ * The cookie string, e.g. "iTunes.cookie" (iTunes desktop), "iTunes.cookieForDefaultURL" (HTML iOS), "itms.cookie" (itml app), "document.cookie" (browser)
+ * NOTE: Callers should override this method if they want to supply a different cookie.
+ * @overridable
+ */
+ cookie: function cookie() {
+ var cookieOwnerObject;
+
+ if (typeof window !== 'undefined' && 'iTunes' in window && 'cookie' in iTunes) {
+ cookieOwnerObject = iTunes;
+ } else if (typeof itms !== 'undefined' && isDefined(itms.cookie)) {
+ cookieOwnerObject = itms;
+ } else if (typeof document !== 'undefined') {
+ cookieOwnerObject = document;
+ } else {
+ throw 'cookies.cookie: No cookie object available';
+ }
+
+ return cookieOwnerObject.cookie;
+ },
+
+ // NOTE: MetricsKit does not currently need to set cookies, so setting functions are commented out until they are needed
+ /**
+ * Normal/Primary cookie setter method.
+ * Invokes JavaScript's "escape()" function on "cookieValue" before passing to "cookies.setUnescaped()"
+ *
+ * Set cookie with the given name and value at the path "/"
+ * @param cookieName the name of the cookie; must not contain whitespace or semicolons.
+ * @param cookieValue must not contain semicolons or whitespace. Use escape() if necessary
+ * @param lifespanInSeconds may be one of: 1. the time-to-live in seconds, 2. null to expire at browser (session) termination, 3. negative to delete a cookie
+ * @param path the path to use (optional,if null, defaults to "/")
+ * @param domain the domain to use (if null, defaults to the current domain)
+ */
+ // this.set = function set(cookieName, cookieValue, lifespanInSeconds, path, domain) { };
+
+ /**
+ * "Normal Use" cookie getter method.
+ * Invokes JavaScript's "unescape()" function on value returned from "cookies.getUnescaped()" and returns that unescaped value.
+ */
+ get: function get(cookieName) {
+ // NOTE: IF YOU MODIFY THIS METHOD, COPY AND TEST THE MODIFICATION TO itmsCheck.js WHICH HAS A COPY/PASTED VERSION (SANS COMMENTS)
+ var returnValue = this.getUnescaped(cookieName);
+ if (returnValue) returnValue = unescape(returnValue);
+ return returnValue;
+ },
+
+ // NOTE, The jingle "setUnescaped" method has a lot of special-case code both for devices and ITML.
+ // That is important functionality for setting cookies on those platforms but we don't currently need to set cookies (we used to do it as a device workaround in iOS6) and
+ // to include it here, we would need to create platform-specific delegates, so let's save all that mess for the day we actually need this functionality at
+ // which point you SHOULD grab and adapt the code from Jingle:
+ setUnescaped: function setUnescaped(cookieName, cookieValue, lifespanInSeconds, path, domain) {},
+
+ /**
+ * Funnel-point cookie-getting method.
+ * Parsing document.cookie is simple, but there are a lot of quirks.
+ *
+ * The simple format is "a=b; c=d;", but according to RFC 2965 (from 2006, but only Opera supports it),
+ * any amount of whitespace (including none) is optional as a separator.
+ * NOTE:KBERN: We trim whitespace from the beginning and end of both keys and values based on my reading of:
+ * http://tools.ietf.org/html/rfc2965
+ * on page 3 it says,
+ * "NOTE: The syntax above allows whitespace between the attribute and the = sign.", and so even though
+ * Meaning that a) there does have to be an "=" sign, and b) there can be whitespace on both sides of it.
+ * Safari seems to collapse whitespace around the "=", but there is no guarantee that all browsers will
+ * behave that way on all platforms, therefore I think that both the keys and values need to be trimmed of
+ * both leading and trailing spaces.
+ *
+ * According to the most widely supported spec (the 1995 Netscape cookie draft doc), the name-value pairs are
+ * "a sequence of characters excluding semi-colon, comma and white space". This means the value can contain
+ * the equals sign, and any other number of weird characters!
+ *
+ * In short, don't mess with this function.
+ *
+ * <rdar://problem/8123192> Javascript cookie parsing is chopping off trailing = signs
+ */
+ getUnescaped: function getUnescaped(cookieName) {
+ var result = null;
+
+ // NOTE: IF YOU MODIFY THIS METHOD, COPY AND TEST THE MODIFICATION TO itmsCheck.js WHICH HAS A COPY/PASTED VERSION (SANS COMMENTS)
+ var cookieString = this._getRaw();
+ if (cookieString && cookieName) {
+ var splitCookies = cookieString.split(';');
+
+ // GO THROUGH THE COOKIES BACKWARDS BECAUSE...
+ // (This comment, and searching from back->front is from Dojo: http://www.bedework.org/trac/bedework/browser/trunk/deployment/resources/javascript/dojo-0.4.1-ajax/src/io/cookie.js?rev=1164
+ // I haven't tried to reproduce this, but it's no skin off our backs to go backwards anyway, so...)
+ // Which cookie should we return?
+ // If there are cookies set for different sub domains in the current
+ // scope there could be more than one cookie with the same name.
+ // I think taking the last one in the list takes the one from the
+ // deepest subdomain, which is what we're doing here.
+ for (var i = splitCookies.length - 1; !result && i >= 0; i--) {
+ var aCookie = splitCookies[i];
+ var separatorIndex = aCookie.indexOf('=');
+
+ if (separatorIndex > 0) {
+ if (separatorIndex + 1 == aCookie.length) {
+ result = ''; // there *is* a cookie key, but there is nothing to the right of the "="
+ } else {
+ // Trim all leading and trailing whitespace from key...
+ var cookieKey = trim(aCookie.substring(0, separatorIndex));
+
+ if (cookieKey == cookieName) {
+ // Trim all leading and trailing whitespace from the value as well, since there may be whitespace to the right of the "=" sign
+ result = trim(aCookie.substring(separatorIndex + 1));
+ }
+ }
+ }
+ }
+ }
+ return result;
+ },
+
+ /**
+ * adding a cover accessor to document.cookie so that in iOS 4.2 and newer we can use iTunes.cookies instead (allows access to private storage)
+ */
+
+ /**
+ * Clear a cookie (expire/delete/remove it)
+ * @param {Object} cookieName
+ */
+ remove: function remove(cookieName, domain) {
+ return this.setUnescaped(cookieName, '.', this.EXPIRE_NOW, null, domain);
+ },
+
+ // PRIVATE METHODS:
+
+ // NOTE: MetricsKit does not currently need to set cookies, so setting functions are commented out until they are needed
+ /**
+ * @param val the raw Cookie string (Webkit), or a Cookie dict (ITML) to be set.
+ */
+ // this._setRaw = function _setRaw(val) { };
+
+ /**
+ * @returns all Cookies as a string.
+ */
+ _getRaw: function _getRaw() {
+ return this.cookie() || '';
+ }
+};
+
+// CONSTANTS:
+// Convenient lifespanInSeconds values:
+cookies.EXPIRE_NOW = -1;
+cookies.EXPIRE_SESSION = null; // or "0"
+cookies.EXPIRE_ONE_SECOND = 1;
+cookies.EXPIRE_ONE_MINUTE = cookies.EXPIRE_ONE_SECOND * 60;
+cookies.EXPIRE_ONE_HOUR = cookies.EXPIRE_ONE_MINUTE * 60;
+cookies.EXPIRE_ONE_DAY = cookies.EXPIRE_ONE_HOUR * 24;
+cookies.EXPIRE_ONE_WEEK = cookies.EXPIRE_ONE_DAY * 7;
+cookies.EXPIRE_ONE_MONTH = cookies.EXPIRE_ONE_DAY * 31;
+cookies.EXPIRE_ONE_YEAR = cookies.EXPIRE_ONE_DAY * 365;
+cookies.EXPIRE_ONE_SIDEREAL_YEAR = cookies.EXPIRE_ONE_DAY * 365.25; // (31556926279 or so)... For those who want decades long accuracy :-( ... of course we could also make special day times since a day is really 24 hours and 2 milliseconds long :-)
+cookies.EXPIRE_SIX_MONTHS = cookies.EXPIRE_ONE_DAY * 180; // <rdar://problem/11067278> Cookies: reduce max age to 6 months
+
+/*
+ * src/utils/delegates_info.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2016 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Used to store information
+ * about attached delegates as a tree,
+ * in which delegates may contain their own
+ * list of "child" delegates as an array
+ * @type {Object}
+ * @example
+ * // If metricsKit.attachDelegate(delegatesITML)
+ * delegatesMap = {
+ '@amp/mt-metricskit5.1.0': {
+ name: '@amp/mt-metricskit',
+ version: '5.1.0'
+ // The accessor delegatesMap[...] would not run correctly if initialized
+ // at the same time as this object, but since it's added after delegatesMap
+ // is created, it works
+ delegates: [delegatesMap['@amp/mt-metricskit-delegates-itml3.1.2']]
+ // The above array contains a pointer to another object in delegatesMap
+ }
+ '@amp/mt-metricskit-delegates-itml3.1.2': {
+ name: '@amp/mt-metricskit-delegates-itml',
+ version: '3.1.2'
+ }
+ }
+ */
+var delegatesMap = {};
+
+/*
+ * Used to keep a record of which delegates have been added to which targets
+ * for use with deduping the child 'delegates' field of a delegate object
+ * (The reason for using this object instead of adding it to the delegatesMap
+ * or changing the delegates subfield in delegatesMap to be an object
+ * is that delegatesMap represents the value of the mt-metricskit base field
+ * xpDelegatesInfo, which requires a certain format.
+ * This map is a theoretical extension of delegatesMap, to facilitate constant
+ * time lookup for use with deduping.)
+ * @type {Object}
+ * @example
+ * After mt-metricskit adds delegates-itml and delegates-html as delegates
+ * and delegates-itml has added base-events as a delegate
+ * {
+ * 'mt-metricskit2.1.1': ['mt-metricskit-delegates-itml3.1.5', 'mt-metricskit-delegates-html0.1.3'],
+ * 'mt-metrickit-delegates-itml3.1.5': ['mt-metricskit-base-events1.1.2']
+ * }
+ */
+var dedupingMap = {};
+
+/**
+ * @param {Object} delegate The object to retrieve name & version info off of
+ * @param {function} delegate.mtName Returns the name of the delegate as a string
+ * @param {function} delegate.mtVersion Returns the version of the delegate as a string
+ * @returns {Object} Contains the passed-in delegate's info, esp. name & version
+ */
+var createDelegateInfoObject = function createDelegateInfoObject(delegate) {
+ var delegateInfo = {};
+
+ if (typeof delegate.mtName === 'function' && typeof delegate.mtVersion === 'function') {
+ // Add delegate name, version, and any previously-attached "child" delegates to delegatesInfoList
+ delegateInfo.name = delegate.mtName();
+ delegateInfo.version = delegate.mtVersion();
+ }
+
+ return delegateInfo;
+};
+
+/**
+ * Returns a concatted string of the passed-in delegate's
+ * name and version, to be used when storing the delegate in delegatesMap
+ * @param {Object} delegate Delegate to create key string from
+ * @param {function} delegate.mtName Returns the name of the delegate as a string
+ * @param {function} delegate.mtVersion Returns the version of the delegate as a string
+ * @returns {String} The concatted name and version of the passed-in delegate
+ * @example
+ * "mt-metricskit-delegates-itml3.1.2"
+ */
+var createDelegateKey = function createDelegateKey(delegate) {
+ var delegateKey;
+ if (typeof delegate.mtName === 'function' && typeof delegate.mtVersion === 'function') {
+ delegateKey = delegate.mtName() + delegate.mtVersion();
+ }
+ return delegateKey;
+};
+
+/**
+ * Creates delegate info objects for passed-in target object and delegate object,
+ * and stores them in the delegatesMap. The info object of the delegate being attached
+ * to the target will be stored in the target's info object's 'delegates' field
+ * @param {Object} target The object being partially or wholly overwritten by the delegate
+ * @param {Object} delegate The object overwriting the target object's functionality
+ * @param {function} target.mtName Returns the name of the target as a string
+ * @param {function} target.mtVersion Returns the version of the target as a string
+ * @param {function} delegate.mtName Returns the name of the delegate as a string
+ * @param {function} delegate.mtVersion Returns the version of the delegate as a string
+ */
+function storeDelegateInfo(target, delegate) {
+ var targetKey = createDelegateKey(target);
+ var delegateKey = createDelegateKey(delegate);
+ if (targetKey && delegateKey) {
+ // Create delegate info objects (containing delegate's name & version)
+ // and add to delegatesMap
+ if (!delegatesMap[delegateKey]) {
+ delegatesMap[delegateKey] = createDelegateInfoObject(delegate);
+ }
+ if (!delegatesMap[targetKey]) {
+ delegatesMap[targetKey] = createDelegateInfoObject(target);
+ dedupingMap[targetKey] = {};
+ }
+ // Add delegate's info object to target's delegates array in delegatesMap
+ if (delegatesMap[targetKey].delegates) {
+ if (!dedupingMap[targetKey][delegateKey]) {
+ delegatesMap[targetKey].delegates.push(delegatesMap[delegateKey]);
+ }
+ } else {
+ delegatesMap[targetKey].delegates = [delegatesMap[delegateKey]];
+ }
+ dedupingMap[targetKey][delegateKey] = true;
+ }
+}
+
+/**
+ * Return the delegate object stored in delegatesMap
+ * for the passed-in delegate
+ * @returns {Object} The stored delegate object for the passed-in delegate
+ */
+function getStoredDelegateObject(delegate) {
+ return delegatesMap[createDelegateKey(delegate)];
+}
+
+var delegates_info = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ storeDelegateInfo: storeDelegateInfo,
+ getStoredDelegateObject: getStoredDelegateObject
+});
+
+/*
+ * src/key_value.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Takes a SINGLE searchSource and string representation of an object path (a namespace) and returns the object at that subpath (possibly after first creating it)
+ * If the objects in the path do not already exist off of searchSource and "createIfNeeded" is true, this method will create all the JavaScript objects required to make it a valid object path
+ * If any component already exists, it will be maintained. New components are created as "{}"
+ * e.g. after this call:
+ * keyValue.valueForKeyPath(rootObject, 'foo.bar.Tot')
+ * there will always be a valid JavaScript object of:
+ * rootObject.foo.bar.Tot
+ * @param searchSource a key/value object
+ * @param keyPath a simple property key name, e.g. "foo", or a nested property key name "path", e.g. foo.bar.tot
+ * @return the discovered and/or created object at the specified path extending off the specified searchSources
+ * If "createIfNeeded" is not specified, and keyPath or searchSources are not both valid, "undefined" will be returned (since "null" could be a valid return value stored at some keyPath)
+ * If the goal of the caller is simply to create the object path and "createIfNeeded" has been specified, the return value may be ignored.
+ * @example valueForKeyPath("foo", {"bar":10, "foo":12}); returns "12"
+ */
+function _valueForKeyPath(keyPath, searchSource, createIfNeeded) {
+ var tailObject = searchSource;
+
+ if (keyPath && searchSource) {
+ var objectStrings = keyPath.split('.');
+
+ for (var ii = 0; tailObject && ii < objectStrings.length; ii++) {
+ var anObjectString = objectStrings[ii];
+ if (!(anObjectString in tailObject) && createIfNeeded) {
+ tailObject[anObjectString] = {};
+ }
+ if (anObjectString in tailObject) {
+ tailObject = tailObject[anObjectString];
+ } else {
+ tailObject = null;
+ }
+ }
+ }
+ return tailObject;
+}
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Takes one or more searchSources and string representation of an object path (a namespace) and returns the object at that subpath
+ * @param {String} keyPath a simple property key name, e.g. "foo", or a nested property key name "path", e.g. foo.bar.tot
+ * @param {varargs} searchSources at least one key/value object(s), or array(s) of key/value objects, or list(s) of key/value objects.
+ * Later sets of key/value pairs overwrite earlier sets.
+ * Callers are not asked to pass a unified (coalesced) set of key/value objects because that is a much more expensive operation
+ * to preform each time we are searching for a keyPath.
+ * @return the discovered object at the specified path extending off the specified searchSources. Later sets of key/value pairs overwrite earlier sets.
+ * @example valueForKeyPath("foo", {"bar":10, "foo":12}); returns "12"
+ * @example valueForKeyPath("foo.cat", [{"bar":10, "foo":12}, {"bar":10, "foo":{"cat":"meow", "dog":"ruff"}}]); returns "meow"
+ * @example valueForKeyPath("foo.cat", {"bar":10, "foo":12}, {"bar":10, "foo":{"cat":"meow", "dog":"ruff"}}); returns "meow"
+ */
+function valueForKeyPath(keyPath /*, searchSources<varargs>*/) {
+ var returnValue = null;
+
+ if (keyPath && arguments.length > 1) {
+ // Pass in all sources after the first ("keyPath"). We do this even if there's only one param, in case that one param is an object instead of an array
+ var normalizedSearchSources = sourcesArray(Array.prototype.slice.call(arguments, 1));
+
+ // Now we just loop through our normalizedSearchSources looking for "keyPath" in any of them.
+ // We start at the end and look backwards, because the later sources take precedence over earlier ones.
+ for (var ii = normalizedSearchSources.length - 1; ii >= 0; ii--) {
+ var aSearchSource = normalizedSearchSources[ii];
+
+ returnValue = _valueForKeyPath(keyPath, aSearchSource);
+ if (isDefinedNonNull(returnValue)) {
+ break;
+ }
+ }
+ }
+ return returnValue;
+}
+
+/**
+ * If the objects in the path do not already exist off of searchSource, this method will create all the JavaScript objects required to make it a valid object path
+ * If any component already exists, it will be maintained. New components are created as "{}"
+ * e.g. after this call:
+ * keyValue.createObjectAtKeyPath(rootObject, 'foo.bar.Tot')
+ * there will always be a valid JavaScript object of:
+ * rootObject.foo.bar.Tot
+ * @param searchSource a key/value object
+ * @param keyPath a simple property key name, e.g. "foo", or a nested property key name "path", e.g. foo.bar.tot
+ * @return the created object at the specified path extending off the specified searchSources
+ * If the goal of the caller is simply to create the object path and "createIfNeeded" has been specified, the return value may be ignored.
+ */
+function createObjectAtKeyPath(keyPath, searchSource) {
+ return _valueForKeyPath(keyPath, searchSource, true);
+}
+
+/**
+ * Expands the sources param, and any varargs that might follow it and puts them all into an array.
+ * Items within "sources" or varargs can, themselves, be arrays, in which case they will be decomposed and their items added to the top level of the returned array.
+ * @example sourcesArray("foo", {"bar":10, "foo":12}); returns ["foo", {"bar":10, "foo":12}]
+ * @example sourcesArray("foo.cat", [{"bar":10, "foo":12}, {"bar":10, "foo":{"cat":"meow", "dog":"ruff"}}]); returns ["foo.cat", {"bar":10, "foo":12}, {"bar":10, "foo":{"cat":"meow", "dog":"ruff"}}]
+ * @example sourcesArray("foo.cat", {"bar":10, "foo":12}, {"bar":10, "foo":{"cat":"meow", "dog":"ruff"}}); returns ["foo.cat", {"bar":10, "foo":12}, {"bar":10, "foo":{"cat":"meow", "dog":"ruff"}}]
+ * @param sources an object, an array of objects, an array of objects where some objects are themselves arrays
+ * @param varargs additional objects to be added to the returned array.
+ * @returns {Array}
+ */
+function sourcesArray(sources /*, varargs*/) {
+ var returnValue = [];
+ var arrayifiedSources = [];
+
+ // This will add in the individual searchSources whether they are in a single object or an array...
+ arrayifiedSources = arrayifiedSources.concat(sources);
+ // This will add in anything that "arguments" had as varargs...
+ if (arguments && arguments.length > 1) {
+ arrayifiedSources = arrayifiedSources.concat(Array.prototype.slice.call(arguments, 1));
+ }
+
+ // If any of the items in "sources" is already an array, this loop will expand it and add each element as an individual source.
+ // We only do this one level deep (i.e. we don't look to see if there are arrays within arrays in this list).
+ for (var ii = 0; ii < arrayifiedSources.length; ii++) {
+ var arrayItem = arrayifiedSources[ii];
+ returnValue = returnValue.concat(arrayItem);
+ }
+
+ return returnValue;
+}
+
+var key_value = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ valueForKeyPath: valueForKeyPath,
+ createObjectAtKeyPath: createObjectAtKeyPath,
+ sourcesArray: sourcesArray
+});
+
+/*
+ * src/metrics/utils/event_fields.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Takes one or more eventFields objects, cleans them (removes keys that are typeof 'function', keys with 'null' values, keys with 'undefined' values),
+ * merges them (later objects take precedence), and returns a single object with the union of all remaining fields.
+ * Passed in objects are treated as immutable and so will never be modified.
+ * @param eventFields an object with keys and values OR an array of objects with keys and values.
+ * If only one parameter is provided, the return value will be the non-null values of that single object.
+ * @param varargs additional objects to be merged in, similar to eventFields (i.e. dictionaries, arrays of dictionaries, or some combination of the two).
+ * Later objects take precedence over earlier ones.
+ * @return a new object with the union of all non-function, non-null and non-undefined fields.
+ * Passed in objects are treated as immutable and so will never be modified.
+ * @example mergeAndCleanEventFields({}) ===> {}
+ * @example mergeAndCleanEventFields(null) ===> {}
+ * @example mergeAndCleanEventFields({"foo":10}) ===> {"foo":10}
+ * @example mergeAndCleanEventFields({"foo":10, "bar":null}) ===> {"foo":10}
+ * @example mergeAndCleanEventFields({"foo":10, "bar":null}, {"cat":null}) ===> {"foo":10}
+ * @example mergeAndCleanEventFields({"foo":10, "bar":null}, {"cat":null, "mouse":"gray"}) ===> {"foo":10, "mouse":"gray"}
+ * @example mergeAndCleanEventFields({"foo":10, "bar":null}, {"cat":null, "mouse":"gray", "dog":"bark"}) ===> {"foo":10, "mouse":"gray", "dog":"bark"}
+ * @example mergeAndCleanEventFields({"foo":10, "bar":null}, {"cat":null, "mouse":"gray", "dog":"bark", "foo":11}) ===> {"foo":11, "mouse":"gray", "dog":"bark"}
+ * @example mergeAndCleanEventFields({"foo":10, "bar":null}, {"cat":null, "mouse":"gray", "dog":"bark", "foo":11}, {"foo":12}) ===> {"foo":12, "mouse":"gray", "dog":"bark"}
+ */
+function mergeAndCleanEventFields(eventFields /*, varargs*/) {
+ var argumentsArray = [false, false, false].concat(Array.prototype.slice.call(arguments));
+ // expand argumentsArray, in case it contains arrays)
+ var expandedArgumentsArray = [];
+
+ for (var ii = 0; ii < argumentsArray.length; ii++) {
+ var itemToPush = argumentsArray[ii];
+ // Either push each item in this item...
+ if (itemToPush && itemToPush.constructor === Array) {
+ for (var jj = 0; jj < itemToPush.length; jj++) {
+ expandedArgumentsArray.push(itemToPush[jj]);
+ }
+ // or push the item itself (if it is not an array)
+ } else {
+ expandedArgumentsArray.push(itemToPush);
+ }
+ }
+ return copyKeysAndValues.apply(null, expandedArgumentsArray);
+}
+
+/**
+ * This method is the workhorse of all the various eventHandlers.
+ * It will take all of the parameters of the callers "metricsData()" method, merge them together,
+ * invoke accessors on their known fields, and return the resultant map.
+ * @param eventHandler the calling eventHandler
+ * @param knownFields the calling eventHandler's list (array) of strings that are that handler's known field values.
+ * If the caller has accessors to be invoked, they must be present in the "knownFields" array
+ * @param {Boolean} includeAllKnownFields if false, only known field accessors that match caller provided field names will be invoked.
+ * @returns {Arguments} all of the arguments that the calling eventHandler received.
+ * @example:
+ * Page.prototype.metricsData = function(pageId, pageType, pageContext, eventFieldsMapN(varargs)) {
+ * var pageFields = { pageId: pageId, pageType: pageType, pageContext: pageContext };
+ * return utils.eventFields.processMetricsData(this, this.knownFields(), true, pageFields, eventFieldsMapN); });
+ */
+function processMetricsData(eventHandler, knownFields, includeAllKnownFields, callerSuppliedEventFieldsMapsArray) {
+ var callerProvidedFields = mergeAndCleanEventFields(callerSuppliedEventFieldsMapsArray);
+ // Initialize returnValue with the passed-in fields in case there are fields we haven't contemplated or don't have accessor methods for, they will still be included.
+ var returnValue = callerProvidedFields;
+
+ if (eventHandler && knownFields) {
+ var knownFieldValues = {};
+
+ if (!includeAllKnownFields) {
+ // only include known field names that were also included in caller provided maps
+ knownFields = knownFields.filter(function (fieldName) {
+ return fieldName in callerProvidedFields;
+ });
+ }
+ if (knownFields.length) {
+ for (var ii = 0; ii < knownFields.length; ii++) {
+ var knownFieldName = knownFields[ii];
+ var knownFieldAccessor = eventHandler[knownFieldName];
+
+ if (isFunction(knownFieldAccessor)) {
+ // NOTE: If the accessor method prefers to use a value from the passed-in callerProvidedFields, it must do that on its own.
+ knownFieldValues[knownFieldName] = knownFieldAccessor.call(eventHandler, callerProvidedFields);
+ }
+ }
+ }
+
+ returnValue = mergeAndCleanEventFields(returnValue, knownFieldValues);
+ }
+
+ return returnValue;
+}
+
+/**
+ * Returns an object containing the intersection of properties in
+ * data and matching string values in the fieldMap property corresponding to 'sectionName'
+ * ( e.g. fieldMap.custom[sectionName] is an object containing arrays of strings which
+ * correspond to the keys desired in the mappedFields object )
+ * @param {Object} data The model data corresponding to element we're mapping fields for
+ * @param {String} sectionName Specifies which section of the fieldMap to use (eg: 'impressions', 'location', or 'custom')
+ * @param {Object} fieldsMap contains one or more field mapping(s)
+ * @param {Function} (optional) onError callback to be invoked with an error message; e.g. console.error
+ * @return {Object} Contains intersection of data and fieldsMap values
+ * @example
+ * // where impressionFieldsMapSection = {
+ * // impressionType: ['type', 'impressionType'],
+ * // id: ['targetId', 'id']
+ * //};
+ * applyFieldsMap({type: 'button', id: '123', name: 'playbutton'}, 'impressions')
+ * // returns {impressionType: 'button', id: '123'}
+ */
+function applyFieldsMap(data, sectionName, fieldsMap, onError) {
+ var fieldsMapSection;
+ var mappedFields;
+ var errorMessage;
+
+ if (data && sectionName && fieldsMap) {
+ mappedFields = {};
+ fieldsMapSection = valueForKeyPath(sectionName, fieldsMap, fieldsMap.custom);
+ if (fieldsMapSection) {
+ var i;
+ var value;
+ if (isArray(fieldsMapSection)) {
+ for (i = 0; i < fieldsMapSection.length; ++i) {
+ value = data[fieldsMapSection[i]];
+ if (isDefinedNonNull(value)) {
+ mappedFields[fieldsMapSection[i]] = value;
+ }
+ }
+ } else if (isObject(fieldsMapSection)) {
+ for (var key in fieldsMapSection) {
+ for (i = 0; i < fieldsMapSection[key].length; ++i) {
+ value = valueForKeyPath(fieldsMapSection[key][i], data);
+ if (isDefinedNonNull(value)) {
+ mappedFields[key] = value;
+ break;
+ }
+ }
+ }
+ } else {
+ errorMessage =
+ 'metrics: incorrect data type provided to applyFieldsMap (only accepts objects and Arrays)';
+ }
+ } else {
+ errorMessage = 'metrics: unable to get ' + sectionName + ' section from fieldsMap';
+ }
+ } else {
+ var missingArgs = [];
+
+ if (!data) {
+ missingArgs.push('data');
+ }
+ if (!sectionName) {
+ missingArgs.push('sectionName');
+ }
+ if (!fieldsMap) {
+ missingArgs.push('fieldsMap');
+ }
+
+ errorMessage = 'metrics: missing argument(s): ' + missingArgs.join(',') + ' not provided to applyFieldsMap';
+ }
+
+ if (errorMessage && isFunction(onError)) {
+ onError(errorMessage);
+ }
+
+ return mappedFields;
+}
+
+var event_fields = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ mergeAndCleanEventFields: mergeAndCleanEventFields,
+ processMetricsData: processMetricsData,
+ applyFieldsMap: applyFieldsMap
+});
+
+/*
+ * src/metrics/utils/network.js
+ * mt-metricskit
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Makes an XHR GET request
+ * @param {String} url of the request
+ * @param {Function} onSuccessHandler function to execute on request success (response returned)
+ * @param {Function} onFailureHandler function to execute on request failure (error returned)
+ */
+function makeAjaxGetRequest(url, onSuccessHandler, onFailureHandler) {
+ makeAjaxRequest(url, 'GET', null, onSuccessHandler, onFailureHandler);
+}
+
+/**
+ * Creates, modifies, and sends an XMLHttpRequest object
+ * @param {String} url url of endpoint
+ * @param {String} method "GET", "POST", etc.
+ * @param {*} data data to send to endpoint if method is "POST", "PUT", etc.
+ * @param {Function} [onSuccess] optional function to execute on request success (takes response returned)
+ * @param {Function} [onFailure] optional function to execute on request failure (takes error returned and optional status code)
+ * @param {Object} [options] optional dictionary of options which define how to modify the XMLHttpRequest
+ * @param {boolean} [options.async] optional boolean which determines if the request should be asynchronous
+ * @param {Number} [options.timeout] optional which determines request timeout
+ * @param {Number} [options.withCredentials] optional which determines request withCredentials
+ */
+function makeAjaxRequest(url, method, data, onSuccess, onFailure, options) {
+ var request = new XMLHttpRequest();
+ data = data || undefined;
+ options = options || {};
+ onSuccess = isFunction(onSuccess) ? onSuccess : function () {};
+ onFailure = isFunction(onFailure) ? onFailure : function () {};
+ // Sets async to true by default
+ var async = options.async === false ? false : true;
+
+ // synchronous requests should not use the timeout property:
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout
+ if (options.timeout && async) {
+ request.timeout = options.timeout;
+ }
+
+ request.onload = function onload() {
+ // Successful response status is defined as 2xx by default
+ if (request.status >= 200 && request.status < 300) {
+ onSuccess(request.response);
+ } else {
+ // Pass in optional status code so status logic can be performed on failure
+ onFailure(
+ new Error('XHR error: server responded with status ' + request.status + ' ' + request.statusText),
+ request.status
+ );
+ }
+ };
+ request.onerror = function onError() {
+ onFailure(new Error('XHR error'));
+ };
+
+ request.open(method, url, async);
+
+ // Because of rdar://72864343, we allow callers to optionally specify "withCredentials".
+ // The default value is true, which allows CORS requests to have cookies set on response
+ // (e.g. we're itunes.apple.com, userxp is xp.apple.com).
+ request.withCredentials = typeof options.withCredentials === 'boolean' ? options.withCredentials : true;
+ request.setRequestHeader('Content-type', 'application/json');
+
+ request.send(data);
+}
+
+var network = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ makeAjaxGetRequest: makeAjaxGetRequest,
+ makeAjaxRequest: makeAjaxRequest
+});
+
+/*
+ * src/metrics/utils/sampling.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PRIVATE METHODS/IVARS ************************************
+ */
+var _sessions = {};
+
+/**
+ * Manually clears an active sampling session
+ * @param {String} sessionName
+ */
+var _clearSession = function _clearSession(sessionName) {
+ if (_sessions[sessionName]) {
+ clearTimeout(_sessions[sessionName]);
+ _sessions[sessionName] = null;
+ }
+};
+
+/**
+ ****************************** PSEUDO-PRIVATE METHODS/IVARS **********************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+function _utClearSessions() {
+ for (var sessionName in _sessions) {
+ _clearSession(sessionName);
+ }
+}
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * A random lottery that is successful with samplingPercentage frequency
+ * @param {Number} samplingPercentage (between 0 and 1)
+ * @return {Boolean} whether or not the lottery was successful
+ */
+function lottery(samplingPercentage) {
+ return Math.random() < samplingPercentage;
+}
+
+/**
+ * Determines whether a particular sampling session is active
+ * @param {String} sessionName the name associated with a particular session
+ * @param {Number} sessionSamplingPercentage (between 0 and 1)
+ * @param {Number} sessionDuration (in ms)
+ * @return {Boolean} whether or not the sampling session associated with sessionName is currently active
+ */
+function sessionSampled(sessionName, sessionSamplingPercentage, sessionDuration) {
+ var returnValue;
+
+ // if a timer is currently running, we are sampled in to this session
+ if (_sessions[sessionName]) {
+ returnValue = true;
+ } else {
+ // roll the dice
+ var sampleNow = lottery(sessionSamplingPercentage);
+
+ // check if we need to enable sampling for sessionDuration ms
+ if (sampleNow && sessionDuration > 0) {
+ _sessions[sessionName] = setTimeout(_clearSession.bind(null, sessionName), sessionDuration);
+ }
+
+ returnValue = sampleNow;
+ }
+
+ return returnValue;
+}
+
+/**
+ * Determines whether an eventType should be sampled.
+ * Session sampling (sessionSamplingPercentage and sessionDuration) will be checked first, and samplingPercentage will be used as a fallback.
+ * @param {Boolean} (optional) samplingForced whether to always sample in. Default false.
+ * @param {Number} (optional) sessionSamplingPercentage (between 0 and 1) the frequency at which to initiate sampling sessions for eventType. Default 0.
+ * @param {Number} (optional) sessionDuration the duration, in milliseconds, of sampling sessions for this eventType. Default 0.
+ * @param {Number} (optional) samplingPercentage (between 0 and 1) the frequency at which to sample in individual events. Default 0.
+ * @example sampling.isSampledIn('pageRender', null, 0.05, 60000);
+ * @return {Boolean} whether or not eventType is currently sampled in
+ */
+function isSampledIn(eventType, samplingForced, sessionSamplingPercentage, sessionDuration, samplingPercentage) {
+ return (
+ samplingForced ||
+ sessionSampled(eventType, sessionSamplingPercentage, sessionDuration) ||
+ lottery(samplingPercentage)
+ );
+}
+
+var sampling = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ _utClearSessions: _utClearSessions,
+ lottery: lottery,
+ sessionSampled: sessionSampled,
+ isSampledIn: isSampledIn
+});
+
+/*
+ * src/storage.js
+ * mt-metricskit-utils-private
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PRIVATE METHODS/IVARS ************************************
+ */
+
+var CONSTANTS = {
+ STORAGE_TYPE: {
+ LOCAL_STORAGE: 'localStorage',
+ SESSION_STORAGE: 'sessionStorage'
+ }
+};
+
+/**
+ * Cover function for sessionStorage/localStorage.
+ * Some clients do not have implementations of storage objects, and some are platform-specific (e.g. iTunes.sessionStorage)
+ * This helper method will shim storage object functionality as necessary
+ * @param {object} storageObject - this is either the platform-specific implementation of session/localStorage, or null if none exists
+ * @returns {object} a storage object, either the provided platform-specific one or a placeholder/shimmed version
+ */
+var _storageObject = function _storageObject(storageObject) {
+ var aStorageObject = null; // We'll capture this variable as a singleton in the closure below...
+ var errorShown = false;
+
+ return function () {
+ if (!storageObject) {
+ if (!errorShown) {
+ console.error(
+ 'storageObject: storage object not found. Override this function if there is a platform-specific implementation'
+ );
+ errorShown = true; // only show error the first time
+ }
+ // Let's not stop the whole app by "throw"ing here, and let's not make all callers be required to check for undefined return values.
+ // We'll just create a placeholder sessionStorage object which will not hold values across page JS contexts, but at least it will hold them for *some* time...
+ if (!aStorageObject) {
+ aStorageObject = {
+ storage: {},
+ getItem: function (key) {
+ return this.storage[key];
+ },
+
+ setItem: function (key, value) {
+ this.storage[key] = value;
+ },
+
+ removeItem: function (key) {
+ delete this.storage[key];
+ }
+ };
+ }
+ } else {
+ aStorageObject = storageObject;
+ }
+ return aStorageObject;
+ };
+};
+
+/**
+ * Fetches the given storage object from the global object. This function wraps around a try-catch to avoid any exception from being thrown
+ * in cases where the local storage API is disabled (say for example by disabling cookies in Safari preferences)
+ * @param {Object} storageObjectType the storage object type to be evaluated on the window object. Possible values are localStorage or sessionStorage and are defined in CONSTANTS.STORAGE_TYPE object above.
+ * @return {Object} the storage object or null if key doesn't exist in storage or the storage api is disabled
+ */
+function _defaultStorageObject(storageObjectType) {
+ var storageObject = null;
+ var storageObjectClass = null;
+ var isLocalStorageObjectType = storageObjectType === CONSTANTS.STORAGE_TYPE.LOCAL_STORAGE;
+ try {
+ storageObjectClass = isLocalStorageObjectType ? typeof localStorage : typeof sessionStorage;
+ if (storageObjectClass !== 'undefined') {
+ storageObject = isLocalStorageObjectType ? localStorage : sessionStorage;
+ } else {
+ storageObject = null;
+ }
+ } catch (e) {
+ // We allow the current process to run without interruption instead of bringing the app down
+ storageObject = null;
+ console.error('_utils.storage._defaultStorageObject: Unable to retrieve storage object: ' + e);
+ }
+ return storageObject;
+}
+
+/**
+ ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+function _utDefaultStorageObject(storageObjectType) {
+ return _defaultStorageObject(storageObjectType);
+}
+
+/**
+ **************************** PUBLIC METHODS/IVARS ****************************
+ */
+var localStorageObject = _storageObject(_defaultStorageObject(CONSTANTS.STORAGE_TYPE.LOCAL_STORAGE));
+var sessionStorageObject = _storageObject(_defaultStorageObject(CONSTANTS.STORAGE_TYPE.SESSION_STORAGE));
+
+/**
+ * Stringifies an object and saves it to storage
+ * @param {Object} storageObject an object that adheres to the Web Storage API
+ * @param {String} key
+ * @param {Object} (optional) objectToSave the object to stringify and save; if null, key will be removed from storageObject
+ * @return {Object} the object that was saved to storage or null if nothing was saved (if removing an item, returns undefined)
+ */
+function saveObjectToStorage(storageObject, key, objectToSave) {
+ var result = null;
+
+ if (objectToSave) {
+ // setItem may throw errors if storage is full, or stringify could error
+ try {
+ storageObject.setItem(key, JSON.stringify(objectToSave));
+ result = objectToSave;
+ } catch (e) {}
+ } else {
+ result = storageObject.removeItem(key);
+ }
+
+ return result;
+}
+
+/**
+ * Fetches an object stored as a serialized JSON string and unpacks it
+ * @param {Object} storageObject an object that adheres to the Web Storage API
+ * @param {String} key
+ * @return {Object} the object from storage or null if key doesn't exist in storage (returns undefined if stored value failed to parse)
+ */
+function objectFromStorage(storageObject, key) {
+ var result = null;
+ var serializedObject = storageObject.getItem(key);
+
+ if (serializedObject) {
+ try {
+ result = JSON.parse(serializedObject);
+ } catch (e) {
+ result = undefined;
+ }
+ }
+
+ return result;
+}
+
+var storage = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ _utDefaultStorageObject: _utDefaultStorageObject,
+ localStorageObject: localStorageObject,
+ sessionStorageObject: sessionStorageObject,
+ saveObjectToStorage: saveObjectToStorage,
+ objectFromStorage: objectFromStorage
+});
+
+export { backoff, config, cookies, delegates_info as delegatesInfo, event_fields as eventFields, key_value as keyValue, network, number, reflect, sampling, storage, string };