From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001
From: rxliuli
Date: Tue, 4 Nov 2025 05:03:50 +0800
Subject: init commit
---
.../dist/ae-client-kit-core.esm.js | 901 +++++
.../mt-client-config/dist/mt-client-config.esm.js | 987 +++++
.../dist/mt-client-constraints.esm.js | 3103 +++++++++++++++
.../dist/mt-client-logger-core.esm.js | 533 +++
.../mt-event-queue/dist/mt-event-queue.esm.js | 1364 +++++++
.../dist/mt-metricskit-delegates-core.esm.js | 289 ++
.../dist/mt-metricskit-delegates-web.esm.js | 728 ++++
.../mt-metricskit-processor-clickstream.esm.js | 4045 ++++++++++++++++++++
.../dist/mt-metricskit-utils-private.esm.js | 2428 ++++++++++++
.../@jet/engine/lib/actions/action-dispatcher.js | 64 +
.../node_modules/@jet/engine/lib/actions/index.js | 13 +
.../@jet/engine/lib/dependencies/index.js | 17 +
.../@jet/engine/lib/dependencies/jet-bag.js | 40 +
.../@jet/engine/lib/dependencies/jet-host.js | 19 +
.../engine/lib/dependencies/jet-network-fetch.js | 39 +
.../lib/dependencies/localized-strings-bundle.js | 68 +
.../dependencies/localized-strings-json-object.js | 21 +
.../node_modules/@jet/engine/lib/index.js | 15 +
.../@jet/engine/lib/metrics/aggregating/index.js | 16 +
.../aggregating/metrics-fields-aggregator.js | 45 +
.../metrics/aggregating/metrics-fields-builder.js | 15 +
.../metrics/aggregating/metrics-fields-context.js | 2 +
.../metrics/aggregating/metrics-fields-provider.js | 2 +
.../engine/lib/metrics/field-providers/index.js | 13 +
.../page-metrics-fields-provider.js | 19 +
.../node_modules/@jet/engine/lib/metrics/index.js | 18 +
.../@jet/engine/lib/metrics/linting/index.js | 13 +
.../lib/metrics/linting/metrics-event-linter.js | 2 +
.../@jet/engine/lib/metrics/metrics-pipeline.js | 35 +
.../@jet/engine/lib/metrics/presenters/index.js | 13 +
.../metrics/presenters/page-metrics-presenter.js | 51 +
.../@jet/engine/lib/metrics/recording/index.js | 14 +
.../metrics/recording/logging-event-recorder.js | 13 +
.../metrics/recording/metrics-event-recorder.js | 2 +
.../engine/node_modules/@jet/environment/index.js | 19 +
.../@jet/environment/json/validation.js | 250 ++
.../environment/models/actions/alert-action.js | 3 +
.../environment/models/actions/compound-action.js | 3 +
.../environment/models/actions/empty-action.js | 3 +
.../models/actions/external-url-action.js | 3 +
.../@jet/environment/models/actions/flow-action.js | 3 +
.../environment/models/actions/flow-back-action.js | 3 +
.../@jet/environment/models/actions/http-action.js | 3 +
.../models/actions/http-template-action.js | 3 +
.../@jet/environment/models/actions/index.js | 22 +
.../environment/models/actions/toast-action.js | 3 +
.../@jet/environment/models/artwork.js | 39 +
.../node_modules/@jet/environment/models/button.js | 3 +
.../node_modules/@jet/environment/models/color.js | 131 +
.../node_modules/@jet/environment/models/index.js | 21 +
.../node_modules/@jet/environment/models/menu.js | 8 +
.../@jet/environment/models/paragraph.js | 4 +
.../@jet/environment/models/programmed-text.js | 5 +
.../node_modules/@jet/environment/models/video.js | 3 +
.../@jet/environment/types/globals/bag.js | 3 +
.../@jet/environment/types/globals/bundle.js | 3 +
.../environment/types/globals/cookie-provider.js | 3 +
.../@jet/environment/types/globals/cryptography.js | 3 +
.../@jet/environment/types/globals/host.js | 3 +
.../@jet/environment/types/globals/index.js | 51 +
.../@jet/environment/types/globals/jscookie.js | 3 +
.../@jet/environment/types/globals/net.js | 3 +
.../@jet/environment/types/globals/platform.js | 3 +
.../@jet/environment/types/globals/plist.js | 3 +
.../@jet/environment/types/globals/preprocessor.js | 3 +
.../@jet/environment/types/globals/random.js | 3 +
.../@jet/environment/types/globals/service.js | 3 +
.../@jet/environment/types/globals/types.js | 16 +
.../environment/types/javascriptcore/console.js | 14 +
.../@jet/environment/types/javascriptcore/index.js | 14 +
.../node_modules/@jet/environment/types/metrics.js | 57 +
.../node_modules/@jet/environment/types/models.js | 3 +
.../@jet/environment/types/optional.js | 71 +
.../node_modules/@jet/environment/util/metatype.js | 10 +
.../node_modules/@jet/environment/util/urls.js | 370 ++
shared/metrics-8/src/constants.ts | 19 +
shared/metrics-8/src/impression-provider.ts | 27 +
.../metrics-8/src/impression-snapshot-provider.ts | 27 +
shared/metrics-8/src/impressions/constants.ts | 1 +
shared/metrics-8/src/impressions/index.ts | 252 ++
shared/metrics-8/src/index.ts | 578 +++
shared/metrics-8/src/recorder/composite.ts | 20 +
shared/metrics-8/src/recorder/funnelkit.ts | 237 ++
shared/metrics-8/src/recorder/logging.ts | 21 +
shared/metrics-8/src/recorder/metricskit.ts | 239 ++
shared/metrics-8/src/recorder/void.ts | 17 +
.../metrics-8/src/utils/get-event-field-topic.ts | 11 +
.../src/utils/metrics-dev-console/constants.ts | 7 +
.../utils/metrics-dev-console/setup-metrics-dev.ts | 55 +
89 files changed, 17632 insertions(+)
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/ae-client-kit-core/dist/ae-client-kit-core.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-client-config/dist/mt-client-config.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-client-constraints/dist/mt-client-constraints.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-client-logger-core/dist/mt-client-logger-core.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-event-queue/dist/mt-event-queue.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-core/dist/mt-metricskit-delegates-core.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-web/dist/mt-metricskit-delegates-web.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-processor-clickstream/dist/mt-metricskit-processor-clickstream.esm.js
create mode 100644 shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-utils-private/dist/mt-metricskit-utils-private.esm.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/actions/action-dispatcher.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/actions/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/dependencies/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-bag.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-host.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-network-fetch.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-bundle.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-json-object.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-aggregator.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-builder.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-context.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-provider.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/page-metrics-fields-provider.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/metrics-event-linter.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/metrics-pipeline.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/page-metrics-presenter.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/logging-event-recorder.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/metrics-event-recorder.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/json/validation.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/alert-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/compound-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/empty-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/external-url-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-back-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-template-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/toast-action.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/artwork.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/button.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/color.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/menu.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/paragraph.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/programmed-text.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/video.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bag.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bundle.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cookie-provider.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cryptography.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/host.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/jscookie.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/net.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/platform.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/plist.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/preprocessor.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/random.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/service.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/types.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/console.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/index.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/metrics.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/models.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/optional.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/metatype.js
create mode 100644 shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/urls.js
create mode 100644 shared/metrics-8/src/constants.ts
create mode 100644 shared/metrics-8/src/impression-provider.ts
create mode 100644 shared/metrics-8/src/impression-snapshot-provider.ts
create mode 100644 shared/metrics-8/src/impressions/constants.ts
create mode 100644 shared/metrics-8/src/impressions/index.ts
create mode 100644 shared/metrics-8/src/index.ts
create mode 100644 shared/metrics-8/src/recorder/composite.ts
create mode 100644 shared/metrics-8/src/recorder/funnelkit.ts
create mode 100644 shared/metrics-8/src/recorder/logging.ts
create mode 100644 shared/metrics-8/src/recorder/metricskit.ts
create mode 100644 shared/metrics-8/src/recorder/void.ts
create mode 100644 shared/metrics-8/src/utils/get-event-field-topic.ts
create mode 100644 shared/metrics-8/src/utils/metrics-dev-console/constants.ts
create mode 100644 shared/metrics-8/src/utils/metrics-dev-console/setup-metrics-dev.ts
(limited to 'shared/metrics-8')
diff --git a/shared/metrics-8/node_modules/@amp-metrics/ae-client-kit-core/dist/ae-client-kit-core.esm.js b/shared/metrics-8/node_modules/@amp-metrics/ae-client-kit-core/dist/ae-client-kit-core.esm.js
new file mode 100644
index 0000000..c5828f0
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/ae-client-kit-core/dist/ae-client-kit-core.esm.js
@@ -0,0 +1,901 @@
+import { reflect, string, storage } from '@amp-metrics/mt-metricskit-utils-private';
+
+/*
+ * src/helpers/constants.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2019 Apple Inc. All rights reserved.
+ *
+ */
+
+var _allBaseFieldNames;
+var _environmentBaseFieldNames;
+
+function requiredEnvironmentBaseFieldNames() {
+ return [
+ 'app',
+ 'appVersion',
+ 'hardwareFamily',
+ 'hardwareModel',
+ 'os',
+ 'osBuildNumber',
+ 'osLanguages',
+ 'osVersion',
+ 'resourceRevNum',
+ 'screenHeight',
+ 'screenWidth',
+ 'userAgent'
+ ];
+}
+
+function optionalEnvironmentBaseFieldNames() {
+ return ['delegateApp', 'hardwareBrand', 'storeFrontCountryCode', 'storeFrontHeader', 'storeFrontLanguage'];
+}
+
+function otherBaseFieldNames() {
+ return [
+ 'baseVersion',
+ 'clientEventId',
+ 'connection',
+ 'eventTime',
+ 'eventType',
+ 'eventVersion',
+ 'timezoneOffset',
+ 'xpPostFrequency',
+ 'xpSendMethod'
+ ];
+}
+
+function environmentBaseFieldNames() {
+ if (!_environmentBaseFieldNames) {
+ _environmentBaseFieldNames = requiredEnvironmentBaseFieldNames().concat(optionalEnvironmentBaseFieldNames());
+ }
+
+ return _environmentBaseFieldNames;
+}
+
+function allBaseFieldNames() {
+ if (!_allBaseFieldNames) {
+ _allBaseFieldNames = environmentBaseFieldNames().concat(otherBaseFieldNames());
+ }
+
+ return _allBaseFieldNames;
+}
+
+/*
+ * src/event_handlers/Base.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var attachDelegate = reflect.attachDelegate;
+var cryptoRandomBase62String = string.cryptoRandomBase62String;
+var exceptionString = string.exceptionString;
+
+var _prototypeInitialized;
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the "base" fields common to all metrics events.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * Kits can also extend this class and additional methods specific to their event model.
+ * @example
+ * // extend kit-core Base class
+ * var MetricsKitBase = function MetricsKitBase() {
+ * kitCore.eventHandlers.Base.apply(this, arguments); // invoke Base constructor
+ * };
+ * MetricsKitBase.prototype = new kitCore.eventHandlers.Base();
+ * MetricsKitBase.prototype.constructor = MetricsKitBase;
+ *
+ * // set Kit-specific methods
+ * MetricsKitBase.prototype.environment = function() { return metricsKit.system.environment; }
+ * MetricsKitBase.prototype.eventRecorder = function() { return metricsKit.system.eventRecorder; }
+ * MetricsKitBase.prototype.metricsData = function(pageId, pageType, pageContext, callerSuppliedFieldsMapsN) { ... }
+ * MetricsKitBase.prototype.processMetricsData = function( ... ) { ... }
+ *
+ * // extend kit-core known fields
+ * MetricsKitBase.prototype.knownFields = function knownFields() {
+ * var parentKnownFields = Object.getPrototypeOf(MetricsKitBase.prototype).knownFields();
+ * return parentKnownFields.concat(['dsId', 'anotherUserExperienceOnlyField']);
+ * };
+ *
+ * // add Kit-specific accessor functions
+ * MetricsKitBase.prototype.dsId = function dsId() { return this.environment().dsId(); };
+ * MetricsKitBase.prototype.anotherUserExperienceOnlyField = function() { ... };
+ *
+ * // create a Kit-specific class instance
+ * metricsKit.eventHandlers.base = new MetricsKitBase(metricsKit);
+ * @param {MetricsKit/PerfKit/VPAFKit} kit
+ * @delegatable
+ * @constructor
+ */
+function Base(processor) {
+ if (!reflect.isDefinedNonNull(processor)) {
+ throw new Error('A processor instance is required for creating BaseEventHandler.');
+ }
+ // @private
+ this._processor = processor;
+
+ if (!_prototypeInitialized) {
+ _prototypeInitialized = true;
+ environmentBaseFieldNames().forEach(function (fieldName) {
+ Base.prototype[fieldName] = function (callerSuppliedEventFields) {
+ var returnValue;
+
+ if (callerSuppliedEventFields && callerSuppliedEventFields.hasOwnProperty(fieldName)) {
+ returnValue = callerSuppliedEventFields[fieldName];
+ } else {
+ returnValue = this.environment()[fieldName]();
+ }
+
+ return returnValue;
+ };
+ });
+ }
+}
+
+Base._className = 'eventHandlers.base';
+
+/**
+ * Allows replacement of one or more of this class instance's 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 (these methods will not be copied to the target object).
+ * It can even act as a delegate for multiple MetricsKit objects, though that is not recommended.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * This allows callers to replace some number of methods that need custom implementations.
+ * If, for example, a client wants to use the standard logger implementation with the exception of, say, the "debug" method, they can
+ * call "setDelegate()" with their own delegate containing only a single method of "debug" as the delegate, which would leave all the other methods intact.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @example:
+ * To override one or more methods, in place:
+ * base.setDelegate({ app: function() { return 'myApp'; });
+ * To override one or more methods with a separate object:
+ * base.setDelegate(customBaseDelegate);
+ * (where "customBaseDelegate" might be defined elsewhere as, e.g.:
+ * var customBaseDelegate = { app: function() { return Device.appIdentifier; };
+ * appVersion: function() { return Device.appVersion; } };
+ * To override one or more methods with an instantiated object from a class definition:
+ * base.setDelegate(new CustomBaseDelegate());
+ * (where "CustomBaseDelegate" might be defined elsewhere as, e.g.:
+ * function CustomBaseDelegate() {}
+ * CustomBaseDelegate.prototype.app = function app() { return Device.appIdentifier; };
+ * CustomBaseDelegate.prototype.appVersion = function appVersion() { return Device.appVersion; };
+ * To override one or more methods with a class object (with "static" methods):
+ * base.setDelegate(CustomBaseDelegate);
+ * (where "CustomBaseDelegate" might be defined elsewhere as, e.g.:
+ * function CustomBaseDelegate() {}
+ * CustomBaseDelegate.app = function app() { return Device.appIdentifier; };
+ * CustomBaseDelegate.appVersion = function appVersion() { return Device.appVersion; };
+ * @param {Object} delegate 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.
+ */
+Base.prototype.setDelegate = function setDelegate(delegate) {
+ return attachDelegate(this, delegate);
+};
+
+/**
+ * The active environment class
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @see src/system/Environment
+ * @return {Environment}
+ * @overridable
+ */
+Base.prototype.environment = function environment() {
+ // Don't wrap the throw in a helper function or the backtrace won't be as nice.
+ throw exceptionString(Base._className, 'environment');
+};
+
+/**
+ * The active eventRecorder
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @return {Object} an event recorder that implements a sendMethod() function
+ * @overridable
+ */
+Base.prototype.eventRecorder = function eventRecorder() {
+ // Don't wrap the throw in a helper function or the backtrace won't be as nice.
+ throw exceptionString(Base._className, 'eventRecorder');
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the "base" fields required by AMP Analytics
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @returns key/value pairs of all "base" fields required by AMP Analytics.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ *
+ * TODO: consider adding default implementation for shared Kit use
+ */
+Base.prototype.metricsData = function metricsData() {
+ // Don't wrap the throw in a helper function or the backtrace won't be as nice.
+ throw exceptionString(Base._className, 'metricsData');
+};
+
+/**
+ * All of the various eventHandlers invoke this method to generate their metrics data
+ * The data is a simple map object (dictionary) with all the fields required by AMP Analytics for that event
+ * Some fields can be derived by this class itself.
+ * This function typically expects to be called with the correct context
+ * (e.g. base.processMetricsData.apply(this, arguments))
+ * @returns {Object} key/value pairs of all fields required by AMP Analytics.
+ * WARNING: May return "null" if metrics and/or the specific eventType for this handler is disabled, or on error.
+ *
+ * TODO: consider adding default implementation for shared Kit use
+ */
+Base.prototype.processMetricsData = function processMetricsData() {
+ // Don't wrap the throw in a helper function or the backtrace won't be as nice.
+ throw exceptionString(Base._className, 'processMetricsData');
+};
+
+/**
+ * @return {Array} all the fields that base eventHandlers know about
+ */
+Base.prototype.knownFields = function knownFields() {
+ return allBaseFieldNames();
+};
+
+/**
+ * ********************* ACCESSOR FUNCTIONS *********************
+ * We create accessor functions for every data field because:
+ * 1. Cleans/simplifies all methods that use it.
+ * 2. Facilitates writing test case shims
+ * 3. Allows specific feature suppliers to be overridden (via setDelegate()))
+ */
+
+/**
+ * The app identifier of the binary app
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} The app identifier of the binary app
+ * @example "com.apple.appstore" or "com.apple.gamecenter"
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.app = function app(callerSuppliedEventFields) { };
+
+/**
+ * The version number of this application
+ * @example "1.0", "5.43", etc.
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the version number of this application
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.appVersion = function appVersion(callerSuppliedEventFields) { };
+
+/**
+ * The version of the set of base data to be sent up
+ * @returns {number} the version of the set of base data to be sent up
+ * @overridable
+ */
+Base.prototype.baseVersion = function baseVersion() {
+ return 1;
+};
+
+/**
+ * A unique identifier for each event
+ * @return {String}
+ * @overridable
+ */
+Base.prototype.clientEventId = function clientEventId(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.clientEventId) || cryptoRandomBase62String(true);
+};
+
+/**
+ * Type of internet connection.
+ * Only applicable to devices
+ * Beware that users on WiFi may actually be receiving 3G speeds (i.e. if device is tethered to a portable hotspot.)
+ * @example "WiFi, "3G, etc.
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} type of internet connection
+ * @overridable
+ */
+Base.prototype.connection = function connection(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.connection) || this.environment().connectionType();
+};
+
+/**
+ * The identifier of the process generating the event, if different from “app”, or blank otherwise.
+ * @example 'web-experience-app'
+ * @returns {String}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.delegateApp = function delegateApp(callerSuppliedEventFields) { };
+
+/**
+ * The id of this user ("directory service id").
+ * This id will get mapped to a consumerId on the server.
+ * @example 659261189
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String}
+ * @overridable
+ */
+Base.prototype.dsId = function dsId(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.dsId) || this.environment().dsId();
+};
+
+/**
+ * The time (UTC) in milliseconds at which this event happened.
+ * This field is central to determining the sequence of user events
+ * Use online epoch converter to test your values.
+ * @example 1437356433388 (http://www.epochconverter.com converts that to: July 19, 2015 at 6:40:33 PM PDT GMT-7:00 DST)
+ * @param {Map} callerSuppliedEventFields
+ * @returns {number} the time (UTC) in milliseconds at which this event happened
+ * @overridable
+ */
+Base.prototype.eventTime = function eventTime(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventTime) || Date.now();
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @return {Number}
+ * @overridable
+ */
+Base.prototype.eventVersion = function eventVersion(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || null;
+};
+
+/**
+ * The hardware brand of the device. Not required for Apple devices.
+ * @example "Samsung", "LG", "Google"
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.hardwareBrand = function hardwareBrand(callerSuppliedEventFields) { };
+
+/**
+ * The hardware family of the device
+ * @example "iPhone", "Macbook Pro"
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.hardwareFamily = function hardwareFamily(callerSuppliedEventFields) { };
+
+/**
+ * The model of the device
+ * @example "iPhone10,2", "MacbookPro11,5"
+ * @returns {String}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.hardwareModel = function hardwareModel(callerSuppliedEventFields) { };
+
+/**
+ * The name of the OS
+ * @example "ios", "macos", "windows"
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.os = function os(callerSuppliedEventFields) { };
+
+/**
+ * The build number of the OS
+ * @example "15D60", "17E192"
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.osBuildNumber = function osBuildNumber(callerSuppliedEventFields) { };
+
+/**
+ * A string array of language IDs, ordered in descending preference
+ * @example ["en-US", "fr-CA"]
+ * @param {Map} callerSuppliedEventFields
+ * @returns {Array}
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.osLanguages = function osLanguages(callerSuppliedEventFields) { };
+
+/**
+ * The full OS version number
+ * In ITML, the value can be retrieved via Device.systemVersion
+ * @example "8.2.1" (iOS) "10.10.3" (Desktop)
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the full OS version number
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.osVersion = function osVersion(callerSuppliedEventFields) { };
+
+/**
+ * The HTML resources revision number
+ * @example 2C97 or 8.4.0.0.103
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the HTML resources revision number
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.resourceRevNum = function resourceRevNum(callerSuppliedEventFields) { };
+
+/**
+ * The client screen height in pixels
+ * @example 568
+ * @param {Map} callerSuppliedEventFields
+ * @returns {number} the client screen height in pixels
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.screenHeight = function screenHeight(callerSuppliedEventFields) { };
+
+/**
+ * The client screen width in pixels
+ * @example 320
+ * @param {Map} callerSuppliedEventFields
+ * @returns {number} the client screen width in pixels
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.screenWidth = function screenWidth(callerSuppliedEventFields) { };
+
+/**
+ * ISO 3166 Country Code. Apps that cannot provide a storeFrontHeader should provide a storeFrontCountryCode instead
+ * @example US
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the store front country code
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.storeFrontCountryCode = function storeFrontCountryCode(callerSuppliedEventFields) { };
+
+/**
+ * The value contained in the X-Apple-Store-Front header value at the time the event is being created.
+ * @example K143441-1,29 ab:rSwnYxS0
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the value contained in the X-Apple-Store-Front header value at the time the event is being created
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.storeFrontHeader = function storeFrontHeader(callerSuppliedEventFields) { };
+
+/**
+ * The difference, in minutes, between GMT (UTC) and timezone of event origin ("local time).
+ * This means that the offset is positive if the local timezone is behind UTC and negative if it is ahead.
+ * Daylight saving time prevents this value from being a constant, even for a given locale
+ * @example 420 (PST, not -420) or -600 (Australian Eastern Standard Time, UTC+10)
+ * @param {Map} callerSuppliedEventFields
+ * @returns {number} the difference, in minutes, between GMT (UTC) and timezone of event origin ("local time).
+ * @overridable
+ */
+Base.prototype.timezoneOffset = function timezoneOffset(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.timezoneOffset) || new Date().getTimezoneOffset();
+};
+
+/**
+ * The client’s user agent string. If the "app field is not provided, "userAgent may be used to derive the value of the "app field
+ * @example AppStore/2.0 iOS/8.3 model/iPhone7,2 build/12F70 (6; dt:106)
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the client’s user agent string. If the "app field is not provided, "userAgent may be used to derive the value of the "app field
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Base.prototype.userAgent = function userAgent(callerSuppliedEventFields) { };
+
+/**
+ * How often, in milliseconds, batches of events should get sent to the server.
+ * This field should be based on the client's most recent config value of "postFrequency".
+ * This is valuable for problem analysis because it indicates if and how clients are honoring the "postFrequency" value
+ * they were supplied with.
+ * This cannot be a "passthrough" field, because it can change (via new config) during program execution, so the value
+ * in effect at event creation time is what is needed.
+ * @example 60000
+ * @param {Map} callerSuppliedEventFields
+ * @returns {number} how often, in milliseconds, batches of events should get sent to the server
+ * @overridable
+ */
+Base.prototype.xpPostFrequency = function xpPostFrequency(callerSuppliedEventFields) {
+ return (
+ (callerSuppliedEventFields && callerSuppliedEventFields.xpPostFrequency) ||
+ this._processor.config.value('postFrequency')
+ );
+};
+
+/**
+ * The methodology being used by the eventRecorder to send batches of events to the server
+ * This field should be hardcoded in the client based on what method it is using to encode and send its events to Figaro.
+ * The three typical values are:
+ * "itms" - use this value when/if JavaScript code enqueues events for sending via the "itms.recordEvent()" method in ITML.
+ * "itunes" - use this value when/if JavaScript code enqueues events by calling the "iTunes.recordEvent()" method in Desktop Store apps.
+ * "javascript" - use this value when/if JavaScript code enqueues events for sending via the JavaScript eventQueue management. This is typically only used by older clients which don't have the built-in functionality of itms or iTunes available to them.
+ * @example "itms", "itunes", "javascript"
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the methodology being used by the eventRecorder to send batches of events to the server
+ * @overridable
+ */
+Base.prototype.xpSendMethod = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.xpSendMethod) || this.eventRecorder().sendMethod();
+};
+
+/*
+ * src/event_handlers/index.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var eventHandlers = {
+ Base: Base
+};
+
+/*
+ * src/system/Environment.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var attachDelegate$1 = reflect.attachDelegate;
+var exceptionString$1 = string.exceptionString;
+var localStorageObject = storage.localStorageObject;
+var sessionStorageObject = storage.sessionStorageObject;
+
+var _prototypeInitialized$1;
+
+/**
+ * Provides a set of environment-specific (platform-specific) functions which can be individually overridden for the needs
+ * of the particular environment, or replaced en masse by providing a single replacement environment delegate object.
+ * Kits can extend this class and additional methods specific to their event model.
+ * @example
+ * // MetricsKit code
+ *
+ * // extend kit-core Environment class
+ * var MetricsKitEnvironment = function MetricsKitEnvironment() {
+ * kitCore.system.Environment.apply(this, arguments); // invoke Environment constructor
+ * };
+ * MetricsKitEnvironment.prototype = new kitCore.system.Environment();
+ * MetricsKitEnvironment.prototype.constructor = MetricsKitEnvironment;
+ *
+ * // add Kit-specific functions
+ * MetricsKitEnvironment.prototype.dsId = function dsId() {
+ * throw this._exceptionString(_utils.reflect.functionName());
+ * };
+ * MetricsKitEnvironment.prototype.anotherUserExperienceOnlyField = function() { ... };
+ *
+ * // create a Kit-specific class instance
+ * metricsKit.system.environment = new MetricsKitEnvironment();
+ *
+ * // client app code remains unchanged:
+ * var customEnvironmentDelegate = {
+ * app: function() { return 'myAppName'; },
+ * appVersion: function() { ... } // etc.
+ * };
+ *
+ * metricsKit.system.environment.setDelegate(customEnvironmentDelegate);
+ *
+ * @constructor
+ */
+function Environment() {
+ // consider moving this code into a separate init method
+ if (!_prototypeInitialized$1) {
+ _prototypeInitialized$1 = true;
+ requiredEnvironmentBaseFieldNames().forEach(function (fieldName) {
+ Environment.prototype[fieldName] = function () {
+ throw exceptionString$1(Environment._className, fieldName);
+ };
+ });
+
+ optionalEnvironmentBaseFieldNames().forEach(function (fieldName) {
+ Environment.prototype[fieldName] = function () {};
+ });
+ }
+}
+
+Environment._className = 'system.environment';
+
+/**
+ * Allows replacement of one or more of this class instance's 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 (these methods will not be copied to the target object).
+ * It can even act as a delegate for multiple MetricsKit objects, though that is not recommended.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * This allows callers to replace some number of methods that need custom implementations.
+ * If, for example, a client wants to use the standard logger implementation with the exception of, say, the "debug" method, they can
+ * call "setDelegate()" with their own delegate containing only a single method of "debug" as the delegate, which would leave all the other methods intact.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @example:
+ * To override one or more methods, in place:
+ * environment.setDelegate({ app: function() { return 'myApp'; });
+ * To override one or more methods with a separate object:
+ * environment.setDelegate(customEnvironmentDelegate);
+ * (where "customEnvironmentDelegate" might be defined elsewhere as, e.g.:
+ * var customEnvironmentDelegate = { app: function() { return Device.appIdentifier; },
+ * appVersion: function() { return Device.appVersion; } };
+ * To override one or more methods with an instantiated object from a class definition:
+ * environment.setDelegate(new CustomEnvironmentDelegate());
+ * (where "CustomEnvironmentDelegate" might be defined elsewhere as, e.g.:
+ * function CustomEnvironmentDelegate() {}
+ * CustomEnvironmentDelegate.prototype.app = function app() { return Device.appIdentifier; };
+ * CustomEnvironmentDelegate.prototype.appVersion = function appVersion() { return Device.appVersion; };
+ * To override one or more methods with a class object (with "static" methods):
+ * environment.setDelegate(CustomEnvironmentDelegate);
+ * (where "CustomEnvironmentDelegate" might be defined elsewhere as, e.g.:
+ * function CustomEnvironmentDelegate() {}
+ * CustomEnvironmentDelegate.app = function app() { return Device.appIdentifier; };
+ * CustomEnvironmentDelegate.appVersion = function appVersion() { return Device.appVersion; };
+ * @param {Object} delegate 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.
+ */
+Environment.prototype.setDelegate = function setDelegate(delegate) {
+ return attachDelegate$1(this, delegate);
+};
+
+/**
+ * The app identifier of the binary app
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "com.apple.appstore" or "com.apple.gamecenter"
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.app = function app() { };
+
+/**
+ * The version number of this application
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "1.0", "5.43", etc.
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.appVersion = function appVersion() { };
+
+/**
+ * Type of internet connection.
+ * Only applicable to devices
+ * Beware that users on WiFi may actually be receiving 3G speeds (i.e. if device is tethered to a portable hotspot.)
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "WiFi, "3G, etc.
+ * @returns {String}
+ */
+Environment.prototype.connectionType = function connectionType() {
+ // Don't wrap the throw in a helper function or the backtrace won't be as nice.
+ // TODO: use a constant instead of a hardcoded string
+ throw exceptionString$1(Environment._className, 'connectionType');
+};
+
+/**
+ * The identifier of the process generating the event, if different from “app”, or blank otherwise.
+ * NO DEFAULT IMPLEMENTATION... HOWEVER: these fields are not required, so an exception will not be thrown if they
+ * are omitted.
+ * @example 'web-experience-app'
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.delegateApp = function delegateApp(callerSuppliedEventFields) { };
+
+/**
+ * The id of this user ("directory service id").
+ * This id will get mapped to a consumerId on the server.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example 659261189
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+Environment.prototype.dsId = function dsId() {
+ // Don't throw an exception to avoid breaking minor version backwards compatibility.
+ // Consider adding an exception in the next major version.
+};
+
+/**
+ * The hardware brand of the device. Not required for Apple devices.
+ * NO DEFAULT IMPLEMENTATION... HOWEVER: these fields are not required, so an exception will not be thrown if they
+ * are omitted.
+ * @example "Samsung", "LG", "Google"
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.hardwareBrand = function hardwareBrand() { };
+
+/**
+ * The hardware family of the device
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "iPhone", "Macbook Pro"
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.hardwareFamily = function hardwareFamily() { };
+
+/**
+ * The model of the device
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "iPhone10,2", "MacbookPro11,5"
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.hardwareModel = function hardwareModel() { };
+
+/**
+ * The name of the OS
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "ios", "macos", "windows"
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.os = function os() { };
+
+/**
+ * The build number of the OS
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "15D60", "17E192"
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.osBuildNumber = function osBuildNumber() { };
+
+/**
+ * A string array of language IDs, ordered in descending preference
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example ["en-US", "fr-CA"]
+ * @returns {Array}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.osLanguages = function osLanguages() { };
+
+/**
+ * The full OS version number
+ * In ITML, the value can be retrieved via Device.systemVersion
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "8.2.1" (iOS) "10.10.3" (Desktop)
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.osVersion = function osVersion() { };
+
+/**
+ * HTML resources revision number
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example 2C97 or 8.4.0.0.103
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.resourceRevNum = function resourceRevNum() { };
+
+/**
+ * Client screen height in pixels
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example 568
+ * @returns {number}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.screenHeight = function screenHeight() { };
+
+/**
+ * Client screen width in pixels
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example 320
+ * @returns {number}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.screenWidth = function screenWidth() { };
+
+/**
+ * ISO 3166 Country Code. Apps that cannot provide a storeFrontHeader should provide a storeFrontCountryCode instead
+ * NO DEFAULT IMPLEMENTATION... Either this method or storeFrontHeader must be replaced.
+ * are omitted.
+ * @example US
+ * @param {Map} callerSuppliedEventFields
+ * @returns {String} the store front country code
+ * @overridable
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.storeFrontCountryCode = function storeFrontCountryCode(callerSuppliedEventFields) { };
+
+/**
+ * The value contained in the X-Apple-Store-Front header value at the time the event is being created.
+ * NO DEFAULT IMPLEMENTATION... Either this method or storeFrontHeader must be replaced.
+ * @example K143441-1,29 ab:rSwnYxS0
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.storeFrontHeader = function storeFrontHeader() { };
+
+/**
+ * Client’s user agent string. If the "app field is not provided, "userAgent may be used to derive the value of the "app field
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example AppStore/2.0 iOS/8.3 model/iPhone7,2 build/12F70 (6; dt:106)
+ * @returns {String}
+ */
+// This prototype method is created dynamically upon instance initialization
+// Environment.prototype.userAgent = function userAgent() { };
+
+/**
+ * Some clients have platform-specific implementations of these objects (e.g. iTunes.sessionStorage),
+ * so we cover them in case they need to be overridden.
+ */
+Environment.prototype.localStorageObject = localStorageObject;
+Environment.prototype.sessionStorageObject = sessionStorageObject;
+
+/**
+ * Fetching identifier entity from AMS Metrics Identifier API
+ * @param {String} idNamespace - The id namespace that is defined under 'metricsIdentifier' in the bag
+ * @param {'userid' | 'clientid'} idType - The identifier type (userid or clientid)
+ * @param {Boolean} crossDeviceSync - The boolean flag to indicate whether the identifier is synced across devices
+ * @returns {Promise}
+ * @overridable
+ */
+Environment.prototype.platformIdentifier = function platformIdentifier() {};
+
+/*
+ * src/system/index.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var system = {
+ Environment: Environment
+};
+
+/*
+ * src/helpers/MetricsData.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns a MetricsData instance with APIs to store event data and create and send events to AMP Analytics
+ * @delegatable
+ * @constructor
+ * @return {MetricsData}
+ */
+function MetricsData() {
+ // @private
+ this._eventData = {}; // this data may be unclean; it should be cleaned before being enqueued as an event
+}
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Add event data to this metricsData object
+ * This data may be unclean; it should be cleaned before being enqueued as an event
+ * @param {Object} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N,
+ * each containing key/value pairs representing event fields to include when sending the event.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * @overridable
+ */
+MetricsData.prototype.updateData = function updateData(/* callerSuppliedEventFieldsMapN(varargs) */) {
+ reflect.extend.apply(null, [this._eventData].concat(Array.prototype.slice.call(arguments)));
+};
+
+/*
+ * src/helpers/index.js
+ * ae-client-kit-core
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var helpers = {
+ MetricsData: MetricsData
+};
+
+export { eventHandlers, helpers, system };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-client-config/dist/mt-client-config.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-client-config/dist/mt-client-config.esm.js
new file mode 100644
index 0000000..555fd31
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-client-config/dist/mt-client-config.esm.js
@@ -0,0 +1,987 @@
+import { storage, reflect, network, keyValue, number } from '@amp-metrics/mt-metricskit-utils-private';
+import Logger from '@amp-metrics/mt-client-logger-core';
+
+/*
+ * src/environment.js
+ * mt-client-config
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides a set of environment-specific (platform-specific) functions which can be individually overridden for the needs
+ * of the particular environment, or replaced en masse by providing a single replacement environment delegate object
+ * The functionality in this class is typically replaced via a delegate.
+ * @see setDelegate
+ * @delegatable
+ * @constructor
+ */
+var Environment = function () {};
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Environment.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+Environment.prototype.localStorageObject = storage.localStorageObject;
+Environment.prototype.sessionStorageObject = storage.sessionStorageObject;
+
+/*
+ * src/network.js
+ * mt-client-config
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Network request methods exposed so delegate callers can override
+ * @constructor
+ */
+var Network = function () {};
+
+/**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Network.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * Covers private utils implementation of makeAjaxRequest for delegation
+ */
+Network.prototype.makeAjaxRequest = network.makeAjaxRequest;
+
+/*
+ * src/config.js
+ * mt-client-config
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+var NO_TOPIC_KEY = 'noTopicConfig';
+var COMPOUND_SEPARATOR = '_';
+
+/**
+ * The default config should not include keys which have meaning if they are omitted.
+ * For example, a configSource without impressions means no impressions should be recorded, and in this case we should
+ * not have impressions in defaults so we don't inadvertently disobey the client's configSource.
+ */
+var DEFAULTS = {
+ configBaseUrl: 'https://xp.apple.com/config/1/report',
+ disabled: true // Prevent sending event if no config is available
+};
+
+// @private
+var _defaultConfig;
+
+/**
+ * Appends the provided source and its subsection if the subsection key matches the provided topic to the provided configSources array.
+ * @param {Array} configSources
+ * @param {String} topic
+ * @param {Object} source
+ */
+var _appendSectionAndSubsectionToConfigSources = function _appendSectionAndSubsectionToConfigSources(
+ configSources,
+ topic,
+ source
+) {
+ if (source) {
+ configSources.push(source);
+ var subsection = source[topic];
+ if (subsection && reflect.hasAnyKeys(subsection)) {
+ configSources.push(subsection);
+ }
+ }
+};
+
+/**
+ * Provides config-related functionality including managing config sources and values.
+ * The functionality in this class is typically replaced via a delegate.
+ * @see setDelegate
+ * @delegatable
+ * @constructor
+ * @param {String} topic
+ */
+var Config = function Config(topic) {
+ // @public
+ this.environment = new Environment();
+
+ /**
+ * @public
+ * @deprecated
+ */
+ this.network = new Network();
+
+ // @public
+ this.logger = /*#__PURE__*/ new Logger('mt-client-config');
+
+ // @private
+ this._topic = topic || NO_TOPIC_KEY;
+
+ // @private
+ this._debugSource = null;
+ // @private
+ this._cachedSource = null;
+ // @private
+ this._serviceSource = null;
+ // @private
+ this._initCalled = false;
+ // @private
+ this._initialized = false;
+ // @private
+ this._showedDebugWarning = false;
+ // @private
+ this._showedNoProvidedSourceWarning = false;
+ // @private
+ this._keyPathsThatSuppressWarning = {
+ configBaseUrl: true // used to fetch a config source, so we do not expect sources to be set when retrieving this value
+ };
+ // @private
+ this._configFetchPromise = null;
+
+ // @const
+ this.DEBUG_SOURCE_KEY = 'mtClientConfig_debugSource' + COMPOUND_SEPARATOR + this._topic;
+ // @const
+ this.CACHED_SOURCE_KEY = 'mtClientConfig_cachedSource' + COMPOUND_SEPARATOR + this._topic;
+};
+
+/**
+ * @return {Config} the default config instance, which is not associated with any topic
+ */
+Config.defaultConfig = function defaultConfig() {
+ if (!_defaultConfig) {
+ _defaultConfig = new Config(NO_TOPIC_KEY);
+ }
+ return _defaultConfig;
+};
+
+/**
+ ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+Config.prototype._defaults = function _defaults() {
+ return DEFAULTS;
+};
+
+Config.prototype._setInitialized = function _setInitialized(initialized) {
+ this._initialized = initialized;
+};
+
+Config.prototype._setInitCalled = function _setInitCalled(initCalled) {
+ this._initCalled = initCalled;
+};
+
+Config.prototype._setShowedDebugWarning = function _setShowedDebugWarning(value) {
+ this._showedDebugWarning = value;
+};
+
+Config.prototype._setShowedNoProvidedSourceWarning = function _setShowedNoProvidedSourceWarning(value) {
+ this._showedNoProvidedSourceWarning = value;
+};
+
+/**
+ ************************************ PUBLIC METHODS ************************************
+ */
+
+/**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Config.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * Reset the attached delegates from
+ * @param delegate
+ */
+Config.prototype.resetDelegate = function resetDelegate() {
+ // reset the delegates from environment, network and logger
+ reflect.detachMethods(this);
+ // Detach the attached methods from prototype(source, debugSource and attached utility methods from mt-metricskit-utils-private, etc.) of the config instance
+ reflect.resetDelegates(this);
+};
+
+/**
+ * @return {String} the Figaro topic that this config corresponds with
+ * Most clients do not need to override this method
+ */
+Config.prototype.topic = function topic() {
+ return this._topic;
+};
+
+/**
+ * Returns the metrics config endpoint hostname
+ * Most clients do not need to override this method
+ * @deprecated
+ * @return {String} a hostname e.g.xp.apple.com
+ */
+Config.prototype.configHostname = function configHostname() {};
+
+/**
+ * Returns a constructed URL to the metrics config endpoint for use with getConfig()
+ * Most clients do not need to override this method
+ * @deprecated
+ * @return {Promise} A Promise with a URL to the metrics config endpoint e.g. https://xp.apple.com/config/1/report/xp_its_main
+ */
+Config.prototype.configUrl = function configUrl() {
+ var configHostname = this.configHostname();
+ var returnUrlPromise;
+
+ if (configHostname) {
+ returnUrlPromise = Promise.resolve('https://' + configHostname + '/config/1/report');
+ } else {
+ returnUrlPromise = this.value('configBaseUrl');
+ }
+
+ return returnUrlPromise.then(
+ function (returnUrl) {
+ if (this._topic !== NO_TOPIC_KEY) {
+ returnUrl += '/' + this.topic();
+ } else {
+ this.logger.error('config.configUrl(): Topic must be provided');
+ }
+ return returnUrl;
+ }.bind(this)
+ );
+};
+
+/**
+ * This function will be used to access all of the sources for configuration data (e.g. disabled, blacklistedEvents, fieldsMap, etc.)
+ * THIS METHOD MUST BE PROVIDED BY A DELEGATE
+ * Returns an array of key/value objects (dictionaries) for all of the config sources (e.g. the bag, the page, the control parent, etc.).
+ * This method will be called frequently and repeatedly from the metrics.config.* functions.
+ * MetricsKit will use this return value to traverse the list of config objects, looking for config values.
+ * If later dictionaries of key/value pairs contain any keys already collected, the most late (farthest in the area) config value will overwrite earlier ones.
+ * IMPORTANT: This function might be called frequently and repeatedly so should be optimized for performance.
+ * It will be called frequently (rather than having this API provide a "setConfig" method for permanently setting the config values) since
+ * these config values can always be changing from one call to the next, so this ensures that they are current.
+ * THEREFORE: This function should NOT try to "simplify" by, e.g., merging all config sources into a single object to return when called.
+ * Doing so is an enormous amount of work to do each time MetricsKit needs to look up a single value.
+ * It is far more efficient for MetricsKit to simply interogate each configSource for the required key, rather than
+ * the delegate function merging *all* keys of *all* sources (looping through all values of all sources) in order to look up a single value.
+ * @example var sources = function() { return [itms.getBag().metrics, pageData.metrics.config]; }; // return an array of sources of app config
+ * @example var sources = function() { return metrics.utils.keyValue.sourcesArray(itms.getBag().metrics, pageData.metrics.config, swooshData.metrics.config); }; // Let MetricsKit help build the array from a varargs list of sources
+ * @example var sources = function() { return metrics.utils.keyValue.sourcesArray(existingArrayOfConfigSources, pageData.metrics.config, swooshData.metrics.config); }; // Let MetricsKit help build the array from a varargs list of sources where some args are already arrays of other sources (only works one level deep)
+ * @see metrics.system.configSources.sources for simple creation of the return value of config sources.
+ */
+Config.prototype.sources = function sources() {};
+
+/**
+ * Search through the configuration sources provided by "metrics.system.configSources()", looking for the highest precedence value for "key"
+ * If no config sources provided, look for values in the DEFAULTS constant
+ * "keypath" can be a simple top-level config key such as "postFrequency" or a compound "path", such as fieldsMap.cookies
+ * @param {String} keyPath the dot-separated (".") path to the desired config value.
+ * @param {String} (optional) topic an 'override' topic which will override the main topic.
+ * @returns {Promise} A Promise of the value at the reached keypath. Returns "null" if the key or key's value is not found.
+ * Values found in later (farthest in array) dictionaries of key/value pairs overwrite earlier dictionaries.
+ * @see metrics.system.configSources.setDelegate()
+ */
+Config.prototype.value = function value(keyPath, topic) {
+ var getConfigValueFn = function () {
+ var cachedSource = this.cachedSource();
+ var serviceSource = this.serviceSource();
+ var configSourcesArray = this.sources();
+ var savedDebugSource = this.debugSource(); // NOTE: Always go through the accessor method, since it may need to be retrieved from localStorage
+ var sourceProvided = cachedSource || serviceSource || configSourcesArray || savedDebugSource;
+ var sourcesToCheck;
+
+ // show relevant warnings
+ if (!configSourcesArray && !serviceSource && !(keyPath in this._keyPathsThatSuppressWarning)) {
+ if (!this._showedNoProvidedSourceWarning) {
+ this._showedNoProvidedSourceWarning = true;
+ this.logger.warn(
+ 'Metrics config: No config provided via delegate or fetched via init(), using cached config values.'
+ );
+ }
+ }
+ if (savedDebugSource) {
+ if (!this._showedDebugWarning) {
+ // We do this in case developers forget to clear this and wonder why the app isn't working correctly in normal running.
+ this._showedDebugWarning = true;
+ this.logger.warn(
+ '"debugSource" found.\nThis will override any same-named client-supplied configSource fields.\nThis setting "sticks" across session, use "setDebugSource(null)" to clear'
+ );
+ }
+ }
+
+ if (!reflect.isArray(configSourcesArray)) {
+ configSourcesArray = [configSourcesArray];
+ }
+
+ // disable the event only if there are no provided sources
+ // In case of fetch config failed and no cached config in local
+ if (keyPath === 'disabled') {
+ if (sourceProvided) {
+ sourcesToCheck = [cachedSource, serviceSource, configSourcesArray, savedDebugSource];
+ } else {
+ sourcesToCheck = [DEFAULTS];
+ }
+ } else {
+ // For non-disabling rules, check all sources
+ // Let "DEFAULTS" be overwritten by cachedSource, then serviceSource, then client-supplied configSources,
+ // and then let "savedDebugSource" overwrite everyone
+ sourcesToCheck = [DEFAULTS, cachedSource, serviceSource, configSourcesArray, savedDebugSource];
+ }
+
+ sourcesToCheck = this.configSourcesWithOverrides(sourcesToCheck, topic || this.topic());
+ // Pass each source as an individual argument; valueForKeyPath won't expand configSourcesArray if it is nested in sourcesToCheck
+ return keyValue.valueForKeyPath.apply(null, [keyPath].concat(sourcesToCheck));
+ }.bind(this);
+
+ if (this._configFetchPromise) {
+ return this._configFetchPromise.then(getConfigValueFn);
+ } else {
+ var value = getConfigValueFn();
+ return Promise.resolve(value);
+ }
+};
+
+/**
+ * Pushes any subsections within a config source to the front of the provided config sources array if the subsection key matches with the specified topic.
+ * This method is used by the default implementation of 'value(keyPath)' to determine precedence
+ * of config sources for the given topic.
+ * Subsections that are later in the array will take precedence over earlier subsections.
+ * Note that if one of the config sources is an array, this method will traverse one level deep
+ * to check for the presence of a dictionary that may contain a desired subsection.
+ * We traverse one level deep to maintain parity with _utils.keyValue.valueForKeyPath() which will be used to traverse the config sources
+ * @param {Array} configSources
+ * @param {String} topic
+ * @returns {Array} returnSources
+ */
+Config.prototype.configSourcesWithOverrides = function configSourcesWithOverrides(configSources, topic) {
+ var returnSources = configSources;
+ if (configSources && configSources.length && topic) {
+ returnSources = [];
+ for (var i = 0; i < configSources.length; i++) {
+ var source = configSources[i];
+ if (source) {
+ if (reflect.isArray(source) && source.length) {
+ var subarray = [];
+ for (var j = 0; j < source.length; j++) {
+ _appendSectionAndSubsectionToConfigSources(subarray, topic, source[j]);
+ }
+ returnSources.push(subarray);
+ } else {
+ _appendSectionAndSubsectionToConfigSources(returnSources, topic, source);
+ }
+ }
+ }
+ }
+ return returnSources;
+};
+
+/**
+ * Set's a "priority" configSource that will override any same-named client-supplied configSource fields.
+ * This can be done in code (e.g. in testcases) or, more typically, in Web Inspector when debugging or testing a running app (client) using MetricsKit.
+ * This is useful when testing, both in testcases and at runtime. One example would be to set a temporary "bag" structure that could then be tweaked at will, e.g. blacklisting fields, etc.
+ * This setting "sticks" across session so that it can be set, the app restarted, and the values will be present at the earliest runnings of the app.
+ * Use "setDebugSource(null)" to clear this value
+ * NOTE: If no "localStorage" is available (e.g. when testing), this value will only last for the session or until it is explicitly cleared.
+ * Here is an example of how to use this to test an app at runtime (and/or launch time)
+ * Run the app
+ * Open Web Inspector
+ * Set a variable to bag contents, e.g. var debugBag = itms.getBag();
+ * Call: metrics.config.setDebugSource(debugBag.metrics);
+ * enter: debugBag.metrics.disabled=true;
+ * Then do some things to generate events and make sure that:
+ * The app doesn’t crash
+ * No JavaScript events are generated in the console
+ * The events aren’t sent
+ * Reset that by entering: delete debugBag.metrics.disabled
+ * enter: debugBag.metrics.blacklistedEvents = [“page”, “click”];
+ * Then do some things to generate events and make sure that:
+ * The app doesn’t crash
+ * No JavaScript events are generated in the console
+ * Those events, and only those events, aren’t sent
+ * I save the object you set via metrics.config.setDebugSource to localStorage to facilitate testing at app startup, so remember to clear it out when you’re done or you’ll be confused why things aren’t working right in the future:
+ * metrics.config.setDebugSource();
+ * If there are values you want to be in effect at app-launch, just do the tweaking to the bag before your call to metrics.config.setDebugSource(debugBag.metrics);, passing in the tweaked bag.
+ * @param aDebugSource a plain old JavaScript object with keys and values which will be searched when config is requested.
+ * @returns {*} debugSource
+ */
+Config.prototype.setDebugSource = function setDebugSource(aDebugSource) {
+ this._debugSource = aDebugSource || null;
+ return storage.saveObjectToStorage(this.environment.localStorageObject(), this.DEBUG_SOURCE_KEY, this._debugSource);
+};
+
+/**
+ * Return any previously set debugSource which would be stored in localStorage.
+ * If not present in localStorage, the class variable value of debugSource will be returned.
+ * @returns {*} debugSource
+ */
+Config.prototype.debugSource = function debugSource() {
+ if (this._debugSource) ; else {
+ // Otherwise look in localStorage for one...
+ this._debugSource = storage.objectFromStorage(this.environment.localStorageObject(), this.DEBUG_SOURCE_KEY);
+ }
+ return this._debugSource;
+};
+
+/**
+ * Save a source to localStorage which will have config values that can be used in the next visit until a fresh config is fetched
+ * @param {Object} aSource
+ * @return {Object}
+ */
+Config.prototype.setCachedSource = function setCachedSource(aSource) {
+ this._cachedSource = aSource || null;
+ return storage.saveObjectToStorage(
+ this.environment.localStorageObject(),
+ this.CACHED_SOURCE_KEY,
+ this._cachedSource
+ );
+};
+
+/**
+ * Return any previously set savedSource which would be stored in localStorage.
+ * The class variable value of savedSource will be returned if non-null to avoid expensive reads from disk.
+ * @return {Object}
+ */
+Config.prototype.cachedSource = function cachedSource() {
+ if (this._cachedSource) ; else {
+ // Otherwise look in localStorage for one...
+ this._cachedSource = storage.objectFromStorage(this.environment.localStorageObject(), this.CACHED_SOURCE_KEY);
+ }
+ return this._cachedSource;
+};
+
+/**
+ * Set a source, typically one that was fetched from the config endpoint,
+ * which will have config values that can be overwritten by clients via config.setDelegate
+ * @param {Object} aServiceSource
+ * @deprecated
+ * @return {Object}
+ */
+Config.prototype.setServiceSource = function setServiceSource(aServiceSource) {
+ this._serviceSource = aServiceSource;
+ return this._serviceSource;
+};
+
+/**
+ * Returns a config source that was retrieved from the metrics config service endpoint
+ * @deprecated
+ * @return {Object}
+ */
+Config.prototype.serviceSource = function serviceSource() {
+ return this._serviceSource;
+};
+
+/**
+ * Initialize config by setting config delegate or fetching it from the config endpoint as necessary
+ * If we wanted config to persist across page turns, we could save it via setCachedSource and fetch it every time we wake up,
+ * but we expect most clients to be single page apps
+ *
+ * @param {Function} (optional) configSourcesFn - a function that returns an array of key/value objects (dictionaries) for all of the config sources
+ * (e.g. the bag, the page, the control parent, etc.).
+ * @return {Promise} A Promise for the Config initialization. Returns an rejected Promise if failed to fetch config from server
+ */
+Config.prototype.init = function init(configSourcesFn) {
+ var isDefaultConfig = this._topic === NO_TOPIC_KEY || !reflect.isFunction(configSourcesFn);
+
+ if (isDefaultConfig) {
+ this.logger.warn(
+ 'config.init(): Falling back to default config because configSourcesFn or a valid topic was not provided'
+ );
+ }
+
+ // default config does not need to initialize
+ if (this._initCalled || isDefaultConfig) {
+ return Promise.resolve(this);
+ }
+
+ this._initCalled = true;
+ var self = this;
+
+ this._configFetchPromise = Promise.resolve(configSourcesFn())
+ .then(function (configSources) {
+ self.setDelegate({
+ sources: function sources() {
+ return configSources;
+ }
+ });
+ self._initialized = true;
+ return self;
+ })
+ .catch(function (error) {
+ self._initCalled = false;
+ self._initialized = false;
+ throw error;
+ });
+
+ return this._configFetchPromise;
+};
+
+/**
+ * Release resources of the config
+ */
+Config.prototype.cleanup = function cleanup() {
+ this._initCalled = this._initialized = false;
+ this.setCachedSource();
+ this.setDebugSource();
+ this.resetDelegate();
+ this.environment = null;
+ this.network = null;
+ this.logger = null;
+ this._topic = null;
+ this._debugSource = null;
+ this._cachedSource = null;
+ this._serviceSource = null;
+ this._initCalled = false;
+ this._initialized = false;
+ this._showedDebugWarning = false;
+ this._showedNoProvidedSourceWarning = false;
+ this._configFetchPromise = null;
+};
+
+/**
+ * Indicates whether or not config has initialized.
+ * Initialization is accomplished in one of the following ways:
+ * 1) a service source is set via a call to config.setServiceSource() TODO: Deprecated
+ * 2) a source function delegate is set via config.setDelegate()
+ * @return {Boolean}
+ */
+Config.prototype.initialized = function initialized() {
+ return this._initialized;
+};
+
+/*
+ * src/impls/metrics_config.js
+ * mt-client-config
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Metrics config implementation for Config
+ * This class is a subclass of Config to provide the business methods for metrics collection
+ * @param topic
+ * @constructor
+ */
+var MetricsConfig = function MetricsConfig(topic) {
+ Config.call(this, topic);
+};
+
+MetricsConfig.prototype = Object.create(Config.prototype);
+MetricsConfig.prototype.constructor = MetricsConfig;
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise}
+ */
+MetricsConfig.prototype.disabled = function disabled(topic) {
+ return this.value('disabled', topic).then(function (disabled) {
+ return !!disabled;
+ });
+};
+
+/** @DEPERECATED per Inclusive Software Efforts; use denylistedEvents 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} Guaranteed to always return a Promise with the valid array, though the array may be empty if the value was unset in config
+ */
+MetricsConfig.prototype.blacklistedEvents = function blacklistedEvents(topic) {
+ return this.denylistedEvents(topic);
+};
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} Guaranteed to always return a Promise with the valid array, though it may be empty if the value was unset in config
+ */
+MetricsConfig.prototype.denylistedEvents = function denylistedEvents(topic) {
+ var blacklistedEventsArray = this.value('blacklistedEvents', topic)
+ .then(function (returnArray) {
+ return returnArray || [];
+ })
+ .catch(function () {
+ return [];
+ });
+ var denylistedEventsArray = this.value('denylistedEvents', topic)
+ .then(function (returnArray) {
+ return returnArray || [];
+ })
+ .catch(function () {
+ return [];
+ });
+ return Promise.all([blacklistedEventsArray, denylistedEventsArray]).then(function (outputs) {
+ var blacklistedEventsArray = outputs[0];
+ var denylistedEventsArray = outputs[1];
+ return dedupedArray(blacklistedEventsArray, denylistedEventsArray);
+ });
+};
+
+/**
+ * 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
+ var returnArray = [];
+ 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
+ }
+ }
+ returnArray = Object.keys(tempDict);
+ return returnArray;
+}
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} Guaranteed to always return a Promise with the valid array, though it may be empty if the value was unset in config
+ */
+MetricsConfig.prototype.blacklistedFields = function blacklistedFields(topic) {
+ return this.denylistedFields(topic);
+};
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} Guaranteed to always return a Promise valid array, though it may be empty if the value was unset in config
+ */
+MetricsConfig.prototype.denylistedFields = function denylistedFields(topic) {
+ var blacklistedFieldsArray = this.value('blacklistedFields', topic)
+ .then(function (returnArray) {
+ return returnArray || [];
+ })
+ .catch(function () {
+ return [];
+ });
+ var denylistedFieldsArray = this.value('denylistedFields', topic)
+ .then(function (returnArray) {
+ return returnArray || [];
+ })
+ .catch(function () {
+ return [];
+ });
+ return Promise.all([blacklistedFieldsArray, denylistedFieldsArray]).then(function (outputs) {
+ var blacklistedFieldsArray = outputs[0];
+ var denylistedFieldsArray = outputs[1];
+ return dedupedArray(blacklistedFieldsArray, denylistedFieldsArray);
+ });
+};
+
+/** @DEPRECATED per Inclusive Software Efforts; use removeDenylistedFields instead
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} the passed-in object with any blacklisted fields removed
+ */
+MetricsConfig.prototype.removeBlacklistedFields = function removeBlacklistedFields(eventFields, topic) {
+ return this.removeDenylistedFields(eventFields, topic);
+};
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} the passed-in object with any denylisted fields removed
+ */
+MetricsConfig.prototype.removeDenylistedFields = function removeDenylistedFields(eventFields, topic) {
+ if (eventFields) {
+ return this.denylistedFields(topic).then(function (denylistedFieldsArray) {
+ 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;
+ });
+ } else {
+ return Promise.resolve(eventFields);
+ }
+};
+
+/** @DEPRECATED per Inclusive Software Efforts; use metricsDisabledOrDenylistedEvent instead
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} returns "true" if either "disabled()" is true or "blacklistedEvents()" contains this eventType
+ */
+MetricsConfig.prototype.metricsDisabledOrBlacklistedEvent = function metricsDisabledOrBlacklistedEvent(
+ anEventType,
+ topic
+) {
+ return this.metricsDisabledOrDenylistedEvent(anEventType, topic);
+};
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Boolean} returns "true" if either "disabled()" is true or "denylistedEvents()" contains this eventType
+ */
+MetricsConfig.prototype.metricsDisabledOrDenylistedEvent = function metricsDisabledOrDenylistedEvent(
+ anEventType,
+ topic
+) {
+ return this.disabled(topic).then(
+ function (disabled) {
+ return (
+ disabled ||
+ (anEventType
+ ? this.denylistedEvents(topic).then(function (denylistedEvents) {
+ return denylistedEvents.indexOf(anEventType) > -1;
+ })
+ : false)
+ );
+ }.bind(this)
+ );
+};
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} 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
+ */
+MetricsConfig.prototype.deResFields = function deResFields(topic) {
+ return this.value('deResFields', topic).then(function (returnArray) {
+ return returnArray || [];
+ });
+};
+
+/**
+ * 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 an 'override' topic which will override the main topic.
+ * @returns {Promise} the passed-in object with any fields de-resed
+ */
+MetricsConfig.prototype.applyDeRes = function applyDeRes(eventFields, topic) {
+ if (eventFields) {
+ return this.deResFields(topic).then(function (deResFieldsConfigArray) {
+ var fieldName;
+
+ deResFieldsConfigArray.forEach(function (deResFieldConfig) {
+ fieldName = deResFieldConfig.fieldName;
+ if (fieldName in eventFields) {
+ eventFields[fieldName] = number.deResNumber(
+ eventFields[fieldName],
+ deResFieldConfig.magnitude,
+ deResFieldConfig.significantDigits
+ );
+ }
+ });
+
+ return eventFields;
+ });
+ } else {
+ return Promise.resolve(eventFields);
+ }
+};
+
+export default Config;
+export { MetricsConfig };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-client-constraints/dist/mt-client-constraints.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-client-constraints/dist/mt-client-constraints.esm.js
new file mode 100644
index 0000000..c72d602
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-client-constraints/dist/mt-client-constraints.esm.js
@@ -0,0 +1,3103 @@
+import { storage, reflect, string, eventFields } from '@amp-metrics/mt-metricskit-utils-private';
+import { loggerNamed } from '@amp-metrics/mt-client-logger-core';
+
+/*
+ * src/system/environment.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides a set of environment-specific (platform-specific) functions which can be individually overridden for the needs
+ * of the particular environment, or replaced en masse by providing a single replacement environment delegate object
+ * The functionality in this class is typically replaced via a delegate.
+ * @see setDelegate
+ * @delegatable
+ * @constructor
+ */
+var Environment = function Environment() {};
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Environment.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * Some clients have platform-specific implementations of these objects (e.g. iTunes.sessionStorage), so we cover them in case they need to be overriden.
+ */
+Environment.prototype.localStorageObject = storage.localStorageObject;
+Environment.prototype.sessionStorageObject = storage.sessionStorageObject;
+
+/**
+ * Fetching identifier entity from AMS Metrics Identifier API
+ * @param {String} idNamespace - The id namespace that is defined under 'metricsIdentifier' in the bag
+ * @param {'userid' | 'clientid'} idType - The identifier type (userid or clientid)
+ * @param {Boolean} crossDeviceSync - The boolean flag to indicate whether the identifier is synced across devices
+ * @returns {Promise}
+ * @overridable
+ */
+Environment.prototype.platformIdentifier = function platformIdentifier(idNamespace, idType, crossDeviceSync) {};
+
+/*
+ * src/system/index.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var System = function System() {
+ this.environment = new Environment();
+ this.logger = loggerNamed('mt-client-constraints');
+};
+
+/*
+ * src/utils/key_value.js
+ * mt-client-constraints
+ *
+ * Copyright © 2023 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Recursively look up the given keyPath in the provided object.
+ * @param {Object} object an object that going to be used for seeking the keyPath
+ * @param {String} keyPath a string used to search one or more fields in the object
+ * @param {Boolean} inplace a boolean to indicate passing the original/cloned parent to the callback.
+ * @param {Function} callback a function will be called when the keyPath has been found in the object
+ * @example
+ *
+ * var target = {
+ * aField: {
+ * bField: {
+ * cArray: [1, 2, 3],
+ * dField: 12345
+ * },
+ * eArray: [{
+ * gField: 'hello'
+ * }, {
+ * gField: 'world'
+ * }]
+ * }
+ * }
+ *
+ * // loop an object field
+ * lookForKeyPath(target, 'aField.bField', false, (value, key, keyPath, object) => {
+ * // value = target.aField.bField
+ * // key = 'bField'
+ * // keyPath = 'aField.bField'
+ * // object = target.aField;
+ * });
+ *
+ * // loop an array field
+ * lookForKeyPath(target, 'aField.bField.cArray[]', false, (value, key, keyPath, object) => {
+ * // Will be called with 3 times with:
+ * // value = 1, key = 0, keyPath = aField.bField.cArray[0], object = target.aField.bField.cArray
+ * // value = 2, key = 1, keyPath = aField.bField.cArray[1], object = target.aField.bField.cArray
+ * // value = 3, key = 2, keyPath = aField.bField.cArray[2], object = target.aField.bField.cArray
+ * });
+ *
+ * // loop a nested field
+ * lookForKeyPath(target, 'aField.bField.dField', false, (value, key, keyPath, object) => {
+ * // value = 12345
+ * // key = 'dField'
+ * // keyPath = 'aField.bField.dField';
+ * // object = target.aField.bField;
+ * });
+ *
+ * // loop a field of an object in an array field
+ * lookForKeyPath(target, 'aField.eArray[].gField',false, (value, key, keyPath, object) => {
+ * // Will be called with 2 times with:
+ * // value = hello, key = gField, keyPath = aField.eArray[0].gField, object = aField.eArray[0]
+ * // value = world, key = gField, keyPath = aField.eArray[1].gField, object = aField.eArray[1]
+ * });
+ */
+function lookForKeyPath(object, keyPath, inplace, callback) {
+ if (!reflect.isDefined(object) || !reflect.isDefinedNonNullNonEmpty(keyPath) || !reflect.isFunction(callback)) {
+ return object;
+ }
+ var keyPathArray = keyPath.split('.');
+ return _lookForKeyPath(object, keyPathArray, null, [], null, inplace, callback);
+}
+
+function _lookForKeyPath(object, keyPathArray, key, keyPath, parent, inplace, callback) {
+ if (reflect.isFunction(object)) {
+ return parent || object;
+ }
+
+ keyPath.push(key);
+
+ // Handle the leaf fields
+ if (keyPathArray.length === 0) {
+ callback(object, key, keyPath.slice(1).join('.'), parent);
+ return parent || object;
+ }
+
+ if (!reflect.isDefined(object)) {
+ return parent || object;
+ }
+
+ var clonedObject = inplace ? object : {};
+ var fieldName = keyPathArray.shift();
+ // Handle array values
+ if (fieldName.length > 2 && fieldName.indexOf('[]') === fieldName.length - 2) {
+ fieldName = fieldName.slice(0, -2); // remove []
+ keyPath.push(fieldName);
+ reflect.extend(clonedObject, object);
+ var arrayValue = clonedObject[fieldName];
+ if (reflect.isDefinedNonNull(arrayValue)) {
+ var processedArray = arrayValue.map(function (arrayItem, i) {
+ var updatedArray = inplace ? arrayValue : arrayValue.slice();
+ _lookForKeyPath(arrayItem, keyPathArray.slice(), i, keyPath, updatedArray, inplace, callback);
+ keyPath.pop();
+ return updatedArray[i];
+ });
+ clonedObject[fieldName] = processedArray;
+ }
+ } else {
+ var fieldValue = object[fieldName];
+ reflect.extend(clonedObject, object);
+ // Handle normal values
+ clonedObject = _lookForKeyPath(fieldValue, keyPathArray, fieldName, keyPath, clonedObject, inplace, callback);
+ }
+
+ keyPath.pop();
+
+ if (parent) {
+ parent[key] = clonedObject;
+ return parent;
+ } else {
+ return clonedObject;
+ }
+}
+
+/*
+ * src/treatment_matchers/nested_fields_match
+ * mt-client-constraints
+ *
+ * Copyright © 2023 Apple Inc. All rights reserved.
+ *
+ */
+
+var MATCH_TYPES_CONFIG = {
+ // "MATCH_TYPES_CONFIG.all" is used for checking if all items of the nested fields meet the filter condition.
+ all: {
+ initMatchValue: true,
+ accumulateMatchResult: function (accumulatedResult, matchResult) {
+ return accumulatedResult && matchResult;
+ }
+ },
+ // "MATCH_TYPES_CONFIG.any" is used for checking if any of the items of the nested fields meet the filter condition.
+ any: {
+ initMatchValue: false,
+ accumulateMatchResult: function (accumulatedResult, matchResult) {
+ return accumulatedResult || matchResult;
+ }
+ }
+};
+
+function getMatchTypeConfig(matchType) {
+ var matchConfig = MATCH_TYPES_CONFIG[matchType];
+ if (!reflect.isDefinedNonNull(matchConfig)) {
+ matchConfig = MATCH_TYPES_CONFIG.all;
+ }
+ return matchConfig;
+}
+
+/**
+ *
+ * @param {String} fieldName - name of field in eventData
+ * @param {Object} eventData - a dictionary of event data
+ * @param {Object} matchOptions - an object contains the configurations that related to nested fields and the match options for the compouned matches.
+ * @param {Object} matchOptions.matchType - a flag to indicate the match type to apply to the nested fields. Available values "all", "any"
+ * @param {Object} matchOptions.matches - an object contains key/value pairs for the actual matches.
+ * @returns {Boolean} return true if the field value exists in "fieldMatchValues" otherwise return false
+ */
+function nestedFieldCompoundMatch(fieldName, eventData, matchOptions) {
+ if (!reflect.isObject(eventData) || !reflect.isObject(matchOptions)) {
+ return false;
+ }
+ var matchType = matchOptions.matchType;
+ var compoundMatches = matchOptions.matches;
+ if (!reflect.isDefinedNonNullNonEmpty(compoundMatches)) {
+ return false;
+ }
+ var matchTypeConfig = getMatchTypeConfig(matchType);
+ var isMatched = matchTypeConfig.initMatchValue;
+ lookForKeyPath(eventData, fieldName, false, function (_value, key, _keyPath, object) {
+ var matchResult = Object.keys(compoundMatches).every(function (matcherName) {
+ var matcherParam = compoundMatches[matcherName];
+
+ if (reflect.isDefinedNonNull(matchers[matcherName])) {
+ return matchers[matcherName](key, object, matcherParam);
+ } else {
+ return false;
+ }
+ });
+ isMatched = matchTypeConfig.accumulateMatchResult(isMatched, matchResult);
+ });
+
+ return !!isMatched;
+}
+
+/*
+ * src/treatment_matchers/non_empty_match
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ *
+ * @param {String} fieldName - name of field in eventData
+ * @param {Object} eventData - a dictionary of event data
+ * @returns {Boolean} return true if the fieldName does exist in the eventData otherwise return false
+ */
+function nonEmptyMatch(fieldName, eventData) {
+ // Since the isObject will return undefined/null if the eventData is undefined/null.
+ // workaround here to convert the return value to boolean here to ensure this function returns boolean value. Should be fix it in the isObject()
+ return (
+ !!reflect.isObject(eventData) &&
+ eventData.hasOwnProperty(fieldName) &&
+ reflect.isDefinedNonNullNonEmpty(eventData[fieldName])
+ );
+}
+
+/*
+ * src/treatment_matchers/value_match
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ *
+ * @param {String} fieldName - name of field in eventData
+ * @param {Object} eventData - a dictionary of event data
+ * @param {Array} fieldMatchValues - a list of possible values to match for that field
+ * @returns {Boolean} return true if the field value exists in "fieldMatchValues" otherwise return false
+ */
+function valueMatch(fieldName, eventData, fieldMatchValues) {
+ if (!reflect.isObject(eventData)) {
+ return false;
+ }
+
+ var fieldValue = eventData[fieldName];
+ return eventData.hasOwnProperty(fieldName) && fieldMatchValues.indexOf(fieldValue) > -1;
+}
+
+/*
+ * src/treatment_matchers/non_value_match
+ * mt-client-constraints
+ *
+ * Copyright © 2023 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ *
+ * @param {String} fieldName - name of field in eventData
+ * @param {Object} eventData - a dictionary of event data
+ * @param {Array} fieldNotMatchValues - a list of values to not match for that field
+ * @returns {Boolean} return true if the field value do not match ALL the values in "fieldNotMatchValues" otherwise return false
+ */
+function nonValueMatch(fieldName, eventData, fieldNotMatchValues) {
+ if (!reflect.isObject(eventData) || !reflect.isArray(fieldNotMatchValues)) {
+ return false;
+ }
+
+ var fieldValue = eventData[fieldName];
+ return eventData.hasOwnProperty(fieldName) && fieldNotMatchValues.indexOf(fieldValue) === -1;
+}
+
+/*
+ * src/treatment_matchers/index.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var matchers = {
+ nonEmpty: nonEmptyMatch,
+ valueMatches: valueMatch,
+ nonValueMatches: nonValueMatch,
+ nestedFieldMatches: nestedFieldCompoundMatch
+};
+
+/*
+ * src/utils/constants.js
+ * mt-client-constraints
+ *
+ * Copyright © 2019 Apple Inc. All rights reserved.
+ *
+ */
+
+var FIELD_RULES = {
+ OVERRIDE_FIELD_VALUE: 'overrideFieldValue'
+};
+
+/*
+ * src/field_handlers/base.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides methods to manage field constraints that apply to all fields
+ * @constructor
+ */
+var Base = function Base() {};
+
+/**
+ ************************************ PUBLIC METHODS ************************************
+ */
+/**
+ * @param {Object} eventFields a dictionary of event data
+ * @param {Object} fieldRules includes information about how to constrain a field
+ * @param {String} fieldName the name of the field to constrain
+ * @return {any} a field value that adheres to the provided rules
+ * @overridable
+ */
+Base.prototype.constrainedValue = function constrainedValue(eventFields, fieldRules, fieldName) {
+ var fieldValue = eventFields && eventFields.hasOwnProperty(fieldName) ? eventFields[fieldName] : null;
+ return this.applyConstraintRules(fieldValue, fieldRules);
+};
+
+/**
+ * @param {any} fieldValue an unconstrained value
+ * @param {Object} fieldRules includes information about how to constrain a field
+ * @return {any} a field value that adheres to the provided rules
+ * @overridable
+ */
+Base.prototype.applyConstraintRules = function applyConstraintRules(fieldValue, fieldRules) {
+ var returnValue = fieldValue;
+ if (fieldRules) {
+ var denylisted = fieldRules.denylisted || fieldRules.blacklisted;
+ if (denylisted) {
+ returnValue = null;
+ } else if (fieldRules.hasOwnProperty(FIELD_RULES.OVERRIDE_FIELD_VALUE)) {
+ returnValue = fieldRules.overrideFieldValue;
+ }
+ }
+ return returnValue;
+};
+
+/*
+ * src/field_actions/base.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+var exceptionString = string.exceptionString;
+
+/**
+ * Parent class of field_actions classes
+ * @param {Object} constraintsInstance - the instance of Constraints class
+ * @constructor
+ */
+var Base$1 = function Base(constraintsInstance) {
+ // @private
+ this._constraintsInstance = constraintsInstance;
+};
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Base$1.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * Abstract method to constrain value
+ * @param {Any} value - the value of fieldName in eventData, null if the eventData does not exist or the fieldName does not exist in the eventData
+ * @param {Object} fieldRules - includes information about how to constrain the field
+ * @param {Object} eventData - a dictionary of event data, which should include a pre-existing (unconstrained) field
+ * @param {String} fieldName - field name/path that can locate the "value" parameter in eventData
+ * @return {Any} the constrained value
+ */
+Base$1.prototype.constrainedValue = function constrainedValue(value, fieldRules, eventData, fieldName) {
+ throw exceptionString('field_actions.Base', 'constrainedValue');
+};
+
+/**
+ * A public method to wrap the "constrainedValue" method to contains common code for all field_actions subclasses.
+ * @param {Any} value - field value in eventData that is performed by field_actions
+ * @param {String} fieldName - field name/path that can locate the "value" parameter in eventData
+ * @param {Object} eventData - a dictionary of event data, which should include a pre-existing (unconstrained) field
+ * @param {Object} fieldRules - includes information about how to constrain the field
+ * @return {Any} the constrained value
+ */
+Base$1.prototype.performAction = function performAction(value, fieldName, eventData, fieldRules) {
+ // return the original value if there are no rules to apply
+ if (reflect.isDefinedNonNull(fieldRules) && !reflect.isEmptyObject(fieldRules)) {
+ value = this.constrainedValue(value, fieldRules, eventData, fieldName);
+ }
+ return value;
+};
+
+/*
+ * src/utils/url.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * @param {String} aUrl
+ * @return {String} the hostname part of the provided url e.g. www.apple.com
+ * @overridable
+ * Note: see https://nodejs.org/api/url.html for a diagram illustrating the different parts of a URL
+ */
+function hostname(aUrl) {
+ aUrl = aUrl || '';
+
+ var urlParts = withoutParams(aUrl).split('/');
+ var hostAndAuth;
+ var host;
+ var hostname;
+
+ if (aUrl.indexOf('//') === -1) {
+ hostAndAuth = urlParts[0];
+ } else {
+ hostAndAuth = urlParts[2];
+ }
+
+ host = hostAndAuth.substring(hostAndAuth.indexOf('@') + 1);
+ hostname = host.split(':')[0];
+
+ return hostname;
+}
+
+/**
+ * @param {String} aUrl
+ * @return {String} the domain part of the provided url shortened to the "main" part of the domain (apple.com or apple.co.uk)
+ * @overridable
+ * Note: this method uses a heuristic to determine if the url has a country code second level domain and will miss
+ * ccSLDs that are not exactly two chracters long or one of: 'com', 'org, 'net', edu', 'gov'
+ * For example, "www.example.ltd.uk" will be shortened to "ltd.uk"
+ * All two-letter top-level domains are ccTLDs: https://en.wikipedia.org/wiki/Country_code_top-level_domain
+ */
+function mainDomain(aUrl) {
+ var urlSegments = hostname(aUrl).split('.');
+ var lastSegment = urlSegments[urlSegments.length - 1];
+ var secondToLastSegment = urlSegments[urlSegments.length - 2];
+ var segmentsToKeep = 2;
+
+ if (
+ lastSegment &&
+ lastSegment.length === 2 &&
+ secondToLastSegment &&
+ (secondToLastSegment.length === 2 || secondToLastSegment in reservedCCSLDs())
+ ) {
+ segmentsToKeep = 3;
+ }
+
+ return urlSegments.slice(-1 * segmentsToKeep).join('.');
+}
+
+/**
+ * @return {Object} a map of country-code second level domains (ccSLDs) used in the heuristic
+ * that determines the main part of a domain (defined as TLD + ccSLD + 1)
+ * @overridable
+ */
+function reservedCCSLDs() {
+ var reservedCCSLDs = {
+ com: true,
+ org: true,
+ net: true,
+ edu: true,
+ gov: true
+ };
+
+ return reservedCCSLDs;
+}
+
+/**
+ * @param {String} aUrl
+ * @param {Array|Object} allowedParams An array of allowed params or an object with each param containing its allowed values
+ * @return {String} the url with any disallowed query parameters and/or hash removed
+ * @overridable
+ *
+ * @example
+ * withoutParams('https://itunes.apple.com/?p1=10&p2=hello', ['p1']);
+ * // returns 'https://itunes.apple.com/?p1=10'
+ *
+ * withoutParams('https://itunes.apple.com/?p1=10&p2=hello', {
+ * p1: {
+ * allowedValues: ['20', '30']
+ * },
+ * . p2: {
+ * allowedValues: ['hello']
+ * }
+ * });
+ * // returns 'https://itunes.apple.com/?p2=hello'
+ */
+function withoutParams(aUrl, allowedParams) {
+ var url = aUrl || '';
+ var urlParts = url.split('?');
+ var urlPrefix = urlParts[0];
+ var urlParams = withoutHash(urlParts[1]).split('&');
+
+ var filteredParams = urlParams
+ .filter(function (paramString) {
+ var keyAndVal = paramString.split('=');
+ var paramName = keyAndVal[0];
+ var paramVal = keyAndVal[1];
+
+ if (reflect.isArray(allowedParams)) {
+ return allowedParams.indexOf(paramName) !== -1;
+ }
+
+ if (reflect.isObject(allowedParams)) {
+ return (
+ reflect.isObject(allowedParams[paramName]) &&
+ reflect.isArray(allowedParams[paramName].allowedValues) &&
+ allowedParams[paramName].allowedValues.indexOf(paramVal) !== -1
+ );
+ }
+
+ return false;
+ })
+ .join('&');
+
+ return filteredParams.length > 0 ? urlPrefix + '?' + filteredParams : urlPrefix;
+}
+
+/**
+ * Returns the url with the hash removed
+ * @param {String} aUrl
+ * @return {String} the url with any hash removed
+ * @overridable
+ * @example
+ * withoutHash('https://itunes.apple.com:80/music?param1=abc¶m2=def¶m3=ghi#someHash')
+ * // returns 'https://itunes.apple.com:80/music?param1=abc¶m2=def¶m3=ghi'
+ */
+function withoutHash(aUrl) {
+ var url = aUrl || '';
+ return url.split('#')[0];
+}
+
+/**
+ * Returns the url with all of the string replacements applied
+ * @param {String} aUrl
+ * @param {Object} replacements The list of replacements that should be applied on the url
+ * @param {String} replacements.searchPattern A stringified regex pattern to search for
+ * @param {String} replacements.replaceVal The string to replace the match with
+ * @param {String} replacements.flags Regex flags to include with the search pattern (ex: 'g')
+ * @return {String} The url with all replacements applied
+ * @overridable
+ *
+ * @example
+ * withReplacements('https://itunes.apple.com:80/music?param1=abc¶m2=def¶m3=ghi#someHash', [
+ * {
+ * searchPattern: 'music',
+ * replaceVal: 'm'
+ * }
+ * ])
+ * // returns 'https://itunes.apple.com:80/m?param1=abc¶m2=def¶m3=ghi#someHash'
+ *
+ * withReplacements('https://apple.com/1234', [
+ * {
+ * searchPattern: '\d',
+ * replaceVal: 'X',
+ * flags: 'g'
+ * }
+ * ])
+ * // returns 'https://apple.com/XXXX'
+ */
+function withReplacements(aUrl, replacements) {
+ var url = aUrl || '';
+ var urlReplacements = replacements || [];
+
+ var replacedUrl = urlReplacements.reduce(function (url, replacement) {
+ var searchPattern = new RegExp(replacement.searchPattern, replacement.flags);
+ var replaceVal = replacement.replaceVal;
+ return url.replace(searchPattern, replaceVal);
+ }, url);
+
+ return replacedUrl;
+}
+
+/*
+ * src/utils/id_generator.js
+ * mt-client-constraints
+ *
+ * Copyright © 2023 Apple Inc. All rights reserved.
+ *
+ */
+
+var ID_SEPARATOR = '-';
+var DEFAULT_GENERATED_ID_SEPARATOR = 'z';
+
+/**
+ * @param {Object} options includes - information about how to generate an ID
+ * @param {Number} options.idVersion - the version of the ID
+ * @param {Number} options.time - the time to be a part of the ID (optional)
+ * @param {String} options.generatedIdSeparator - a token-separated hex string of metadata to attach to a ID (optional) default to "z"
+ * @return {String} a generated ID
+ */
+function generateId(options) {
+ if (!reflect.isDefinedNonNull(options) || !reflect.isInteger(options.idVersion)) {
+ return '0';
+ }
+ var uuid = string.uuid();
+ var generatedIdSeparator = options.generatedIdSeparator || DEFAULT_GENERATED_ID_SEPARATOR;
+ var idString = generatedIdMetadata(options) + ID_SEPARATOR + uuid || '';
+
+ var convertedIdString = idString
+ .split(ID_SEPARATOR)
+ .map(function (segment) {
+ var segmentAsNumber = parseInt(segment, 16);
+ return string.convertNumberToBaseAlphabet(segmentAsNumber, string.base61Alphabet);
+ })
+ .join(generatedIdSeparator);
+
+ return convertedIdString;
+}
+
+/**
+ * @param {Object} options includes - information about how to generate an ID
+ * @param {Number} options.idVersion - the version of the ID
+ * @param {Number} options.time - the time to be a part of the ID (optional)
+ * @return {String} a token-separated hex string of metadata to attach to a ID,
+ */
+function generatedIdMetadata(options) {
+ var parameters = [options.idVersion];
+
+ if (options.time) {
+ parameters.push(options.time);
+ }
+
+ return parameters
+ .map(function (param) {
+ return param.toString(16);
+ })
+ .join(ID_SEPARATOR);
+}
+
+/*
+ * src/field_actions/id_action/time_based_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * The time-based ID generating strategy
+ * The ID value will change after its lifespan expires
+ */
+function constrainedValue(idString, idRules, eventData, fieldName) {
+ var storageKey = this.storageKey(fieldName, eventData, idRules);
+ var environment = this._constraintsInstance.system.environment;
+ var idData = storage.objectFromStorage(environment.localStorageObject(), storageKey) || {};
+
+ idData.value = this.idString(idData, idRules);
+ if (
+ this.rulesHaveLifespan(idRules) &&
+ (!reflect.isNumber(idData.expirationTime) || this.timeExpired(idData.expirationTime))
+ ) {
+ idData.expirationTime = this.expirationTime(idRules.lifespan);
+ }
+
+ storage.saveObjectToStorage(environment.localStorageObject(), storageKey, idData);
+ idString = idData.value;
+
+ return idString;
+}
+
+/*
+ * src/field_actions/id_action/session_time_based_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+// @private
+// A global cache storage to store the served clientIds by the clientId storageKey
+// Make it as a global variable to ensure the cached clientId can be shared between multiple MK instances
+var _sessionIdCache = {};
+
+/**
+ * The user-session-based + time-based ID generating strategy
+ * When the id getting expired, this function will return a consistent ID until the current user session ends, even if the ID is scheduled to expire in the middle of the session
+ */
+function constrainedValue$1(idString, idRules, eventData, fieldName) {
+ var storageKey = this.storageKey(fieldName, eventData, idRules);
+ var returnedIdString = _sessionIdCache[storageKey];
+
+ if (!returnedIdString) {
+ returnedIdString = constrainedValue.apply(this, arguments);
+ _sessionIdCache[storageKey] = returnedIdString;
+ }
+ return returnedIdString;
+}
+
+/*
+ * src/field_actions/id_action/id_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var STORAGE_KEY_SEPARATOR = '_';
+var MT_ID_NAMESPACE = 'mtId';
+
+var IdAction = function IdAction() {
+ Base$1.apply(this, arguments);
+};
+
+IdAction.prototype = Object.create(Base$1.prototype);
+IdAction.prototype.constructor = IdAction;
+
+/*
+ * Possible strategies that can be used to scope an ID value
+ */
+IdAction.prototype.SCOPE_STRATEGIES = {
+ ALL: 'all',
+ MAIN_DOMAIN: 'mainDomain'
+};
+
+/**
+ * @param {Object} (optional) idRules includes information about when to expire the ID
+ * @return {Boolean}
+ */
+IdAction.prototype.rulesHaveLifespan = function rulesHaveLifespan(idRules) {
+ idRules = idRules || {};
+
+ return reflect.isNumber(idRules.lifespan);
+};
+
+/**
+ * @param {Number} (optional) lifespan the amount of time, in milliseconds, that an ID should be valid for
+ * @return {Number} a timestamp in ms since epoch, or null if no lifespan was provided
+ */
+IdAction.prototype.expirationTime = function expirationTime(lifespan) {
+ return lifespan ? Date.now() + lifespan : null;
+};
+
+/**
+ * @param {String} fieldName - name of the field being field_actions in eventData
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} idRules includes information about how to namespace/scope the id
+ * @return {String} the key that id data should be stored under
+ * @example
+ * (storageKeyPrefix ? storageKeyPrefix : mtId_)__(scopeStrategy ? : 'all')
+ * @overridable
+ */
+IdAction.prototype.storageKey = function storageKey(fieldName, eventData, idRules) {
+ var scope = this.scope(eventData, idRules);
+ return this.storageKeyPrefix(idRules, fieldName) + (scope ? STORAGE_KEY_SEPARATOR + scope : '');
+};
+
+/**
+ * @param {Object} idRules includes information about how to namespace/scope the id
+ * @param {String} fieldName - name of the field being field_actions in eventData
+ * @return {String} a prefix to be used when storing id data in localStorage
+ * @overridable
+ */
+IdAction.prototype.storageKeyPrefix = function storageKeyPrefix(idRules, fieldName) {
+ return idRules && reflect.isString(idRules.storageKeyPrefix) && idRules.storageKeyPrefix.length > 0
+ ? idRules.storageKeyPrefix
+ : MT_ID_NAMESPACE + STORAGE_KEY_SEPARATOR + fieldName;
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} idRules includes information about how to namespace/scope the id
+ * @return {String} the namespace/scope for this set of event data and rules
+ * @overridable
+ */
+IdAction.prototype.scope = function scope(eventData, idRules) {
+ var idKey = '';
+
+ if (idRules) {
+ if (idRules.namespace) {
+ idKey += idRules.namespace;
+ }
+ if (idRules.scopeStrategy) {
+ var domainScope;
+
+ switch (idRules.scopeStrategy) {
+ case this.SCOPE_STRATEGIES.MAIN_DOMAIN:
+ var scopeFieldName = idRules.scopeFieldName;
+ domainScope = mainDomain(eventData[scopeFieldName]) || 'unknownDomain';
+ break;
+ case this.SCOPE_STRATEGIES.ALL: /* fall through */
+ default:
+ // no scope
+ domainScope = this.SCOPE_STRATEGIES.ALL;
+ break;
+ }
+
+ if (idKey.length) {
+ idKey += STORAGE_KEY_SEPARATOR;
+ }
+ idKey += domainScope;
+ }
+ }
+
+ return idKey;
+};
+
+/**
+ * @param {Object} (optional) existingIdData
+ * @param {Object} (optional) idRules includes information about when to expire the ID
+ * @return {String} an ID
+ * @overridable
+ */
+IdAction.prototype.idString = function idString(existingIdData, idRules) {
+ var existingId = existingIdData ? existingIdData.value : null;
+ var returnValue = existingId;
+
+ if (
+ !existingId ||
+ (reflect.isNumber(existingIdData.expirationTime) && this.timeExpired(existingIdData.expirationTime))
+ ) {
+ returnValue = this.generateId(idRules);
+ }
+
+ return returnValue;
+};
+
+/**
+ * @param {Object} (optional) idRules includes information about how to constrain the field
+ * @return {String} a generated ID
+ * @overridable
+ * @see comments in the related MTClientId.java
+ */
+IdAction.prototype.generateId = function generateId$1(idRules) {
+ idRules = idRules || {};
+ return generateId({
+ idVersion: this.generatedIdVersion(),
+ time: this.expirationTime(idRules.lifespan),
+ generatedIdSeparator: this.generatedIdSeparator(idRules.tokenSeparator)
+ });
+};
+
+/**
+ * @return {Number} the version of the generated ID
+ * @overridable
+ */
+IdAction.prototype.generatedIdVersion = function generatedIdVersion() {
+ return 4;
+};
+
+/**
+ * @return {String} the separator used to tokenize sections of an unformatted ID string
+ * @overridable
+ */
+IdAction.prototype.idTokenSeparator = function idTokenSeparator() {
+ return '-';
+};
+
+/**
+ * @param {String} (optional) separator
+ * @return {String} the separator used to tokenize sections of a finalized, formatted ID string
+ * @overridable
+ */
+IdAction.prototype.generatedIdSeparator = function generatedIdSeparator(separator) {
+ return separator || 'z';
+};
+
+/**
+ * @param {Number} timestamp a timestamp in ms since epoch
+ * @return {Boolean}
+ * @overridable
+ */
+IdAction.prototype.timeExpired = function timeExpired(timestamp) {
+ return timestamp <= Date.now();
+};
+
+/**
+ * @param {String} idString - the ID field in eventData
+ * @param {Object} idRules - includes information about how to constrain the field
+ * @param {String}(optional) idRules.storageKeyPrefix - a prefix to be used when storing ID data in localStorage, default is MT_ID_NAMESPACE
+ * @param {String}(optional) idRules.namespace - a string to be used when storing ID data in localStorage.
+ * @param {String}(optional) idRules.scopeStrategy - a strategy that can be used to scope a ID value [all/mainDomain]
+ * @param {String}(optional) idRules.scopeFieldName - name of the scope field in eventData, the value would be an URL and used to get the main domain as a part of scope. It is used when parameters.scopeStrategy set to "mainDomain"
+ * @param {String}(optional) idRules.tokenSeparator - the separator used to tokenize sections of a finalized, formatted ID string. Default is 'z'
+ * @param {Integer}(optional) idRules.lifespan - the expiration period for the ID (milliseconds)
+ * @param {Boolean}(optional) idRules.persistIdForSession - a boolean to indicate whether to persist the ID until the current user session ends, even if it is scheduled to expire in the middle of the session
+ * @param {Object} eventData - a dictionary of event data, which should include a pre-existing (unconstrained) ID field
+ * @param {String} fieldName - name of the field being field_actions in eventData
+ * @return {String} the constrained ID
+ */
+IdAction.prototype.constrainedValue = function constrainedValue$2(idString, idRules, eventData, fieldName) {
+ if (eventData && idRules && !reflect.isEmptyObject(idRules)) {
+ if (idRules.persistIdForSession === true) {
+ idString = constrainedValue$1.apply(this, arguments);
+ } else {
+ idString = constrainedValue.apply(this, arguments);
+ }
+ }
+ return idString;
+};
+
+/*
+ * src/field_handlers/client_id.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides methods to manage clientId field constraints.
+ * @constructor
+ */
+var ClientId = function ClientId(base, constraintsInstance) {
+ // @private
+ this._base = base;
+ // @private
+ this._idAction = new IdAction(constraintsInstance);
+ this._idAction.setDelegate({
+ storageKey: function storageKey(fieldName, eventData, idRules) {
+ return this.storageKeyPrefix() + '_' + this.scope(eventData, idRules);
+ }.bind(this._idAction),
+ storageKeyPrefix: function storageKeyPrefix() {
+ return 'mtClientId';
+ }
+ });
+};
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * @param {Object} eventFields a dictionary of event data, which may include a pre-existing (unconstrained) clientId
+ * @param {Object} clientIdRules includes information about when to expire the clientId and how to namespace/scope it
+ * @return {String} a clientId that adheres to the provided rules
+ * @overridable
+ */
+ClientId.prototype.constrainedValue = function constrainedValue(eventFields, clientIdRules) {
+ // adapt expirationPeriod to lifespan
+ var clonedRules = clientIdRules;
+ if (clientIdRules && reflect.isNumber(clientIdRules.expirationPeriod)) {
+ clonedRules = reflect.extend({}, clientIdRules);
+ clonedRules.lifespan = clonedRules.expirationPeriod;
+ delete clonedRules.expirationPeriod;
+ }
+ var clientId = eventFields ? eventFields.clientId : null;
+ var clientIdString = this._idAction.performAction(clientId, 'clientId', eventFields, clonedRules);
+
+ return this._base.applyConstraintRules(clientIdString, clientIdRules);
+};
+
+/*
+ * src/field_actions/url_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var UrlAction = function UrlAction() {
+ Base$1.apply(this, arguments);
+};
+
+UrlAction.prototype = Object.create(Base$1.prototype);
+UrlAction.prototype.constructor = UrlAction;
+
+/*
+ * Possible truncation strategies that can be applied to a parentPageUrl
+ */
+UrlAction.prototype.SCOPES = {
+ HOSTNAME: 'hostname',
+ FULL: 'full',
+ FULL_WITHOUT_PARAMS: 'fullWithoutParams',
+ FULL_WITH_REPLACEMENTS: 'fullWithReplacements'
+};
+
+/**
+ * @param {String} url - the URL field in eventData
+ * @param {Object} fieldRules - includes information about how to constrain the field
+ * @return {String} the constrained URL
+ */
+UrlAction.prototype.constrainedValue = function constrainedValue(url, fieldRules) {
+ if (url && fieldRules && fieldRules.scope) {
+ switch (fieldRules.scope) {
+ case this.SCOPES.HOSTNAME:
+ url = hostname(url);
+ break;
+ case this.SCOPES.FULL_WITHOUT_PARAMS:
+ url = withoutParams(url, fieldRules.allowedParams);
+ break;
+ case this.SCOPES.FULL_WITH_REPLACEMENTS:
+ url = withReplacements(url, fieldRules.replacements);
+ break;
+ case this.SCOPES.FULL: /* fall through */
+ }
+ }
+
+ return url;
+};
+
+/*
+ * src/field_handlers/parent_page_url.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides methods to manage parentPageUrl field constraints.
+ * @constructor
+ */
+var ParentPageUrl = function ParentPageUrl(base) {
+ // @private
+ this._base = base;
+ // @private
+ this._urlAction = new UrlAction();
+};
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * @param {Object} eventFields a dictionary of event data, which should include a pre-existing (unconstrained) parentPageUrl
+ * @param {Object} parentPageUrlRules includes information about whether to strip certain parts of the URL
+ * @return {String} a parentPageUrl modified according to the provided rules
+ * @overridable
+ */
+ParentPageUrl.prototype.constrainedValue = function constrainedValue(eventFields, parentPageUrlRules) {
+ var parentPageUrl = eventFields ? eventFields.parentPageUrl : null;
+ var modifiedUrl = this._urlAction.performAction(parentPageUrl, 'parentPageUrl', eventFields, parentPageUrlRules);
+ return this._base.applyConstraintRules(modifiedUrl, parentPageUrlRules);
+};
+
+/*
+ * src/field_handlers/index.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * @deprecated the field handlers has been deprecated and replaced with "De-res treatments(src/field_actions/*)"
+ * @param constraintsInstance
+ * @constructor
+ */
+var FieldHandlers = function (constraintsInstance) {
+ this.base = new Base(constraintsInstance);
+ this.clientId = new ClientId(this.base, constraintsInstance);
+ this.parentPageUrl = new ParentPageUrl(this.base, constraintsInstance);
+};
+
+/*
+ * src/treatment/legacy_treatment.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var LegacyTreatment = function LegacyTreatment(constraintInstance) {
+ // @private
+ this._fieldHandlers = new FieldHandlers(constraintInstance);
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} eventConstraints a set of constraints to apply to this event
+ * @return {Object} the event data modified according to the appropriate constraints
+ * Note: event fields will be modified in place and also returned
+ * @example
+ * var eventData = {
+ * eventType: 'click',
+ * pageType: 'TopCharts',
+ * parentPageUrl: 'https://itunes.apple.com/music/topcharts/12345',
+ * // etc.
+ * };
+ * var eventConstraints = {
+ * fieldConstraints: { parentPageUrl: { scope: 'hostname' } }
+ * }
+ * legacyTreatment.applyConstraints(eventData, eventConstraints) =>
+ * {
+ * eventType: click,
+ * pageType: 'TopCharts',
+ * parentPageUrl: 'itunes.apple.com', // truncated to hostname only
+ * etc... // all other fields remain the same
+ * }
+ */
+LegacyTreatment.prototype.applyConstraints = function applyConstraints(eventData, eventConstraints) {
+ if (eventConstraints && eventConstraints.fieldConstraints) {
+ eventData = this.applyFieldConstraints(eventData, eventConstraints.fieldConstraints);
+ }
+
+ return eventData;
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} fieldConstraints a set of constraints to apply to fields in this event, keyed by field name
+ * @return {Object} the event data modified according to the appropriate constraints
+ * Note: event fields will be modified in place and also returned
+ */
+LegacyTreatment.prototype.applyFieldConstraints = function applyFieldConstraints(eventData, fieldConstraints) {
+ if (fieldConstraints) {
+ var constrainedFieldValues = {};
+ var constrainedValue;
+ var fieldRules;
+ var fieldName;
+
+ for (fieldName in fieldConstraints) {
+ fieldRules = fieldConstraints[fieldName];
+ if (
+ eventData.hasOwnProperty(fieldName) ||
+ fieldRules.generateValue === true ||
+ fieldRules.hasOwnProperty(FIELD_RULES.OVERRIDE_FIELD_VALUE)
+ ) {
+ if (fieldName in this._fieldHandlers) {
+ constrainedValue = this._fieldHandlers[fieldName].constrainedValue(eventData, fieldRules);
+ } else {
+ constrainedValue = this._fieldHandlers.base.constrainedValue(eventData, fieldRules, fieldName);
+ }
+
+ constrainedFieldValues[fieldName] = constrainedValue;
+ }
+ }
+
+ // assign constrained values only after all of them have been calculated
+ // in case some constrained values depend on more than one original field values
+ for (fieldName in constrainedFieldValues) {
+ eventData[fieldName] = constrainedFieldValues[fieldName];
+ }
+
+ eventData = eventFields.mergeAndCleanEventFields(eventData);
+ }
+
+ return eventData;
+};
+
+/*
+ * src/utils/constraint_generator
+ * mt-client-constraints
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Adding/replacing the rule properties of targetRules with the ones of newRules
+ * @param {Object} targetRules - an object contains an property with field rules in it.
+ * @param {Object} newRules - an object contains an property with new field rules in it.
+ * @param {String} fieldRulesKeyName - the field rule property name
+ * @param {Function} initialFieldRules - a callback function to decide the target field rule object. Function signature: function (targetRules, sourceRules, fieldName)
+ * @returns {Object} updated target rules, all replacements are in-place updating unless the passed-in targetRules object does not exist
+ */
+function updateFieldRulesets(targetRules, newRules, fieldRulesKeyName, initialFieldRules) {
+ var updatedRules = targetRules || {};
+ initialFieldRules =
+ initialFieldRules ||
+ function (targetRules, sourceRules, fieldName) {
+ return targetRules[fieldName] || {};
+ };
+
+ if (newRules && newRules[fieldRulesKeyName]) {
+ var fieldName;
+ var propertyName;
+ var updatedFieldRules = updatedRules[fieldRulesKeyName] || {};
+ updatedRules[fieldRulesKeyName] = updatedFieldRules;
+
+ // copying the top level rules over is sufficient for a simple rule structure
+ for (fieldName in newRules[fieldRulesKeyName]) {
+ var fieldRules = initialFieldRules(updatedFieldRules, newRules[fieldRulesKeyName], fieldName);
+ updatedFieldRules[fieldName] = fieldRules;
+ for (propertyName in newRules[fieldRulesKeyName][fieldName]) {
+ fieldRules[propertyName] = newRules[fieldRulesKeyName][fieldName][propertyName];
+ }
+ }
+ }
+
+ return updatedRules;
+}
+
+/*
+ * src/constraint_generator/legacy_constraint_generator.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var LegacyConstraintGenerator = function LegacyConstraintGenerator(constraintInstance) {
+ this.treatment = new LegacyTreatment(constraintInstance);
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} topicConfig the AMP Metrics topic config to use to look up constraint_generator values
+ * @param {String}(optional) topic defines the AMP Analytics "topic" to look up the constraint profile
+ * @return {Object} a set of constraints to apply to this event
+ * @overridable
+ * Constraint rules will be applied in the order they are provided in config.
+ * @example
+ * Given the following config:
+ * { constraints: {
+ * defaultProfile: 'strict',
+ * profiles: {
+ * strict: {
+ * precedenceOrderedRules: [
+ * {
+ * filters: 'any',
+ * fieldConstraints: {
+ * clientId: {
+ * generateValue: true,
+ * tokenSeparator: 'z',
+ * scopeFieldName: 'parentPageUrl',
+ * scopeStrategy: 'mainDomain', // apple.com or apple.co.uk
+ * expirationPeriod: 86400000 // 24h
+ * },
+ * parentPageUrl: {
+ * scope: 'hostname' // www.apple.com
+ * }
+ * }
+ * },
+ * {
+ * filters: {
+ * valueMatches: {
+ * eventType: ['click'],
+ * actionType: ['signUp']
+ * },
+ * nonEmptyFields: ['pageHistory']
+ * },
+ * fieldConstraints: {
+ * parentPageUrl: {
+ * scope: 'fullWithoutParams'
+ * }
+ * }
+ * },
+ * {
+ * filters: {
+ * valueMatches: {
+ * userType: ['signedIn']
+ * }
+ * },
+ * fieldConstraints: {
+ * clientId: {
+ * scopeStrategy: 'all'
+ * },
+ * dsId: { blacklisted: true }
+ * }
+ * }
+ * ]
+ * }
+ * }
+ * } }
+ *
+ * new Constraints(config).constraintsForEvent({ eventType: 'click', userType: 'signedIn', actionType: 'navigate', ... }) returns:
+ * {
+ * fieldConstraints: {
+ * clientId: {
+ * generateValue: true, // from 'any' match
+ * tokenSeparator: 'z', // from 'any' match
+ * scopeFieldName: 'parentPageUrl', // from 'any' match
+ * scopeStrategy: 'all', // from userType=signdIn match
+ * expirationPeriod: 86400000 // from 'any' match
+ * },
+ * dsId: { blacklisted: true }, // from userType=signedIn match
+ * parentPageUrl: {
+ * scope: 'hostname' // from 'any' match
+ * // (the event did not match the rule with eventType=click,
+ * // actionType=signUp, nonEmpty pageHistory)
+ * }
+ * }
+ * }
+ */
+LegacyConstraintGenerator.prototype.constraintsForEvent = function constraintsForEvent(eventData, topicConfig, topic) {
+ if (!topicConfig) {
+ return Promise.resolve(null);
+ }
+ var self = this;
+
+ // Use Promise.resolve to wrap the constraintProfiles() here in case of the client delegate the constraintProfile method and returns a non-promise value
+ return Promise.resolve(topicConfig.constraintProfile(topic))
+ .then(function (constraintProfile) {
+ if (!constraintProfile) {
+ return null;
+ }
+ var profilePath = 'constraints.profiles.' + constraintProfile;
+ return topicConfig.value(profilePath, topic);
+ })
+ .then(function (constraintsConfig) {
+ var constraints = null;
+
+ if (constraintsConfig && constraintsConfig.precedenceOrderedRules) {
+ constraints = constraintsConfig.precedenceOrderedRules.reduce(function (accumulatedRules, rule) {
+ if (self.eventMatchesRule(eventData, rule)) {
+ accumulatedRules = self.updateRules(accumulatedRules, rule);
+ }
+
+ return accumulatedRules;
+ }, {});
+ }
+
+ return constraints;
+ });
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} matchRule contains information about whether an event matches a rule
+ * @return {Boolean}
+ * @example
+ * var event = { eventType: 'click', userType: 'signedIn', actionType: 'navigate', ... };
+ * var matchRule = {
+ * filters: {
+ * valueMatches: {
+ * eventType: ['click']
+ * },
+ * nonEmptyFields: ['actionType']
+ * },
+ * fieldConstraints: { ... }
+ * };
+ * eventMatchesRule(event, matchRule); // => true
+ */
+LegacyConstraintGenerator.prototype.eventMatchesRule = function eventMatchesRule(eventData, matchRule) {
+ var returnValue = false;
+
+ if (eventData && matchRule.filters) {
+ if (matchRule.filters === 'any') {
+ returnValue = true;
+ } else if (reflect.isObject(matchRule.filters)) {
+ returnValue =
+ this.eventMatchesNonEmptyFields(eventData, matchRule.filters.nonEmptyFields) &&
+ this.eventMatchesFieldValues(eventData, matchRule.filters.valueMatches);
+ }
+ }
+
+ return returnValue;
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Array} nonEmptyFieldNames
+ * @return {Boolean}
+ */
+LegacyConstraintGenerator.prototype.eventMatchesNonEmptyFields = function eventMatchesNonEmptyFields(
+ eventData,
+ nonEmptyFieldNames
+) {
+ var returnValue = false;
+
+ if (eventData) {
+ if (!nonEmptyFieldNames || !reflect.isArray(nonEmptyFieldNames)) {
+ returnValue = true;
+ } else {
+ returnValue = nonEmptyFieldNames.every(function (fieldName) {
+ return matchers.nonEmpty(fieldName, eventData);
+ });
+ }
+ }
+
+ return returnValue;
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} valueMatches a mapping of field names to lists of possible values to match for that field. A matching field value is determined using strict equality. Field values can be of any primitive type.
+ * @return {Boolean}
+ */
+LegacyConstraintGenerator.prototype.eventMatchesFieldValues = function eventMatchesFieldValues(
+ eventData,
+ valueMatches
+) {
+ var returnValue = false;
+
+ if (eventData) {
+ if (!valueMatches || !reflect.isObject(valueMatches) || reflect.isEmptyObject(valueMatches)) {
+ returnValue = true;
+ } else {
+ returnValue = Object.keys(valueMatches).every(function (fieldMatchName) {
+ var fieldMatchValues = valueMatches[fieldMatchName];
+ return matchers.valueMatches(fieldMatchName, eventData, fieldMatchValues);
+ });
+ }
+ }
+
+ return returnValue;
+};
+
+/**
+ * @param {Object} currentRules a dictionary of constraint rules
+ * @param {Object} newRule a dictionary of rule data to be added to the current rules
+ * @return {Object} the updated set of rules
+ * Note: the current rules will be modified in place and also returned
+ */
+LegacyConstraintGenerator.prototype.updateRules = function updateRules(currentRules, newRule) {
+ return updateFieldRulesets(currentRules, newRule, 'fieldConstraints');
+};
+
+/*
+ * src/event_actions/denylisted_event_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var DenylistedEventAction = function DenylistedEventAction() {};
+
+/**
+ * denylisting the event if the denylisted parameter is true
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} originalEventData the original event data
+ * @param {Boolean} isDenylisted true if denylisting the entire event
+ * @return {Object} return the passed-in eventData if denylisted not equals to false, otherwise, return "null"
+ */
+DenylistedEventAction.prototype.performAction = function performAction(eventData, originalEventData, isDenylisted) {
+ return isDenylisted !== true ? eventData : null;
+};
+
+/*
+ * src/event_actions/denylisted_fields_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+var DenylistedFieldsAction = function DenylistedFieldsAction() {};
+
+/**
+ * remove the denylisted event fields from the passed-in eventData
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} originalEventData the original event data
+ * @param {Array} denylistedFields the denylisted fields
+ * @return {Object} return a dictionary of event data that excluded the denylisted fields or "null" if all fields are removed.
+ */
+DenylistedFieldsAction.prototype.performAction = function performAction(
+ eventData,
+ originalEventData,
+ denylistedFields
+) {
+ if (!eventData || !reflect.isArray(denylistedFields) || reflect.isEmptyArray(denylistedFields)) {
+ return eventData;
+ }
+ eventData = reflect.extend({}, eventData);
+
+ denylistedFields.forEach(function (denylistedField) {
+ delete eventData[denylistedField];
+ });
+
+ return reflect.isEmptyObject(eventData) ? null : eventData;
+};
+
+/*
+ * src/event_actions/allowlisted_fields_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+var AllowlistedFieldsAction = function AllowlistedFieldsAction() {};
+
+/**
+ * remove the event fields from the passed-in eventData if it is not in the allowlistedFields
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} originalEventData the original event data
+ * @param {Array} allowlistedFields the allowlisted fields
+ * @return {Object} return a dictionary of event data that only included the allowlisted fields
+ */
+AllowlistedFieldsAction.prototype.performAction = function performAction(
+ eventData,
+ originalEventData,
+ allowlistedFields
+) {
+ // Ignoring an empty allowlistedFields to have consistent behavior with Native MetricsKit
+ if (!eventData || !reflect.isArray(allowlistedFields) || reflect.isEmptyArray(allowlistedFields)) {
+ return eventData;
+ }
+ var returnedData = {};
+
+ allowlistedFields.forEach(function (allowlistedField) {
+ if (reflect.isDefinedNonNull(eventData[allowlistedField])) {
+ returnedData[allowlistedField] = eventData[allowlistedField];
+ }
+ });
+
+ return !reflect.isEmptyObject(returnedData) ? returnedData : null;
+};
+
+/*
+ * src/event_actions/sessionization_fields_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2023 Apple Inc. All rights reserved.
+ *
+ */
+
+var MT_SESSIONIZATION_NAMESPACE = 'mtSessionization';
+var STORAGE_KEY_SEPARATOR$1 = '_';
+var ID_TOKEN_SEPARATOR = '-';
+var SESSION_ID_KEY = 'sessionId';
+var SESSION_START_TIME_KEY = 'sessionStartTime';
+var SessionizationFieldsAction = function SessionizationFieldsAction(constraintsInstance) {
+ this._constraintsInstance = constraintsInstance;
+};
+
+/**
+ * attach the session related fields to the event
+ * @param {Object} eventData - a dictionary of event data
+ * @param {Object} originalEventData - the original event data
+ * @param {Object} sessionRules - the session rule object
+ * @param {String}(optional) sessionRules.storageKeyPrefix - a prefix to be used when storing ID data in localStorage, default is "mtSessionization"
+ * @param {String}(optional) sessionRules.namespace - a string to be used when storing session metadata in localStorage.
+ * @param {String}(optional) sessionRules.scopeFieldName - the field name to indicate the scope for the session metadata
+ * @param {Number}(optional) sessionRules.idVersion - the version of the session ID
+ * @param {String}(optional) sessionRules.tokenSeparator - the separator used to tokenize sections of a finalized, formatted ID string. Default is 'z'
+ * @param {Boolean}(optional) sessionRules.sessionStartTime - the flag to indicate whether record "sessionStartTime". Default is false
+ * @param {String}(optional) sessionRules.endSessionConditions - the object that contains the conditions to end the existing session
+ * @param {String}(optional) sessionRules.endSessionConditions.lifespan - the maximum lifespan for the session (milliseconds)
+ * @param {String}(optional) sessionRules.endSessionConditions.idleSpan - the maximum idle span to end the session (milliseconds)
+ * @param {String}(optional) sessionRules.endSessionConditions.eventCount - the maximum event count for the session
+ * @param {Object}(optional) sessionRules.sessionResetOptions - the reset session options to determine resetting session (deleting the session metadata from storage)
+ * @param {Object} sessionRules.sessionResetOptions.filters - the filters-like conditions to execute the session reset
+ * @param {Boolean}(optional) sessionRules.sessionResetOptions.newSessionAfterReset - the flag to indicate whether generate a new session after resetting the previous session. Default is false
+ * @param {Object}(optional) sessionRules.sessionFields - the mapping of the session field in the event payload
+ * @return {Object} return a dictionary of event data that included the session fields
+ */
+SessionizationFieldsAction.prototype.performAction = function performAction(
+ eventData,
+ originalEventData,
+ sessionRules
+) {
+ if (!reflect.isDefinedNonNull(eventData) || !reflect.isDefined(sessionRules)) {
+ return eventData;
+ }
+
+ if (reflect.isDefinedNonNull(sessionRules.sessionResetOptions)) {
+ if (!reflect.isDefinedNonNull(sessionRules.sessionResetOptions.filters)) {
+ throw new SyntaxError('sessionizationFields Action: unable to find the required config "filters"');
+ }
+ var newSessionAfterReset = this._resetSession(eventData, originalEventData, sessionRules);
+ if (newSessionAfterReset !== true) {
+ return eventData;
+ }
+ }
+
+ eventData = reflect.extend({}, eventData);
+
+ var storageKey = this._storageKey(eventData, sessionRules);
+ var environment = this._constraintsInstance.system.environment;
+ var sessionMetadata = storage.objectFromStorage(environment.localStorageObject(), storageKey) || {};
+
+ if (this._shouldCreateNewSession(originalEventData, sessionMetadata, sessionRules)) {
+ sessionMetadata.sessionId = this._generateSessionId(sessionRules);
+ sessionMetadata.rawFirstEventTimeInSession = originalEventData.eventTime;
+ sessionMetadata.firstEventTimeInSession = eventData.eventTime;
+ sessionMetadata.eventCount = 0;
+ }
+ sessionMetadata.rawLastEventTimeInSession = originalEventData.eventTime;
+ sessionMetadata.lastEventTimeInSession = eventData.eventTime;
+ sessionMetadata.eventCount += 1;
+
+ storage.saveObjectToStorage(environment.localStorageObject(), storageKey, sessionMetadata);
+
+ var sessionFieldMap = this._getSessionFieldNames(sessionRules);
+ eventData[sessionFieldMap.sessionId] = sessionMetadata.sessionId;
+ if (sessionRules.sessionStartTime) {
+ eventData[sessionFieldMap.sessionStartTime] = sessionMetadata.firstEventTimeInSession;
+ }
+
+ return eventData;
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} sessionRules includes information about how to namespace/scope the session data
+ * @return {String} the key that session data should be stored under
+ * @example
+ * (storageKeyPrefix ? storageKeyPrefix : mtSessionization)__(scopeFieldName ? : '')
+ */
+SessionizationFieldsAction.prototype._storageKey = function _storageKey(eventData, sessionRules) {
+ var scope = this._scope(eventData, sessionRules);
+ return this._storageKeyPrefix(sessionRules) + (!reflect.isEmptyString(scope) ? STORAGE_KEY_SEPARATOR$1 + scope : '');
+};
+
+/**
+ * @param {Object} sessionRules includes information about how to namespace/scope the session data
+ * @return {String} a prefix to be used when storing session data in localStorage
+ */
+SessionizationFieldsAction.prototype._storageKeyPrefix = function _storageKeyPrefix(sessionRules) {
+ return sessionRules && reflect.isString(sessionRules.storageKeyPrefix) && sessionRules.storageKeyPrefix.length > 0
+ ? sessionRules.storageKeyPrefix
+ : MT_SESSIONIZATION_NAMESPACE;
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} sessionRules includes information about how to namespace/scope the session data
+ * @return {String} the namespace/scope for this set of event data and rules
+ */
+SessionizationFieldsAction.prototype._scope = function _scope(eventData, sessionRules) {
+ var sessionScope = '';
+
+ if (reflect.isDefined(sessionRules)) {
+ if (reflect.isString(sessionRules.namespace)) {
+ sessionScope += sessionRules.namespace;
+ }
+
+ if (
+ reflect.isString(sessionRules.scopeFieldName) &&
+ reflect.isDefinedNonNull(eventData[sessionRules.scopeFieldName])
+ ) {
+ sessionScope += STORAGE_KEY_SEPARATOR$1;
+ sessionScope += eventData[sessionRules.scopeFieldName].toString();
+ }
+ }
+
+ return sessionScope;
+};
+
+/**
+ * generate session ID based on the provided session rules
+ * @param {Object} sessionRules
+ * @returns {String} the generated session ID
+ * @private
+ */
+SessionizationFieldsAction.prototype._generateSessionId = function _generateSessionId(sessionRules) {
+ return generateId({
+ idVersion: 1,
+ time: Date.now(),
+ idTokenSeparator: ID_TOKEN_SEPARATOR,
+ generatedIdSeparator: sessionRules.tokenSeparator
+ });
+};
+
+/**
+ * Decide whether create a new session based on the current session metadata and the session rules
+ * @param sessionMetadata
+ * @param sessionRules
+ * @returns {Boolean}
+ * @private
+ */
+SessionizationFieldsAction.prototype._shouldCreateNewSession = function _shouldCreateNewSession(
+ event,
+ sessionMetadata,
+ sessionRules
+) {
+ var shouldCreateNewSession = false;
+ shouldCreateNewSession |= !reflect.isDefinedNonNull(sessionMetadata.sessionId);
+
+ if (reflect.isDefinedNonNull(sessionRules.endSessionConditions)) {
+ if (reflect.isDefinedNonNull(sessionRules.endSessionConditions.lifespan)) {
+ shouldCreateNewSession |=
+ event.eventTime >=
+ sessionMetadata.rawFirstEventTimeInSession + sessionRules.endSessionConditions.lifespan;
+ }
+
+ if (reflect.isDefinedNonNull(sessionRules.endSessionConditions.idleSpan)) {
+ shouldCreateNewSession |=
+ event.eventTime >=
+ sessionMetadata.rawLastEventTimeInSession + sessionRules.endSessionConditions.idleSpan;
+ }
+
+ if (reflect.isDefinedNonNull(sessionRules.endSessionConditions.eventCount)) {
+ shouldCreateNewSession |= sessionMetadata.eventCount >= sessionRules.endSessionConditions.eventCount;
+ }
+ }
+
+ return shouldCreateNewSession;
+};
+
+/**
+ * Reset the existing session based on the session rules
+ * @param {Object} eventData
+ * @param {Object} originalEventData
+ * @param {Object} sessionRules
+ * @returns {Boolean} The flag indicates whether to process the rest of logic
+ */
+SessionizationFieldsAction.prototype._resetSession = function _resetSession(
+ eventData,
+ originalEventData,
+ sessionRules
+) {
+ var sessionResetOptions = sessionRules.sessionResetOptions;
+ var constraintsGenerator = this._constraintsInstance._constraintGenerator;
+ if (
+ reflect.isDefinedNonNull(constraintsGenerator) &&
+ reflect.isDefinedNonNull(constraintsGenerator.eventMatchesTreatment) &&
+ constraintsGenerator.eventMatchesTreatment(originalEventData, sessionResetOptions)
+ ) {
+ var storageKey = this._storageKey(eventData, sessionRules);
+ var environment = this._constraintsInstance.system.environment;
+ storage.saveObjectToStorage(environment.localStorageObject(), storageKey, undefined);
+
+ return sessionResetOptions.newSessionAfterReset;
+ }
+ // Always continue the sessionization logic if the resetting is not applied.
+ return true;
+};
+
+/**
+ * Return a map from session field names to their associated event field names
+ * @param {Object} sessionRoles
+ * @returns {Object} A map between the session field names and their associated event field names
+ */
+SessionizationFieldsAction.prototype._getSessionFieldNames = function _getSessionFieldNames(sessionRoles) {
+ var sessionFieldNames = {
+ sessionId: SESSION_ID_KEY,
+ sessionStartTime: SESSION_START_TIME_KEY
+ };
+ if (reflect.isDefinedNonNull(sessionRoles.sessionFields)) {
+ if (reflect.isDefinedNonNullNonEmpty(sessionRoles.sessionFields[SESSION_ID_KEY])) {
+ sessionFieldNames.sessionId = sessionRoles.sessionFields[SESSION_ID_KEY];
+ }
+ if (reflect.isDefinedNonNullNonEmpty(sessionRoles.sessionFields[SESSION_START_TIME_KEY])) {
+ sessionFieldNames.sessionStartTime = sessionRoles.sessionFields[SESSION_START_TIME_KEY];
+ }
+ }
+
+ return sessionFieldNames;
+};
+
+/*
+ * src/event_actions/index.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var ACTIONS = {
+ blacklistedEventAction: 'blacklisted', // DEPRECATED, use denylistedEventAction instead
+ denylistedEventAction: 'denylisted',
+ blacklistedFieldsAction: 'blacklistedFields', // DEPRECATED, use denylistedFieldsAction instead
+ denylistedFieldsAction: 'denylistedFields',
+ whitelistedFieldsAction: 'whitelistedFields', // DEPRECATED, use allowlistedFieldsAction instead
+ allowlistedFieldsAction: 'allowlistedFields',
+ sessionizationFieldsAction: 'sessionizationFields'
+};
+
+var EventActions = function EventActions(constraintsInstance) {
+ var denylistedEventAction = new DenylistedEventAction();
+ var denylistedFieldsAction = new DenylistedFieldsAction();
+ var allowlistedFieldsAction = new AllowlistedFieldsAction();
+ var sessionizationFieldsAction = new SessionizationFieldsAction(constraintsInstance);
+
+ // @private
+ this._actions = {};
+ this._actions[ACTIONS.blacklistedEventAction] = denylistedEventAction; // mapping to equivalent but Inclusive Termed method
+ this._actions[ACTIONS.denylistedEventAction] = denylistedEventAction;
+ this._actions[ACTIONS.blacklistedFieldsAction] = denylistedFieldsAction; // mapping to equivalent but Inclusive Termed method
+ this._actions[ACTIONS.denylistedFieldsAction] = denylistedFieldsAction;
+ this._actions[ACTIONS.whitelistedFieldsAction] = allowlistedFieldsAction; // mapping to equivalent but Inclusive Termed method
+ this._actions[ACTIONS.allowlistedFieldsAction] = allowlistedFieldsAction;
+ this._actions[ACTIONS.sessionizationFieldsAction] = sessionizationFieldsAction;
+};
+
+EventActions.prototype.getAction = function getAction(actionName) {
+ return this._actions[actionName];
+};
+
+/*
+ * src/field_actions/number_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var START_KEY = 'start';
+var VALUE_KEY = 'value';
+
+/**
+ * Returns the index at which you should insert the object in order to maintain a sorted array
+ * @param {Array} array - The sorted array to inspect, must be a list without undefined/null values.
+ * @param {Number} value - The value to evaluate
+ * @returns {number} Returns the index at which `value` should be inserted, -1 if the value is less than the first item, the array or value is undefined/null
+ */
+var searchInsertionIndexOf = function searchInsertionIndexOf(array, value) {
+ var NOT_FOUND_OUTPUT = -1;
+ var index = NOT_FOUND_OUTPUT;
+
+ if (
+ !reflect.isDefinedNonNull(value) ||
+ array.length === 0 ||
+ // classify the numbers less than the lowest bucket
+ // -> array = [10, 20, 30], value = 9
+ // <- -1
+ (reflect.isDefinedNonNull(array[0]) && value < array[0][START_KEY])
+ ) {
+ return NOT_FOUND_OUTPUT;
+ }
+
+ // Using a linear search instead of binary search because the array won't be large and less error-prone
+ if (array[array.length - 1][START_KEY] < value) {
+ index = array.length - 1;
+ } else {
+ for (var i = 0; i < array.length; i++) {
+ var start = array[i][START_KEY];
+ if (start === value) {
+ index = i;
+ break;
+ } else if (start > value) {
+ index = i - 1;
+ break;
+ }
+ }
+ }
+
+ return index;
+};
+
+var NumberAction = function NumberAction() {
+ Base$1.apply(this, arguments);
+};
+
+NumberAction.prototype = Object.create(Base$1.prototype);
+NumberAction.prototype.constructor = NumberAction;
+
+/**
+ * @param {Number} aNumber - a number being constrained
+ * @param {Object} fieldRules - includes information about how to constrain the field
+ * @param {Number} fieldRules.precision - must be a positive integer
+ * @return {Number} the constrained number
+ */
+NumberAction.prototype.constrainedValue = function constrainedValue(aNumber, fieldRules) {
+ var precision = fieldRules ? fieldRules.precision : 0;
+ var buckets = fieldRules ? fieldRules.buckets : null;
+
+ if (reflect.isDefinedNonNullNonEmpty(buckets)) {
+ buckets = buckets.slice().sort(function (a, b) {
+ return a[START_KEY] - b[START_KEY];
+ });
+
+ var bucketIndex = searchInsertionIndexOf(buckets, aNumber);
+ var bucket = buckets[bucketIndex];
+
+ if (reflect.isDefinedNonNull(bucket)) {
+ aNumber = bucket[VALUE_KEY];
+ }
+ } else if (reflect.isNumber(aNumber) && reflect.isNumber(precision) && precision > 0) {
+ aNumber = Math.floor(aNumber / precision) * precision;
+ }
+
+ return aNumber;
+};
+
+/*
+ * src/utils/serial_number_generator.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var DEFAULT_NAMESPACE = 'mt_serial_number';
+var EXPIRATION_STORAGE_KEY = 'exp';
+var SERIAL_NUMBER_STORAGE_KEY = 'sn';
+
+/**
+ *
+ * @param {Object} options - An object that contains parameters for generating serial numbers
+ * @param {String} options.namespace (optional) - A key that is used to store the serial numbers in the Storage. Default to "mt_serial_number"
+ * @param {Number} options.initialSerialNumber (optional) - An initialization serial number. Default to 0
+ * @param {Number} options.nextRotationTime (optional) - A timestamp that indicates when should reset the serial number. Default to Number.POSITIVE_INFINITY(never rotate)
+ * @param {Number} options.rotationPeriod (optional) - A millisecond to indicate how long the serial number could be alive. Default to Number.POSITIVE_INFINITY(never rotate)
+ * @constructor
+ */
+var SerialNumberGenerator = function SerialNumberGenerator(options) {
+ options = options || {};
+ // @private
+ this._nextRotationTime = options.nextRotationTime || Number.POSITIVE_INFINITY;
+ // @private
+ this._storageKey = options.namespace || DEFAULT_NAMESPACE;
+ // @private
+ this._initialSerialNumber = options.initialSerialNumber || 0;
+ // @private
+ this._rotationPeriod = options.rotationPeriod || Number.POSITIVE_INFINITY;
+};
+
+SerialNumberGenerator.prototype.setDelegate = function setDelegate(delegate) {
+ reflect.attachDelegate(this, delegate);
+};
+
+SerialNumberGenerator.prototype.localStorageObject = function localStorageObject() {
+ return storage.localStorageObject();
+};
+
+/**
+ * Return the increased serial number
+ * @param {Number} increment (optional) - The amount to increment. Defaults to 1
+ * @returns {Number} the increased serial number
+ */
+SerialNumberGenerator.prototype.getNextSerialNumber = function getNextSerialNumber(increment) {
+ var storageKey = this._storageKey;
+ var serialNumberData = this._getCurrentSerialNumberData(storageKey);
+
+ var serialNum = serialNumberData[SERIAL_NUMBER_STORAGE_KEY];
+ increment = reflect.isNumber(increment) ? increment : 1;
+ serialNum = parseInt(serialNum, 10);
+
+ if (isNaN(serialNum)) {
+ // Reset the serial number to the initialized one, to ensure the logic won't break if the sequence number is an invalid number.
+ serialNum = this._initialSerialNumber;
+ }
+ serialNum = this._increaseSerialNumber(serialNum, increment);
+ // Store the increased serial number to storage
+ serialNumberData[SERIAL_NUMBER_STORAGE_KEY] = serialNum;
+ storage.saveObjectToStorage(this.localStorageObject(), this._storageKey, serialNumberData);
+
+ return serialNum;
+};
+
+/**
+ * Reset the serial number
+ */
+SerialNumberGenerator.prototype.resetSerialNumber = function resetSerialNumber() {
+ var serialNumberData = storage.objectFromStorage(this.localStorageObject(), this._storageKey);
+
+ if (reflect.isDefinedNonNull(serialNumberData)) {
+ this._resetSerialNumber(serialNumberData[EXPIRATION_STORAGE_KEY]);
+ }
+};
+
+/**
+ * Delegable method to return the time for calculating rotation
+ * @returns {number}
+ */
+SerialNumberGenerator.prototype.getTime = function getTime() {
+ return Date.now();
+};
+
+/**
+ * Increasing the giving serial number by plus one
+ * @param {Number} serialNum
+ * @returns {number}
+ * @private
+ */
+SerialNumberGenerator.prototype._increaseSerialNumber = function _increaseSerialNumber(serialNum, increment) {
+ return serialNum + increment;
+};
+
+/**
+ * Rotate and return the serial number data
+ * @param {String} storageKey
+ * @returns {Object} rotated serial number data
+ */
+SerialNumberGenerator.prototype._getCurrentSerialNumberData = function _getCurrentSerialNumberData(storageKey) {
+ var serialNumberData = storage.objectFromStorage(this.localStorageObject(), storageKey);
+ var rotationTime;
+ var nextRotationTime;
+ if (serialNumberData) {
+ rotationTime = serialNumberData[EXPIRATION_STORAGE_KEY];
+ rotationTime = parseInt(rotationTime, 10);
+ serialNumberData[EXPIRATION_STORAGE_KEY] = rotationTime = isNaN(rotationTime)
+ ? this._nextRotationTime
+ : rotationTime;
+ } else {
+ // use the "nextRotationTime - rotationPeriod" as the rotationTime if the serialNumberData is not existing in the storage, to check if need to reset serial number
+ rotationTime = this._nextRotationTime - this._rotationPeriod;
+ }
+
+ // Reset the serial number data if it has expired or never initialized
+ // Checking "!serialNumberData" in here to cover the case of when both of this._nextRotationTime and this._rotationPeriod are not provided, "this._nextRotationTime(Infinite) - this._rotationPeriod(Infinite) = NaN" which is always less than "this.getTime()"
+ // Use while loop here to catch up to the latest rotation time.
+ while (!serialNumberData || this.getTime() >= rotationTime) {
+ rotationTime = nextRotationTime = rotationTime + this._rotationPeriod;
+ serialNumberData = this._resetSerialNumber(nextRotationTime);
+ }
+ return serialNumberData;
+};
+
+/**
+ * Reset the serial number and expiration
+ * @param {Number} nextRotationTime - A timestamp that indicates when should reset the serial number
+ * @returns reset serialNumberData
+ * {
+ * exp: nextRotationTime,
+ * sn: serialNumber
+ * }
+ */
+SerialNumberGenerator.prototype._resetSerialNumber = function _resetSerialNumber(nextRotationTime) {
+ var serialNumberData = {};
+ serialNumberData[EXPIRATION_STORAGE_KEY] = nextRotationTime;
+ serialNumberData[SERIAL_NUMBER_STORAGE_KEY] = this._initialSerialNumber;
+ storage.saveObjectToStorage(this.localStorageObject(), this._storageKey, serialNumberData);
+ return serialNumberData;
+};
+
+/*
+ * src/field_actions/time_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var STORAGE_KEY_SEPARATOR$2 = '_';
+var STORAGE_PREFIX_DEFAULT = 'mtTimestamp';
+
+var TimeAction = function TimeAction() {
+ Base$1.apply(this, arguments);
+ // @private
+ this._storage = this._constraintsInstance.system.environment.localStorageObject();
+ // @private
+ /*
+ * Store the end time of the giving time precision base on namespace + time fields
+ */
+ this._precisionEndTimeCache = {};
+ // @private
+ this._serialNumberGenerator = null;
+};
+
+TimeAction.prototype = Object.create(Base$1.prototype);
+TimeAction.prototype.constructor = TimeAction;
+
+/**
+ * @param {Number} time - a timestamp being constrained
+ * @param {Object} fieldRules - includes information about how to constrain the field
+ * @param {Object} fieldRules.precision - The time must be a positive integer
+ * @param {String} fieldRules.storageKeyPrefix - a prefix to be used when storing timestamp de-res related data, default is "mt-timestamp"
+ * @param {String} fieldRules.namespace - a namespace for the timestamp de-res related data, default is empty.
+ * @param {Object} eventData - a dictionary of event data, which should include a pre-existing (unconstrained) field
+ * @param {String} fieldName - name of the field being modified in eventData
+ * @return {Number} the constrained time or the original value if time is not defined or fieldRules is unavailable
+ */
+TimeAction.prototype.constrainedValue = function constrainedValue(time, fieldRules, eventData, fieldName) {
+ var returnTimestamp = time;
+ if (
+ reflect.isNumber(time) &&
+ reflect.isObject(fieldRules) &&
+ reflect.isNumber(fieldRules.precision) &&
+ fieldRules.precision > 0
+ ) {
+ var precisionStartTime = this._computePrecisionStartTime(time, fieldRules);
+ this._serialNumberGenerator = new SerialNumberGenerator({
+ namespace: this._persistentStorageKey(fieldRules, fieldName),
+ nextRotationTime: precisionStartTime + fieldRules.precision,
+ rotationPeriod: fieldRules.precision
+ });
+ this._serialNumberGenerator.setDelegate(this._constraintsInstance.system.environment);
+ this._serialNumberGenerator.setDelegate({
+ getTime: function () {
+ return time;
+ }
+ });
+ var serialNumber = this._serialNumberGenerator.getNextSerialNumber();
+ returnTimestamp = this._computeTimestamp(precisionStartTime, serialNumber);
+ this._serialNumberGenerator = null; // Release the serial number generator.
+ }
+
+ return returnTimestamp;
+};
+
+TimeAction.prototype._computeTimestamp = function _computeTimestamp(precisionStartTime, sequenceNum) {
+ return precisionStartTime + sequenceNum;
+};
+
+TimeAction.prototype._persistentStorageKey = function _persistentStorageKey(fieldRules, fieldName) {
+ var namespaceSegment = fieldRules.namespace ? STORAGE_KEY_SEPARATOR$2 + fieldRules.namespace : '';
+ return (
+ (fieldRules.storageKeyPrefix || STORAGE_PREFIX_DEFAULT) + namespaceSegment + STORAGE_KEY_SEPARATOR$2 + fieldName
+ );
+};
+
+TimeAction.prototype._computePrecisionStartTime = function _computePrecisionStartTime(time, fieldRules) {
+ var precision = fieldRules.precision;
+ return Math.floor(time / precision) * precision;
+};
+
+/*
+ * src/field_actions/hash_action.js
+ * mt-client-constraints
+ *
+ * Copyright © 2022 Apple Inc. All rights reserved.
+ *
+ */
+
+var STORAGE_KEY_SEPARATOR$3 = '_';
+var STORAGE_PREFIX_DEFAULT$1 = 'mtHash';
+var STORAGE_SALT_KEY = 'salt';
+var SALT_CHAR_LENGTH = 10;
+
+var HashAction = function HashAction() {
+ Base$1.apply(this, arguments);
+};
+HashAction.prototype = Object.create(Base$1.prototype);
+HashAction.prototype.constructor = HashAction;
+
+/**
+ * Build the storage key for salt data
+ * key format: __salt_
+ * @param fieldRules
+ * @param fieldName
+ * @returns {string}
+ */
+function buildSaltStorageKey(fieldRules, fieldName) {
+ var namespaceSegment = fieldRules.namespace ? STORAGE_KEY_SEPARATOR$3 + fieldRules.namespace : '';
+ return (
+ (fieldRules.storageKeyPrefix || STORAGE_PREFIX_DEFAULT$1) +
+ namespaceSegment +
+ STORAGE_KEY_SEPARATOR$3 +
+ STORAGE_SALT_KEY +
+ STORAGE_KEY_SEPARATOR$3 +
+ fieldName
+ );
+}
+
+function generateSalt() {
+ var salt = '';
+ while (salt.length < SALT_CHAR_LENGTH) {
+ salt += string.randomHexCharacter();
+ }
+ return salt;
+}
+
+// The hash logic is borrowed from String.hashcode() of Java
+function hashCode(value, salt) {
+ return [value, salt]
+ .map(function (segment) {
+ var hash = 0;
+ // undefined, null and '' will return 0 as the hash code.
+ if (reflect.isDefinedNonNullNonEmpty(segment)) {
+ for (var i = 0; i < segment.length; i++) {
+ var charCode = segment.charCodeAt(i);
+ hash = (hash << 5) - hash + charCode; // "(hash << 5) - hash" is similar to "hash * 31" but faster.
+ }
+ }
+ var hashedValue = Math.abs(hash);
+ hashedValue = parseInt(hashedValue, 16);
+ return string.convertNumberToBaseAlphabet(hashedValue, string.base62Alphabet);
+ })
+ .join('');
+}
+
+/**
+ *
+ * @param {String} value The value that will be hashed
+ * @param {Object} fieldRules - includes information about how to constrain the field
+ * @param {String} fieldRules.storageKeyPrefix - a prefix to be used when storing hash related data, default is "mtHash"
+ * @param {String} fieldRules.namespace - a namespace for storing the hash data, default is empty.
+ * @param {Number} fieldRules.saltLifespan - a lifespan (milliseconds) of the salt
+ * @param {Object} fieldRules.platformBasedSalt - a config section includes the configures for loading salt from platform API
+ * @param {String} fieldRules.platformBasedSalt.saltNamespace - a namespace that stores the salt configuration in the "metricsIdentifier" section of the bag
+ * @param {String} fieldRules.platformBasedSalt.crossDeviceSync
+ * @param {Object} eventData - a dictionary of event data, which should include a pre-existing (unconstrained) field
+ * @param {String} fieldName - name of the field being modified in eventData
+ * @return {String | Promise} The hashed value on the top of provided value with the stored salt (rotated for every milliseconds).
+ */
+HashAction.prototype.constrainedValue = function constrainedValue(value, fieldRules, _eventData, fieldName) {
+ if (reflect.isDefinedNonNullNonEmpty(value)) {
+ return this._loadPlatformBasedSalt(fieldRules, fieldName).then(function (salt) {
+ return hashCode(value, salt);
+ });
+ }
+ return value;
+};
+
+/**
+ * @param {Number} timestamp a timestamp in ms since epoch
+ * @return {Boolean} return false if timestamp does not exist
+ * @overridable
+ */
+HashAction.prototype.timeExpired = function timeExpired(timestamp) {
+ return timestamp ? timestamp <= Date.now() : false;
+};
+
+/**
+ * @param {Number} (optional) lifespan the amount of time, in milliseconds, that an ID should be valid for
+ * @return {Number} a timestamp in ms since epoch, or null if no lifespan was provided
+ * @overridable
+ */
+HashAction.prototype.expirationTime = function expirationTime(lifespan) {
+ return lifespan ? Date.now() + lifespan : null;
+};
+
+HashAction.prototype._loadPlatformBasedSalt = function _loadPlatformBasedSalt(fieldRules, fieldName) {
+ var saltPromise = null;
+ var self = this;
+ var platformBasedSaltConfig = fieldRules.platformBasedSalt;
+ if (reflect.isDefinedNonNull(platformBasedSaltConfig)) {
+ saltPromise = this._constraintsInstance.system.environment.platformIdentifier(
+ platformBasedSaltConfig.saltNamespace,
+ 'userid',
+ platformBasedSaltConfig.crossDeviceSync || true
+ );
+ if (reflect.isDefinedNonNull(saltPromise)) {
+ saltPromise = saltPromise.then(function (salt) {
+ if (!reflect.isDefinedNonNull(salt)) {
+ self._constraintsInstance.system.logger.warn(
+ 'Hash: platform returned an empty salt. Will use default salt generator to generate the salt.'
+ );
+ salt = self._getSalt(fieldRules, fieldName);
+ }
+ return salt;
+ });
+ } else {
+ saltPromise = Promise.resolve(this._getSalt(fieldRules, fieldName));
+ }
+ } else {
+ saltPromise = Promise.resolve(this._getSalt(fieldRules, fieldName));
+ }
+
+ return saltPromise;
+};
+
+// This method retrieves the salt from storage, otherwise generates a new salt if it doesn't exist or has expired
+HashAction.prototype._getSalt = function _getSalt(fieldRules, fieldName) {
+ var saltMetadata = this._retrieveSaltFromStorage(fieldRules, fieldName);
+ var saltLifespan = fieldRules.saltLifespan;
+ if (!reflect.isDefinedNonNull(saltMetadata) || this.timeExpired(saltMetadata.expirationTime)) {
+ var localStorage = this._constraintsInstance.system.environment.localStorageObject();
+ var salt = generateSalt();
+ saltMetadata = {
+ salt: salt,
+ expirationTime: this.expirationTime(saltLifespan)
+ };
+ storage.saveObjectToStorage(localStorage, buildSaltStorageKey(fieldRules, fieldName), saltMetadata);
+ }
+
+ return saltMetadata.salt;
+};
+
+HashAction.prototype._retrieveSaltFromStorage = function _retrieveSaltFromStorage(fieldRules, fieldName) {
+ var localStorage = this._constraintsInstance.system.environment.localStorageObject();
+ var saltMetadata = storage.objectFromStorage(localStorage, buildSaltStorageKey(fieldRules, fieldName));
+ return saltMetadata;
+};
+
+/*
+ * src/field_actions/index.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var ACTIONS$1 = {
+ ID: 'idGenerator',
+ NUMBER: 'numberDeres',
+ TIME: 'timeDeres',
+ URL: 'urlDeres',
+ HASH: 'hash'
+};
+
+var FieldActions = function DeresHandlers(constraintsInstance) {
+ this.actions = {};
+ this.actions[ACTIONS$1.ID] = new IdAction(constraintsInstance);
+ this.actions[ACTIONS$1.NUMBER] = new NumberAction(constraintsInstance);
+ this.actions[ACTIONS$1.TIME] = new TimeAction(constraintsInstance);
+ this.actions[ACTIONS$1.URL] = new UrlAction(constraintsInstance);
+ this.actions[ACTIONS$1.HASH] = new HashAction(constraintsInstance);
+};
+
+FieldActions.prototype.getAction = function getAction(actionName) {
+ return this.actions[actionName];
+};
+
+/*
+ * src/treatment/action_treatment.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+var ActionTreatment = function ActionTreatment(constraintInstance) {
+ // @private
+ this._eventActions = new EventActions(constraintInstance);
+
+ // @private
+ this._fieldActions = new FieldActions(constraintInstance);
+};
+
+/**
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} eventConstraints a set of constraints to apply to this event
+ * @return {Object | Promise} constraints the event data modified according to the appropriate constraints or "null" if the event is blacklisted
+ * Note:
+ * 1. create a new dictionary if the event data is constrained/modified
+ * 2. return the original eventData if the constraints parameter is null or an empty dictionary
+ * @example
+ * var eventData = {
+ * eventType: 'click',
+ * pageType: 'TopCharts',
+ * parentPageUrl: 'https://itunes.apple.com/music/topcharts/12345',
+ * // etc.
+ * };
+ * var eventConstraints = {
+ * eventActions: { blacklistedFields: ['cookies', 'pageDetails'] },
+ * fieldActions: {
+ * parentPageUrl: {
+ * treatmentType: 'urlDeres',
+ * scope: 'hostname'
+ * }
+ * }
+ * }
+ * constraints.eventFields.applyEventConstraints(eventData, eventConstraints) =>
+ * {
+ * eventType: click,
+ * pageType: 'TopCharts',
+ * parentPageUrl: 'itunes.apple.com', // truncated to hostname only
+ * etc... // all other fields remain the same, except "cookies", "pageDetails"
+ * }
+ */
+ActionTreatment.prototype.applyConstraints = function applyConstraints(eventData, constraints) {
+ var returnEventData = eventData; // Set the original eventData to the returning variable to return the original eventData if neither event actions nor field actions were applied.
+
+ if (constraints && !reflect.isEmptyObject(constraints)) {
+ var promiseTasks = [];
+ var self = this;
+
+ if (constraints.fieldActions && !reflect.isEmptyObject(constraints.fieldActions)) {
+ var isAnyFieldChanged = false;
+ var eventDataCopy = returnEventData;
+ eventDataCopy = Object.keys(constraints.fieldActions).reduce(function (accumulatedFields, fieldName) {
+ var fieldRules = constraints.fieldActions[fieldName];
+ if (fieldRules) {
+ var denylisted = fieldRules.denylisted || fieldRules.blacklisted;
+ var fieldAction = fieldRules.treatmentType;
+ var fieldActionHandler = self._fieldActions.getAction(fieldAction);
+
+ accumulatedFields = lookForKeyPath(
+ accumulatedFields,
+ fieldName,
+ false,
+ function (value, key, keyPath, object) {
+ if (denylisted) {
+ delete object[key];
+ isAnyFieldChanged = true;
+ } else if (fieldRules.hasOwnProperty(FIELD_RULES.OVERRIDE_FIELD_VALUE)) {
+ object[key] = fieldRules[FIELD_RULES.OVERRIDE_FIELD_VALUE];
+ isAnyFieldChanged = true;
+ } else if (fieldActionHandler) {
+ var returnedValue = fieldActionHandler.performAction(
+ value,
+ fieldName,
+ returnEventData,
+ fieldRules
+ );
+ object[key] = returnedValue;
+ if (returnedValue instanceof Promise) {
+ promiseTasks.push(
+ returnedValue.then(function (processedValue) {
+ lookForKeyPath(
+ eventDataCopy,
+ fieldName,
+ true,
+ function (_value, targetKey, targetKeyPath, targetObject) {
+ if (targetKeyPath === keyPath) {
+ targetObject[targetKey] = processedValue;
+ }
+ }
+ );
+ })
+ );
+ }
+ isAnyFieldChanged = true;
+ }
+ }
+ );
+ }
+ return accumulatedFields;
+ }, eventDataCopy);
+
+ // If any field has been constrained, we create a new object to contain the merged fields instead of merging the changes to the original eventData.
+ // eventDataCopy has been re-built by "lookForKeyPath" above.
+ if (isAnyFieldChanged) {
+ if (promiseTasks.length > 0) {
+ returnEventData = Promise.all(promiseTasks).then(function () {
+ return eventDataCopy;
+ });
+ } else {
+ returnEventData = eventDataCopy;
+ }
+ }
+ }
+
+ // perform event actions after the field actions to ensure removing denied fields or keeping allowed fields for those generated fields(e.g. IdGenerator).
+ if (constraints.eventActions && !reflect.isEmptyObject(constraints.eventActions)) {
+ var eventActionNames = Object.keys(constraints.eventActions);
+ var processEventActions = function (processingEventData) {
+ eventActionNames.forEach(function (eventAction) {
+ var eventActionHandler = self._eventActions.getAction(eventAction);
+ if (eventActionHandler) {
+ var actionRules = constraints.eventActions[eventAction];
+ processingEventData = eventActionHandler.performAction(
+ processingEventData,
+ eventData,
+ actionRules
+ );
+ }
+ });
+ return processingEventData;
+ };
+
+ if (returnEventData instanceof Promise) {
+ returnEventData = Promise.resolve(returnEventData).then(function (processedEventData) {
+ return processEventActions(processedEventData);
+ });
+ } else {
+ returnEventData = processEventActions(returnEventData);
+ }
+ }
+ }
+
+ return returnEventData;
+};
+
+/*
+ * src/constraint_generator/treatment_generator.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+var TREATMENT_FILTERS_FIELD = 'filters';
+var TREATMENT_FILTERS_ALL = 'any';
+var TREATMENT_EVENT_ACTIONS = 'eventActions';
+var TREATMENT_FIELD_ACTIONS = 'fieldActions';
+
+function _updateTreatment(accumulatedTreatment, treatment) {
+ var currentTreatment = accumulatedTreatment || {};
+ // update event actions
+ _updateEventActions(currentTreatment, treatment);
+ // update field actions
+ _updateFieldActions(currentTreatment, treatment);
+
+ return currentTreatment;
+}
+
+function _updateEventActions(targetTreatment, sourceTreatment) {
+ if (!targetTreatment[TREATMENT_EVENT_ACTIONS]) {
+ targetTreatment[TREATMENT_EVENT_ACTIONS] = {};
+ }
+ var currentTreatmentEventActions = targetTreatment[TREATMENT_EVENT_ACTIONS];
+ var treatmentEventActions = sourceTreatment[TREATMENT_EVENT_ACTIONS];
+
+ if (treatmentEventActions) {
+ Object.keys(treatmentEventActions).reduce(function (accumulatedEventActions, eventAction) {
+ var existingActionValue = accumulatedEventActions[eventAction];
+ var actionValue = treatmentEventActions[eventAction];
+ // Merge the event action values from different treatments
+ if (reflect.isArray(existingActionValue)) {
+ // If the action value is not an array, treat it as a bad data and discard it.
+ if (reflect.isArray(actionValue)) {
+ actionValue.forEach(function (value) {
+ if (existingActionValue.indexOf(value) === -1) {
+ existingActionValue.push(value);
+ }
+ });
+ }
+ } else {
+ // Currently only have array type and primitive type parameters. Ignore the other types of parameter values.
+ if (reflect.isArray(actionValue)) {
+ // Clone the array value, to avoid the original array is changed by other treatments.
+ accumulatedEventActions[eventAction] = actionValue.slice();
+ } else if (
+ reflect.isObject(actionValue) ||
+ (!reflect.isObject(actionValue) && !reflect.isFunction(actionValue))
+ ) {
+ // object, primitive type, null and undefined
+ // set the existing action value with the primitive type value.
+ accumulatedEventActions[eventAction] = actionValue;
+ }
+ }
+
+ return accumulatedEventActions;
+ }, currentTreatmentEventActions);
+ }
+}
+
+function _updateFieldActions(targetTreatment, sourceTreatment) {
+ if (!targetTreatment[TREATMENT_FIELD_ACTIONS]) {
+ targetTreatment[TREATMENT_FIELD_ACTIONS] = {};
+ }
+ updateFieldRulesets(
+ targetTreatment,
+ sourceTreatment,
+ TREATMENT_FIELD_ACTIONS,
+ function (targetRules, sourceRules, fieldName) {
+ // if the target field rule has the same treatmentType as the source field rule, then return the target field rule to replace its rule props with the source ones.
+ // otherwise, all of the source field rules will overwrite all of the target field rules
+ if (
+ targetRules[fieldName] &&
+ targetRules[fieldName].treatmentType === sourceRules[fieldName].treatmentType
+ ) {
+ return targetRules[fieldName];
+ } else {
+ // if the treatmentType is not the same between field rules, return an empty object to take the latter field rules
+ /*
+ {
+ treatments: [{
+ ...,
+ fieldActions: {
+ afield: { treatmentType: 'a', propA: 123 }
+ }
+ }, {
+ ...,
+ fieldActions: {
+ afield: { treatmentType: 'b', propB: 123 }
+ }
+ }]
+ }
+
+ expected output:
+ {
+ treatments: [{
+ ...,
+ fieldActions: {
+ afield: { treatmentType: 'b', propB: 123 }
+ }
+ }]
+ }
+ */
+ return {};
+ }
+ }
+ );
+}
+
+var TreatmentGenerator = function TreatmentGenerator(constraintsInstance) {
+ // @private
+ this._constraintsInstance = constraintsInstance;
+ this.treatment = new ActionTreatment(constraintsInstance);
+};
+
+/**
+ * Combine treatments from multiple profiles
+ * @param {Array} ConstraintProfiles the constraint profile names
+ * @param {Object} topicConfig An AMP Metrics Config
+ * @param {String}(optional) topic defines the AMP Analytics "topic" to look up the constraint profile
+ * @returns {Promise} a Promise that returns an Array of combined treatments from multiple constraint profiles
+ * @private
+ */
+TreatmentGenerator.prototype._combineTreatments = function _combineTreatments(constraintProfiles, topicConfig, topic) {
+ var combinedTreatmentsPromise;
+ var buildTreatmentTasks = [];
+
+ if (reflect.isArray(constraintProfiles)) {
+ constraintProfiles.forEach(function (constraintProfile) {
+ if (!constraintProfile) {
+ return;
+ }
+
+ var profileName = 'treatmentProfiles.' + constraintProfile;
+ var constraintsPromise = topicConfig.value(profileName, topic).then(function (constraints) {
+ return constraints && constraints.treatments ? constraints.treatments : [];
+ });
+ buildTreatmentTasks.push(constraintsPromise);
+ });
+
+ combinedTreatmentsPromise = Promise.all(buildTreatmentTasks).then(function (profilesTreatments) {
+ var combinedTreatments = [];
+ profilesTreatments.forEach(function (profileTreatments) {
+ combinedTreatments = combinedTreatments.concat(profileTreatments);
+ });
+
+ return combinedTreatments;
+ });
+ } else {
+ combinedTreatmentsPromise = Promise.resolve([]);
+ }
+
+ return combinedTreatmentsPromise;
+};
+
+/**
+ * Build the properly constraints for the passed-in event data
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object} topicConfig An AMP Metrics Config
+ * @param {String}(optional) topic defines the AMP Analytics "topic" to look up the constraint profile
+ * @return {Object} a set of constraints to apply to this event.
+ * returns null (MetricsKit will send the original event) if:
+ * 1. no topic config available
+ * 2. defaultTreatmentProfile is undefined
+ * 3. the profile is found but no treatment matched
+ * @throws {TypeError} throws a type error if the topic config contains any invalid element.
+ * @throws {SyntaxError} throws a syntax error if:
+ * 1. topicConfig.constraintProfiles(topic) is not found in the topic config
+ * 2. the treatment configuration is invalid
+ * @overridable
+ * Constraint rules will be applied in the order they are provided in config.
+ * @example
+ * Given the following config:
+ * metrics: {
+ * ...
+ * low_res_topic: {
+ * defaultTreatmentProfiles: ['iosStores', 'embeddedWeb'],
+ * },
+ * defaultTreatmentProfiles: ['embeddedWeb']
+ * treatmentProfiles: {
+ * iosStores: { version: 1, treatments: [ ... ] },
+ * embeddedWeb: {
+ * version: 2,
+ * treatments: [
+ * {
+ * filters: {
+ * eventType: { valueMatches: ['enter', 'exit' ] },
+ * isSignedIn: { valueMatches: [true] }
+ * },
+ * eventActions: {
+ * blacklistedFields: ['cookies']
+ * }
+ * },
+ * {
+ * filters: {
+ * eventType: { valueMatches: [ 'exit' ] }
+ * },
+ * eventActions: {
+ * blacklistedFields: ['cookies', 'pageDetails']
+ * },
+ * fieldActions: {
+ * clientId: {
+ * treatmentType: 'idDeres',
+ * storageKeyPrefix: 'mtClientId',
+ * namespace: 'test',
+ * scopeStrategy: 'mainDomain',
+ * scopeFieldName: 'https://www.apple.com/products/',
+ * tokenSeparator: 'z',
+ * lifespan: 86400000
+ * }
+ * }
+ * },
+ * {
+ * filters: {
+ * userType: { valueMatches: ['signedIn'] },
+ * actionType: { valueMatches: ['navigate'] }
+ * },
+ * fieldActions: {
+ * os: { blacklisted: true },
+ * // round down time to 1 day
+ * eventTime: {
+ * treatmentType: "numberDeres",
+ * precision: 86400000
+ * },
+ * // remove query params
+ * pageUrl: {
+ * treatmentType: "urlDeres",
+ * scope: 'fullWithoutParams'
+ * },
+ * // Deres disk available space round down to MB
+ * capacityDiskAvailable: {
+ * treatmentType: "numberDeres",
+ * precision: 1000000, // 1MB
+ * },
+ * clientId: {
+ * treatmentType: 'idDeres',
+ * scopeFieldName: 'https://www.apple.com/',
+ * lifespan: 3600000
+ * }
+ * }
+ * }
+ * ]
+ * }
+ * },
+ * ...
+ * }
+ *
+ * constraintsForEvent({ eventType: 'exit', isSignedIn: true, userType: 'signedIn', actionType: 'navigate', ... }, topicConfig) returns:
+ * {
+ * eventActions: {
+ * blacklistedFields: ['cookies', 'pageDetails'] // from "eventType: { valueMatches: [ 'exit' ] }", override the one of "treatments[0]"
+ * },
+ * fieldActions: {
+ * os: { blacklisted: true },
+ * eventTime: {
+ * treatmentType: "numberDeres",
+ * precision: 86400000
+ * },
+ * pageUrl: {
+ * treatmentType: "urlDeres",
+ * scope: 'fullWithoutParams'
+ * },
+ * capacityDiskAvailable: {
+ * treatmentType: "numberDeres",
+ * precision: 1000000, // 1MB
+ * },
+ * clientId: {
+ * treatmentType: 'idDeres',
+ * storageKeyPrefix: 'mtClientId',
+ * namespace: 'test',
+ * scopeStrategy: 'mainDomain',
+ * scopeFieldName: 'https://www.apple.com/', // override the value from "treatments[1].clientId"
+ * tokenSeparator: 'z',
+ * lifespan: 3600000 // override the value from "treatments[1].clientId"
+ * }
+ * }
+ * }
+ */
+TreatmentGenerator.prototype.constraintsForEvent = function constraintsForEvent(eventData, topicConfig, topic) {
+ if (!topicConfig) {
+ return Promise.resolve(null);
+ }
+ var self = this;
+
+ // Use Promise.resolve to wrap the constraintProfiles() here in case of the client delegate the constraintProfiles method and returns non-promise value
+ return Promise.resolve(topicConfig.constraintProfiles(topic))
+ .then(function (constraintProfiles) {
+ // Adapt the v1 profile to v2 profiles
+ if (!reflect.isDefinedNonNull(constraintProfiles)) {
+ return Promise.resolve(topicConfig.constraintProfile(topic)).then(function (constraintProfile) {
+ return reflect.isDefinedNonNull(constraintProfile) ? [constraintProfile] : null;
+ });
+ } else {
+ return constraintProfiles;
+ }
+ })
+ .then(function (constraintProfiles) {
+ // rdar://71993234 if there is no default treatment profile and the client did not declare a treatment profile, do not modify the event
+ if (reflect.isDefinedNonNull(constraintProfiles)) {
+ if (!reflect.isArray(constraintProfiles)) {
+ throw new TypeError(
+ '"constraintProfiles" should be an Array, but got: ' +
+ (constraintProfiles ? constraintProfiles.constructor : constraintProfiles)
+ );
+ }
+ return self
+ ._combineTreatments(constraintProfiles, topicConfig, topic)
+ .then(function (combinedTreatments) {
+ // rdar://71993234 if the treatments are not found in the topic config
+ if (combinedTreatments.length === 0) {
+ throw new SyntaxError(
+ 'The constraintProfiles: ' +
+ constraintProfiles.join(', ') +
+ ' are not found in the topic config'
+ );
+ }
+ return combinedTreatments;
+ });
+ } else {
+ return Promise.resolve([]);
+ }
+ })
+ .then(function (combinedTreatments) {
+ var returnTreatments = combinedTreatments.reduce(function (accumulatedTreatment, treatment) {
+ if (self.eventMatchesTreatment(eventData, treatment)) {
+ accumulatedTreatment = _updateTreatment(accumulatedTreatment, treatment);
+ }
+ return accumulatedTreatment;
+ }, null);
+
+ return returnTreatments;
+ });
+};
+
+TreatmentGenerator.prototype.eventMatchesTreatment = function eventMatchesTreatment(eventData, treatment) {
+ var filters = treatment[TREATMENT_FILTERS_FIELD];
+
+ // Fast false for free-form filter since JS does not support it at the moment
+ // Applying the treatment to all events if there is no filters to align the behavior with the native implementation.
+ if (!reflect.isDefinedNonNull(filters)) {
+ return true;
+ }
+ // Applying the treatment to all events if the value equals to "any"
+ if (reflect.isString(filters)) {
+ return filters === TREATMENT_FILTERS_ALL;
+ }
+
+ // If the filter element is an empty filter list. We consider it is an incorrect config.
+ if (Object.keys(filters).length === 0) {
+ throw new SyntaxError('Unable to find the filter in \n' + JSON.stringify(treatment));
+ }
+
+ return Object.keys(filters).every(function (filterField) {
+ var fieldFilters = filters[filterField];
+
+ // Fast false for free-form filter since JS does not support it at the moment
+ if (fieldFilters && reflect.isString(fieldFilters)) {
+ return false;
+ }
+ // if a field isn't an object or doesn't have any matchers. We consider this is a bad filter and discard the event
+ if (!fieldFilters || !reflect.isObject(fieldFilters) || reflect.isEmptyObject(fieldFilters)) {
+ throw new SyntaxError(
+ 'Invalid filter object for field (' + filterField + ') in \n' + JSON.stringify(treatment)
+ );
+ }
+ // Only return the treatments where all treatments match.
+ // Current, only one condition for one field.
+ return Object.keys(fieldFilters).every(function (matcherName) {
+ var matcherParam = fieldFilters[matcherName];
+
+ if (matchers[matcherName]) {
+ return matchers[matcherName](filterField, eventData, matcherParam);
+ } else {
+ throw new SyntaxError(
+ 'Unable to find the filter (' +
+ matcherName +
+ ') for field (' +
+ filterField +
+ ')in \n' +
+ JSON.stringify(treatment)
+ );
+ }
+ });
+ });
+};
+
+/*
+ * src/config.js
+ * mt-client-constraints
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * The constraints config delegate
+ * Constraints attach this delegate to the topic config to have constraints features on the topic config
+ */
+var constraintsConfig = {
+ /**
+ * Return the constraint profile from a config with constraint syntax v1
+ * @param {String}(optional) topic defines the AMP Analytics "topic" that this event should be stored under
+ * @return {Promise} a Promise that returns the name of the constraint profile from constraint syntax v1 to use
+ */
+ constraintProfile: function constraintProfile(topic) {
+ return this.value('constraints.defaultProfile', topic);
+ },
+
+ /**
+ * Return the constraint profiles from a config with constraint syntax v2
+ * @param {String}(optional) topic defines the AMP Analytics "topic" that this event should be stored under
+ * @return {Promise} a Promise that returns an array of the names of the constraint profile to use
+ */
+ constraintProfiles: function constraintProfiles(topic) {
+ return this.value('defaultTreatmentProfiles', topic);
+ }
+};
+
+/*
+ * src/index.js
+ * mt-client-constraints
+ *
+ * Copyright © 2017-2018 Apple Inc. All rights reserved.
+ *
+ */
+
+function _validateConfig(config) {
+ var isValid = true;
+
+ isValid &= reflect.isDefinedNonNull(config);
+ if (isValid) {
+ isValid &= !reflect.isEmptyObject(config);
+ isValid &= reflect.isFunction(config.initialized);
+ isValid &= reflect.isFunction(config.value);
+ isValid &= reflect.isFunction(config.constraintProfile);
+ }
+
+ return isValid;
+}
+
+/**
+ * Attaching config related methods for Constraints
+ * @param {Config} topicConfig An AMP Metrics Config
+ * @returns {Config} the passed-in config with constraint-related methods attached
+ */
+function connectConstraintConfig(topicConfig) {
+ // return the topic config if it has already been attached with the Constraint methods.
+ if (reflect.isFunction(topicConfig.constraintProfile) && reflect.isFunction(topicConfig.constraintProfiles)) {
+ return topicConfig;
+ }
+ reflect.attachMethods(topicConfig, constraintsConfig, topicConfig);
+
+ return topicConfig;
+}
+
+/**
+ * Supplies the single JavaScript entrypoint to constraint functionality
+ * Since JavaScript is prototype-based and not class-based, and doesn't provide
+ * an "official" object model, this API is presented as a functional API, but
+ * still retains the ability to override and customize functionality via the
+ * "setDelegate()" method. In this way, it doesn't carry with it the spare
+ * baggage of exposing a bolt-on object model which may differ from a bolt-on
+ * (or homegrown) object model already existing in the app.
+ * @module
+ * @param {Object} topicConfig a topic config
+ * @param {Object} delegates
+ * @constructor
+ *
+ * @example
+ * import * as delegates from '@amp-metrics/mt-metricskit-delegates-html';
+ * import Constraints, { connectConstraintConfig } from '@amp-metrics/mt-client-constraints';
+ * import Config from '@amp-metrics/mt-client-config';
+ *
+ * const topicConfig = new Config('topic');
+ * connectConstraintConfig(topicConfig);
+ *
+ * var eventData = {...};
+ * var constraints = new Constraints(topicConfig, delegates);
+ * var constrainedEventData = constraints.applyConstraintTreatments(eventData);
+ */
+var Constraints = function Constraints(topicConfig, delegate) {
+ if (!_validateConfig(topicConfig)) {
+ throw new Error('The topic config is not a valid instance of "mt-client-config".');
+ }
+
+ // @private
+ this._isInitialized = false;
+
+ // @private
+ this._topicConfig = topicConfig;
+
+ /**
+ * constraint generator for specific topic config
+ * @type {ConstraintGenerator}
+ */
+ // @private
+ this._constraintGenerator = null;
+
+ /**
+ * system/platform-specific classes
+ */
+ this.system = new System();
+
+ reflect.setDelegates(this.system, delegate || {});
+};
+
+/**
+ * get constraint generator based on the Constraints' config
+ * @returns {Promise} a Promise that returns the active constraint generator
+ */
+Constraints.prototype._getConstraintGenerator = function _getConstraintGenerator() {
+ var self = this;
+
+ if (this._constraintGenerator) {
+ return Promise.resolve(this._constraintGenerator);
+ } else {
+ return this._topicConfig.value('treatmentProfiles').then(function (treatmentConfig) {
+ if (reflect.isDefinedNonNull(treatmentConfig)) {
+ self._constraintGenerator = new TreatmentGenerator(self);
+ } else {
+ self._constraintGenerator = new LegacyConstraintGenerator(self);
+ }
+ return self._constraintGenerator;
+ });
+ }
+};
+
+/**
+ * Build constraints with the eventData
+ * @param {Object} eventData a dictionary of event data
+ * @param {String}(optional) topic defines the AMP Analytics "topic" that this event should be stored under
+ * @return {Promise} a Promise that returns a set of constraints to apply to this event
+ * @throws {SyntaxError/TypeError} throws a type error if the topic config contains any invalid element.
+ */
+Constraints.prototype.constraintsForEvent = function constraintsForEvent(eventData, topic) {
+ var self = this;
+ return this._getConstraintGenerator().then(function (constraintGenerator) {
+ return constraintGenerator.constraintsForEvent(eventData, self._topicConfig, topic);
+ });
+};
+
+/**
+ * Apply the given eventData with associated constraints
+ * @param {Object} eventData a dictionary of event data
+ * @param {Object}(optional) constraints a set of constraints to apply to this event
+ * @returns {Promise} a Promise that returns the performed event Data with the given constraints or null if the event is blacklisted or should be discard
+ */
+Constraints.prototype.applyConstraintTreatments = function applyConstraints(eventData, constraints) {
+ var constraintsPromise = constraints ? Promise.resolve(constraints) : this.constraintsForEvent(eventData);
+ var self = this;
+
+ return Promise.all([constraintsPromise, this._getConstraintGenerator()])
+ .then(function (output) {
+ var constraints = output[0];
+ var constraintGenerator = output[1];
+ return constraintGenerator.treatment.applyConstraints(eventData, constraints);
+ })
+ .catch(function (e) {
+ self.system.logger.warn('An error occurred while applying constraints: ' + e.message || e);
+ return null;
+ });
+};
+
+export default Constraints;
+export { connectConstraintConfig };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-client-logger-core/dist/mt-client-logger-core.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-client-logger-core/dist/mt-client-logger-core.esm.js
new file mode 100644
index 0000000..2ef3193
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-client-logger-core/dist/mt-client-logger-core.esm.js
@@ -0,0 +1,533 @@
+import { reflect, string } from '@amp-metrics/mt-metricskit-utils-private';
+
+/*
+ * src/utils.js
+ * mt-client-logger-core
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+function FlagSymbol(key) {
+ this.key = key;
+}
+FlagSymbol.prototype.toString = function toString() {
+ return this.key;
+};
+
+var utils = {
+ /**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+ /** Special flag classes that can be passed as arguments to logger methods in order to dictate logging behavior
+ * Use class instances to guarantee that flag arguments are unique, and use constructor names for O(1) lookup */
+ flagArguments: {
+ /**
+ * When logging, if any of the arguments is an instance of this class, the log output will include a call stack trace.
+ * @example usage: logger.warn('danger!', logger.INCLUDE_CALL_STACK);
+ */
+ INCLUDE_CALL_STACK: new FlagSymbol('INCLUDE_CALL_STACK'),
+ /**
+ * When logging, if any of the arguments is an instance of this class, the remaining arguments will be mirrored to the logging server
+ * @example usage: logger.info('some message', logger.MIRROR_TO_SERVER);
+ */
+ MIRROR_TO_SERVER: new FlagSymbol('MIRROR_TO_SERVER'),
+ /**
+ * When logging, if any of the arguments is an instance of this class, the client (console) output will be suppressed
+ * This would typically be used when callers want to log an event to the server without printing it
+ * @example usage: logger.debug(someDiagnosticsInfoObject, logger.MIRROR_TO_SERVER, logger.SUPPRESS_CLIENT_OUTPUT);
+ */
+ SUPPRESS_CLIENT_OUTPUT: new FlagSymbol('SUPPRESS_CLIENT_OUTPUT')
+ },
+
+ /**
+ * 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 (these methods will not be copied to the target object).
+ * It can even act as a delegate for multiple MetricsKit objects, though that is not recommended.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * This allows callers to replace some number of methods that need custom implementations.
+ * If, for example, a client wants to use the standard logger implementation with the exception of, say, the "debug" method, they can
+ * call "setDelegate()" with their own delegate containing only a single method of "debug" as the delegate, which would leave all the other methods intact.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @example:
+ * To override one or more methods, in place:
+ * logger.setDelegate({ debug: console.debug });
+ * To override one or more methods with a separate object:
+ * logger.setDelegate(customLoggerDelegate);
+ * (where "customLoggerDelegate" might be defined elsewhere as, e.g.:
+ * var customLoggerDelegate = { debug: function(msg) { document.getElementById('debugMsg').innerHTML = msg; },
+ * serverUrl: function() { return 'https://custom-log-server.apple.com'; } };
+ * To override one or more methods with an instantiated object from a class definition:
+ * eventRecorder.setDelegate(new CustomLoggerDelegate());
+ * (where "CustomLoggerDelegate" might be defined elsewhere as, e.g.:
+ * function CustomLoggerDelegate() {
+ * }
+ * CustomLoggerDelegate.prototype.debug = function debug(msg) {
+ * document.getElementById('debugMsg').innerHTML = msg;
+ * };
+ * CustomLoggerDelegate.prototype.serverUrl = function serverUrl() {
+ * return 'https://custom-log-server.apple.com';
+ * };
+ * To override one or more methods with a class object (with "static" methods):
+ * eventRecorder.setDelegate(CustomLoggerDelegate);
+ * (where "CustomLoggerDelegate" might be defined elsewhere as, e.g.:
+ * function CustomLoggerDelegate() {
+ * }
+ * CustomLoggerDelegate.debug = function debug(msg) {
+ * document.getElementById('debugMsg').innerHTML = msg;
+ * };
+ * CustomLoggerDelegate.serverUrl = function serverUrl() {
+ * return 'https://custom-log-server.apple.com';
+ * };
+ * @param {Object} delegate 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 reflect.attachDelegate(this, delegate);
+ },
+
+ /**
+ * If the log level allows, logs/throws an error to the console and mirrors the log event to the server
+ * @param {Logger} logger
+ * @param {String} methodName
+ * @param {Array-like Object} origArguments
+ */
+ execute: function execute(logger, methodName, origArguments) {
+ var methodLevel = logger.levelStringToIntMap[methodName];
+
+ if (logger.level() !== logger.NONE && logger.level() <= methodLevel) {
+ var argumentsArray = Array.prototype.slice.call(origArguments);
+ var logArguments = utils.nonFlagLogArguments(argumentsArray);
+ var logOptions = utils.logOptions(logger, methodLevel, argumentsArray);
+ var callstack = logOptions.includeCallStack ? new Error().stack : null;
+ var enrichedLogArguments = callstack ? logArguments.concat('\n' + callstack) : logArguments; // add newline for nicer output
+
+ // so testing harness can verify logging done within tested functions:
+ logger[methodName]._lastLog = enrichedLogArguments;
+
+ if (logOptions.mirrorToServer) {
+ utils.sendToServer(logger, methodName, logArguments, callstack);
+ }
+
+ if (logOptions.throwInsteadOfPrint) {
+ throw new Error(logArguments.toString());
+ } else if (!logOptions.suppressClientOutput) {
+ if (console[methodName]) {
+ console[methodName].apply(console, enrichedLogArguments);
+ } else {
+ // fallback to console.log - node does not have console.debug
+ console.log.apply(console, enrichedLogArguments);
+ }
+ }
+ }
+ },
+
+ /**
+ * Indicates whether an item is a specific flag object that dictates logging behavior
+ * @param {*} argument
+ * @return {Boolean}
+ */
+ isFlagObject: function isFlagObject(argument) {
+ return argument && argument === utils.flagArguments[argument.toString()];
+ },
+
+ /**
+ * Creates a new array without specific arguments that dictate logging behavior (and are not intended to be logged)
+ * @param {Array} argumentsArray
+ * @return {Array}
+ */
+ nonFlagLogArguments: function nonFlagLogArguments(argumentsArray) {
+ return argumentsArray.filter(function (argument) {
+ return !utils.isFlagObject(argument);
+ });
+ },
+
+ /**
+ * Inspects an array of arguments for specific flag objects that dictate log behavior and returns an object representing the intended behavior
+ * By checking for all of the various flags in one pass, we avoid looping over the arguments array more than necessary
+ * @param {Logger} logger
+ * @param {Int} methodLevel
+ * @param {Array} argumentsArray
+ * @return {Object}
+ */
+ logOptions: function logOptions(logger, methodLevel, argumentsArray) {
+ var logOptions = {};
+ var optionName;
+
+ argumentsArray.forEach(function (argument) {
+ if (utils.isFlagObject(argument)) {
+ optionName = string.snakeCaseToCamelCase(argument.toString());
+ logOptions[optionName] = true;
+ }
+ });
+
+ if (
+ reflect.isFunction(logger.mirrorToServerLevel) &&
+ logger.mirrorToServerLevel() !== logger.NONE &&
+ logger.mirrorToServerLevel() <= methodLevel
+ ) {
+ logOptions.mirrorToServer = true;
+ }
+ if (logger.throwLevel() !== logger.NONE && logger.throwLevel() <= methodLevel) {
+ logOptions.throwInsteadOfPrint = true;
+ }
+
+ return logOptions;
+ },
+
+ /**
+ * Sends a log event to the server immediately without checking resolution
+ * TODO: refactor to use eventRecorder once it is a standalone package
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @param {Logger} logger
+ * @param {String} level
+ * @param {Array} logArguments
+ * @param {String} (optional) callstack
+ * @return {String} the JSON-stringified event that was sent to the server
+ * @overridable
+ */
+ sendToServer: function sendToServer(logger, level, logArguments, callstack) {}
+};
+
+/*
+ * src/logger.js
+ * mt-client-logger-core
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ ************************************ PRIVATE METHODS/IVARS ************************************
+ */
+
+// Define log levels separately to expose this constant.
+// TODO clean constants up when consolidate.
+var LOG_LEVELS = {
+ NONE: 0,
+ DEBUG: 1,
+ INFO: 2,
+ WARN: 3,
+ ERROR: 4
+};
+var LOGGER_LEVELS = {
+ MIN_LEVEL: LOG_LEVELS.NONE,
+ MAX_LEVEL: LOG_LEVELS.ERROR,
+ levelIntToStringMap: {
+ 0: 'none',
+ 1: 'debug',
+ 2: 'info',
+ 3: 'warn',
+ 4: 'error'
+ },
+ levelStringToIntMap: {
+ none: 0,
+ debug: 1,
+ info: 2,
+ warn: 3,
+ error: 4
+ }
+};
+
+reflect.extend(LOGGER_LEVELS, LOG_LEVELS);
+
+/** Global properties */
+var LOGGER_PROPERTIES = {
+ loggerName: 'defaultLogger',
+ level: LOGGER_LEVELS.INFO,
+ throwLevel: LOGGER_LEVELS.NONE
+};
+
+var _initialized = false;
+
+/** A map of logger names to Logger instances */
+var _loggers = {};
+
+/**
+ * Provides basic "log4j" type functionality.
+ * The functionality in this class is typically replaced via a delegate.
+ * NOTE: This class has a "secret" field extending each logger function called "_lastLog" which allows us to inspect logged errors from within our test cases of various functionality
+ * to ensure that the correct errors are thrown.
+ * DEFAULT implementation: console logging
+ * DEFAULT logger level: INFO
+ * @see setDelegate
+ * @delegatable
+ * @constructor
+ * @param {String} loggerName
+ */
+function Logger(loggerName) {
+ // @private
+ this._loggerName = loggerName;
+
+ /* These variables are enumerated here for clarity */
+ // @private
+ this._level;
+ // @private
+ this._throwLevel;
+
+ // lazily add prototype properties
+ if (!_initialized) {
+ _initialized = true;
+ reflect.extend(Logger.prototype, LOGGER_LEVELS);
+ reflect.extend(Logger.prototype, utils.flagArguments);
+ }
+}
+
+/**
+ * Returns the logger instance that has the name , creating a new one if it doesn't exist
+ * @param {String} loggerName
+ * @return {Logger}
+ */
+function loggerNamed(loggerName) {
+ loggerName = loggerName || LOGGER_PROPERTIES.loggerName;
+ var returnLogger = _loggers[loggerName];
+
+ if (!returnLogger) {
+ returnLogger = new Logger(loggerName);
+ _loggers[loggerName] = returnLogger;
+ }
+
+ return returnLogger;
+}
+
+/**
+ * Remove a logger from the cache
+ * @param loggerName
+ */
+function removeLogger(loggerName) {
+ if (_loggers) {
+ delete _loggers[loggerName];
+ }
+}
+
+function resetLoggerCache() {
+ _loggers = {};
+}
+
+/** Default class property setters and getters */
+Logger.level = function level() {
+ return LOGGER_PROPERTIES.level;
+};
+Logger.throwLevel = function throwLevel() {
+ return LOGGER_PROPERTIES.throwLevel;
+};
+
+// TODO: new PR with this, flesh out and make app-wide with docs
+// Logger.setDelegate = function setDelegate() { };
+// Logger.logCallback = function logCallback() { };
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Allows replacement of one or more of this class instance's 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 (these methods will not be copied to the target object).
+ * It can even act as a delegate for multiple MetricsKit objects, though that is not recommended.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * This allows callers to replace some number of methods that need custom implementations.
+ * If, for example, a client wants to use the standard logger implementation with the exception of, say, the "debug" method, they can
+ * call "setDelegate()" with their own delegate containing only a single method of "debug" as the delegate, which would leave all the other methods intact.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @example:
+ * To override one or more methods, in place:
+ * logger.setDelegate({ debug: console.debug });
+ * To override one or more methods with a separate object:
+ * logger.setDelegate(customLoggerDelegate);
+ * (where "customLoggerDelegate" might be defined elsewhere as, e.g.:
+ * var customLoggerDelegate = { debug: function(msg) { document.getElementById('debugMsg').innerHTML = msg; },
+ * serverUrl: function() { return 'https://custom-log-server.apple.com'; } };
+ * To override one or more methods with an instantiated object from a class definition:
+ * eventRecorder.setDelegate(new CustomLoggerDelegate());
+ * (where "CustomLoggerDelegate" might be defined elsewhere as, e.g.:
+ * function CustomLoggerDelegate() {
+ * }
+ * CustomLoggerDelegate.prototype.debug = function debug(msg) {
+ * document.getElementById('debugMsg').innerHTML = msg;
+ * };
+ * CustomLoggerDelegate.prototype.serverUrl = function serverUrl() {
+ * return 'https://custom-log-server.apple.com';
+ * };
+ * To override one or more methods with a class object (with "static" methods):
+ * eventRecorder.setDelegate(CustomLoggerDelegate);
+ * (where "CustomLoggerDelegate" might be defined elsewhere as, e.g.:
+ * function CustomLoggerDelegate() {
+ * }
+ * CustomLoggerDelegate.debug = function debug(msg) {
+ * document.getElementById('debugMsg').innerHTML = msg;
+ * };
+ * CustomLoggerDelegate.serverUrl = function serverUrl() {
+ * return 'https://custom-log-server.apple.com';
+ * };
+ * @param {Object} delegate 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.
+ */
+Logger.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * The name of this logger
+ * @returns {String}
+ * @overridable
+ */
+Logger.prototype.loggerName = function loggerName() {
+ return this._loggerName;
+};
+
+/**
+ * Deduces the integer level from either a string or integer
+ * @param {*} level loglevel which may be either a string (e.g. 'debug', 'DEBUG', 'Debug', etc.) or an integer (e.g. 1, 2, 3 or logger.DEBUG, logger.INFO, logger.WARN, etc.
+ * @return {Int} the level as an integer or null if an invalid level argument was passed
+ * @overrideable
+ */
+Logger.prototype.levelParameterAsInt = function levelParameterAsInt(level) {
+ var returnLevel = null;
+ var integerLevel;
+
+ if (reflect.isString(level)) {
+ integerLevel = this.levelStringToIntMap[level.toLowerCase()];
+ } else if (reflect.isNumber(level)) {
+ integerLevel = level;
+ }
+
+ if (integerLevel >= this.MIN_LEVEL && integerLevel <= this.MAX_LEVEL) {
+ returnLevel = integerLevel;
+ }
+
+ return returnLevel;
+};
+
+/**
+ * Sets the level at which we will log at or above
+ * @param {*} level loglevel which may be either a string (e.g. 'debug', 'DEBUG', 'Debug', etc.) or an integer (e.g. 1, 2, 3 or logger.DEBUG, logger.INFO, logger.WARN, etc.
+ * @overridable
+ */
+Logger.prototype.setLevel = function setLevel(level) {
+ var integerLevel = this.levelParameterAsInt(level);
+ if (integerLevel !== null) {
+ this._level = integerLevel;
+ }
+};
+
+/**
+ * NOTE: This setting should be honored by all delegates.
+ * This setting will cause any emitted log message at or above the specified level to throw an exception with the log message instead of logging to the console.
+ * This is useful during testcase execution when we would expect to have no log output, or perhaps only "info" log output, etc.
+ * @param {*} throwLevel loglevel which may be either a string (e.g. 'debug', 'DEBUG', 'Debug', etc.) or an integer (e.g. 1, 2, 3 or logger.DEBUG, logger.INFO, logger.WARN, etc.
+ */
+Logger.prototype.setThrowLevel = function setThrowLevel(throwLevel) {
+ var integerLevel = this.levelParameterAsInt(throwLevel);
+ if (integerLevel !== null) {
+ this._throwLevel = integerLevel;
+ }
+};
+
+/**
+ * Returns the current logger level as an integer
+ * @overridable
+ */
+Logger.prototype.level = function level() {
+ var level = this._level;
+ return reflect.isNumber(level) ? level : Logger.level();
+};
+
+/**
+ * Returns the current logger level as a string
+ * @overridable
+ */
+Logger.prototype.levelString = function levelString() {
+ return this.levelIntToStringMap[this.level()];
+};
+
+/**
+ * Returns the current logger throw level as an integer
+ * @overridable
+ */
+Logger.prototype.throwLevel = function throwLevel() {
+ var throwLevel = this._throwLevel;
+ return reflect.isNumber(throwLevel) ? throwLevel : Logger.throwLevel();
+};
+
+/**
+ * Emits the log message if log level is set to "debug".
+ * DEFAULT implementation: console.debug()
+ * @param {Object} a list of objects (perhaps a single string) to be stringified and emitted as the log message
+ * @api public
+ * @overridable
+ */
+Logger.prototype.debug = function debug() {
+ utils.execute(this, 'debug', arguments);
+};
+
+/**
+ * Emits the log message if log level is set to "info".
+ * DEFAULT implementation: console.info()
+ * @param {Object} a list of objects (perhaps a single string) to be stringified and emitted as the log message
+ * @api public
+ * @overridable
+ */
+Logger.prototype.info = function info() {
+ utils.execute(this, 'info', arguments);
+};
+
+/**
+ * Emits the log message if log level is set to "warn".
+ * DEFAULT implementation: console.warn()
+ * @param {Object} a list of objects (perhaps a single string) to be stringified and emitted as the log message
+ * @api public
+ * @overridable
+ */
+Logger.prototype.warn = function warn() {
+ utils.execute(this, 'warn', arguments);
+};
+
+/**
+ * Emits the log message if log level is set to "error".
+ * DEFAULT implementation: console.error()
+ * @param {Object} a list of objects (perhaps a single string) to be stringified and emitted as the log message
+ * @api public
+ * @overridable
+ */
+Logger.prototype.error = function error() {
+ utils.execute(this, 'error', arguments);
+};
+
+/**
+ * @param {String} levelString
+ * @return {String} the most recent log event for this level
+ */
+Logger.prototype.lastLog = function lastLog(levelString) {
+ return this[levelString] ? this[levelString]._lastLog : null;
+};
+
+var level = Logger.level;
+var throwLevel = Logger.throwLevel;
+
+/*
+ * mt-client-logger-core/index.js
+ * mt-client-logger-core
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+export default Logger;
+export { LOG_LEVELS, level, loggerNamed, removeLogger, resetLoggerCache, throwLevel, utils };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-event-queue/dist/mt-event-queue.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-event-queue/dist/mt-event-queue.esm.js
new file mode 100644
index 0000000..772b4cc
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-event-queue/dist/mt-event-queue.esm.js
@@ -0,0 +1,1364 @@
+import { loggerNamed } from '@amp-metrics/mt-client-logger-core';
+import { reflect, network as network$1 } from '@amp-metrics/mt-metricskit-utils-private';
+
+/*
+ * src/environment.js
+ * mt-event-queue
+ *
+ * Copyright © 2016-2019 Apple Inc. All rights reserved.
+ *
+ */
+
+var environment = {
+ /**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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 reflect.attachDelegate(this, delegate);
+ },
+
+ /**
+ * An object that conforms to the WindowOrWorkerGlobalScope API:
+ * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
+ * @return {WindowOrWorkerGlobalScope}
+ */
+ globalScope: function globalScope() {
+ return reflect.globalScope();
+ }
+};
+
+/*
+ * src/utils/constants.js
+ * mt-event-queue
+ *
+ * Copyright © 2019 Apple Inc. All rights reserved.
+ *
+ */
+
+// These constants are exposed publicly
+
+/**
+ * Possible send method types to record events
+ */
+var SEND_METHOD = {
+ AJAX: 'ajax',
+ AJAX_SYNCHRONOUS: 'ajaxSynchronous',
+ IMAGE: 'image',
+ BEACON: 'beacon',
+ BEACON_SYNCHRONOUS: 'beaconSynchronous'
+};
+
+/*
+ * src/event_recorder/base.js
+ * mt-event-queue
+ *
+ * Copyright © 2016-2019 Apple Inc. All rights reserved.
+ *
+ */
+
+var IDENTIFIABLE_FIELDS = ['dsId', 'consumerId'];
+
+/**
+ * Clone the passed-in source config with the provided topic
+ *
+ * Since we support multiple config instances instead of singleton config in mt-client-config.
+ * We have to pass the topicConfig instance to all places that we were passing a topic string to in the past.
+ * The problem there was if a caller wants to send an event to a sub-topic of the config and the config has some overriding values for the sub-topic,
+ * we need both config and the sub-topic to collect the values for the sub-topic.
+ * we either have to pass topicConfig and the sub-topic everywhere or create a new config with the sub-topic based on the topic config of the main topic.
+ * So we clone a new config by the main topic config with the sub-topic in the root entry(evenRecorder.recordEvent()) then all related logic would use that cloned config to load the config for the sub topic
+ *
+ * NOTE: We create an object and put the main topic config to the prototype of it because in that way,
+ * the cloned config still be able to share the same functions from the source config and when the source config getting cleanup, the cloned config also gets cleanup.
+ * The main purpose of the clone is overriding the main topic with the passed-in topic
+ *
+ * For Example:
+ * // We have a topic config with "topic_A" as the main topic, and a "topic_B" as the sub-topic inside of the config
+ * var topicConfig = {
+ * metricsUrl: 'https://xp.apple.com/report',
+ * postFrequency: 60000
+ * topic_B: {
+ * postFrequency: 30000
+ * }
+ * };
+ * var metricsKit = new MetricsKit('topic_A'); // which will have the above topicConfig for "topic_A"
+ * var eventFields = metricsKit.eventHandlers.enter.metricsData();
+ * var eventRecorder = new EventRecorder(metricsKit);
+ * eventRecorder.recordEvent('topic_B', eventFields);
+ *
+ * For above example, the recordEvent function will clone a new config by the topicConfig(topic_A) with the "topic_B".
+ * So underneath of the event recorder, we could get the correct values for "topic_B"
+ * topicConfig.value('postFrequency'); -> 30000
+ * event_queue.metricsUrlForConfig() -> https://xp.apple.com/report/topic_B
+ *
+ * @param sourceConfig
+ * @param topic
+ */
+function cloneTopicConfigWithTopic(sourceConfig, topic) {
+ var clonedConfig = {
+ _topic: topic
+ };
+
+ // inherit the class to have fields, methods and delegated methods from the source config
+ Object.setPrototypeOf(clonedConfig, sourceConfig);
+
+ return clonedConfig;
+}
+
+/**
+ * Abstract Event Recorder class
+ * @constructor
+ */
+var Base = function Base(configInstance) {
+ this._validateConfig(configInstance);
+ // @private
+ this._config = configInstance;
+
+ // @private
+ this._topicConfigCache = {};
+
+ // @private
+ this._topicPropsCache = {};
+
+ this.logger = loggerNamed('mt-event-queue');
+};
+
+/**
+ * Previously, this private method validated the config from metricskit but now it will validate the
+ * config directly
+ *
+ * @param {Object} config Configuration object
+ */
+Base.prototype._validateConfig = function validateConfig(config) {
+ var errorPostfix = 'please call constructor with a valid Config instance first.';
+ if (!config) {
+ throw new TypeError('Unable to find config, ' + errorPostfix);
+ } else if (!config.topic || !reflect.isFunction(config.topic)) {
+ throw new TypeError('Unable to find config.topic function, ' + errorPostfix);
+ } else if (
+ !config.metricsDisabledOrDenylistedEvent ||
+ !reflect.isFunction(config.metricsDisabledOrDenylistedEvent)
+ ) {
+ throw new TypeError('Unable to find config.metricsDisabledOrDenylistedEvent function, ' + errorPostfix);
+ } else if (!config.removeDenylistedFields || !reflect.isFunction(config.removeDenylistedFields)) {
+ throw new TypeError('Unable to find config.removeDenylistedFields function, ' + errorPostfix);
+ }
+};
+
+/**
+ * Remove identify fields from the eventFields based on the topic properties
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @param {Object} eventFields a JavaScript object which will be converted to a JSON string and sent to AMP Analytics immediately.
+ * @private
+ */
+Base.prototype._removeIdentifiableFieldsForTopic = function _removeIdentifiableFieldsForTopic(topic, eventFields) {
+ this._topicPropsCache[topic] = this._topicPropsCache[topic] || {};
+ if (this._topicPropsCache[topic].anonymous) {
+ IDENTIFIABLE_FIELDS.forEach(function (field) {
+ delete eventFields[field];
+ });
+ }
+};
+
+/**
+ * Record event
+ * Subclasses implement this method to handle how to record an event
+ * @abstract
+ * @param {Object} eventFields a JavaScript object which will be converted to a JSON string and sent to AMP Analytics immediately.
+ * @returns {Promise}
+ */
+Base.prototype._record = function _record(eventFields) {};
+
+/**
+ * clean resources of event recorder
+ * Subclasses implement this method to handle how to clean resources
+ * @returns {Promise} returns a Promise if the cleanup will asynchronously execute or undefined for synchronously executing
+ */
+Base.prototype.cleanup = function cleanup() {
+ this._config = null;
+ this._topicConfigCache = null;
+ this._topicPropsCache = {};
+};
+
+/**
+ * Records an event as JSON
+ * TODO: We should look at simplifying the process of using multiple topics. By deprecating recordEvent(topic, eventFields) in favor of recordEvent(eventFields) using the topic from the Kit.
+ * @param {String} topic an 'override' topic which will override the main topic.
+ * NOTE:
+ * 1. RecordEvent needs to check the denylisted event/fields. The passed-in topic may not be enough to check them. If MetricsKit's config had a subsection for the passed-in topic, then the denylisting would work properly.
+ * 2. The eventFields were generated with the config of Metricskit. If sending them to another topic, the eventFields might have incorrect values.
+ * @param {Object} eventFields a JavaScript object which will be converted to a JSON string and sent to AMP Analytics immediately.
+ * @returns {Promise} a Promise that returns the recorded event, or "null" if no object was recorded (e.g. if "eventFields" is null, or "disabled" is true, eventFields.eventType is one of the denylistedEvents, etc.)
+ */
+Base.prototype.recordEvent = function recordEvent(topic, eventFields) {
+ if (!this._config || !eventFields) {
+ return Promise.resolve(null);
+ }
+ var config = this._config;
+ var self = this;
+ var args = arguments;
+
+ // We do this if `topic` is a sub-topic of the configuration
+ if (reflect.isDefinedNonNullNonEmpty(topic) && topic !== config.topic()) {
+ config = this._topicConfigCache[topic];
+
+ if (!config) {
+ config = this._topicConfigCache[topic] = cloneTopicConfigWithTopic(this._config, topic);
+ }
+ }
+
+ // NOTE: Typically all event_handlers will check for this as well because that way if a client overrides "recordEvent", these checks will still take effect.
+ // We also test it here in case someone creates their own event_handler, we'd still want to exclude what needs to be excluded, in case they don't.
+ return config
+ .metricsDisabledOrDenylistedEvent(eventFields.eventType)
+ .then(function (disabledOrBlacklistedEvent) {
+ if (disabledOrBlacklistedEvent) {
+ return null;
+ }
+ return config
+ .removeDenylistedFields(eventFields)
+ .then(function () {
+ self._removeIdentifiableFieldsForTopic(topic, eventFields);
+ return self._record.apply(self, [config].concat(Array.prototype.slice.call(args, 1)));
+ })
+ .then(function () {
+ return eventFields;
+ });
+ })
+ .catch(function (error) {
+ self.logger.error(error);
+ return null;
+ });
+};
+
+/**
+ * The methodology being used to send batches of events to the server
+ * This field should be hardcoded in the client based on what method it is using to encode and send its events to Figaro.
+ * The three typical values are:
+ * "itms" - use this value when/if JavaScript code enqueues events for sending via the "itms.recordEvent()" method in ITML.
+ * "itunes" - use this value when/if JavaScript code enqueues events by calling the "iTunes.recordEvent()" method in Desktop Store apps.
+ * "javascript" - use this value when/if JavaScript code enqueues events for sending via the JavaScript eventQueue management. This is typically only used by older clients which don't have the built-in functionality of itms or iTunes available to them.
+ * @example "itms", "itunes", "javascript"
+ * @returns {String}
+ */
+Base.prototype.sendMethod = function sendMethod() {
+ return 'javascript';
+};
+
+/**
+ * Set related properties for the giving topic
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @param {Object} properties the properties for the topic
+ * @param {Boolean} properties.anonymous true if sending all events for the topic with credentials omitted(no cookies, no PII fields)
+ */
+Base.prototype.setProperties = function setProperties(topic, properties) {
+ this._topicPropsCache[topic] = this._topicPropsCache[topic] || {};
+ this._topicPropsCache[topic] = properties;
+};
+
+/*
+ * src/network.js
+ * mt-event-queue
+ *
+ * Copyright © 2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Network request methods exposed so delegate callers can override
+ * @constructor
+ */
+var network = {
+ /**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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 reflect.attachDelegate(this, delegate);
+ },
+ /**
+ * Covers private util network functions for delegation
+ */
+ makeAjaxRequest: network$1.makeAjaxRequest
+};
+
+/*
+ * src/event_queue.js
+ * mt-event-queue
+ *
+ * A low-level, cross-platform, metrics batcher and emitter.
+ * Adapted from Jingle (its.MetricsQueue)
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Some ugly user agent detection, because iOS browsers refuse to send image pings onpagehide
+ * @return {Boolean} true if this browser is running on an iOS device
+ */
+function _isIOS() {
+ var userAgent = navigator.userAgent;
+
+ // IE for Windows Phone 8.1 contains the string 'iPhone', so we have to check for that too...
+ // https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
+ return /iPad|iPhone|iPod/.test(userAgent) && userAgent.indexOf('IEMobile') == -1;
+}
+
+/**
+ * Check if the fetch API is supported by the Browser and check if "keepalive" is available by checking for Firefox
+ * @returns {Boolean} true if fetch API is supported
+ */
+function _isFetchAndKeepaliveAvailable() {
+ return reflect.isFunction(environment.globalScope().fetch) && !/Firefox/.test(navigator.userAgent);
+}
+
+function _logger() {
+ return loggerNamed('mt-event-queue');
+}
+
+var CONSTANTS = {
+ DEFAULT_REQUEST_TIMEOUT: 10000, // TODO: move to config
+ EVENTS_KEY: 'events',
+ EVENT_DELIVERY_VERSION: '1.0',
+ MAX_PERSISTENT_QUEUE_SIZE: 100, // TODO: move to config
+ RETRY_EXPONENT_BASE: 2, // TODO: move to config
+ SEND_METHOD: SEND_METHOD,
+ URL_DELIVERY_VERSION: 2,
+ PROPERTIES_KEY: 'properties'
+};
+
+var _eventQueue = {
+ /**
+ * A dictionary of event queues by topic
+ * Each topic queue is an object that holds an array of the events themselves and information about send attempts for that topic (postIntervalToken, retryAttempts)
+ * We use an in-memory queue for two reasons:
+ * 1) We expect all remaining events to get sent on app exit via flushUnreportedEvents (ignoring the failed send case)
+ * 2) Stringifying and writing is expensive - as much as 5ms to write 100 events to localStorage on an older (2013) mobile device,
+ * and that would have to happen each time we did an event insertion, since localStorage only allows the saving of stringified (serialized) data.
+ */
+ eventQueues: {},
+
+ /**
+ * Determines whether events will be sent via a post interval.
+ * If this value is false then the client must perform flushing manually and events will not be scheduled or sent automatically.
+ */
+ postIntervalEnabled: true,
+
+ /**
+ * Enqueue an event to a particular topic queue
+ * @param {Object} topicConfig a config instance for the Figaro "topic" that this event should be stored under
+ * @param {Object} eventFields a JavaScript object which will be converted to a JSON string and enqued for sending to Figaro according to the postFrequency schedule
+ * @return {Promise} a Promise that returns the current event, if it was successfully queued, otherwise null
+ */
+ enqueueEvent: function enqueueEvent(topicConfig, eventFields) {
+ if (topicConfig && eventFields && topicConfig.topic()) {
+ var topic = topicConfig.topic();
+ _eventQueue.eventQueues = _eventQueue.eventQueues || {};
+ _eventQueue.eventQueues[topic] = _eventQueue.eventQueues[topic] || {};
+ _eventQueue.eventQueues[topic].topicConfig = topicConfig;
+ _eventQueue.eventQueues[topic].flushConfig = _eventQueue.eventQueues[topic].flushConfig || {};
+ _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY] =
+ _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY] || [];
+ _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY].push(eventFields); // Add the new event to the end of our eventQueue
+
+ // Set flushing related config values
+ if (Object.keys(_eventQueue.eventQueues[topic].flushConfig).length === 0) {
+ Promise.all([
+ topicConfig.value('metricsUrl'),
+ topicConfig.value('requestTimeout'),
+ topicConfig.value('postFrequency')
+ ]).then(function (results) {
+ var flushConfig = _eventQueue.eventQueues[topic].flushConfig;
+ flushConfig.metricsUrl = results[0];
+ flushConfig.requestTimeout = results[1];
+ flushConfig.postFrequency = results[2];
+ });
+ }
+
+ return topicConfig.value('maxPersistentQueueSize').then(function (maxQueueSize) {
+ maxQueueSize = maxQueueSize || CONSTANTS.MAX_PERSISTENT_QUEUE_SIZE;
+ _eventQueue.trimEventQueues(_eventQueue.eventQueues, maxQueueSize);
+ return eventFields;
+ });
+ } else {
+ return Promise.resolve(null);
+ }
+ },
+
+ /**
+ * Currently this just ensures that each queue is <= maxEventsPerQueue, but in the future we could remove oldest events in a queue-independent way
+ * @param {Object} eventQueues a dictionary of event queues (arrays) by topic
+ * @param {int} maxEventsPerQueue
+ */
+ trimEventQueues: function trimEventQueues(eventQueues, maxEventsPerQueue) {
+ var topics = Object.keys(eventQueues);
+ if (topics.length) {
+ topics.forEach(function (topic) {
+ var events = eventQueues[topic][CONSTANTS.EVENTS_KEY];
+ if (events && events.length && events.length > maxEventsPerQueue) {
+ _logger().warn(
+ 'eventQueue overflow, deleting LRU events: size is: ' +
+ events.length +
+ ' which is over max size: ' +
+ maxEventsPerQueue
+ );
+ eventQueues[topic][CONSTANTS.EVENTS_KEY] = events.slice(-maxEventsPerQueue);
+ }
+ });
+ }
+ },
+
+ /**
+ * Clears an event queue for a topic
+ * @param {String} topic defines the Figaro "topic" queue that should be cleared
+ */
+ resetTopicQueue: function resetTopicQueue(topic) {
+ if (_eventQueue.eventQueues[topic]) {
+ _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY] = null;
+ }
+ },
+
+ /**
+ * Resets the retry attempt counter for a topic, called when a send for that topic is successful
+ * @param {String} topic defines the Figaro "topic" queue whose counter should be cleared
+ */
+ resetTopicRetryAttempts: function resetTopicRetryAttempts(topic) {
+ if (_eventQueue.eventQueues[topic]) {
+ _eventQueue.eventQueues[topic].retryAttempts = 0;
+ }
+ },
+
+ /**
+ * Increments the retry attempt counter for a topic, called when a send for that topic results in a 5XX response
+ * Sets the next send time for the topic according to an exponential backoff strategy
+ * @param {Object} A config for the Figaro "topic" queue whose counter should be incremented
+ * @returns {Promise}
+ */
+ scheduleNextTopicRetryAttempt: function scheduleNextTopicRetryAttempt(topicConfig) {
+ var topic = topicConfig.topic();
+ if (_eventQueue.eventQueues[topic] && this.postIntervalEnabled) {
+ return topicConfig.value('postFrequency').then(function (postFrequency) {
+ var topicEventQueue = _eventQueue.eventQueues[topic];
+ topicEventQueue.retryAttempts = topicEventQueue.retryAttempts || 0;
+ topicEventQueue.retryAttempts++;
+
+ var nextSendTime =
+ Math.pow(CONSTANTS.RETRY_EXPONENT_BASE, topicEventQueue.retryAttempts) * postFrequency;
+ _eventQueue.resetTopicPostInterval(topic);
+ _eventQueue.setTopicPostInterval(topicConfig, nextSendTime);
+ });
+ } else {
+ return Promise.resolve();
+ }
+ },
+
+ /**
+ * Send queued events to the ingestion server
+ * If a particular topic send has previously failed, RETRY_BACKOFF_SKIP_COUNT_KEY will be nonzero, indicating we should skip sending that number of times
+ * @param {String} sendMethod "image" or "ajax" (default) or "ajaxSynchronous"
+ * @param {Boolean} postNow will be true when clients force a send outside of the regular postFrequency interval
+ * @returns {Promise}
+ */
+ sendEvents: function sendEvents(sendMethod, postNow) {
+ var sendingTasks = [];
+ for (var topic in _eventQueue.eventQueues) {
+ var topicConfig = _eventQueue.eventQueues[topic].topicConfig;
+ var sendingPromise = _eventQueue.sendEventsForTopicConfig(topicConfig, sendMethod, postNow);
+ sendingTasks.push(sendingPromise);
+ }
+
+ return Promise.all(sendingTasks);
+ },
+
+ /**
+ * Send events for a single topic queue
+ * @param {Object} topicConfig defines the Figaro "topic" queue
+ * @param {String} sendMethod "image" or "ajax" (default) or "ajaxSynchronous"
+ * @param {Boolean} postNow will be true when clients force a send outside of the regular postFrequency interval
+ * @returns {Promise}
+ */
+ sendEventsForTopicConfig: function sendEventsForTopicConfig(topicConfig, sendMethod, postNow) {
+ var topic = topicConfig.topic();
+ var topicQueue = _eventQueue.eventQueues[topic];
+
+ return Promise.all([
+ topicConfig.value('testExponentialBackoff'),
+ topicConfig.value('metricsUrl'),
+ topicConfig.disabled(),
+ topicConfig.value('postFrequency')
+ ]).then(function (outputs) {
+ var testExponentialBackoff = outputs[0];
+ var metricsUrl = outputs[1];
+ var topicDisabled = outputs[2];
+ var postFrequency = outputs[3];
+
+ if (topicQueue && metricsUrl && !topicDisabled && !testExponentialBackoff) {
+ // Do not send if we are trying to postNow in the middle of a backoff
+ if (!(topicQueue.retryAttempts && postNow)) {
+ // The rule is "we post every postFrequency milliseconds", so even if it's been less than that (e.g. postNow), we reset
+ _eventQueue.resetTopicPostInterval(topic);
+ _eventQueue.setTopicPostInterval(topicConfig, postFrequency);
+ var sendingPromise;
+
+ switch (sendMethod) {
+ case CONSTANTS.SEND_METHOD.IMAGE:
+ sendingPromise = _eventQueue.sendEventsViaImage(topicConfig);
+ break;
+ case CONSTANTS.SEND_METHOD.BEACON:
+ sendingPromise = _eventQueue.sendEventsViaBeacon(topicConfig);
+ break;
+ case CONSTANTS.SEND_METHOD.AJAX_SYNCHRONOUS:
+ sendingPromise = _eventQueue.sendEventsViaAjax(topicConfig, false);
+ break;
+ case CONSTANTS.SEND_METHOD.AJAX: /* falls through */
+ default:
+ sendingPromise = _eventQueue.sendEventsViaAjax(topicConfig, true);
+ break;
+ }
+
+ return sendingPromise;
+ }
+ }
+ // Fail automatically if test flag present
+ else if (testExponentialBackoff) {
+ return _eventQueue.scheduleNextTopicRetryAttempt(topicConfig);
+ }
+ });
+ },
+
+ /**
+ * Makes one image ping per event in a queue of events, then clears the queue
+ * This is typically called on page / app close when the JS context is about to disappear and thus we will not know if the events made it to the server
+ * Current testing shows that browsers support 100+ image pings sent onpagehide, but we may need to alter our approach if this becomes unreliable
+ * (For example, we could send multiple events in a single ping instead, but there might be URL length issues in IE)
+ * @param {Object} topicConfig a config instance for the Figaro "topic" to send to
+ * @returns {Promise}
+ */
+ sendEventsViaImage: function sendEventsViaImage(topicConfig) {
+ var topic = topicConfig.topic();
+ var returnedPromise = Promise.resolve();
+
+ if (_eventQueue.eventQueues[topic]) {
+ returnedPromise = resolveTopicConfigWithCallback(topicConfig, function (resolvedTopicConfig) {
+ var topicUrl = resolvedTopicConfig.metricsUrl;
+ var qpSeparator = topicUrl.indexOf('?') == -1 ? '?' : '&';
+ var imageBaseUrl = topicUrl + qpSeparator + 'responseType=image';
+ var events = _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY];
+
+ if (events && events.length) {
+ events.forEach(function (event) {
+ var imageParams = _eventQueue.createQueryParams(event);
+ if (imageParams) {
+ var imgUrl = imageBaseUrl + '&' + imageParams;
+ var imgObject = new Image();
+ var properties = _eventQueue.eventQueues[topic][CONSTANTS.PROPERTIES_KEY];
+ if (properties && properties.anonymous) {
+ imgObject.setAttribute('crossOrigin', 'anonymous');
+ }
+ imgObject.src = imgUrl;
+ }
+ });
+ }
+
+ _eventQueue.resetTopicQueue(topic);
+ });
+ }
+ return returnedPromise;
+ },
+
+ /**
+ * Convert an event object into a query parameter string, without a leading separator
+ * Guaranteed to return "null" if there are no event fields
+ * @param {Object} event key/value pairs containing event data
+ */
+ createQueryParams: function createQueryParams(event) {
+ var val;
+ var stringVal;
+ var returnValue = '';
+ Object.keys(event).forEach(function (key, index, eventKeys) {
+ val = event[key];
+ // do not double-encode strings otherwise they will be reported as: ''
+ stringVal = reflect.isString(val) ? val : JSON.stringify(val);
+ returnValue += key + '=' + encodeURIComponent(stringVal);
+ if (index < eventKeys.length - 1) {
+ // don't add a trailing ampersand
+ returnValue += '&';
+ }
+ });
+ return returnValue.length ? returnValue : null;
+ },
+
+ /**
+ * Makes one AJAX request per topic and clears the queue for that topic on success
+ * If any queue fails, retry using an exponential backoff strategy for that queue
+ * Refer to Metrics documentation for more details
+ * @param {Object} topicConfig a config instance for the Figaro "topic" to send to
+ * @param {Boolean} async - send asynchronously
+ * @returns {Promise}
+ */
+ sendEventsViaAjax: function sendEventsViaAjax(topicConfig, async) {
+ var returnedPromise = Promise.resolve();
+ var topic = topicConfig.topic();
+ if (_eventQueue.eventQueues[topic] && _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY]) {
+ // Store events to be sent and reset the queue
+ var events = _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY];
+ var jsonEventsString = enrichAndSerializeEvents(events);
+ _eventQueue.resetTopicQueue(topic);
+
+ if (jsonEventsString) {
+ returnedPromise = resolveTopicConfigWithCallback(topicConfig, function (resolvedTopicConfig) {
+ var topicUrl = resolvedTopicConfig.metricsUrl;
+ var requestTimeout = resolvedTopicConfig.requestTimeout;
+ var resetRetryAttempts = function resetRetryAttempts() {
+ _eventQueue.resetTopicRetryAttempts(topic);
+ };
+ var onAjaxFailure = function onAjaxFailure(error, statusCode) {
+ // We're being told not to keep resending these events.
+ if (statusCode >= 400 && statusCode < 500) {
+ resetRetryAttempts();
+ } else {
+ // Prepend the events that failed to send back onto the queue
+ var newEvents = _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY] || [];
+ _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY] = events.concat(newEvents);
+
+ _eventQueue.scheduleNextTopicRetryAttempt(topicConfig);
+ }
+ };
+ var eventQueueProps = _eventQueue.eventQueues[topic][CONSTANTS.PROPERTIES_KEY] || {};
+ var options = {
+ async: async,
+ timeout: requestTimeout
+ };
+
+ if (eventQueueProps.anonymous) {
+ options.withCredentials = false;
+ }
+ network.makeAjaxRequest(topicUrl, 'POST', jsonEventsString, resetRetryAttempts, onAjaxFailure, options);
+ });
+ }
+ }
+ return returnedPromise;
+ },
+
+ /**
+ * Makes an HTTP POST request via navigator.sendBeacon() if it is in the environment.
+ * Note: The sendBeacon() method returns true if the user agent successfully queued the data for transfer, otherwise false.
+ * sendBeacon() always includes credentials, so we fallback to the IMAGE/AJAX_SYNCHRONOUS methods to handle the anonymous use case.
+ * While testing out possible failures of sendBeacon(), the only 2 cases we encountered was using a non-"simple" content type like "application/json",
+ * or by passing data that is too large. Both cases would lead to failures when retrying, so retrying logic has intentionally been omitted in this approach.
+ * @param {Object} topicConfig a config instance for the Figaro "topic" to send to
+ */
+ sendEventsViaBeacon: function sendEventsViaBeacon(topicConfig) {
+ var sendPromise = Promise.resolve();
+
+ if (!reflect.isFunction(navigator.sendBeacon)) {
+ _logger().error('navigator.sendBeacon() is not available in the environment');
+ return sendPromise;
+ }
+
+ var topic = topicConfig.topic();
+ var topicQueue = _eventQueue.eventQueues[topic];
+
+ if (topicQueue) {
+ var eventQueueProps = topicQueue[CONSTANTS.PROPERTIES_KEY];
+
+ // Fallback to other send methods to handle the anonymous use case
+ if (eventQueueProps && eventQueueProps.anonymous) {
+ // using fetch with keepalive to send events to fix the large event content issue (impressions field).
+ if (_isFetchAndKeepaliveAvailable()) {
+ sendPromise = _eventQueue.sendEventsViaFetch(topicConfig, { keepalive: true });
+ } else if (_isIOS()) {
+ // iOS browsers do not allow image pings to send onpagehide; use (deprecated) synchronous AJAX in those browsers
+ sendPromise = _eventQueue.sendEventsViaAjax(topicConfig, false);
+ } else {
+ sendPromise = _eventQueue.sendEventsViaImage(topicConfig);
+ }
+ } else {
+ var jsonEventsString = enrichAndSerializeEvents(topicQueue[CONSTANTS.EVENTS_KEY]);
+ if (jsonEventsString) {
+ _eventQueue.resetTopicQueue(topic);
+
+ sendPromise = resolveTopicConfigWithCallback(topicConfig, function (resolvedTopicConfig) {
+ var topicUrl = resolvedTopicConfig.metricsUrl;
+ var Blob = environment.globalScope().Blob;
+ var eventsBlob = new Blob([jsonEventsString], { type: 'application/json' });
+ var beaconResponse = navigator.sendBeacon(topicUrl, eventsBlob);
+
+ if (!beaconResponse) {
+ _logger().error('navigator.sendBeacon() was unable to queue the data for transfer');
+ }
+ });
+ }
+ }
+ }
+
+ return sendPromise;
+ },
+
+ /**
+ * Makes an HTTP POST request via fetch()
+ * @param {Object} topicConfig a config instance for the Figaro "topic" to send to
+ * @param {Object} options an object contains the options of fetch API
+ * @param {Boolean} options.keepalive whether allow the request to outlive the page
+ */
+ sendEventsViaFetch: function sendEventsViaFetch(topicConfig, options) {
+ var sendPromise = Promise.resolve();
+ var topic = topicConfig.topic();
+ var topicQueue = _eventQueue.eventQueues[topic];
+ var keepalive = reflect.isDefinedNonNull(options) ? options.keepalive : null;
+
+ if (reflect.isDefinedNonNull(topicQueue)) {
+ var jsonEventsString = enrichAndSerializeEvents(topicQueue[CONSTANTS.EVENTS_KEY]);
+ if (jsonEventsString) {
+ _eventQueue.resetTopicQueue(topic);
+
+ var eventQueueProps = topicQueue[CONSTANTS.PROPERTIES_KEY] || {};
+ sendPromise = resolveTopicConfigWithCallback(topicConfig, function (resolvedTopicConfig) {
+ var topicUrl = resolvedTopicConfig.metricsUrl;
+ reflect.globalScope().fetch(topicUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: jsonEventsString,
+ credentials: eventQueueProps.anonymous === true ? 'omit' : 'same-origin',
+ keepalive: reflect.isDefinedNonNull(keepalive) ? keepalive : true
+ });
+ });
+ }
+ }
+ return sendPromise;
+ },
+
+ /**
+ * If no postInterval is currently set, sets postInterval to "postInterval"
+ * Note: Currently, only _sendEvents calls this function, but in the future, if other callers wanted to set a new interval due to, say, a config change,
+ * then events enqueued under the old postFrequency time will now have to wait (or be expedited) to the new time. To fix this, when any old
+ * interval fires, the callback can check to see if the interval passed in is different than _eventQueue.postIntervalToken and if so, it will tear down its timer.
+ * @param {Object} topic config
+ * @param {int} postInterval in ms
+ */
+ setTopicPostInterval: function setTopicPostInterval(topicConfig, postInterval) {
+ var topic = topicConfig.topic();
+ if (_eventQueue.eventQueues[topic] && postInterval && this.postIntervalEnabled) {
+ this.resetTopicPostInterval(topic);
+ _eventQueue.eventQueues[topic].postIntervalToken = environment
+ .globalScope()
+ .setInterval(function onPostIntervalTrigger() {
+ _logger().debug(
+ 'MetricsKit: triggering postIntervalTimer for ' + topic + ' at ' + new Date().toString()
+ );
+ _eventQueue.sendEventsForTopicConfig(topicConfig);
+ }, postInterval);
+ }
+ },
+
+ resetTopicPostInterval: function resetTopicPostInterval(topic) {
+ if (_eventQueue.eventQueues[topic]) {
+ environment.globalScope().clearInterval(_eventQueue.eventQueues[topic].postIntervalToken);
+ _eventQueue.eventQueues[topic].postIntervalToken = null;
+ }
+ },
+
+ resetQueuePostIntervals: function resetQueuePostIntervals() {
+ for (var topic in _eventQueue.eventQueues) {
+ _eventQueue.resetTopicPostInterval(topic);
+ }
+ },
+
+ setQueuePostIntervals: function setQueuePostIntervals() {
+ var tasks = [];
+ var setTopicPostIntervalFn = function (topicConfig) {
+ return function (postFrequency) {
+ _eventQueue.setTopicPostInterval(topicConfig, postFrequency);
+ };
+ };
+ for (var topic in _eventQueue.eventQueues) {
+ var events = _eventQueue.eventQueues[topic][CONSTANTS.EVENTS_KEY];
+ var topicConfig = _eventQueue.eventQueues[topic].topicConfig;
+ if (events && events.length) {
+ var taskPromise = topicConfig.value('postFrequency').then(setTopicPostIntervalFn(topicConfig));
+ tasks.push(taskPromise);
+ }
+ }
+ return Promise.all(tasks);
+ },
+
+ /**
+ * Determines whether the object contains the provided value.
+ * Note that values that are functions will be ignored.
+ * @param {Object} object the object whose values will be evaluated
+ * @param {Any} value the requested value to search for on the provided object
+ * @returns {Boolean} whether the object contains the value
+ * TODO: consider moving to utils
+ */
+ objectContainsValue: function objectContainsValue(object, value) {
+ var result = false;
+ for (var property in object) {
+ var aValue = object[property];
+ if (object.hasOwnProperty(property) && !reflect.isFunction(aValue) && aValue === value) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Set event queue related properties for the giving topic
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @param {Object} properties the event queue properties for the topic
+ * @param {Boolean} properties.anonymous true if sending all events for the topic with credentials omitted(no cookies, no PII fields)
+ */
+ setProperties: function setProperties(topic, properties) {
+ _eventQueue.eventQueues = _eventQueue.eventQueues || {};
+ _eventQueue.eventQueues[topic] = _eventQueue.eventQueues[topic] || {};
+ _eventQueue.eventQueues[topic][CONSTANTS.PROPERTIES_KEY] = properties;
+ }
+};
+
+/**
+ ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+function _utQueue() {
+ return _eventQueue;
+}
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Adds all the supplemental fields like "postTime", etc.
+ * Guaranteed to return "null" if there are no events
+ * @param {Array} eventQueue a list of events to send
+ * @return A stringified version of our eventQueue as a batch, including supplementary top-level fields, all ready to deliver in a ping
+ */
+function enrichAndSerializeEvents(eventQueue) {
+ var eventsBatchString = null;
+
+ if (eventQueue && eventQueue.length) {
+ var eventsDict = {};
+ eventsDict['deliveryVersion'] = CONSTANTS.EVENT_DELIVERY_VERSION;
+ eventsDict['postTime'] = Date.now();
+ eventsDict[CONSTANTS.EVENTS_KEY] = eventQueue;
+ try {
+ eventsBatchString = JSON.stringify(eventsDict);
+ } catch (e) {
+ _logger().error('Error stringifying events as JSON: ' + e);
+ }
+ }
+ return eventsBatchString;
+}
+
+function enrichAndSerializeEvent(event) {
+ return enrichAndSerializeEvents([event]);
+}
+
+/**
+ * @param {Object} topicConfig instance
+ * @return {Promise} a Promise that returns the metrics URL for this topic
+ */
+function metricsUrlForConfig(topicConfig) {
+ return topicConfig.value('metricsUrl').then(function (metricsUrl) {
+ var baseMetricsUrl = metricsUrl + '/' + CONSTANTS.URL_DELIVERY_VERSION + '/';
+ return baseMetricsUrl + topicConfig.topic();
+ });
+}
+
+/**
+ * This method is used to hide the topicConfig.value implementation for multiple properties, in order to support async and sync logic in sendEventsViaAjax().
+ * @param {Object} topicConfig an instance of TopicConfig(with a method named "value" and returns Promise from it) or an object that contains the "metricsUrl" and "topic()" properties
+ * @param {Function} callback a function to invoke with an object has metricsUrl, requestTimeout
+ */
+function resolveTopicConfigWithCallback(topicConfig, callback) {
+ if (!reflect.isFunction(callback)) {
+ _logger().warn('No callback function is provided for resolveTopicConfigWithCallback()');
+ return;
+ }
+
+ if (reflect.isString(topicConfig.metricsUrl) && !reflect.isFunction(topicConfig.value)) {
+ var topic = reflect.isFunction(topicConfig.topic) ? topicConfig.topic() : topicConfig.topic;
+ var metricsUrl = topicConfig.metricsUrl + '/' + CONSTANTS.URL_DELIVERY_VERSION + '/' + topic;
+ var requestTimeout = topicConfig.requestTimeout || CONSTANTS.DEFAULT_REQUEST_TIMEOUT;
+ var postFrequency = topicConfig.postFrequency;
+
+ return callback({
+ requestTimeout: Math.min(requestTimeout, postFrequency),
+ metricsUrl: metricsUrl
+ });
+ } else {
+ return Promise.all([
+ topicConfig.value('requestTimeout'),
+ topicConfig.value('postFrequency'),
+ metricsUrlForConfig(topicConfig)
+ ]).then(function (outputs) {
+ var requestTimeout = outputs[0] || CONSTANTS.DEFAULT_REQUEST_TIMEOUT;
+ var postFrequency = outputs[1];
+ var metricsUrl = outputs[2];
+ return callback({
+ requestTimeout: Math.min(requestTimeout, postFrequency),
+ metricsUrl: metricsUrl
+ });
+ });
+ }
+}
+
+/**
+ * @param {Object} topicConfig instance
+ * @return {Promise} a Promise that returns the request timeout for this config
+ * Defaults to CONSTANTS.DEFAULT_REQUEST_TIMEOUT
+ */
+function requestTimeoutForConfig(topicConfig) {
+ return Promise.all([topicConfig.value('requestTimeout'), topicConfig.value('postFrequency')]).then(function (
+ outputs
+ ) {
+ var requestTimeout = outputs[0] || CONSTANTS.DEFAULT_REQUEST_TIMEOUT;
+ var postFrequency = outputs[1];
+ return Math.min(requestTimeout, postFrequency);
+ });
+}
+
+/**
+ * Determines whether events will be sent via a post interval.
+ * If this value is false then the client must perform flushing manually and events will not be scheduled or sent automatically.
+ * @param {Bool} enabled whether the postInterval is enabled or not
+ * @returns {Promise}
+ */
+function setPostIntervalEnabled(enabled) {
+ _eventQueue.postIntervalEnabled = enabled;
+ if (enabled) {
+ return _eventQueue.setQueuePostIntervals();
+ } else {
+ return Promise.resolve(_eventQueue.resetQueuePostIntervals());
+ }
+}
+
+/**
+ * Enqueues events to be sent to the server in batches after "postFrequency" milliseconds since the previous send.
+ * The queue is stored in memory, and will retry on failed sends for as long as the session is open.
+ * Immediately before a page turn/tab close (usually onpagehide), clients can call flushUnreportedEvents() to send up any events still left in the queue.
+ *
+ * Flow:
+ * 1. Normal:
+ * a. [recordEvent()} Events posted to in-memory eventQueue via "recordEvent() (this method)".
+ * NOTE1: Only remember the most recent MAX_PERSISTENT_QUEUE_SIZE events per queue so we don't eat up all browser memory if we can't send for a very long time.
+ * NOTE2: We could have another set of "waitingForAck" queues which was also in-memory, but then
+ * we'd need code to merge two queues on re-tries (since _sendEvents() already merges in previously-queued events before attempting to [re]send)
+ * Since the send failure case is a rare case anyway, we err on the side of not adding code complexity to deal with it.
+ * b. [_sendEvents()] Wait "_postFrequency()" milliseconds before attempting actual "_sendEvents()"
+ * c. Set a timeout for the send which should be the lower of "_postFrquency()" or config.requestTimeout (which defaults to DEFAULT_REQUEST_TIMEOUT).
+ * This guarantees that only one send attempt per topic is pending at any given time.
+ * d. Wait for status=200 response ack, and clear event queue when received (see "Edge Cases", below)
+ * 2. Unsent due to ingestion server problem (5XX response):
+ * a. Continue gathering events, adding to in-memory eventQueue
+ * b. Use an exponential back-off strategy by waiting 2^1 = times "_postFrequency()" milliseconds before attempting another "_sendEvents()", as in "Normal" case, above
+ * c. If the ingestion server responds with another 5XX code, wait 2^2 = 4 times "_postFrequency()" milliseconds before attempting another send, and so on
+ * d. Continue as in 1c, above
+ * 3. Unsent due to failed conection or other error:
+ * a. Continue gathering events, adding to in-memory eventQueue
+ * b. Wait "_postFrequency()" milliseconds before attempting another "_sendEvents()", as in "Normal" case, above
+ * c. Continue as in 1c, above
+ *
+ * Edge Cases: Here are the small edge-case windows of failure in the in-memory event queue facility:
+ * 1. If two batches of events are sent out before the first one returns and clears the queue, the second one will try to send any queued events as well.
+ * For this to happen, we'd have to be in a situation where we're not leaving the page, event sends are taking some time to respond,
+ * causing us to build up a queue, and then two "sends" are attempted back-to-back by a client calling recordEvent() with postNow=true or flushUnreportedEvents().
+ * It's so rare that the code complexity to try and prevent this case is not worth it (synchronous event posting is the most straightforward way to handle it).
+ * 2. If we send a batch of events, the last of which causes, say, a page turn, and invokes flushUnreportedEvents(), we will never get the ack that the batch made it to the server
+ * so we just clear the queue and assume that they made it. During empirical testing, events did continue to get sent event after the browser is closed as long as there is a network connection.
+ * 3. If the ingestion server is down, we would only retry as long as the session is open, and lose all events once the user leaves. Events are usually flushed on page turns
+ * without the opportunity to see if the send was successful (see 2 above) so if we wanted to preserve events in the case of ingestion server failure,
+ * we would have to a) keep track of the previous send attempts, b) not flush events on page turn, c) stash the events and the retry state in persistent storage,
+ * and d) fetch the events from persistent storage and restore the retry state, which we choose not to do in order to avoid all of this complexity for what is probably a rare scenario.
+ *
+ * Note: each topic has its own queue, and all of the above logic applies to each individual queue.
+ * For example, exponential backoff works independently on a per-topic basis, in case the server tells us to back off of one topic, but not another.
+ * Testing tips:
+ * 1) A debug message is logged every time a postInterval timer is triggered, but the metricskit logger debug messages are off by default.
+ * They can be enabled in Web inspector via: metrics.system.logger.setLevel(metrics.system.logger.DEBUG);
+ * 2) Exponential backoff can be tested by using a debug source with the 'testExpontentialBackoff' flag enabled.
+ * When this flag is enabled, the metrics queue will skip sending and instead schedule the next retry for _postFrequency() * RETRY_EXPONENT_BASE ^ n milliseconds
+ * where n is the current retry attempt. (see mt-metricskit docs for more details on using debug sources)
+ * To see this in action, you can enable debug logs as in (1) above, then set a debug source with testExponentialBackoff=true (and optionally, a lower postFrequency),
+ * then call recordEvent() to enqueue an event.
+ * You should see a debug message (which includes a timestamp) after postFrequency milliseconds, and then every postFrequency * 2^n milliseconds thereafter.
+ *
+ * @param {Object} topicConfig - a instance of Config class(mt-client-config) which contains the topic defined the Figaro "topic" that this event should be stored under
+ * @param {Object} eventFields a JavaScript object which will be converted to a JSON string and enqued for sending to Figaro according to the postFrequency schedule
+ * @param {Boolean} postNow - effectively forces an immediate send, along with any other messages that may be sitting in the queue
+ * @returns {Promise}
+ */
+function recordEvent(topicConfig, eventFields, postNow) {
+ var topic = topicConfig.topic();
+
+ return topicConfig.disabled().then(function (disabled) {
+ if (!disabled) {
+ return topicConfig
+ .value('postFrequency')
+ .then(function (postFrequency) {
+ if (postFrequency === 0) {
+ postNow = true;
+ }
+ // The "pagehide" event was tested and works reliably, so we only need to keep the queue in memory, and expect clients to clear the queue on app close
+ return _eventQueue.enqueueEvent(topicConfig, eventFields).then(function () {
+ return postFrequency;
+ });
+ })
+ .then(function (postFrequency) {
+ if (postNow) {
+ return _eventQueue.sendEvents(CONSTANTS.SEND_METHOD.AJAX, true);
+ } else if (!_eventQueue.eventQueues[topic].postIntervalToken && _eventQueue.postIntervalEnabled) {
+ // Schedule the next send if the timer isn't already running
+ _eventQueue.setTopicPostInterval(topicConfig, postFrequency);
+ }
+ });
+ }
+ });
+}
+
+/**
+ * Sends any remaining events in the queue, then clears it.
+ * Events will be sent as individual image pings or as synchronous AJAX requests for iOS (these are the only send methods that actually
+ * get through during a pagehide event). We expect clients to call this function before the app closes in order to clear the event queues,
+ * otherwise any remaining events will be lost. We could theoretically add our own event listener so this call happens automatically,
+ * but single page apps could be using history.pushState which triggers onpagehide, and in those cases they would not
+ * need to flush until the app is actually closing.
+ * Note: This is typically called on page / app close when the JS context is about to disappear and thus we will not know if the events made it to the server
+ * @param {Boolean} appIsExiting - Pass true if events are being flushed due to your app exiting or page going away
+ * (the send method will be different in order to attempt to post events prior to actual termination)
+ * @param {String} appExitSendMethod (optional) the send method for how events will be flushed when the app is exiting.
+ * Possible options are enumerated in the `eventRecorder.SEND_METHOD` object.
+ * Note: This argument will be ignored if appIsExiting is false.
+ * @returns {Promise}
+ */
+function flushUnreportedEvents(appIsExiting, appExitSendMethod) {
+ if (appIsExiting) {
+ if (appExitSendMethod === SEND_METHOD.BEACON_SYNCHRONOUS) {
+ return flushUnreportedEventsSynchronously();
+ }
+ if (reflect.isString(appExitSendMethod) && _eventQueue.objectContainsValue(SEND_METHOD, appExitSendMethod)) {
+ return _eventQueue.sendEvents(appExitSendMethod, true);
+ } else if (reflect.isFunction(navigator.sendBeacon)) {
+ return _eventQueue.sendEvents(CONSTANTS.SEND_METHOD.BEACON, true);
+ } else {
+ // iOS browsers do not allow image pings to send onpagehide; use (deprecated) synchronous AJAX in those browsers
+ if (_isIOS()) {
+ return _eventQueue.sendEvents(CONSTANTS.SEND_METHOD.AJAX_SYNCHRONOUS, true);
+ } else {
+ return _eventQueue.sendEvents(CONSTANTS.SEND_METHOD.IMAGE, true);
+ }
+ }
+ } else {
+ return _eventQueue.sendEvents(CONSTANTS.SEND_METHOD.AJAX, true);
+ }
+}
+
+function flushUnreportedEventsSynchronously() {
+ for (var topic in _eventQueue.eventQueues) {
+ var topicQueue = _eventQueue.eventQueues[topic];
+ var flushConfig = topicQueue.flushConfig;
+ var resolvedTopicConfig = reflect.extend({}, flushConfig, {
+ _topic: topic,
+ topic: function () {
+ return this._topic;
+ }
+ });
+
+ _eventQueue.sendEventsViaBeacon(resolvedTopicConfig);
+ }
+}
+
+/*
+ * src/event_recorder.js
+ * mt-event-queue
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides a pre-built delegate to use against the metrics.system.eventRecorder delegate via metrics.system.eventRecorder.setDelegate()
+ * If you want to use *most* of these methods, but not *all* of them, you can set this delegate and then create your own with whichever few methods you need to
+ * customize additionally, and then setDelegate() *that* delegate, in order to override those methods.
+ * @constructor
+ * @param {Object} kit An object that implements the Kit interface
+ */
+var QueuedEventRecorder = function QueuedEventRecorder(kit) {
+ Base.apply(this, arguments);
+};
+
+QueuedEventRecorder.prototype = Object.create(Base.prototype);
+
+QueuedEventRecorder.prototype.constructor = QueuedEventRecorder;
+
+/**
+ ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+
+QueuedEventRecorder.prototype._utResetQueue = function _utResetQueue() {
+ for (var topic in _utQueue().eventQueues) {
+ _utQueue().resetTopicPostInterval(topic);
+ }
+ _utQueue().eventQueues = {};
+};
+
+/**
+ * An implementation of _record method in the parent class.
+ * @param {Object} topicConfig a config instance for the Figaro "topic" to send to
+ * @param {Object} eventFields a JavaScript object which will be converted to a JSON string and enqued for sending to Figaro according to the postFrequency schedule.
+ * @param {Boolean} postNow - effectively forces an immediate send, along with any other messages that may be sitting in the queue
+ * @returns {Promise}
+ */
+QueuedEventRecorder.prototype._record = function record(topicConfig, eventFields, postNow) {
+ return recordEvent(topicConfig, eventFields, postNow);
+};
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+QueuedEventRecorder.prototype.SEND_METHOD = SEND_METHOD;
+
+/**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+QueuedEventRecorder.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * Sends any remaining events in the queue, then clears it
+ * This is typically called on page / app close when the JS context is about to disappear and thus we will
+ * not know if the events made it to the server
+ * @param {Boolean} appIsExiting - Pass true if events are being flushed due to your app exiting or page going away
+ * (the send method will be different in order to attempt to post events prior to actual termination)
+ * @param {String} appExitSendMethod (optional) the send method for how events will be flushed when the app is exiting.
+ * Possible options are enumerated in the `eventRecorder.SEND_METHOD` object.
+ * Note: This argument will be ignored if appIsExiting is false.
+ * @returns {Promise}
+ */
+QueuedEventRecorder.prototype.flushUnreportedEvents = function flushUnreportedEvents$1(appIsExiting, appExitSendMethod) {
+ return flushUnreportedEvents.apply(null, arguments);
+};
+
+/**
+ * Set event queue related properties for the giving topic
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @param {Object} properties the event queue properties for the topic
+ * @param {Boolean} properties.anonymous true if sending all events for the topic with credentials omitted(no cookies, no PII fields)
+ */
+QueuedEventRecorder.prototype.setProperties = function setProperties(topic, properties) {
+ Object.getPrototypeOf(QueuedEventRecorder.prototype).setProperties.call(this, topic, properties);
+ _utQueue().setProperties(topic, properties);
+};
+
+/**
+ * An implementation of cleanup method in the parent class.
+ */
+QueuedEventRecorder.prototype.cleanup = function cleanup() {
+ Object.getPrototypeOf(QueuedEventRecorder.prototype).cleanup.call(this);
+ this._utResetQueue();
+};
+
+/*
+ * src/immediate_event_recorder.js
+ * mt-event-queue
+ *
+ * Copyright © 2016-2019 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides a pre-built delegate to use against the metrics.system.eventRecorder delegate via metrics.system.eventRecorder.setDelegate()
+ * If you want to use *most* of these methods, but not *all* of them, you can set this delegate and then create your own with whichever few methods you need to
+ * customize additionally, and then setDelegate() *that* delegate, in order to override those methods.
+ * @constructor
+ * @param {Object} kit An object that implements the Kit interface
+ */
+var ImmediateEventRecorder = function ImmediateEventRecorder(kit) {
+ Base.apply(this, arguments);
+};
+
+ImmediateEventRecorder.prototype = Object.create(Base.prototype);
+ImmediateEventRecorder.prototype.constructor = ImmediateEventRecorder;
+
+/**
+ ************************************ PRIVATE METHODS/IVARS ************************************
+ */
+
+/**
+ * An implementation of _record method in the parent class.
+ * @param {Object} topicConfig a config instance for the Figaro "topic" to send to
+ * @param eventFields
+ * @returns {Promise}
+ */
+ImmediateEventRecorder.prototype._record = function record(topicConfig, eventFields) {
+ var jsonEventsString = enrichAndSerializeEvent(eventFields);
+ if (jsonEventsString) {
+ return Promise.all([metricsUrlForConfig(topicConfig), requestTimeoutForConfig(topicConfig)]).then(
+ function (outputs) {
+ var topicUrl = outputs[0];
+ var requestTimeout = outputs[1];
+ var options = { timeout: requestTimeout };
+ if (
+ this._topicPropsCache[topicConfig.topic()] &&
+ this._topicPropsCache[topicConfig.topic()].anonymous
+ ) {
+ options.withCredentials = false;
+ }
+ network.makeAjaxRequest(topicUrl, 'POST', jsonEventsString, null, null, options);
+ }.bind(this)
+ );
+ }
+};
+
+/*
+ * mt-event-queue/index.js
+ * mt-event-queue
+ *
+ * Copyright © 2016-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+var logger = /*#__PURE__*/ loggerNamed('mt-event-queue');
+
+export { QueuedEventRecorder as EventRecorder, ImmediateEventRecorder, environment, logger, network, setPostIntervalEnabled as setEventQueuePostIntervalEnabled };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-core/dist/mt-metricskit-delegates-core.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-core/dist/mt-metricskit-delegates-core.esm.js
new file mode 100644
index 0000000..b2e713f
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-core/dist/mt-metricskit-delegates-core.esm.js
@@ -0,0 +1,289 @@
+import { MetricsConfig } from '@amp-metrics/mt-client-config';
+import { reflect } from '@amp-metrics/mt-metricskit-utils-private';
+
+/*
+ * src/delegate.js
+ * mt-metricskit-delegates-core
+ *
+ * Copyright © 2022 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Abstract class for delegates.
+ * All delegate implementations should extend from this class.
+ * @param {String} topic - Defines the AMP Analytics "topic" for events to be stored under
+ * @param {Object} platformImpls - A map that include the platform-based components.
+ * @param {Environment} platformImpls.environment - An Environment that provide the event field accessor for the platform
+ * @param {EventRecorder} platformImpls.eventRecorder - An EventRecorder that provide the EventRecorder implementation for the platform
+ * @constructor
+ */
+var Delegates = function Delegates(topic, platformImpls) {
+ if (!reflect.isDefinedNonNullNonEmpty(topic) || !reflect.isString(topic)) {
+ throw new Error('No valid topic was provided to Delegates.');
+ }
+
+ this.config = this.getOrCreateConfig(topic);
+ // TODO change platformImpls as an required argument in the next major version or supporting Typescript
+ // since every platforms should provide their own impl for the Environment and EventHandler to the Delegates.
+ if (reflect.isDefinedNonNull(platformImpls)) {
+ this.environment = platformImpls.environment;
+ this.eventRecorder = platformImpls.eventRecorder;
+ }
+ // A flag to indicate whether using the original execution context when calling the delegate methods. Default is false
+ // TODO Consider to change this to true by default in the next major release
+ // because using the execution context of the delegate methods may not necessary as accessing the the delegate methods' execution context is straightforward.
+ this._useOrginalContextForDelegateFunc = false;
+};
+
+/**
+ * Initializes the delegate by setting up the config.
+ * @param delegates
+ * @return {Promise}
+ */
+Delegates.prototype.init = function () {
+ if (!reflect.isDefinedNonNull(this.environment)) {
+ throw new Error('No environment was provided to Delegate options.');
+ }
+
+ if (!reflect.isDefinedNonNull(this.eventRecorder)) {
+ throw new Error('No eventRecorder was provided to Delegate options.');
+ }
+
+ this.config.environment.setDelegate(this.environment);
+ this.config.logger.setDelegate(this.logger);
+ this.config.network.setDelegate(this.network);
+
+ var configSources = reflect.isFunction(this.configSources) ? this.configSources.bind(this) : null;
+
+ return this.config.init(configSources);
+};
+
+/**
+ * Merge the provided delegates into the current Delegate.
+ * NOTE: Any delegates which already exist in the Delegate won't be merged.
+ * @param {Object} delegates - A key/value map of the system delegates
+ */
+Delegates.prototype.mergeDelegates = function (delegates) {
+ var self = this;
+ for (var key in delegates) {
+ if (!self[key]) {
+ self[key] = delegates[key];
+ }
+ }
+
+ this.config.setDelegate(delegates.config);
+};
+
+/**
+ * Cleans up resources used by the delegate.
+ */
+Delegates.prototype.cleanup = function cleanup() {
+ if (reflect.isFunction(this.eventRecorder.cleanup)) {
+ this.eventRecorder.cleanup();
+ }
+
+ this.config = null;
+ this.eventRecorder = null;
+ this.environment = null;
+};
+
+/**
+ * Overrides the platform-specific event recorder for the Delegate.
+ * @param {object} eventRecorderDelegates
+ * @returns {Delegates}
+ */
+Delegates.prototype.setEventRecorder = function setEventRecorder(eventRecorderDelegates) {
+ if (reflect.isDefinedNonNull(eventRecorderDelegates)) {
+ if (!reflect.isDefinedNonNull(this.eventRecorder)) {
+ this.eventRecorder = eventRecorderDelegates;
+ } else {
+ reflect.setDelegates(this.eventRecorder, eventRecorderDelegates);
+ this.eventRecorder.setDelegate(eventRecorderDelegates);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Overrides the existing environment implementations.
+ * @param {object} environment
+ * @return {Delegates}
+ */
+Delegates.prototype.setEnvironment = function setEnvironment(environment) {
+ if (!reflect.isDefinedNonNull(this.environment)) {
+ this.environment = environment;
+ } else {
+ var newEnvironment = Object.create(this.environment);
+ reflect.extend(newEnvironment, environment);
+ this.environment = newEnvironment;
+ }
+
+ return this;
+};
+
+/**
+ * Overrides the config methods
+ * @param config
+ * @return {Delegates}
+ */
+Delegates.prototype.setConfig = function setConfig(config) {
+ this.config.setDelegate(config);
+
+ return this;
+};
+
+/**
+ * Access the config instance from the child delegates before/during calling Delegates.constructor,
+ * this method will create new config instance and set it to the Delegates instance and return the config when the config doesn't exist
+ * @param {String} topic - The topic to create the metrics config instance
+ * @return {MetricsConfig}
+ */
+Delegates.prototype.getOrCreateConfig = function getOrCreateConfig(topic) {
+ if (!reflect.isDefinedNonNull(this.config)) {
+ this.config = new MetricsConfig(topic);
+ }
+
+ return this.config;
+};
+
+/**
+ * Retrieves the config sources. This method must be implemented by subdelegates.
+ * @abstract
+ * @return {Promise}
+ */
+Delegates.prototype.configSources = function configSources() {
+ throw new Error('This method should be implemented by subdelegates.');
+};
+
+/*
+ * src/abstract_event_recorder.js
+ * mt-metricskit-delegates-core
+ *
+ * Copyright © 2022 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * An abstract event recorder to provide common logic across platform event recorders
+ * @constructor
+ */
+function AbstractEventRecorder() {
+ this._operationPromiseChain = Promise.resolve();
+}
+
+/**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: when the delegate function is called, it will include an additional final parameter representing the original function that it replaced (the callee would typically name this parameter "replacedFunction").
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "replacedFunction" parameter will be the previous delegate.
+ * @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.
+ */
+AbstractEventRecorder.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect.attachDelegate(this, delegate);
+};
+
+/**
+ * Public method to interact with Processors to record an event
+ * @param {String} topic - an 'override' topic which will override the main topic.
+ * @param {Promise|Object} eventFields - a Promise/JavaScript object which will be converted to a JSON string and sent to AMP Analytics.
+ * @return {Promise}
+ */
+AbstractEventRecorder.prototype.recordEvent = function recordEvent(topic, eventFields) {
+ var vargs = Array.prototype.slice.call(arguments, 2);
+ var self = this;
+ this._operationPromiseChain = this._operationPromiseChain.then(function () {
+ return Promise.resolve(eventFields).then(function (eventFields) {
+ return self._recordEvent.apply(self, [topic, eventFields].concat(vargs));
+ });
+ });
+
+ return this._operationPromiseChain;
+};
+
+/**
+ * Sends any remaining events in the queue, then clears it
+ * This is typically called on page / app close when the JS context is about to disappear and thus we will
+ * not know if the events made it to the server
+ * @param {Boolean} appIsExiting - Pass true if events are being flushed due to your app exiting or page going away
+ * (the send method will be different in order to attempt to post events prior to actual termination)
+ * @param {String} appExitSendMethod (optional) the send method for how events will be flushed when the app is exiting.
+ * Possible options are enumerated in the `eventRecorder.SEND_METHOD` object.
+ * Note: This argument will be ignored if appIsExiting is false.
+ * @returns {Promise}
+ */
+AbstractEventRecorder.prototype.flushUnreportedEvents = function flushUnreportedEvents() {
+ var args = Array.prototype.slice.call(arguments);
+ var self = this;
+ return this._operationPromiseChain.then(function () {
+ // Reset the promise chain
+ self._operationPromiseChain = Promise.resolve();
+ return self._flushUnreportedEvents.apply(self, args);
+ });
+};
+
+/**
+ * Abstract recordEvent method
+ * Subclasses implement this method to handle how to record an event
+ * @abstract
+ * @protected
+ * @param {String} topic - an 'override' topic which will override the main topic.
+ * @param {Object} eventFields - a JavaScript object which will be converted to a JSON string and sent to AMP Analytics.
+ * @returns {Promise}
+ */
+AbstractEventRecorder.prototype._recordEvent = function _recordEvent(topic, eventFields) {};
+
+/**
+ * Abstract flushUnreportedEvents method
+ * Subclasses implement this method to handle how to flush the cache events
+ * @abstract
+ * @protected
+ * @param {Boolean} appIsExiting - if events are being flushed due to your app exiting (or page going away for web-apps), pass "true".
+ * This allows MetricsKit to modify its flush strategy to attempt to post events prior to actual termination.
+ * In cases where appIsExiting==false, the parameter may be omitted.
+ * @returns {Promise}
+ */
+AbstractEventRecorder.prototype._flushUnreportedEvents = function _flushUnreportedEvents(appIsExiting) {};
+
+export { AbstractEventRecorder, Delegates };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-web/dist/mt-metricskit-delegates-web.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-web/dist/mt-metricskit-delegates-web.esm.js
new file mode 100644
index 0000000..0772179
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-delegates-web/dist/mt-metricskit-delegates-web.esm.js
@@ -0,0 +1,728 @@
+import { AbstractEventRecorder, Delegates } from '@amp-metrics/mt-metricskit-delegates-core';
+import { EventRecorder as EventRecorder$1, ImmediateEventRecorder, environment, network, logger } from '@amp-metrics/mt-event-queue';
+import { reflect, backoff } from '@amp-metrics/mt-metricskit-utils-private';
+
+/*
+ * src/environment.js
+ * mt-metricskit-delegates-web
+ *
+ * Copyright © 2022 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides a pre-built HTML delegate to use against the metrics.system.environment delegate via metrics.system.environment.setDelegate()
+ * If you want to use *most* of these methods, but not *all* of them, you can set this delegate and then create your own with whichever few methods you need to
+ * customize additionally, and then setDelegate() *that* delegate, in order to override those methods.
+ * @constructor
+ * @param {Config} config (optional) - An instance of Config, which contains the topic and the relevant configurations for the topic.
+ */
+
+function Environment(config) {
+ this._config = config;
+}
+
+/**
+ ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
+ * These functions need to be accessible for ease of testing, but should not be used by clients
+ */
+Environment.prototype._document = function _document() {
+ if (typeof document != 'undefined') {
+ return document;
+ } else {
+ throw "metricskit-delegates-html.environment HTML delegate 'document' object not found";
+ }
+};
+
+Environment.prototype._window = function _window() {
+ if (typeof window != 'undefined') {
+ return window;
+ } else {
+ throw "metricskit-delegates-html.environment HTML delegate 'window' object not found";
+ }
+};
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * The name of the browser that generated the event, it only return browser field if "browserMaps" config property exists in the topic config
+ * Common userAgent format: "Mozilla/5.0 () () "
+ * This implementation will read the browser identifer from the first item in the extensions part of the userAgent.
+ * For those browsers which do not store their browser identifier in the first item (can be configured in "specifiedBrowsers" config, see the below example),
+ * the function will search the defined string in the userAgent to verify if it is the case.
+ * It will load the browser related configurations from "browserMaps" in the topic. The config looks like
+ * {
+ * specifiedBrowsers: string[], // listing the browsers identifiers which does not located in the first item in "extensions" in userAgent.
+ * browserMap: Record // map the browser identifiers to readable string. For example, Edg -> Edge, OPR -> Opera
+ * }
+ * @example Safari
+ * @returns {Promise | null}
+ * @overridable
+ */
+Environment.prototype.browser = function browser() {
+ var userAgent = this._window().navigator.userAgent;
+
+ if (!reflect.isDefinedNonNull(this._config) || !reflect.isDefinedNonNullNonEmpty(userAgent)) {
+ return null;
+ }
+
+ return this._config.value('browserMaps').then(function (browserConfig) {
+ if (!reflect.isDefinedNonNull(browserConfig)) {
+ return null;
+ }
+ var specifiedBrowsers = browserConfig.specifiedBrowsers || [];
+ var browserMap = browserConfig.browserMap || {};
+ var browserIdentifier = null;
+ for (var i = 0; i < specifiedBrowsers.length; i++) {
+ var specifiedBrowser = specifiedBrowsers[i];
+ if (userAgent.indexOf(specifiedBrowser) > -1) {
+ browserIdentifier = specifiedBrowser;
+ break;
+ }
+ }
+ if (!reflect.isDefinedNonNull(browserIdentifier)) {
+ // The Named capturing group: (?...) requires "ECMAScript 2018"
+ var matches = userAgent.match(/Mozilla\/5.0 \((.*?)\)(\s|$)((.*?)\/(.*?)(\s)\(.*\))?(.*?)\/(.*?)(\s|$)/);
+ if (reflect.isDefinedNonNullNonEmpty(matches) && matches.length >= 8) {
+ browserIdentifier = matches[7];
+ }
+ }
+
+ if (reflect.isDefinedNonNull(browserIdentifier)) {
+ browserIdentifier = browserIdentifier.trim();
+ } else {
+ browserIdentifier = 'unknown';
+ }
+
+ var browser = browserMap[browserIdentifier] || browserIdentifier;
+ return browser;
+ });
+};
+
+/**
+ * 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
+ */
+Environment.prototype.cookie = function cookie() {
+ return this._window().document.cookie;
+};
+
+/**
+ * The URL that represents this page.
+ * Typically this is a "deep link" type URL.
+ * If no URL is available, this field may be omitted.
+ * @example "https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewGrouping?cc=us&mt=8"
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.pageUrl = function pageUrl() {
+ return this._window().location.href;
+};
+
+/**
+ * The URL of the parent page, if the app is embedded in a parent context.
+ * Typically this is a "deep link" type URL.
+ * If no URL is available, or if the app is not embedded, this field may be omitted.
+ * @example "https://www.apple.com/blog/top-tracks.html"
+ * @returns {String}
+ * @overridable
+ * Note: due to iframe sandbox rules, the parent window's location may not be accessible.
+ * In that case, we fall back to document.referrer, which should be reliable if the app
+ * within the iframe is a single page app (document.referrer changes on every page turn).
+ * If the app in the iframe is not a single page app, we will have to persist the
+ * original referrer from the first page across page turns via e.g. localStorage.
+ * However, this use case is not currently needed by any client.
+ */
+Environment.prototype.parentPageUrl = function parentPageUrl() {
+ var windowObject = this._window();
+ var parentWindow = windowObject.parent;
+ var parentPageUrl;
+
+ if (parentWindow !== windowObject) {
+ try {
+ parentPageUrl = parentWindow.location.href;
+ } catch (e) {
+ parentPageUrl = this._document().referrer;
+ }
+ }
+
+ return parentPageUrl;
+};
+
+/**
+ * Pixel multiplier factor
+ * @example 2
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.pixelRatio = function pixelRatio() {
+ return this._window().devicePixelRatio;
+};
+
+/**
+ * Client screen height in pixels
+ * @example 568
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.screenHeight = function screenHeight() {
+ return this._window().screen.height;
+};
+
+/**
+ * Client screen width in pixels
+ * @example 320
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.screenWidth = function screenWidth() {
+ return this._window().screen.width;
+};
+
+/**
+ * Client’s user agent string. If the "app field is not provided, "userAgent may be used to derive the value of the "app field
+ * @example AppStore/2.0 iOS/8.3 model/iPhone7,2 build/12F70 (6; dt:106)
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.userAgent = function userAgent() {
+ return this._window().navigator.userAgent;
+};
+
+/**
+ * App viewport height in pixels. Does not include window “chrome”, status bars, etc.
+ * Typically only available on desktop windowing systems.
+ * @example 1920
+ * @returns {number/undefined}
+ * @overridable
+ */
+Environment.prototype.windowInnerHeight = function windowInnerHeight() {
+ return this._window().innerHeight;
+};
+
+/**
+ * App viewport width in pixels. Does not include window “chrome”, status bars, etc.
+ * Typically only available on desktop windowing systems.
+ * @example 1080
+ * @returns {number/undefined}
+ * @overridable
+ */
+Environment.prototype.windowInnerWidth = function windowInnerWidth() {
+ return this._window().innerWidth;
+};
+
+/**
+ * Height in pixels of containing window, encompassing app viewport as well as window chrome, status bars, etc.
+ * Typically only available on desktop windowing systems.
+ * @example 1080
+ * @returns {number/undefined}
+ * @overridable
+ */
+Environment.prototype.windowOuterHeight = function windowOuterHeight() {
+ return this._window().outerHeight;
+};
+
+/**
+ * Width in pixels of containing window, encompassing app viewport as well as window chrome, status bars, etc.
+ * Typically only available on desktop windowing systems.
+ * @example 1920
+ * @returns {number/undefined}
+ * @overridable
+ */
+Environment.prototype.windowOuterWidth = function windowOuterWidth() {
+ return this._window().outerWidth;
+};
+
+/**
+ * The offset between W3C timing entry timestamps (which are relative to the page lifecycle) and the epoch time
+ * and the epoch time, in milliseconds
+ * @return {Number}
+ * @overridable
+ * Note: this is only currently used by PerfKit
+ * TODO: Refactor: Delegates: revisit HTML delegate packaging
+ */
+Environment.prototype.timeOriginOffset = function timeOriginOffset() {
+ var returnValue = null;
+ var performance = this._window().performance;
+
+ if (performance && performance.timing) {
+ returnValue = performance.timing.navigationStart;
+ }
+
+ return returnValue;
+};
+
+/**
+ * THE FOLLOWING DATA ARE UNAVAILABLE IN A PURE WEB BROWSER CONTEXT,
+ * BUT MAY BE IMPLEMENTED (VIA POTENTIALLY DIFFERENT APIS) IN VARIOUS HTML WEB VIEW CONTEXTS (iOS vs Desktop vs tvOS)
+ * THEY ARE LEFT UNIMPLEMENTED FOR CONTEXT-SPECIFIC DELEGATES TO OVERWRITE IF APPLICABLE
+ */
+
+/**
+ * The app identifier of the binary app
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example "com.apple.appstore" or "com.apple.gamecenter"
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.app = function app() {};
+
+/**
+ * The version number of this application
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example "1.0", "5.43", etc.
+ * @returns {String}
+ * @overridable
+ * @defaultimpl navigator.appVersion
+ */
+Environment.prototype.appVersion = function appVersion() {};
+
+/**
+ * The total data capacity of the system, without regard for what's already been used or not.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.capacityData = function capacityData() {};
+
+/**
+ * The total available data capacity of the system.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.capacityDataAvailable = function capacityDataAvailable() {};
+
+/**
+ * The total disk capacity of the system, without regard for what's already been used or not.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.capacityDisk = function capacityDisk() {};
+
+/**
+ * The total system capacity, without regard for what's already been used or not.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @returns {number}
+ * @overridable
+ */
+Environment.prototype.capacitySystem = function capacitySystem() {};
+
+/**
+ * The total available system capacity of the system.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @api public
+ * @overridable
+ */
+Environment.prototype.capacitySystemAvailable = function capacitySystemAvailable() {};
+
+/**
+ * Type of internet connection.
+ * Only applicable to devices
+ * Beware that users on WiFi may actually be receiving 3G speeds (i.e. if device is tethered to a portable hotspot.)
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example "WiFi, "3G, etc.
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.connectionType = function connectionType() {};
+
+/**
+ * The id of this user ("directory service id").
+ * This id will get anonymized on the server prior to being saved.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example 659261189
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.dsId = function dsId() {};
+
+/**
+ * The hardware brand of the device. Not required for Apple devices.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "Samsung", "LG", "Google"
+ * @returns {String}
+ */
+Environment.prototype.hardwareBrand = function hardwareBrand() {};
+
+/**
+ * The hardware family of the device
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "iPhone", "Macbook Pro"
+ * @returns {String}
+ */
+Environment.prototype.hardwareFamily = function hardwareFamily() {};
+
+/**
+ * The model of the device
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "iPhone10,2", "MacbookPro11,5"
+ * @returns {String}
+ */
+Environment.prototype.hardwareModel = function hardwareModel() {};
+
+/**
+ * App that is hosting the storesheet or app
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example com.rovio.AngryBirds
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.hostApp = function hostApp() {};
+
+/**
+ * Version of the app that is hosting the storesheet or app
+ * NO DEFAULT IMPLEMENTATION... HOWEVER: this field is optional
+ * @example "1.0.1"
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.hostAppVersion = function hostAppVersion() {
+ // Optional field value
+};
+
+/**
+ * The name of the OS
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "ios", "macos", "windows"
+ * @returns {String}
+ */
+Environment.prototype.os = function os() {};
+
+/**
+ * The build number of the OS
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "15D60", "17E192"
+ * @returns {String}
+ */
+Environment.prototype.osBuildNumber = function osBuildNumber() {};
+
+/**
+ * A string array of language IDs, ordered in descending preference
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example ["en-US", "fr-CA"]
+ * @returns {Array}
+ */
+Environment.prototype.osLanguages = function osLanguages() {};
+
+/**
+ * The full OS version number
+ * In ITML, the value can be retrieved via Device.systemVersion
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example "8.2.1" (iOS) "10.10.3" (Desktop)
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.osVersion = function osVersion() {};
+
+/**
+ * HTML resources revision number
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example 2C97 or 8.4.0.0.103
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.resourceRevNum = function resourceRevNum() {};
+
+/**
+ * ISO 3166 Country Code. Apps that cannot provide a storeFrontHeader should provide a storeFrontCountryCode instead
+ * NO DEFAULT IMPLEMENTATION... Either this method or storeFrontHeader must be replaced.
+ * @example US
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.storeFrontCountryCode = function storeFrontCountryCode() {};
+
+/**
+ * The value contained in the X-Apple-Store-Front header value at the time the event is being created.
+ * NO DEFAULT IMPLEMENTATION... Either this method or storeFrontCountryCode must be replaced.
+ * @example K143441-1,29 ab:rSwnYxS0
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.storeFrontHeader = function storeFrontHeader() {};
+
+/**
+ * The best supported language for a storefront
+ * NO DEFAULT IMPLEMENTATION... HOWEVER: this field is optional
+ * @example en-US
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.storeFrontLanguage = function storeFrontLanguage() {};
+
+/**
+ * The type of subscriber this user is.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
+ * @example subscribed, notSubscribed, unknown, needsAuthentication
+ * @returns {String}
+ * @overridable
+ */
+Environment.prototype.userType = function userType() {};
+
+/*
+ * src/event_recorder
+ * mt-metricskit-delegates-web
+ *
+ * Copyright © 2022 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * A proxy EventRecorder to bridge the delegates layer to the mt-event-queue lib
+ * @param eventRecorder - A eventRecorder implementation from the mt-event-queue lib
+ * @constructor
+ */
+function EventRecorder(eventRecorder) {
+ AbstractEventRecorder.call(this);
+ this._proxyEventRecorder = eventRecorder;
+ this.SEND_METHOD = eventRecorder.SEND_METHOD;
+}
+
+EventRecorder.prototype = Object.create(AbstractEventRecorder.prototype);
+EventRecorder.prototype.constructor = EventRecorder;
+
+/**
+ * recordEvent implementation
+ * This is an implementation for "AbstractEventRecorder._recordEvent"
+ * @override
+ * @param topic
+ * @param eventFields
+ * @private
+ */
+EventRecorder.prototype._recordEvent = function _recordEvent(topic, eventFields) {
+ return this._proxyEventRecorder.recordEvent.apply(this._proxyEventRecorder, arguments);
+};
+
+/**
+ * This overrides the same method in AbstractEventRecorder to ignore the pending recorded event when the appExitSendMethod === 'SEND_METHOD.BEACON_SYNCHRONOUS'.
+ * @param {Boolean} appIsExiting - Pass true if events are being flushed due to your app exiting or page going away
+ * (the send method will be different in order to attempt to post events prior to actual termination)
+ * @param {String} appExitSendMethod (optional) the send method for how events will be flushed when the app is exiting.
+ * Possible options are enumerated in the `eventRecorder.SEND_METHOD` object.
+ * Note: This argument will be ignored if appIsExiting is false.
+ * @returns {Promise}
+ */
+EventRecorder.prototype.flushUnreportedEvents = function flushUnreportedEvents(appIsExiting, appExitSendMethod) {
+ var self = this;
+ var args = Array.prototype.slice.call(arguments);
+ // if this._proxyEventRecorder is an instance of QueuedEventRecorder and the callers wanted to flush events synchronously, ignore the pending events
+ if (
+ reflect.isDefinedNonNull(this._proxyEventRecorder.SEND_METHOD) &&
+ appExitSendMethod === this._proxyEventRecorder.SEND_METHOD.BEACON_SYNCHRONOUS
+ ) {
+ return this._proxyEventRecorder.flushUnreportedEvents.apply(this._proxyEventRecorder, arguments);
+ } else {
+ return this._operationPromiseChain.then(function () {
+ // Reset the promise chain
+ self._operationPromiseChain = Promise.resolve();
+ return self._proxyEventRecorder.flushUnreportedEvents.apply(self._proxyEventRecorder, args);
+ });
+ }
+};
+
+/**
+ * Sends any remaining events in the queue
+ * This is an implementation for "AbstractEventRecorder._flushUnreportedEvents"
+ * @override
+ */
+EventRecorder.prototype._flushUnreportedEvents = function _flushUnreportedEvents() {
+ return this._proxyEventRecorder.flushUnreportedEvents.apply(this._proxyEventRecorder, arguments);
+};
+
+/**
+ * The methodology being used to send batches of events to the server
+ * This field should be hardcoded in the client based on what method it is using to encode and send its events to Figaro.
+ * The three typical values are:
+ * "itms" - use this value when/if JavaScript code enqueues events for sending via the "itms.recordEvent()" method in ITML.
+ * "itunes" - use this value when/if JavaScript code enqueues events by calling the "iTunes.recordEvent()" method in Desktop Store apps.
+ * "javascript" - use this value when/if JavaScript code enqueues events for sending via the JavaScript eventQueue management. This is typically only used by older clients which don't have the built-in functionality of itms or iTunes available to them.
+ * DEFAULT implementation: console.debug()
+ * @example "itms", "itunes", "javascript"
+ * @returns {String}
+ * @overridable
+ */
+EventRecorder.prototype.sendMethod = function sendMethod() {
+ return this._proxyEventRecorder.sendMethod.apply(this._proxyEventRecorder, arguments);
+};
+
+/**
+ * Set event queue related properties for the giving topic
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @param {Object} properties the event queue properties for the topic
+ * @param {Boolean} properties.anonymous true if sending all events for the topic with credentials omitted(no cookies, no PII fields)
+ */
+EventRecorder.prototype.setProperties = function setProperties(topic, properties) {
+ return this._proxyEventRecorder.setProperties.apply(this._proxyEventRecorder, arguments);
+};
+
+/**
+ * clean resources of event recorder
+ * Subclasses implement this method to handle how to clean resources
+ * @returns {Promise} returns a Promise if the cleanup will asynchronously execute or undefined for synchronously executing
+ */
+EventRecorder.prototype.cleanup = function cleanup() {
+ return this._proxyEventRecorder.cleanup.apply(this._proxyEventRecorder, arguments);
+};
+
+/*
+ * src/web_delegate.js
+ * mt-metricskit-delegates-web
+ *
+ * Copyright © 2022 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Delegate for providing access to the "canned" web MetricsKit delegates.
+ * If further modification of these delegates is required, clients may pass in delegate options to override any of the fields within any delegate.
+ * @constructor
+ * @param {String} topic - Defines the AMP Analytics "topic" for events to be stored under
+ * @param {Object} delegateOptions (optional) - Options that can be passed to either add additional delegates or to extend/override existing ones.
+ */
+var WebDelegates = function WebDelegates(topic, delegateOptions) {
+ var config = this.getOrCreateConfig(topic);
+ Delegates.call(this, topic, {
+ environment: new Environment(config),
+ eventRecorder: new EventRecorder(new EventRecorder$1(config))
+ });
+
+ this.immediateEventRecorder = new EventRecorder(new ImmediateEventRecorder(config));
+ this.network = null;
+ this.logger = null;
+
+ if (delegateOptions) {
+ this.mergeDelegates(delegateOptions);
+
+ if (delegateOptions.environment) {
+ this.setEnvironment(delegateOptions.environment);
+ }
+
+ if (delegateOptions.eventRecorder) {
+ this.setEventRecorder(delegateOptions.eventRecorder);
+ }
+
+ if (delegateOptions.network) {
+ this.setNetwork(delegateOptions.network);
+ }
+
+ if (delegateOptions.logger) {
+ this.setLogger(delegateOptions.logger);
+ }
+
+ /**
+ * TODO: We are temporarily setting this configUrl() delegate on the config to avoid breaking changes.
+ * In the next major release, we will remove this and just fetch the config from
+ * the delegateOptions.config.url directly instead of reading it from
+ * this config method (defaulting to https://xp.apple.com/config/1/report/).
+ */
+ if (delegateOptions.config) {
+ if (delegateOptions.config.url) {
+ this.config.setDelegate({
+ configUrl: delegateOptions.config.url
+ });
+ }
+ this.setConfig(delegateOptions.config);
+ }
+ }
+};
+
+/**
+ * Inherit from the base Delegate class
+ */
+WebDelegates.prototype = Object.create(Delegates.prototype);
+WebDelegates.prototype.constructor = WebDelegates;
+
+/**
+ * Sets the environment for the web-specific event queue as well as setting it on the delegate itself.
+ * @param {Object} environment
+ * @returns {WebDelegates}
+ */
+WebDelegates.prototype.setEnvironment = function (environment$1) {
+ environment.setDelegate(environment$1);
+ return Delegates.prototype.setEnvironment.call(this, environment$1);
+};
+
+/**
+ * Sets the network for the web-specific event queue as well as setting it on the delegate itself.
+ * @param {Object} network
+ * @returns {WebDelegates}
+ */
+WebDelegates.prototype.setNetwork = function (network$1) {
+ if (network$1) {
+ this.network = network$1;
+ network.setDelegate(network$1);
+ }
+ return this;
+};
+
+/**
+ * Sets the logger for the web-specific event queue as well as setting it on the delegate itself.
+ * @param {Object} logger
+ * @returns {WebDelegates}
+ */
+WebDelegates.prototype.setLogger = function (logger$1) {
+ if (logger$1) {
+ this.logger = logger$1;
+ logger.setDelegate(logger$1);
+ }
+ return this;
+};
+
+/**
+ * Sets the immediate event recorder for the delegate.
+ * Note: Immediate event recorders are specific to web delegates.
+ * @param {Object} immediateEventRecorder
+ * @returns {WebDelegates}
+ */
+WebDelegates.prototype.setImmediateEventRecorder = function (immediateEventRecorder) {
+ if (immediateEventRecorder) {
+ var newImmediateEventRecorder = Object.create(this.immediateEventRecorder);
+ Object.assign(newImmediateEventRecorder, immediateEventRecorder);
+ this.immediateEventRecorder = newImmediateEventRecorder;
+ }
+ return this;
+};
+
+/**
+ * @returns {Object} The config sources.
+ */
+WebDelegates.prototype.configSources = function configSources() {
+ var self = this;
+
+ return new Promise(function (resolve, reject) {
+ var onFetchSuccess = function onFetchSuccess(response) {
+ try {
+ var configObject = JSON.parse(response);
+ self.config.setCachedSource(configObject);
+ self.config.setServiceSource(configObject); // TODO: Deprecated
+ resolve(configObject);
+ } catch (error) {
+ onFetchFailure(error);
+ }
+ };
+
+ var onFetchFailure = function onFetchFailure(error) {
+ self.config.setCachedSource(self.config.cachedSource());
+ reject(error);
+ };
+
+ var configUrlPromise = Promise.resolve(self.config.configUrl());
+
+ configUrlPromise
+ .then(function (configUrl) {
+ backoff.exponentialBackoff(
+ self.config.network.makeAjaxRequest.bind(self.config.network, configUrl, 'GET', null),
+ onFetchSuccess,
+ onFetchFailure
+ );
+ })
+ .catch(onFetchFailure);
+ });
+};
+
+export { Environment, WebDelegates };
diff --git a/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-processor-clickstream/dist/mt-metricskit-processor-clickstream.esm.js b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-processor-clickstream/dist/mt-metricskit-processor-clickstream.esm.js
new file mode 100644
index 0000000..517960f
--- /dev/null
+++ b/shared/metrics-8/node_modules/@amp-metrics/mt-metricskit-processor-clickstream/dist/mt-metricskit-processor-clickstream.esm.js
@@ -0,0 +1,4045 @@
+import { reflect as reflect$1, string as string$1, cookies, eventFields, delegatesInfo, keyValue, network } from '@amp-metrics/mt-metricskit-utils-private';
+import Constraints, { connectConstraintConfig } from '@amp-metrics/mt-client-constraints';
+import { system, eventHandlers } from '@amp-metrics/ae-client-kit-core';
+import { loggerNamed } from '@amp-metrics/mt-client-logger-core';
+
+var info = { version: '8.6.3', name: 'mt-metricskit-processor-clickstream' };
+
+/*
+ * src/metrics/utils/delegate_extension.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2016 Apple Inc. All rights reserved.
+ *
+ */
+/*
+ * Delegate info functions to be used by _utils.reflect.attachDelegate
+ */
+var delegateInfo = {
+ /**
+ * Returns the name of this repository
+ * Used by _utils.reflect.attachDelegate to create
+ * a delegate dependence tree for the optional
+ * base field, xpDelegatesInfo
+ * @returns {String} Name of this repository
+ */
+ mtName: function () {
+ return info.name;
+ },
+ /**
+ * Returns version of this repo
+ * Used by _utils.reflect.attachDelegate to create
+ * a delegate dependence tree for the optional
+ * base field, xpDelegatesInfo
+ * @returns {String} Version of this repo
+ */
+ mtVersion: function () {
+ return info.version;
+ }
+};
+
+/**
+ * Delegate-related functions (intended to eventually contain
+ * all needed functions to be delegateable, like 'setDelegate')
+ * @constructor
+ */
+var delegateExtension = {
+ /**
+ * Adds delegate info methods to the target (delegateable) object
+ * @param {Object} targetObj Delegateable object to attach info methods to
+ */
+ attachDelegateInfo: function (targetObj) {
+ reflect$1.extend(targetObj, delegateInfo);
+ }
+};
+
+/*
+ * src/merics/config.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015-2016 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Extend the metrics config instance with convenience functions and delegate info
+ * This should be done for every config instance that MetricsKit uses:
+ * once for the default config, and once for the topic-specific config if metrics.init() is used
+ * @param {Config} configInstance
+ */
+function initializeConfig(configInstance) {
+ connectConstraintConfig(configInstance);
+ delegateExtension.attachDelegateInfo(configInstance);
+}
+
+function cleanupConfig(topicConfig) {
+ topicConfig.cleanup();
+}
+
+/*
+ * src/metrics/utils/constants.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2019 Apple Inc. All rights reserved.
+ *
+ */
+
+var BASE_FIELDS$1 = [
+ 'constraintProfile',
+ 'constraintProfiles',
+ 'clientId',
+ 'isSignedIn',
+ 'page',
+ 'pageContext',
+ 'pageDetails',
+ 'pageId',
+ 'pageType',
+ 'xpVersionMetricsKit',
+ 'xpDelegatesInfo'
+];
+
+var ENVIRONMENT_REQUIRED_FIELDS = [
+ 'capacityData',
+ 'capacityDataAvailable',
+ 'capacityDisk',
+ 'capacitySystem',
+ 'capacitySystemAvailable',
+ 'dsId',
+ 'hostApp',
+ 'pageUrl',
+ 'pixelRatio',
+ 'userType',
+ 'windowInnerHeight',
+ 'windowInnerWidth',
+ 'windowOuterHeight',
+ 'windowOuterWidth'
+];
+
+var ENVIRONMENT_OPTIONAL_FIELDS = [
+ 'browser',
+ 'consumerId',
+ 'consumerNs',
+ 'hostAppVersion',
+ 'parentPageUrl',
+ 'userId',
+ 'xpUserIdSyncState',
+ 'xpAccountsMatch'
+];
+
+// These constants are used internally but not exposed publicly
+var constants = {
+ METRICS_KIT_BASE_FIELDS: BASE_FIELDS$1.concat(ENVIRONMENT_REQUIRED_FIELDS, ENVIRONMENT_OPTIONAL_FIELDS),
+ // Used to remove the fields which are not base field in MetricsKit
+ IGNORED_BASE_FIELDS: [
+ 'osLanguages' // only available in "enter" event handler
+ ],
+ REQUIRED_ENVIRONMENT_FIELD_NAMES: ENVIRONMENT_REQUIRED_FIELDS.concat('connectionType'),
+ OPTIONAL_ENVIRONMENT_FIELD_NAMES: ENVIRONMENT_OPTIONAL_FIELDS.concat(['clientId', 'cookie', 'osLanguages']) // This is an optional field in MetricsKit, but a required field in client-kit-core
+};
+
+/*
+ * src/metrics/system/environment.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+var Environment = system.Environment;
+var exceptionString$1 = string$1.exceptionString;
+var REQUIRED_ENVIRONMENT_FIELD_NAMES = constants.REQUIRED_ENVIRONMENT_FIELD_NAMES;
+var OPTIONAL_ENVIRONMENT_FIELD_NAMES = constants.OPTIONAL_ENVIRONMENT_FIELD_NAMES;
+var _prototypeInitialized = false;
+var noOp = function () {};
+
+/**
+ * Provides a set of environment-specific (platform-specific) functions which can be individually overridden for the needs
+ * of the particular environment, or replaced en masse by providing a single replacement environment delegate object
+ * The functionality in this class is typically replaced via a delegate.
+ * DEFAULT implementation: console logging
+ * @see setDelegate
+ * @delegatable
+ * @constructor
+ */
+var MetricsKitEnvironment = function MetricsKitEnvironment() {
+ Environment.apply(this, arguments);
+ if (!_prototypeInitialized) {
+ _prototypeInitialized = true;
+ REQUIRED_ENVIRONMENT_FIELD_NAMES.forEach(function (fieldName) {
+ MetricsKitEnvironment.prototype[fieldName] = function () {
+ throw exceptionString$1('metrics.system.environment', fieldName);
+ };
+ });
+
+ OPTIONAL_ENVIRONMENT_FIELD_NAMES.forEach(function (fieldName) {
+ MetricsKitEnvironment.prototype[fieldName] = noOp;
+ });
+ }
+};
+
+MetricsKitEnvironment.prototype = new Environment();
+MetricsKitEnvironment.prototype.constructor = MetricsKitEnvironment;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+MetricsKitEnvironment.prototype.setDelegate = function setDelegate(delegate) {
+ // This is a bit of a cheat, but it allows us to not force our users to have a whole delegate just for cookies...
+ // we let them merge it in with their environment delegate:
+ cookies.setDelegate(delegate);
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/*
+ * src/metrics/system/event_recorder.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Provides a replaceable "recordEvent" function to enqueue events to be sent to the metrics server.
+ * The functionality in this class is typically replaced via a delegate.
+ * DEFAULT implementation: error logged via logger.
+ * @see setDelegate
+ * @delegatable
+ * @constructor
+ */
+var EventRecorder = function EventRecorder() {};
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+EventRecorder.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Enqueues a JSON an event as JSON
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @param {Promise|Object} eventFields a Promise/JavaScript object which will be converted to a JSON string and enqued for sending to Figaro according to the postFrequency schedule.
+ * @returns {Object} the recorded event, or "null" if no object was recorded (e.g. if "eventFields" is null, or "disabled" is true, eventFields.eventType is one of the blacklistedEvents, etc.)
+ * @overridable
+ */
+EventRecorder.prototype.recordEvent = function recordEvent(topic, eventFields) {
+ // Don't wrap this in a helper function or the backtrace won't be as nice.
+ throw string$1.exceptionString('metrics.system.event_recorder', 'recordEvent');
+};
+
+/**
+ * The methodology being used to send batches of events to the server
+ * This field should be hardcoded in the client based on what method it is using to encode and send its events to Figaro.
+ * The three typical values are:
+ * "itms" - use this value when/if JavaScript code enqueues events for sending via the "itms.recordEvent()" method in ITML.
+ * "itunes" - use this value when/if JavaScript code enqueues events by calling the "iTunes.recordEvent()" method in Desktop Store apps.
+ * "javascript" - use this value when/if JavaScript code enqueues events for sending via the JavaScript eventQueue management. This is typically only used by older clients which don't have the built-in functionality of itms or iTunes available to them.
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
+ * @example "itms", "itunes", "javascript"
+ * @returns {String}
+ * @overridable
+ */
+EventRecorder.prototype.sendMethod = function sendMethod() {
+ // Don't wrap this in a helper function or the backtrace won't be as nice.
+ throw string$1.exceptionString('metrics.system.event_recorder', 'sendMethod');
+};
+
+/**
+ * Sends any remaining events in the queue, then clears it
+ * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED IF THE DELEGATE ENVIRONMENT SUPPORTS IT
+ * @param {Boolean} appIsExiting - if events are being flushed due to your app exiting (or page going away for web-apps), pass "true".
+ * This allows MetricsKit to modify its flush strategy to attempt to post events prior to actual termination.
+ * In cases where appIsExiting==false, the parameter may be omitted.
+ */
+EventRecorder.prototype.flushUnreportedEvents = function flushUnreportedEvents(appIsExiting) {};
+
+/*
+ * src/metrics/system/logger.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+var logger = loggerNamed('mt-metricskit-processor-clickstream');
+
+/*
+ * src/metrics/system/index.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+var System = function System() {
+ this.environment = new MetricsKitEnvironment();
+ this.eventRecorder = new EventRecorder();
+ this.logger = logger;
+
+ for (var key in this) {
+ delegateExtension.attachDelegateInfo(this[key]);
+ }
+};
+
+/*
+ * src/utils/metrics_data.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ *
+ * @param props
+ * @param props.processor
+ * @param props.eventMetricsDataPromise
+ * @constructor
+ */
+var MetricsData = function MetricsData(props) {
+ // @private
+ this._processor = props.processor;
+ // @private
+ this._eventMetricsDataPromise = props.eventMetricsDataPromise;
+};
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+MetricsData.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+MetricsData.prototype.toJSON = function toJSON() {
+ return this._eventMetricsDataPromise.catch(
+ function (err) {
+ this._processor.system.logger.error('An error occurred when generating Metrics Data. Error: \n' + err);
+ return null; // return null to ignore the event
+ }.bind(this)
+ );
+};
+
+/**
+ * Enqueues the event data to the topic queue
+ * @param {String} topic defines the Figaro "topic" that this event should be stored under
+ * @returns {Promise} a Promise that includes the recorded event or "null" if no object was recorded (e.g. if "eventFields" is null, or "disabled" is true, eventFields.eventType is one of the blacklistedEvents, etc.)
+ */
+MetricsData.prototype.recordEvent = function recordEvent(topic) {
+ var vargs = Array.prototype.slice.call(arguments, 1);
+ return this._processor.system.eventRecorder.recordEvent.apply(
+ this._processor.system.eventRecorder,
+ [topic, this.toJSON()].concat(vargs)
+ );
+};
+
+/*
+ * src/metrics/event_handlers/click_stream_event_handler.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+var exceptionString = string$1.exceptionString;
+var ClickStreamEventHandler = function ClickStreamEventHandler(clickstreamProcessor) {
+ // @private
+ this._processor = clickstreamProcessor;
+};
+
+/**
+ ************************************ 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 PerfKit objects, though that is not recommended.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ * @return {Boolean} true if one or more methods on the delegate object match one or more methods on the default object,
+ * otherwise returns false.
+ */
+ClickStreamEventHandler.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ * @overridable
+ * NOTE: this method must be overridden by the sub class
+ */
+ClickStreamEventHandler.prototype.knownFields = function knownFields() {
+ throw exceptionString('ClickStreamEventHandler', 'knownFields');
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ * NOTE: this method must be overridden by the sub class
+ */
+ClickStreamEventHandler.prototype.eventType = function (callerSuppliedEventFields) {
+ throw exceptionString('ClickStreamEventHandler', 'eventType');
+};
+
+/**
+ * All of the various eventHandlers invoke this method to generate their metrics data
+ * The data is a simple map object (dictionary) with all the fields required by Figaro for that event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * This function expects to be called with the correct context (eg base.processMetricsData.apply(this, arguments))
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * This method will check for any disabled/blacklisted events or fields, add base fields, and process/merge all data
+ * @returns {Object} key/value pairs of all "page" fields + "base" fields required by Figaro
+ * WARNING: May return "null" if metrics and/or the specific eventType for this handler is disabled, or on error.
+ */
+ClickStreamEventHandler.prototype.processMetricsData = function processMetricsData(
+ pageId,
+ pageType,
+ pageContext /*, callerSuppliedEventFieldsMapN(varargs) */
+) {
+ var callArguments = arguments;
+ var callerSuppliedEventFieldsMapsArray = Array.prototype.slice.call(callArguments, 3);
+ var eventType = this.eventType(callerSuppliedEventFieldsMapsArray);
+ var config = this._processor.config;
+ var constraints = this._processor._constraints;
+ var logger = this._processor.system.logger;
+
+ var eventMetricsDataPromise = config
+ .metricsDisabledOrBlacklistedEvent(eventType)
+ .then(function (disabled) {
+ if (disabled) {
+ throw 'event was disabled';
+ }
+ })
+ .then(
+ function () {
+ var includeBaseFields =
+ typeof this.mtIncludeBaseFields == 'function' ? this.mtIncludeBaseFields() : true;
+ var baseEventFields = null;
+ if (includeBaseFields) {
+ // Get the base fields from the base event handler
+ var baseEventHandler = this._processor.eventHandlers.base;
+ baseEventFields = baseEventHandler.metricsData.apply(baseEventHandler, callArguments);
+ } else {
+ baseEventFields = {};
+ }
+
+ return baseEventFields;
+ }.bind(this)
+ )
+ .then(
+ function (baseMetricsFields) {
+ var valueHandlerTasks = [];
+ callerSuppliedEventFieldsMapsArray = [baseMetricsFields].concat(callerSuppliedEventFieldsMapsArray);
+ var eventFieldPromises = eventFields.processMetricsData(
+ this,
+ this.knownFields(),
+ true,
+ callerSuppliedEventFieldsMapsArray
+ );
+ var metricsData = {};
+ Object.keys(eventFieldPromises).forEach(function (field) {
+ var fieldValue = eventFieldPromises[field];
+ var valueHandler = Promise.resolve(fieldValue).then(function (value) {
+ metricsData[field] = value;
+ });
+ valueHandlerTasks.push(valueHandler);
+ });
+
+ return Promise.all(valueHandlerTasks).then(function () {
+ return metricsData;
+ });
+ }.bind(this)
+ )
+ .then(function (eventFields) {
+ return constraints.applyConstraintTreatments(eventFields);
+ })
+ .then(function (eventFields) {
+ return config.removeBlacklistedFields(eventFields);
+ })
+ .then(function (eventFields) {
+ return config.applyDeRes(eventFields);
+ })
+ .catch(
+ function (e) {
+ logger.error(
+ 'MetricsKit: Unable to generate the event (' +
+ this.eventType(callerSuppliedEventFieldsMapsArray) +
+ ') for the topic ' +
+ this._processor.config.topic() +
+ ', due to ' +
+ e
+ );
+ return null;
+ }.bind(this)
+ );
+
+ return new MetricsData({
+ processor: this._processor,
+ eventMetricsDataPromise: eventMetricsDataPromise
+ });
+};
+
+/*
+ * src/metrics/event_handlers/account.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Account = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Account.prototype = Object.create(ClickStreamEventHandler.prototype);
+Account.prototype.constructor = Account;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Account.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @example a "hint", "related" click, "filter" click, etc.
+ * If this event is representing a plain typed account, this field's value may be null
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}; someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Account.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Account.prototype.knownFields = function knownFields() {
+ var knownFields = ['eventType', 'eventVersion', 'type'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Account.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'account';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Account.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/*
+ * src/metrics/event_handlers/base.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+var BASE_FIELDS = constants.METRICS_KIT_BASE_FIELDS;
+var IGNORED_BASE_FIELDS = constants.IGNORED_BASE_FIELDS;
+var Base$1 = eventHandlers.Base;
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the "base" fields common to all metrics events.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var ClickstreamProcessorBase = function (clickstreamProcessor) {
+ Base$1.apply(this, arguments);
+};
+
+ClickstreamProcessorBase.prototype = Object.create(Base$1.prototype);
+ClickstreamProcessorBase.prototype.constructor = ClickstreamProcessorBase;
+
+/**
+ * The active environment class
+ * @see src/metrics/system/Environment
+ * @return {Environment}
+ */
+ClickstreamProcessorBase.prototype.environment = function environment() {
+ return this._processor.system.environment;
+};
+
+/**
+ * The active eventRecorder
+ * @see src/metrics/system/eventRecorder
+ * @return {Object} an eventRecorder
+ */
+ClickstreamProcessorBase.prototype.eventRecorder = function eventRecorder() {
+ return this._processor.system.eventRecorder;
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+ClickstreamProcessorBase.prototype.knownFields = function knownFields() {
+ var parentKnownFields = Base$1.prototype.knownFields.call(this);
+ if (IGNORED_BASE_FIELDS && IGNORED_BASE_FIELDS.length > 0) {
+ parentKnownFields = parentKnownFields.slice();
+ IGNORED_BASE_FIELDS.forEach(function (ignoredField) {
+ var ignoredFieldIndex = parentKnownFields.indexOf(ignoredField);
+ if (ignoredFieldIndex > -1) {
+ parentKnownFields.splice(ignoredFieldIndex, 1);
+ }
+ });
+ }
+
+ return parentKnownFields.concat(BASE_FIELDS);
+};
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Creates a simple map object (dictionary) with all the "base" fields required by Figaro
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each *known* field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.base.metricsData(appData.pageId, appData.pageType, appData.pageContext, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}; someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns {Promise} A Promise that with key/value pairs of all "page" fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.metricsData = function metricsData(
+ pageId,
+ pageType,
+ pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/
+) {
+ var metricsData = {};
+ var valueTasks = [];
+ var callerSuppliedEventFieldsMapsArray = Array.prototype.slice.call(arguments, 3);
+ var utils = this._processor.utils;
+
+ return this._processor.config
+ .value('metricsBase')
+ .then(
+ function (configBaseFields) {
+ if (configBaseFields) {
+ callerSuppliedEventFieldsMapsArray.push(configBaseFields);
+ }
+ var eventFieldPromises = utils.eventFields.processMetricsData(
+ this,
+ this.knownFields(),
+ pageId,
+ pageType,
+ pageContext,
+ callerSuppliedEventFieldsMapsArray
+ );
+ Object.keys(eventFieldPromises).forEach(function (field) {
+ var fieldValue = eventFieldPromises[field];
+ var valueHandler = Promise.resolve(fieldValue).then(function (value) {
+ metricsData[field] = value;
+ });
+ valueTasks.push(valueHandler);
+ });
+
+ return valueTasks;
+ }.bind(this)
+ )
+ .then(function (valueTasks) {
+ return Promise.all(valueTasks).then(function () {
+ return metricsData;
+ });
+ });
+};
+
+// ********************* ACCESSOR FUNCTIONS *********************
+/**
+ * We create accessor functions for every data field because:
+ * 1. Cleans/simplifies all methods that use it.
+ * 2. Facilitates writing test case shims
+ * 3. Allows specific feature suppliers to be overridden (via setDelegate()))
+ */
+
+// Generate the metricskit specified base field accessors
+// NOTE: dynamically generate environment based accessors before the customized accessors to make these accessors to be overridable by the customized accessors.
+BASE_FIELDS.forEach(function (fieldName) {
+ ClickstreamProcessorBase.prototype[fieldName] = function (callerSuppliedEventFields) {
+ var environment = this._processor.system.environment;
+ return (callerSuppliedEventFields && callerSuppliedEventFields[fieldName]) || environment[fieldName]();
+ };
+});
+
+/**
+ * The name of the constraint profile used to apply constraints to fields within an event.
+ * @example "strict"
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {Promise|string}
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.constraintProfile = function constraintProfile(callerSuppliedEventFields) {
+ var config = this._processor.config;
+ return (callerSuppliedEventFields && callerSuppliedEventFields.constraintProfile) || config.constraintProfile();
+};
+
+/**
+ * The names of the constraint profiles used to apply constraints to fields within an event.
+ * NOTE: This method returns the constraint profiles that are used for Constraints v2 syntax. The constraintProfile() returns the profile that is used for Constraints v1 syntax
+ * @example "[strict]"
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {Promise|string}
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.constraintProfiles = function constraintProfiles(callerSuppliedEventFields) {
+ var config = this._processor.config;
+ return (callerSuppliedEventFields && callerSuppliedEventFields.constraintProfiles) || config.constraintProfiles();
+};
+
+/**
+ * A unique identifier for each event
+ * @returns {String}
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.clientEventId = function clientEventId(callerSuppliedEventFields) {
+ var eventId = callerSuppliedEventFields && callerSuppliedEventFields.clientEventId;
+ if (!eventId) {
+ if (string$1.cryptoRandomBase62String) {
+ eventId = string$1.cryptoRandomBase62String(true);
+ }
+ if (!eventId) {
+ eventId = string$1.uuid();
+ }
+ }
+ return eventId;
+};
+
+/**
+ * Return the value of the "xp_ci" cookie
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {Promise|String} the value of the "xp_ci" cookie
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.clientId = function clientId(callerSuppliedEventFields) {
+ var clientId;
+ var config = this._processor.config;
+
+ if (callerSuppliedEventFields && callerSuppliedEventFields.clientId) {
+ clientId = callerSuppliedEventFields.clientId;
+ } else if (this._processor.system.environment.clientId()) {
+ clientId = this._processor.system.environment.clientId();
+ } else {
+ clientId = config.value('ignoreClientIdCookie').then(function (ignoreClientIdCookie) {
+ if (!ignoreClientIdCookie) {
+ return cookies.get('xp_ci');
+ }
+ return undefined;
+ });
+ }
+
+ return clientId;
+};
+
+/**
+ * Whether or not the user is signed in
+ * @example true, false
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {boolean} true if the user is signed in (app has access to dsid), false if not
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.isSignedIn = function isSignedIn(callerSuppliedEventFields) {
+ return callerSuppliedEventFields && 'isSignedIn' in callerSuppliedEventFields
+ ? callerSuppliedEventFields.isSignedIn
+ : !!this.dsId(callerSuppliedEventFields);
+};
+
+/**
+ * A unique descriptor for the page.
+ * Usually [pageType_pageId]
+ * If no pageId exists, "page" may be a textual identifier (e.g. "NewStation_Genres", "LearnMore_HD").
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app),
+ * then this field would be generated by the client.
+ * @example Genre_168577
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {Promise|String} a unique descriptor for the page
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.page = function page(callerSuppliedEventFields) {
+ if (callerSuppliedEventFields) {
+ if (callerSuppliedEventFields.page) {
+ return callerSuppliedEventFields.page;
+ } else if (this.pageType(callerSuppliedEventFields) && this.pageId(callerSuppliedEventFields)) {
+ var config = this._processor.config;
+ var pageType = this.pageType(callerSuppliedEventFields);
+ var pageId = this.pageId(callerSuppliedEventFields);
+
+ return config.value('compoundSeparator').then(function (separator) {
+ return pageType + separator + pageId;
+ });
+ }
+ } else {
+ throw 'No data provided to event_handlers/base page function';
+ }
+};
+
+/**
+ * The context within which a page is viewed. Contexts usually provide independent streams of activity.
+ * Many implementations return the tab that a user is currently on.
+ * @example
+ * iTunes Desktop: "InTheStore" or "Main"
+ * iOS iTunes apps: tab name (e.g. "Featured", "TopCharts", etc)
+ * iOS Store sheets: "Sheet" (e.g. AppStore page launched as a sheet within Mail, Springboard’s "Near Me", Maps’ "Nearby Apps")
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {String} the context within which a page is viewed. Contexts usually provide independent streams of activity
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.pageContext = function pageContext(callerSuppliedEventFields) {
+ return callerSuppliedEventFields && callerSuppliedEventFields.pageContext;
+};
+
+/**
+ * IMPORTANT: This field cannot be deduced by this class, so either:
+ * a. this method needs to be overridden by the caller,
+ * b. the caller should pass this in as part of the key/value in the "callerSuppliedEventFieldsMapN(varargs)" parameter on the metricsData() call
+ * User-readable details describing page.
+ * May contain localized values (e.g. "Alicia Keys-Girl On Fire", "MusicMain").
+ * Max length 25 characters.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app),
+ * then this field would be generated by the client, if needed.
+ * @example "Top Paid iPhone Apps_Mobile Software Applications"
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {String} user-readable details describing page
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.pageDetails = function pageDetails(callerSuppliedEventFields) {
+ return callerSuppliedEventFields && callerSuppliedEventFields.pageDetails;
+};
+
+/**
+ * IMPORTANT: This field cannot be deduced by this class, so either:
+ * a. this method needs to be overridden by the caller,
+ * b. the caller should pass this in as part of the key/value in the "callerSuppliedEventFieldsMapN(varargs)" parameter on the metricsData() call
+ * String ID of the page’s content.
+ * Used to concatenate with the "pageType" param using the "compoundSeparator" config value (typically "_") to produce "page" values of the form: pageType_pageId
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally the identifier in some persistent store.
+ * Search Results pages may have a pageId which refers to their specific dataSet, otherwise known as dataSetId.
+ * If the page is better identified by a descriptive string rather than a content ID, this field may be hard-coded, but it should be unique within the set of pages displayable by the app
+ * the descriptive string may appear in the "page" field instead.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app),
+ * then this field would be generated by the client.
+ * @example 168577, 1, "Welcome", etc.
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {String} string ID of the page’s content
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.pageId = function pageId(callerSuppliedEventFields) {
+ // TODO:KBERN: add screamer here if this is not provided?
+ return callerSuppliedEventFields && callerSuppliedEventFields.pageId;
+};
+
+/**
+ * IMPORTANT: This field cannot be deduced by this class, so either:
+ * a. this method needs to be overridden by the caller,
+ * b. the caller should pass this in as part of the key/value in the "callerSuppliedEventFieldsMapN(varargs)" parameter on the metricsData() call
+ * Name for the group of pages this page is (e.g. "Album" or "Grouping").
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app),
+ * then this field would be generated by the client.
+ * Used to concatenate with the "pageId" param using the "compoundSeparator" config value (typically "_") to produce "page" values of the form: pageType_pageId
+ * @example "Genre", "Album", "Grouping", "Picker", "Recommendations", "Feed", "Search", "Subscribe", etc.)
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {String} name for the group of pages this page is (e.g. "Album" or "Grouping").
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.pageType = function pageType(callerSuppliedEventFields) {
+ // TODO:KBERN: add screamer here if this is not provided?
+ return callerSuppliedEventFields && callerSuppliedEventFields.pageType;
+};
+
+/**
+ * The percentage, from 0 to 1, that an item should be on screen before being considered impressionable
+ * This field should be based on the client's most recent config value of "viewablePercentage".
+ * This is valuable for problem analysis because it indicates if and how clients are honoring the "viewablePercentage" value
+ * they were supplied with.
+ * This cannot be a "passthrough" field, because it can change (via new config) during program execution, so the value
+ * in effect at event creation time is what is needed.
+ * @example 0.5
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {Promise|number} the percentage, from 0 to 1, that an item should be on screen before being considered impressionable
+ * @overridable
+ */
+ClickstreamProcessorBase.prototype.xpViewablePercentage = function xpViewablePercentage(callerSuppliedEventFields) {
+ var config = this._processor.config;
+ return (
+ (callerSuppliedEventFields && callerSuppliedEventFields.xpViewablePercentage) ||
+ config.value('impressions.viewablePercentage')
+ );
+};
+
+/**
+ * The version of MetricsKit being used
+ * Pulled from the version in the package.json via
+ * the dynamically created info.js
+ * @example "0.1.3", "2.0.1"
+ * @returns {String} version of MetricsKit being used
+ * @overridable //this should be changed to not be overridable
+ */
+ClickstreamProcessorBase.prototype.xpVersionMetricsKit = function xpVersionMetricsKit() {
+ return info.version;
+};
+
+/**
+ * The versions and names, and delegate dependencies of delegates attached to MetricsKit
+ * @example
+ * // If a delegate with the same name/version value is added
+ * // to the same target multiple times, (such as a case where
+ * // portions of one delegate are attached separately to a target)
+ * // each delegate is only represented
+ * // once per 'level' of delegate children
+ * [{
+ * version: '1.2.3',
+ * name: '@amp-metrics/mt-metricskit-delegates-itml'
+ * }
+ * {
+ * version: '3.2.1',
+ * name: '@amp-metrics/mt-metricskit-delegates-html',
+ * delegates:[{
+ * version: '2.1.3',
+ * name: '@amp-metrics/mt-metricskit-delegates-html-ios'
+ * }]
+ * }]
+ * @returns {Array} Info of delegates attached to MetricsKit
+ * @overridable //this should be changed to not be overridable
+ */
+ClickstreamProcessorBase.prototype.xpDelegatesInfo = function xpDelegatesInfo() {
+ var delegateTree = delegatesInfo.getStoredDelegateObject(this);
+ var delegateChildren = delegateTree && delegateTree.delegates;
+ // If no delegates have been attached to MetricsKit,
+ // return undefined so the field is removed from the event
+ return delegateChildren ? delegateChildren : undefined;
+};
+
+/*
+ * src/metrics/event_handlers/buyConfirmed.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ *
+ * PLEASE READ:
+ * What follows is an important discussion of how buys operate in iTunes Stores, and how to accurately collect metrics data during that flow.
+ *
+ * The typical flow of a "buy" in an iTunes Store is:
+ *
+ * - User performs some "buy" action (e.g. click a "buy" button or a "subscribe" button)
+ *
- JavaScript code then creates some "buyData" (including some "buyParams") needed by the Finance servers in order to execute the buy.
+ *
- JavaScript code then invokes a native "buy" call such as itms.buy(buyData) or itunes.buy(buyData).
+ *
- In response to the "buy()" call, the native code returns immediately, making the rest of the procedure asynchronous to the JavaScript code.
+ *
- Native code then makes an HTTP request to the Finance servers with the buyData
+ *
- Finance servers then reply with a "success" or "failure" response.
+ *
- When Finance response comes back from Finance, the native code will invoke a JavaScript callback such as "onBuy(buyInfo)".
+ *
- The "buyInfo" parameter may contain a "result" value (buyInfo.result).
+ *
- JavaScript code checks buyInfo.return and if present and non-zero/non-null typically invokes (or publishes) a "buyConfirmed" JavaScript event, and if not, invokes (or publishes) a "buyFailed" event.
+ *
- Within that "buyConfirmed" callback function (perhaps subscribed to), is where the metrics "buyConfirmed" event should be created a and recorded.
+ *
+ *
+ * That is the typical flow. But things can go wrong.
+ * The Finance servers can, for example, in the response to the "buy" request, instruct the native client code to "detour" the user through a Finance flow such as credit card validation, terms of service agreement, etc.
+ * When that occurs, the native code will invoke the JavaScript "onBuy" function with a result value indicating buy failure!.
+ * However, the buy may not have "really" failed... after the user completes the Finance flow, the "buy" request might eventually succeed, in which case the native code is notified and it will call the JavaScript client's "onBuy" callback with a success return value (causing the "buyConfirmed" to be published), even though it has already called it with a failure code!
+ *
+ * Due to this special case, the metrics code needs to stash away the metrics data in place at the time the user requests the "buy" and then use that data later (perhaps much later) when/if the "buyConfirmed" actually occurs.
+ * In order to marry the metrics data from the time of the "buy", to the event returned in any given "buyConfirmed" (remember, it's all asynchronous), the client creates a "clientBuyId" and uses that as a key to store metrics data.
+ * It also passes that key as a query param on the "buy" request so that...
+ * When/if the "buyConfirmed" callback is finally called, it will contain the "clientBuyId" value which the metrics code can then use to look up the metrics data that was in place at the time of the "buy".
+ *
+ * This "detoured" "edge case" of users being redirected through Finance flows can happen a significant percentage of the time (possibly 10-20%) so it has to be handled for accurate metrics.
+ *
+ * MetricsKit attempts to hide as much of this edge-case craziness as possible, and aid in facilitating the handling of it.
+ *
+ * To properly instrument buyConfirmed events, JavaScript clients should:
+ *
+ * - at "buy" time
+ *
+ * - Create a "clientBuyId" via a call to:
+ *
metrics.eventHandlers.buyConfirmed.createClientBuyId().
+ *
NOTE: each successive call to this function will return a different clientBuyId
+ * - Include that value with the "buyParams" via a call to:
+ *
metrics.eventHandlers.buyConfirmed.clientBuyIdQueryParamString(clientBuyId)
+ * - Cache all metrics data needed for the (ulitmate) "buyConfirmed" event, keying off that clientBuyId, via a call to:
+ *
metrics.eventHandlers.buyConfirmed.cacheMetricsBuyData(clientBuyId, metricsBuyData)
+ *
Caching the data here ensures that the metrics buy data accurately reflects the user state at the time of the "buy" vs. their state when the asynchronous "buyConfirmed" callback is received.
+ * - Invoke the native "itms/iTunes.buy(buyData)" function.
+ *
The clientBuyId will be passed through all the subsequent Finance calls, and eventually be returned to the client when the buy finally succeeds or fails.
+ *
+ * - at "buyFailed" time (which will only happen if user gets "detoured" through a Finance flow)
+ *
+ * - notify MetricsKit that the buy may be detoured via a call to:
+ *
metrics.eventHandlers.buyConfirmed.buyFailureOccurred(clientBuyId) (MetricsKit will add a "detoured" flag to the stashed data)
+ *
+ * - at "buyConfirmed" time (regardless of whether or not it previously failed)
+ *
+ * - retrieve the clientBuyId from the buyData included with the "buyConfirmed" native callback, e.g.:
+ *
var clientBuyId = buyInfo.options.clientBuyId;
+ *
This is the same clientBuyId that was supplied as a query parameter during the initial itms/iTunes.buy() call and is plumbed through any Finance flows that occurred.
+ * - retrieve the cached metrics data via a call to:
+ *
metrics.eventHandlers.buyConfirmed.uncacheMetricsBuyData(clientBuyId).
+ *
IMPORTANT: If cached data is successfully retrieved for the supplied clientBuyId, that data will be removed in order to keep the cache size under control
+ * - prepare the data required by the "buyConfirmed" event (be sure to include the "detoured" flag, if present)
+ *
- create the "buyConfirmed" metrics event via a call to:
+ *
metrics.eventHandlers.buyConfirmed.metricsData(), including the retrieved cached metrics data.
+ *
+ *
+ *
+ * JavaScript clients should
+ *
+ * @delegatable
+ * @constructor
+ */
+var BuyConfirmed = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+BuyConfirmed.prototype = Object.create(ClickStreamEventHandler.prototype);
+BuyConfirmed.prototype.constructor = BuyConfirmed;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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} delegate 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.
+ */
+BuyConfirmed.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @example a "hint", "related" click, "filter" click, etc.
+ * If this event is representing a plain typed buyConfirmed, this field's value may be null
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}; someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns {map} key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+BuyConfirmed.prototype.metricsData = function (
+ pageId,
+ pageType,
+ pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/
+) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @returns {array} all the fields that this eventHandler knows about
+ */
+BuyConfirmed.prototype.knownFields = function knownFields() {
+ var knownFields = ['eventType', 'eventVersion'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+BuyConfirmed.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'buyConfirmed';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+BuyConfirmed.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/**
+ * NOTE: Please read the full description of the "buy" process at the top of this page before attempting to utilize these methdos
+ * Called at the time a user initiates a "buy" (e.g. typically immediately before the itms/iTunes.buy(buyData) calls)
+ * This function is used as a cacheKey to store metrics data when the user initiates a "buy" and to retrieve that information if the buy "fails" by virtue of the user
+ * being forced through, e.g., a Finance flow (CC validation, TOS acceptance, etc.), and then later a "buySuccess" (buyConfirmed) comes through, this value allows
+ * the client application to bind to the metrics data that would have been included if the buy had initially succeeded.
+ * @returns {number} returns a unique integer value with each call. The uniqueness only last for a single JavaScript "session" (it is merely an incremented value)
+ */
+BuyConfirmed.prototype.createClientBuyId = function () {
+ var newClientId = null;
+ var environment = this._processor.system.environment;
+ var previousClientBuyId = environment.sessionStorageObject().getItem('mtMetricsKit_previousClientBuyId');
+
+ // May coerce type of previousClientBuyId to Number, NaN result should be reset to 1
+ newClientId = ++previousClientBuyId;
+ if (!previousClientBuyId) {
+ this._processor.system.logger.warn(
+ 'Metrics: buyConfirmed.createClientBuyId: clientBuyId did not exist or was of incorrect type, reset to 1.'
+ );
+ newClientId = 1;
+ }
+ // We use sessionStorage in case our page/JS context goes away and then comes back, we continue where we left off.
+ environment.sessionStorageObject().setItem('mtMetricsKit_previousClientBuyId', newClientId);
+
+ return newClientId;
+};
+
+// NOTE: I know this method is a trivial convenience and is not so helpful to callers, but what it *does* do is that its existence makes it explicit to callers that clientBuyId needs to be added to their buParams query parameters
+// It also leaves the name of that query param under our control, preventing errors and allowing for transparent changes in the future.
+/**
+ * NOTE: Please read the full description of the "buy" process at the top of this page before attempting to utilize these methdos
+ * Called at the time a user initiates a "buy" (e.g. typically immediately before the itms/iTunes.buy(buyData) calls)
+ * Convenience method for creating the appropriate query param string to include with the "buyParams" on a typical "itms/iTunes.buy(buyInfo)" call
+ * @param {number} clientBuyId is the value returned by the "createClientBuyId()". Remember to only call that method once, as it returns a different value each time.
+ * @returns {String} a query param string with "clientBuyId" as a key and the value of the passed-in "clientBuyId" as a value, e.g. "&clientBuyId=2"
+ *
NOTE: The returned string is always prefaced with an ampersand (&), never a question mark (?)
+ * @example
+ * var clientBuyId = metrics.eventHandlers.buyConfirmed.createClientBuyId();
+ * buyData.offers[i].buyParams = buyData.offers[i].buyParams + "&someBuyParam=" + someBuyParamValue + metrics.eventHandlers.buyConfirmed.clientBuyIdQueryParamString(clientBuyId(clientBuyId);
+ */
+BuyConfirmed.prototype.clientBuyIdQueryParamString = function (clientBuyId) {
+ return '&clientBuyId=' + clientBuyId;
+};
+
+/**
+ * NOTE: Please read the full description of the "buy" process at the top of this page before attempting to utilize these methods
+ * NOTE: This code is not actually used in the generation of "buyConfirmed" events, but still seems like the best home for it.
+ * Called at the time a user initiates a "buy" (e.g. typically immediately before the itms/iTunes.buy(buyData) calls)
+ * Convenience method for creating the appropriate query param string to include with the "buyParams" on a typical "itms/iTunes.buy(buyInfo)" call
+ * @param {String} pageId is the pageId of the page where the buy originated.
+ * @param {String} pageType is the pageType of the page where the buy originated.
+ * @param {String} pageContext is the pageContext of the page where the buy originated.
+ * @param {String} (optional) topic is the topic that will be used to send "dialog" events to if dialogs are presented as a result of the "buy()" call. If omitted, it will default to "xp_its_main"
+ * @param {varargs} (optional) callerSuppliedBuyParamsMapsN a variable number of Object arguments from 0-N,
+ * each containing key/value pairs representing buy parameters to include in
+ * the returned string
+ * @returns {Promise} a Promise that returns an unescaped query param string with 'mt' prefixed keys and the value of the passed-in parameters, plus 'mtPrevPage', derived from the
+ * MetricsKit-cached pageHistory (see page event documentation)
+ * - mtPrevPage (The previous page, using the pageHistory field)
+ * - mtPageType (The pageType field of the page from which the purchase occurs)
+ * - mtPageId (The pageId field of the page from which the purchase occurs). Figaro can use this to ensure that the app being purchased shares an ID with the product page.
+ * - mtPageContext (The pageContext field of the page from which the purchase occurs)
+ * - mtTopic (The context of an event (e.g. “xp_its_main”).)
+ * - mtRequestId (A client generated UUID for every request, to be used in visit stitching to stitch client side event together with server side buy.)
+ * Refer to base field documentation for undocumented fields below
+ * - mtApp
+ * - mtEventTime
+ * - mtClientId
+ * e.g. "&mtPrevPage=Picker_Welcome&mtPageId=Subscribe&pageType=Picker&pageContext=ForYou"
+ *
NOTE: The returned string is always prefaced with an ampersand (&), never a question mark (?)
+ * @example
+ * var metricsBuyParamsString = await metrics.eventHandlers.buyConfirmed.metricsBuyParamsString(pageId, pageType, pageContext, "xp_its_main", { mtHardwareModel: "iPhone", extRefApp2: "com.apple.Mail" });
+ * buyData.offers[i].buyParams = buyData.offers[i].buyParams + "&someBuyParam=" + someBuyParamValue + "&" + metricsBuyParamsString;
+ */
+BuyConfirmed.prototype.metricsBuyParamsString = function (
+ pageId,
+ pageType,
+ pageContext,
+ topic /*, callerSuppliedBuyParamsMapsN(varargs)*/
+) {
+ var base = this._processor.eventHandlers.base;
+ var page = this._processor.eventHandlers.page;
+ var callerSuppliedBuyParamsMapsArray = Array.prototype.slice.call(arguments, 4);
+ var pageHistory = page.pageHistory();
+ var clientIdPromise = base.clientId();
+ var mtPrevPage;
+
+ if (Array.isArray(pageHistory)) {
+ if (pageHistory.length >= 2) {
+ mtPrevPage = pageHistory[pageHistory.length - 2];
+ }
+ } else {
+ this._processor.system.logger.warn('MetricsKit: metricsBuyParamsString: pageHistory is not an Array');
+ }
+
+ return Promise.resolve(clientIdPromise).then(
+ function (clientId) {
+ var buyParams = {
+ mtApp: base.app(callerSuppliedBuyParamsMapsArray),
+ mtEventTime: Date.now(),
+ mtHardwareBrand: base.hardwareBrand(callerSuppliedBuyParamsMapsArray),
+ mtHardwareFamily: base.hardwareFamily(callerSuppliedBuyParamsMapsArray),
+ mtHardwareModel: base.hardwareModel(callerSuppliedBuyParamsMapsArray),
+ mtHostApp: base.hostApp(callerSuppliedBuyParamsMapsArray),
+ mtHostAppVersion: base.hostAppVersion(callerSuppliedBuyParamsMapsArray),
+ mtOs: base.os(callerSuppliedBuyParamsMapsArray),
+ mtOsBuildNumber: base.osBuildNumber(callerSuppliedBuyParamsMapsArray),
+ mtOsVersion: base.osVersion(callerSuppliedBuyParamsMapsArray),
+ mtPageId: pageId,
+ mtPageType: pageType,
+ mtPageContext: pageContext,
+ mtTopic: topic || 'xp_its_main',
+ mtPrevPage: mtPrevPage,
+ mtRequestId: string$1.uuid(),
+ mtClientId: clientId
+ };
+
+ reflect$1.extend.apply(reflect$1, [buyParams].concat(callerSuppliedBuyParamsMapsArray));
+
+ // NOTE: if any of these fields are null or "undefined",
+ // they will be omitted in the "paramString()" function.
+ return string$1.paramString(buyParams);
+ }.bind(this)
+ );
+};
+
+/**
+ * NOTE: Please read the full description of the "buy" process at the top of this page before attempting to utilize these methdos
+ * IMPORTANT: If this method successfully retrieves data for the supplied clientBuyId, that data will be removed in order to keep the cache size under control
+ * Called at the time a user initiates a "buy" (e.g. typically immediately before the itms/iTunes.buy(buyData) calls)
+ * This function is used to serialize (to string) and cache metrics data when the user initiates a "buy".
+ * This data will be retrieved later during the "buyConfirmed" native callback (via the "uncacheMetricsBuyData()" function) and ensures that the metrics buy data accurately reflects the user state at the time of the "buy" vs. their state when the asynchronous "buyConfirmed" callback is received.
+ * For example, the user may have been forced through a Finance flow (CC validation, TOS acceptance, etc.) and then later a "buySuccess" (buyConfirmed) comes through.
+ * @param {number} clientBuyId is the value returned by the "createClientBuyId()". Remember to only call that method once, as it returns a different value each time.
+ * @param {object} metricsBuyData is the value returned by the "createClientBuyId()". Remember to only call that method once, as it returns a different value each time.
+ */
+BuyConfirmed.prototype.cacheMetricsBuyData = function (clientBuyId, metricsBuyData) {
+ var environment = this._processor.system.environment;
+ // Since these methods are similarly named, let's just help out the caller by checking...
+ if (arguments.length != 2) {
+ this._processor.system.logger.error(
+ 'buyConfirmed.cacheMetricsBuyData(): function invoked with incorrect number of parameters. Perhaps you meant to retrieve cached data instead of setting it, which would be a call to uncacheMetricsBuyData(clientBuyId)?'
+ );
+ } else {
+ // sessionStorage only accepts string values...
+ var serializedData = JSON.stringify(metricsBuyData);
+ environment
+ .sessionStorageObject()
+ .setItem('mtMetricsKit_metricsBuyData_for_clientBuyId_' + clientBuyId, serializedData);
+ }
+};
+
+/**
+ * NOTE: Please read the full description of the "buy" process at the top of this page before attempting to utilize these methdos
+ * Called at the time JavaScript receives a "buyConfirmed" callback from the native code.
+ * This function is used to deserialize (from string) and return the metrics data that was cached when the user initiated the "buy" via the cacheMetricsBuyData() function.
+ * This data should be the data used to include when creating the buyConfirmed event via the metrics.eventHandlers.buyConfirmed.metricsData() function.
+ * @param {number} clientBuyId is retrieved from the buyData included with the "buyConfirmed" native callback, e.g.:
+ *
var clientBuyId = buyInfo.options.clientBuyId;
+ *
This is the same clientBuyId that was supplied as a query parameter during the initial itms/iTunes.buy() call and is plumbed through any Finance flows that occurred.
+ * @returns {object} the same data that was initially stored with this clientBuyId at itms/iTunes.buy() time via the call to: "cacheMetricsBuyData()"
+ * If no data is found for this clientBuyId, "null" will be returned (typically that should never happen)
+ */
+BuyConfirmed.prototype.uncacheMetricsBuyData = function (clientBuyId) {
+ var returnValue = null;
+ var environment = this._processor.system.environment;
+
+ // Since these methods are similarly named, let's just help out the caller by checking...
+ if (arguments.length != 1) {
+ this._processor.system.logger.error(
+ 'buyConfirmed.uncacheMetricsBuyData(): function invoked with incorrect number of parameters. Perhaps you meant to set cached data instead of retrieving it, which would be a call to cacheMetricsBuyData(clientBuyId, metricsBuyData)?'
+ );
+ } else {
+ // sessionStorage only accepts string values...
+ var serializedData = environment
+ .sessionStorageObject()
+ .getItem('mtMetricsKit_metricsBuyData_for_clientBuyId_' + clientBuyId);
+
+ if (serializedData) {
+ returnValue = JSON.parse(serializedData);
+ environment.sessionStorageObject().removeItem('mtMetricsKit_metricsBuyData_for_clientBuyId_' + clientBuyId);
+ }
+ }
+ return returnValue;
+};
+
+/**
+ * NOTE: Please read the full description of the "buy" process at the top of this page before attempting to utilize these methdos
+ * Called at the time JavaScript receives a "buyFailed" callback from the native code.
+ * This function is used to flag "buys" that have been "detoured" through a Finance flow with the "detoured=true" flag.
+ * If a buy fails (which is why "buyFailed" would be called) but then later succeeds (after a Finance detour), we will have set the "detoured" flag by virtue of this call.
+ * @param {number} clientBuyId is retrieved from the buyData included with the "buyFailed" native callback, e.g.:
+ *
var clientBuyId = buyInfo.options.clientBuyId;
+ *
This is the same clientBuyId that was supplied as a query parameter during the initial itms/iTunes.buy() call and is plumbed through any Finance flows that occurred.
+ */
+BuyConfirmed.prototype.buyFailureOccurred = function (clientBuyId) {
+ var uncacheMetricsBuyData = this.uncacheMetricsBuyData(clientBuyId);
+
+ if (uncacheMetricsBuyData) {
+ uncacheMetricsBuyData.detoured = true;
+ // Put it back, since a) we added a field and b) asking for it removed it.
+ this.cacheMetricsBuyData(clientBuyId, uncacheMetricsBuyData);
+ }
+};
+
+/*
+ * src/metrics/event_handlers/click.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+// TODO: move to utils-private
+var safeJSONParse = function safeJSONParse(text) {
+ var returnValue = null;
+
+ try {
+ returnValue = JSON.parse(text);
+ } catch (e) {
+ logger.error('MetricsKit: error parsing click data - ' + e);
+ }
+
+ return returnValue;
+};
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Click = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Click.prototype = Object.create(ClickStreamEventHandler.prototype);
+Click.prototype.constructor = Click;
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @param {Object} targetElement The element from which the click event originated
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * @overridable
+ */
+Click.prototype.metricsData = function (
+ pageId,
+ pageType,
+ pageContext,
+ targetElement /*, callerSuppliedEventFieldsMapN(varargs)*/
+) {
+ var argumentsArray = [pageId, pageType, pageContext];
+ var utils = this._processor.utils;
+
+ if (targetElement) {
+ argumentsArray.push({
+ location: utils.eventFields.buildLocationStructure(targetElement, this.locationDataForElement)
+ });
+ argumentsArray.push(this.dataForElement(targetElement) || {});
+ }
+ argumentsArray = argumentsArray.concat(Array.prototype.slice.call(arguments, 4));
+
+ return this.processMetricsData.apply(this, argumentsArray);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Click.prototype.knownFields = function knownFields() {
+ var knownFields = [
+ 'actionContext',
+ 'actionDetails',
+ 'actionType',
+ 'actionUrl',
+ 'eventType',
+ 'eventVersion',
+ 'impressions',
+ 'location',
+ 'targetId',
+ 'targetType',
+ 'positionX',
+ 'positionY',
+ 'xpViewablePercentage'
+ ];
+
+ return knownFields;
+};
+
+/**
+ * The parsed click data that was attached to a clicked element
+ * @param {Object} targetElement The clicked element
+ * @return {Object} the parsed click data that was attached to the element as a data attribute, if present and valid, otherwise null
+ * @overridable
+ */
+Click.prototype.dataForElement = function dataForElement(element) {
+ var returnValue = null;
+
+ if (element && reflect$1.isFunction(element.hasAttribute) && reflect$1.isFunction(element.getAttribute)) {
+ var clickDataAttribute = this.dataAttribute();
+ if (element.hasAttribute(clickDataAttribute)) {
+ returnValue = safeJSONParse(element.getAttribute(clickDataAttribute));
+ }
+ }
+
+ return returnValue;
+};
+
+/**
+ * The data attribute used to attach click data to DOM-like elements in the view model
+ * @returns {String}
+ * @overridable
+ */
+Click.prototype.dataAttribute = function dataAttribute() {
+ return 'data-metrics-click';
+};
+
+/**
+ * The relevant location dictionary for an element
+ * @param {Object} element an element in the view heirarchy
+ * @return {Object} the location data for the element, if present and valid, otherwise null
+ * The default implementation looks for a data attribute called 'data-metrics-location'
+ * and, if present, parses the attribute into a dictionary, and adds the elements' position
+ * within its parent container as a field called 'locationPosition'
+ * @overridable
+ */
+Click.prototype.locationDataForElement = function locationDataForElement(element) {
+ var parentNode = element.parentNode;
+ var position = 0;
+ var location = null;
+
+ var siblingElements;
+ var sibling;
+ var siblingLocationInfo;
+ var siblingLocationType;
+
+ // if this element has a location type defined (in other words, it has a semantic value from the end user
+ // point of view like shelf, lockup etc) then add it to the location stack otherwise don't.
+ // for ex. we may have a bunch of elements in our dom tree but there is no point in including
+ // them in the location stack
+ if (element.hasAttribute && element.hasAttribute('data-metrics-location')) {
+ location = safeJSONParse(element.getAttribute('data-metrics-location'));
+ if (location.locationType) {
+ if (parentNode) {
+ // determine our location in the children array of our parent
+ siblingElements = parentNode.childNodes;
+ for (var i = 0; i < siblingElements.length; i++) {
+ // ITML childNodes list uses .item(i) accessor instead of [i]
+ sibling =
+ (typeof siblingElements.item === 'function' && siblingElements.item(i)) || siblingElements[i];
+ siblingLocationInfo =
+ sibling.hasAttribute && sibling.hasAttribute('data-metrics-location')
+ ? safeJSONParse(sibling.getAttribute('data-metrics-location'))
+ : undefined;
+ siblingLocationType = siblingLocationInfo ? siblingLocationInfo.locationType : undefined;
+ if (siblingLocationType) {
+ if (sibling === element) {
+ // we now know the position of the element, so stop here
+ break;
+ } else {
+ // we consider all siblings that have the data-metrics-location attribute and the same parent container as the target element
+ // for calculating position. for ex. on a grouping page, we may have 2 swooshes, followed by a title text div and then
+ // one more swoosh. We count the preceding swooshes and the title text div sibling element when calculating the position
+ // of the 3rd swoosh. The position of the last swoosh should be '3' (with '0' based index).
+ position++;
+ }
+ }
+ }
+ }
+ location.locationPosition = position;
+ }
+ }
+
+ return location;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Click.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'click';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Click.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 4;
+};
+
+/**
+ * A list of flattened impression objects for this event
+ * Default behavior assumes the impressions have already been flattened and just returns them, but clients can override this with their own flattening implementation
+ * @returns {Array} an array of impression objects, or undefined if no event fields were provided by the caller
+ * @overridable
+ */
+Click.prototype.impressions = function impressions(callerSuppliedEventFields) {
+ return callerSuppliedEventFields ? callerSuppliedEventFields.impressions : undefined;
+};
+
+/**
+ * The percentage, from 0 to 1, that an item should be on screen before being considered impressionable
+ * This field should be based on the client's most recent config value of "viewablePercentage".
+ * @returns {number} the percentage, from 0 to 1, that an item should be on screen before being considered impressionable
+ * @overridable
+ */
+Click.prototype.xpViewablePercentage = function xpViewablePercentage(callerSuppliedEventFields) {
+ return this._processor.eventHandlers.base.xpViewablePercentage(callerSuppliedEventFields);
+};
+
+/*
+ * src/metrics/event_handlers/dialog.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Dialog = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Dialog.prototype = Object.create(ClickStreamEventHandler.prototype);
+Dialog.prototype.constructor = Dialog;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Dialog.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @example a "hint", "related" click, "filter" click, etc.
+ * If this event is representing a plain typed dialog, this field's value may be null
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Dialog.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Dialog.prototype.knownFields = function knownFields() {
+ var knownFields = ['buttons', 'code', 'details', 'message', 'type', 'eventType', 'eventVersion', 'type'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Dialog.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'dialog';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Dialog.prototype.eventVersion = function (callerSuppliedEventFields) {
+ // Since these have an additional field "type" beyond eventVersion 1 (sent via server-requested "GET" URL ping in MXPFailure.java)
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 2;
+};
+
+/*
+ * src/metrics/event_handlers/enter.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Enter = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Enter.prototype = Object.create(ClickStreamEventHandler.prototype);
+Enter.prototype.constructor = Enter;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Enter.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @example a "hint", "related" click, "filter" click, etc.
+ * If this event is representing a plain typed enter, this field's value may be null
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Enter.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Enter.prototype.knownFields = function knownFields() {
+ var knownFields = ['eventType', 'eventVersion', 'extRefUrl', 'osLanguages', 'refApp', 'type'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Enter.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'enter';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Enter.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/**
+ * OS language preferences; a string array of language IDs, ordered in descending preference
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke its accessor, if one is available, in case *its* value is derived or massaged.
+ * @returns {Array} a list of preferred languages (strings) e.g. ['en-US', 'fr-CA']
+ * @overridable
+ */
+Enter.prototype.osLanguages = function osLanguages(callerSuppliedEventFields) {
+ return (
+ (callerSuppliedEventFields && callerSuppliedEventFields.osLanguages) ||
+ this._processor.system.environment.osLanguages()
+ );
+};
+
+/*
+ * src/metrics/event_handlers/exit.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Exit = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Exit.prototype = Object.create(ClickStreamEventHandler.prototype);
+Exit.prototype.constructor = Exit;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Exit.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @example a "hint", "related" click, "filter" click, etc.
+ * If this event is representing a plain typed exit, this field's value may be null
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Exit.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Exit.prototype.knownFields = function knownFields() {
+ var knownFields = ['destinationUrl', 'eventType', 'eventVersion', 'type'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Exit.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'exit';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Exit.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/*
+ * src/metrics/event_handlers/flexible.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Flexible = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Flexible.prototype = Object.create(ClickStreamEventHandler.prototype);
+Flexible.prototype.constructor = Flexible;
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+/**
+ * Creates a simple map object (dictionary)
+ * @param {String} eventType required for all event creation.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.flexible.metricsData('anEventType', {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns {Promise} A promise that will return key/value pairs of all event fields, merged and cleaned (removes keys that are typeof 'function', keys with 'null' values, keys with 'undefined' values)
+ * WARNING: The Promise may return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Flexible.prototype.metricsData = function (eventType /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ var eventData = [undefined, undefined, undefined]; // leave page fields undefined
+
+ // include eventType so it gets processed
+ eventData.push({ eventType: eventType });
+
+ var callerSuppliedEventFieldsMapsArray = Array.prototype.slice.call(arguments, 1);
+ eventData = eventData.concat(callerSuppliedEventFieldsMapsArray);
+
+ return this.processMetricsData.apply(this, eventData);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Flexible.prototype.knownFields = function knownFields() {
+ var knownFields = ['eventTime', 'eventType'];
+
+ return knownFields;
+};
+
+/**
+ * A flag indicating whether this event should include base fields. Defaults to false for flexible events.
+ * @returns {Boolean}
+ * @overridable
+ */
+Flexible.prototype.mtIncludeBaseFields = function mtIncludeBaseFields() {
+ return false;
+};
+
+/**
+ * The time (UTC) in milliseconds at which this event happened.
+ * This field is central to determining the sequence of user events
+ * Use online epoch converter to test your values.
+ * @example 1437356433388 (http://www.epochconverter.com converts that to: July 19, 2015 at 6:40:33 PM PDT GMT-7:00 DST)
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke it's accessor, if one is available, in case *it's* value is derived or massaged.
+ * @returns {number} the time (UTC) in milliseconds at which this event happened
+ * @overridable
+ */
+Flexible.prototype.eventTime = function eventTime(callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventTime) || Date.now();
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Flexible.prototype.eventType = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventType) || undefined;
+};
+
+/*
+ * src/metrics/event_handlers/impressions.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Impressions = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Impressions.prototype = Object.create(ClickStreamEventHandler.prototype);
+Impressions.prototype.constructor = Impressions;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Impressions.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Impressions.prototype.metricsData = function (
+ pageId,
+ pageType,
+ pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/
+) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Impressions.prototype.knownFields = function knownFields() {
+ var knownFields = ['eventType', 'eventVersion', 'impressions', 'xpViewablePercentage', 'xpViewableThreshold'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Impressions.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'impressions';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Impressions.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 3;
+};
+
+/**
+ * A list of flattened impression objects for this event
+ * Default behavior assumes the impressions have already been flattened and just returns them, but clients can override this with their own flattening implementation
+ * @returns {Array} an array of impression objects, or undefined if no event fields were provided by the caller
+ * @overridable
+ */
+Impressions.prototype.impressions = function impressions(callerSuppliedEventFields) {
+ return callerSuppliedEventFields ? callerSuppliedEventFields.impressions : undefined;
+};
+
+/**
+ * The percentage, from 0 to 1, that an item should be on screen before being considered impressionable
+ * This field should be based on the client's most recent config value of "viewablePercentage".
+ * @returns {number} the percentage, from 0 to 1, that an item should be on screen before being considered impressionable
+ * @overridable
+ */
+Impressions.prototype.xpViewablePercentage = function xpViewablePercentage(callerSuppliedEventFields) {
+ var base = this._processor.eventHandlers.base;
+ return base.xpViewablePercentage(callerSuppliedEventFields);
+};
+
+/**
+ * The continuous duration, in milliseconds, that an item should be on screen before being considered impressed
+ * This field should be based on the client's most recent config value of "viewableThreshold".
+ * This is valuable for problem analysis because it indicates if and how clients are honoring the "viewableThreshold" value
+ * they were supplied with.
+ * This cannot be a "passthrough" field, because it can change (via new config) during program execution, so the value
+ * in effect at event creation time is what is needed.
+ * @example 1000
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke it's accessor, if one is available, in case *it's* value is derived or massaged.
+ * @returns {number} the continuous duration, in milliseconds, that an item should be on screen before being considered impressed
+ * @overridable
+ */
+Impressions.prototype.xpViewableThreshold = function xpViewableThreshold(callerSuppliedEventFields) {
+ var config = this._processor.config;
+ return (
+ (callerSuppliedEventFields && callerSuppliedEventFields.xpViewableThreshold) ||
+ config.value('impressions.viewableThreshold')
+ );
+};
+
+/*
+ * src/metrics/event_handlers/media.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the "media" fields + all of the "base" events common to all metrics events.
+ * To override any functionality in this class, use the "utils.override() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Media = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Media.prototype = Object.create(ClickStreamEventHandler.prototype);
+Media.prototype.constructor = Media;
+
+/**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Media.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.media.metricsData(appData.pageId, appData.pageType, appData.pageContext, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Media.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Media.prototype.knownFields = function knownFields() {
+ var knownFields = [
+ 'eventType',
+ 'eventVersion',
+ 'id',
+ 'idType',
+ 'type',
+ 'typeDetails',
+ 'actionType',
+ 'actionDetails',
+ 'url',
+ 'duration',
+ 'position'
+ ];
+ return knownFields;
+};
+
+// ********************* ACCESSOR FUNCTIONS *********************
+/**
+ * We create accessor functions for every data field because:
+ * 1. Cleans/simplifies all methods that use it.
+ * 2. Facilitates writing test case shims
+ * 3. Allows specific feature suppliers to be overridden (via setDelegate())
+ */
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Media.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'media';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Media.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/*
+ * src/metrics/event_handlers/page.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the "page" fields + all of the "base" events common to all metrics events.
+ * To override any functionality in this class, use the "utils.override() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Page = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+ // @private
+ this.pageHistoryCache = [];
+};
+
+Page.prototype = Object.create(ClickStreamEventHandler.prototype);
+Page.prototype.constructor = Page;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Page.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Page.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Page.prototype.knownFields = function knownFields() {
+ var knownFields = [
+ 'eventType',
+ 'eventVersion',
+ 'extRefUrl',
+ 'hostApp',
+ 'refApp',
+ 'refUrl',
+ 'requestStartTime',
+ 'responseStartTime',
+ 'responseEndTime',
+ 'pageHistory',
+ 'pageLoadTime',
+ 'pageRenderTime',
+ 'searchFilters',
+ 'searchTerm'
+ ];
+
+ return knownFields;
+};
+
+// ********************* ACCESSOR FUNCTIONS *********************
+/**
+ * We create accessor functions for every data field because:
+ * 1. Cleans/simplifies all methods that use it.
+ * 2. Facilitates writing test case shims
+ * 3. Allows specific feature suppliers to be overridden (via setDelegate())
+ */
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Page.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'page';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Page.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/**
+ * If a value for this accessor's field exists in "callerSuppliedEventFields", the accessor may want to honor the caller's desire and return that, iff no massaging is needed.
+ * Returns an array of the past <= 5 values of 'page' fields from prior page events,
+ * storing the current value for use in the next request
+ * @param {Map} callerSuppliedEventFields - NOTE: If you want to use one of these fields to help derive *this* eventField, you should
+ * invoke it's accessor, if one is available, in case *it's* value is derived or massaged.
+ * @return {Array} The past 5 or fewer page event's 'page' fields, *not including* the current page.
+ */
+Page.prototype.pageHistory = function pageHistory(callerSuppliedEventFields) {
+ var returnValue;
+
+ callerSuppliedEventFields = callerSuppliedEventFields || {};
+ if (callerSuppliedEventFields.pageHistory) {
+ returnValue = callerSuppliedEventFields.pageHistory;
+ } else {
+ returnValue = this.pageHistoryCache.slice(0); // returns a clone
+ var currentPageName = callerSuppliedEventFields.page;
+
+ if (currentPageName) {
+ if (this.pageHistoryCache.length >= 5) {
+ this.pageHistoryCache.shift();
+ }
+ this.pageHistoryCache.push(currentPageName);
+ }
+ }
+ return returnValue;
+};
+
+/*
+ * src/metrics/event_handlers/search.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Search = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Search.prototype = Object.create(ClickStreamEventHandler.prototype);
+Search.prototype.constructor = Search;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Search.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. pageId, pageType, pageContext)
+ * @param {String} pageId required for all event creation. Indicates the id of the page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * The type of ID may vary (e.g. adam id, grouping id, result id, etc), but is generally
+ * the identifier in some persistent store. Search Results pages may have a pageId which refers to their specific dataSet,
+ * otherwise known as dataSetId. If the page is better identified by a descriptive string rather than a content ID,
+ * this field may be hard-coded, but it should be unique within the set of pages displayable by the app.
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client.
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageType required for all event creation. Indicates the type of page this event took place on.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * This value should represent the group of pages (e.g. "Genre", “Album”, “Grouping”, "Picker", "Recommendations", "Feed",
+ * "Search", "Subscribe", etc.).
+ * If an application is not client/server "page" oriented (where the client needs to request data from the server for each "page" of the app), then
+ * this field would be generated by the client (potentially being hard-coded).
+ * Note: A unique "page" value will created by concatenating pageType+"_"+pageId
+ * @param {String} pageContext required for all event creation. Indicates the context within which a page is viewed.
+ * This value *will* be overridden if found in any of the provided callerSuppliedEventFieldsMapN dictionaries, which is consistent with later dictionary fields overriding earlier ones.
+ * Contexts usually provide independent streams of activity and can typically appear as UI "tab" elements or primary navigation elements.
+ * @example: iTunes Desktop: “InTheStore” or “Main”.
+ * @example: iOS iTunes apps: tab name (e.g. “Featured”, “TopCharts”, etc)
+ * @example: iOS Store sheets: “Sheet” (e.g. AppStore page launched as a sheet within Mail, Springboard’s “Near Me”, Maps’ “Nearby Apps”)
+ * @example a "hint", "related" click, "filter" click, etc.
+ * If this event is representing a plain typed search, this field's value may be null
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the known page fields will be included
+ * @example metrics.eventHandlers.page.metricsData(appData.pageId, appData.pageType, appData.pageContext, element, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Search.prototype.metricsData = function (pageId, pageType, pageContext /*, callerSuppliedEventFieldsMapN(varargs)*/) {
+ // TODO:ATHOMPSON: Add back in with ability to add/not add targetElement param
+ return this.processMetricsData.apply(this, arguments);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Search.prototype.knownFields = function knownFields() {
+ var knownFields = [
+ 'actionDetails',
+ 'actionType',
+ 'actionUrl',
+ 'eventType',
+ 'eventVersion',
+ 'filters',
+ 'location',
+ 'targetId',
+ 'targetType',
+ 'term'
+ ];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Search.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'search';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Search.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 2;
+};
+
+/*
+ * src/metrics/event_handlers/transaction.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Creates and returns an object (key/value data map (dictionary)) containing all of the fields needed for this metrics event.
+ * To override any functionality in this class, use the "setDelegate() method in order to override the specific functions that need customization.
+ * @delegatable
+ * @constructor
+ */
+var Transaction = function (metricsKit) {
+ ClickStreamEventHandler.apply(this, arguments);
+};
+
+Transaction.prototype = Object.create(ClickStreamEventHandler.prototype);
+Transaction.prototype.constructor = Transaction;
+
+/**
+ ************************************ 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Transaction.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Creates a simple map object (dictionary) with all the fields required by Figaro for this event
+ * Some fields can be derived by this class itself.
+ * Some fields need to be provided by callers (e.g. the transaction fields and values)
+ * @param {Map} transactionMetrics key/value pairs associated with this transaction; typically provided in the response from the buyProduct endpoint
+ * @param {varargs} callerSuppliedEventFieldsMapN a variable number of Object arguments from 0-N, each containing key/value pairs representing event fields to include with the returned metricsData
+ * All event fields will be merged.
+ * An attempt will be made to invoke an accessor method for each field, providing the caller the opportunity to override accessor methods.
+ * If no accessor method is found, the event field value will be included in the returned metricsData as-is.
+ * Later objects take precedence over earlier ones, overriding any field value that may have already been there.
+ * If this parameter is null or omitted, only the transaction metrics will be included
+ * @example metrics.eventHandlers.transaction.metricsData(buyProductResponseMetrics, {some:"some", known:"known", eventFieldValues:"eventFieldValues"}, someOtherKnownEventValues, appData.someAdditionalBaseFieldValues);
+ * @returns key/value pairs of all event fields + "base" fields required by Figaro.
+ * WARNING: May return "null" if metrics are disabled via the metrics.disabled config source value, or on error.
+ * @overridable
+ */
+Transaction.prototype.metricsData = function metricsData(
+ transactionMetrics /*, callerSuppliedEventFieldsMapN(varargs)*/
+) {
+ var argsArray = [null, null, null].concat(Array.prototype.slice.call(arguments));
+ return this.processMetricsData.apply(this, argsArray);
+};
+
+/**
+ * Returns all the fields that this eventHandler knows about.
+ * The eventHandler may have accessor functions which derive some of these fields.
+ * The "metricsData()" method will use this list of fields to attempt to invoke accessor methods to get field values.
+ * Therefore, if callers override this function, and add additional values, and provide accessors for those values, then
+ * the "metricsData()" function will ultimately call those accessors as well.
+ * @return all the fields that this eventHandler knows about
+ */
+Transaction.prototype.knownFields = function knownFields() {
+ var knownFields = ['eventType', 'eventVersion'];
+
+ return knownFields;
+};
+
+/**
+ * The type of event this is
+ * @returns {String}
+ * @overridable
+ */
+Transaction.prototype.eventType = function (callerSuppliedEventFields) {
+ return 'transaction';
+};
+
+/**
+ * The version of the set of data to be sent up
+ * @returns {number}
+ * @overridable
+ */
+Transaction.prototype.eventVersion = function (callerSuppliedEventFields) {
+ return (callerSuppliedEventFields && callerSuppliedEventFields.eventVersion) || 1;
+};
+
+/*
+ * src/metrics/event_handlers/index.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+var EventHandlers = function (metricsKitInstance) {
+ this.account = new Account(metricsKitInstance);
+ this.base = new ClickstreamProcessorBase(metricsKitInstance);
+ this.buyConfirmed = new BuyConfirmed(metricsKitInstance);
+ this.click = new Click(metricsKitInstance);
+ this.dialog = new Dialog(metricsKitInstance);
+ this.enter = new Enter(metricsKitInstance);
+ this.exit = new Exit(metricsKitInstance);
+ this.flexible = new Flexible(metricsKitInstance);
+ this.impressions = new Impressions(metricsKitInstance);
+ this.media = new Media(metricsKitInstance);
+ this.page = new Page(metricsKitInstance);
+ this.search = new Search(metricsKitInstance);
+ this.transaction = new Transaction(metricsKitInstance);
+
+ delegateExtension.attachDelegateInfo(this.account);
+ delegateExtension.attachDelegateInfo(this.base);
+ delegateExtension.attachDelegateInfo(this.buyConfirmed);
+ delegateExtension.attachDelegateInfo(this.click);
+ delegateExtension.attachDelegateInfo(this.enter);
+ delegateExtension.attachDelegateInfo(this.exit);
+ delegateExtension.attachDelegateInfo(this.flexible);
+ delegateExtension.attachDelegateInfo(this.impressions);
+ delegateExtension.attachDelegateInfo(this.media);
+ delegateExtension.attachDelegateInfo(this.page);
+ delegateExtension.attachDelegateInfo(this.search);
+ delegateExtension.attachDelegateInfo(this.transaction);
+};
+
+/*
+ * src/metrics/utils/event_fields.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Removes duplicate impressions from an array of sibling impressions (i.e. impressions with the same parent) based on whether its index is unique
+ * This is needed because the impressions spec:
+ * https://connectme.apple.com/docs/DOC-240953
+ * states that:
+ * iii. If items come into view, then go out of view, then come back, they are not counted twice.
+ * So, if a user scrolls a swoosh away from a lockup, and then back, so that the lockup is in view again, it will appear twice (with the same index-within-parent) in the impressions-object-tree
+ * returned from ITMLKit, but we don't want it included twice, so we remove the dupes here.
+ * NOTE: This should never happen with an impressions list retrieved via "itms.snapshotImpressions()" but for caller-simplicity, and since our caller is typically walking the tree already, some of our callers may invoke it even when unnecessary.
+ * @param {Array} impressions An array of impressions to dedupe
+ * @return {Array} A set of unique impressions
+ */
+var dedupeSiblingImpressions = function (impressions) {
+ var uniqueIndices = {};
+ var dedupedSiblingImpressions = [];
+ var currentIndex;
+ if (impressions && impressions[0] && impressions[0].index !== undefined) {
+ for (var i = 0; i < impressions.length; ++i) {
+ currentIndex = impressions[i].index;
+ if (!uniqueIndices[currentIndex]) {
+ uniqueIndices[currentIndex] = true;
+ dedupedSiblingImpressions.push(impressions[i]);
+ }
+ }
+ }
+ return dedupedSiblingImpressions;
+};
+
+/**
+ * These routines are useful for clients to create and access Metrics data and config
+ * @constructor
+ */
+var EventFields$1 = function (processor) {
+ // @private
+ this._processor = processor;
+};
+
+/**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+/**
+ * Uses the provided id to generate its idType
+ * @param {String} idString The id to generate a type for
+ * @param {String} (Optional) separator id separator, if not provided, this function will return a Promise otherwise the function will return the id string.
+ * @return {Promise|String} returns a string of the idType of the provided id if providing separator otherwise returns a Promise
+ */
+EventFields$1.prototype.getIdType = function (idString, separator) {
+ var config = this._processor.config;
+ var defaultPrefix = 'its';
+ var prefixSeparatorIndex = idString.indexOf('.');
+ var prefix = prefixSeparatorIndex !== -1 ? idString.substring(0, prefixSeparatorIndex) : defaultPrefix;
+
+ return reflect$1.isString(separator)
+ ? prefix + separator + 'id'
+ : config.value('compoundSeparator').then(function (separator) {
+ return prefix + separator + 'id';
+ });
+};
+
+/**
+ * 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
+ * @returns {Arguments} all of the arguments that the calling eventHandler received.
+ * @example:
+ * Page.prototype.metricsData = function(pageId, pageType, pageContext, eventFieldsMapN(varargs))
+ * {return utils.eventFields.processMetricsData(this, arguments);
+ */
+EventFields$1.prototype.processMetricsData = function (
+ eventHandler,
+ knownFields,
+ pageId,
+ pageType,
+ pageContext,
+ callerSuppliedEventFieldsMapsArray
+) {
+ // Combine all passed-in fields...
+ var combinedFieldsMapsArray = [
+ {
+ pageId: pageId,
+ pageType: pageType,
+ pageContext: pageContext
+ }
+ ];
+ if (reflect$1.isArray(callerSuppliedEventFieldsMapsArray)) {
+ combinedFieldsMapsArray = combinedFieldsMapsArray.concat(callerSuppliedEventFieldsMapsArray);
+ }
+
+ return eventFields.processMetricsData(eventHandler, knownFields, true, combinedFieldsMapsArray);
+};
+/**
+ * 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 custom fieldMap to use (eg: 'impressions', 'location', or 'custom')
+ * @return {Promise} A Promise that returns an 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'}
+ */
+EventFields$1.prototype.applyFieldsMap = function (data, sectionName) {
+ var config = this._processor.config;
+ var self = this;
+
+ if (data && sectionName) {
+ return config.value('fieldsMap').then(function (fieldsMap) {
+ var mappedFields = {};
+ fieldsMap = fieldsMap || {};
+ var fieldsMapSection = keyValue.valueForKeyPath(sectionName, fieldsMap, fieldsMap.custom);
+ if (fieldsMapSection) {
+ var i;
+ if (Array.isArray(fieldsMapSection)) {
+ for (i = 0; i < fieldsMapSection.length; ++i) {
+ if (data[fieldsMapSection[i]]) {
+ mappedFields[fieldsMapSection[i]] = data[fieldsMapSection[i]];
+ }
+ }
+ } else if (typeof fieldsMapSection === 'object') {
+ for (var key in fieldsMapSection) {
+ for (i = 0; i < fieldsMapSection[key].length; ++i) {
+ var value = keyValue.valueForKeyPath(fieldsMapSection[key][i], data);
+ if (value) {
+ mappedFields[key] = value;
+ break;
+ }
+ }
+ }
+ } else {
+ self._processor.system.logger.error(
+ 'mt-metricskit-processor-clickstream: incorrect data type provided to applyFieldsMap (only accepts objects and Arrays)'
+ );
+ }
+ } else {
+ self._processor.system.logger.error(
+ 'mt-metricskit-processor-clickstream: unable to get fieldsMap from config-source'
+ );
+ }
+
+ return mappedFields;
+ });
+ } else {
+ if (!data) {
+ this._processor.system.logger.error(
+ 'mt-metricskit-processor-clickstream: No data provided to applyFieldsMap'
+ );
+ }
+ if (!sectionName) {
+ this._processor.system.logger.error(
+ 'mt-metricskit-processor-clickstream: No sectionName provided to applyFieldsMap'
+ );
+ }
+ return Promise.resolve(undefined);
+ }
+};
+/**
+ * Creates a deduped & flattened representation of the list of impressions tree nodes provided
+ * reassigns some impressions values for use with metrics processing
+ * @param {Array} impressions Array of IKJSImpressions objects
+ * @param {number} currentId The index at which to increment impresionId off of
+ * @return {Promise} A Promise returns impressionsArray array of Objects corresponding to and created from data in impressions
+ */
+EventFields$1.prototype.flattenImpressions = function (impressions, currentId) {
+ var config = this._processor.config;
+
+ // Because this function needs to load "compoundSeparator" from config in the loop and also recursively build impressions.
+ // So create the internal flattenImpressions function to make we load the "compoundSeparator" outside of the function to make the function simple and clean.
+ var recursivelyFlattenImpressions = function recursivelyFlattenImpressions(impressions, currentId, separator) {
+ var impressionsArray = [];
+ var anImpression;
+ var impressionData;
+ var childrenArray;
+ var decodedData;
+ var impressionId = currentId || 1;
+
+ if (impressions) {
+ impressions = dedupeSiblingImpressions(impressions);
+ for (var i = 0; i < impressions.length; i++) {
+ anImpression = impressions[i];
+ if (typeof anImpression.data === 'string') {
+ try {
+ impressionData = JSON.parse(anImpression.data);
+ } catch (anException) {
+ // Lets' see if the exception happened because someone put URIEncoded data in the impression...
+ decodedData = decodeURIComponent(anImpression.data);
+ try {
+ impressionData = JSON.parse(decodedData);
+ } catch (anotherException) {
+ // So, the error is not [necessarily] due to encoding. Let's not stop the app here by letting this exception propogate, throw the original exception.
+ // (see the discussion about catching exceptions here in this radar: Metricskit: JSON parse error in flattenImpressions
+ this._processor.system.logger.error(
+ 'mt-metricskit-processor-clickstream: non-JSON serialized data found on impression object. Cannot parse.',
+ anException
+ );
+ }
+ }
+ } else {
+ impressionData = anImpression;
+ }
+ if (impressionData) {
+ impressionData.impressionTimes = anImpression.timestamps;
+ impressionData.impressionIndex = anImpression.index;
+ if (impressionData.id && !impressionData.idType) {
+ if (anImpression.kind === 'genre') {
+ // Remove when server provides genre id prefixes
+ impressionData.idType = 'label' + separator + 'id';
+ } else {
+ impressionData.idType = this.getIdType(impressionData.id.toString(), separator);
+ }
+ }
+ if (anImpression.parent && anImpression.parent.impressionId !== undefined) {
+ // if no parent, parent === null
+ impressionData.impressionParentId = anImpression.parent.impressionId;
+ }
+ impressionData.impressionId = impressionId;
+ anImpression.impressionId = impressionId; // saves this value for use as parentId
+ ++impressionId;
+ impressionsArray.push(impressionData);
+ if (keyValue.valueForKeyPath('children.length', anImpression) > 0) {
+ childrenArray = recursivelyFlattenImpressions(anImpression.children, impressionId, separator);
+ impressionsArray = impressionsArray.concat(childrenArray);
+ impressionId += childrenArray.length;
+ }
+ }
+ }
+ } else {
+ this._processor.system.logger.warn('Fuse-Metrics: No impressions provided to to flattenImpressions');
+ }
+
+ return impressionsArray;
+ }.bind(this);
+
+ return config.value('compoundSeparator').then(function (separator) {
+ return recursivelyFlattenImpressions(impressions, currentId, separator);
+ });
+};
+
+/**
+ * Returns a flat array representing the location of the clicked element
+ * @param {Object} targetElement The clicked element, requires attribute data-metrics-location with obj key of locationType
+ * @param {Function} locationFn a function that takes an element as an argument and calculates
+ * the location dictionary for that element
+ * @return {Array} A flat array representing the location of the target element
+ */
+EventFields$1.prototype.buildLocationStructure = function buildLocationStructure(targetElement, locationFn) {
+ var currentElement = targetElement;
+ var locationStack = [];
+ var location;
+
+ //loop and add element locations to the the location stack to be returned
+ while (currentElement) {
+ location = locationFn.call(locationFn, currentElement);
+ if (location) {
+ locationStack.push(location);
+ }
+
+ currentElement = currentElement.parentNode;
+ }
+ return locationStack;
+};
+
+/*
+ * src/network.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2018 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Network request methods exposed so delegate callers can override
+ * @constructor
+ */
+var Network = function () {};
+
+/**
+ * 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.
+ *
+ * "setDelegate()" may be called repeatedly, with the functions in the most-recently set delegates replacing any functions matching those in the earlier delegates, as well as any as-yet unreplaced functions.
+ * 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, for example, a client wants to use the "canned" itml/environment delegate with the exception of, say, the "appVersion" method, they can set itml/environment as the delegate, and
+ * then call "setDelegate()" again with their own delegate containing only a single method of "appVersion" as the delegate, which would leave all the other "replaced" methods intact,
+ * but override the "appVersion" method again, this time with their own supplied delegate.
+ *
+ * NOTE: The delegate function will have a property called origFunction representing the original function that it replaced.
+ * This allows the delegate to, essentially, call "super" before or after it does some work.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @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.
+ */
+Network.prototype.setDelegate = function setDelegate(delegate) {
+ return reflect$1.attachDelegate(this, delegate);
+};
+
+/**
+ * Covers private util network functions for delegation
+ */
+Network.prototype.makeAjaxRequest = network.makeAjaxRequest;
+
+/*
+ * src/metrics/utils/reflect.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Object Reflection Primitive Methods (e.g. "Map" type methods)
+ * copied from private utils so delegate callers can use them
+ * @constructor
+ */
+var reflect = {
+ /**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+ /**
+ * 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.
+ * NOTE: Only methods present on "target" will be replaced. If a delegate method is not found on target, "false" will be returned.
+ * If a replaced method is overridden again with a subsequent "setDelegate()" call, the "origFunction" property will be the previous delegate's function.
+ * @param target
+ * @param delegate
+ * @returns {Boolean} true if one or more methods on the delegate object match one or more methods on the default object,
+ */
+ attachDelegate: function attachDelegate(target, delegate) {
+ return reflect$1.attachDelegate(target, delegate);
+ },
+
+ /**
+ * 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..
+ */
+ extend: function extend(targetObject /* , ...sourceObjectN(varargs) */) {
+ return reflect$1.extend.apply(reflect$1, arguments);
+ },
+
+ /**
+ * Bind the execution context of the methods of given Object to itself.
+ * NOTE: Only bind the functions that are owned by the parameter object
+ * @param {Object} targetObject an object with keys and values.
+ */
+ bindFunctionsContext: function bindFunctionsContext(targetObject) {
+ if (targetObject) {
+ for (var key in targetObject) {
+ if (typeof targetObject[key] === 'function') {
+ targetObject[key] = targetObject[key].bind(targetObject);
+ }
+ }
+ }
+ }
+};
+
+/*
+ * src/metrics/utils/string.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015-2017 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * String related util methods
+ * copied from private utils so delegate callers can use them
+ * @constructor
+ */
+var string = {
+ /**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+ /**
+ * 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
+ * productVersionFromUserAgent('iTunes/12.6 (Macintosh; OS X 10.12.4) AppleWebKit/603.1.30.0.34') returns '12.6'
+ * productVersionFromUserAgent('iTunes/12.6 (Macintosh; OS X 10.12.4) AppleWebKit/603.1.30.0.34', 'AppleWebKit') returns '603.1.30.0.34'
+ * productVersionFromUserAgent('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)
+ */
+ versionStringFromUserAgent: function versionStringFromUserAgent(userAgent, productName) {
+ return string$1.versionStringFromUserAgent(userAgent, productName);
+ }
+};
+
+/*
+ * src/metrics/utils/index.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2015-2016 Apple Inc. All rights reserved.
+ *
+ */
+
+var Utils = function Utils(mtkitInstance) {
+ // TODO if no client is using it, maybe it could be removed from Utils class
+ this.delegateExtension = delegateExtension;
+ // assign new eventFields instance to utils, to give eventFields the metricsKit instance context.
+ this.eventFields = new EventFields$1(mtkitInstance);
+ // bind the context to eventHandlers to avoid losing the runtime context if the client assign function to a variable.
+ reflect.bindFunctionsContext(this.eventFields);
+
+ // copy keyValue from private utils for historical reasons in case clients are using it
+ // this can probably be removed with more research
+ // TODO if no client is using it, maybe it could be removed from Utils class
+ this.keyValue = keyValue;
+ this.network = new Network();
+ // TODO if no client is using it, maybe it could be removed from Utils class
+ this.reflect = reflect;
+ // TODO if no client is using it, maybe it could be removed from Utils class
+ this.string = string;
+};
+
+/*
+ * src/utils/event_field_accessors/base.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+var Base = function (metricsKit) {
+ // @private
+ this._eventHandler = new Flexible(metricsKit);
+};
+
+/**
+ ************************************ PUBLIC METHODS ************************************
+ */
+
+/**
+ * Return the corresponding clientId with the giving status
+ * @param {Object} qualifiers
+ * @param {Boolean} qualifiers.isSignedIn - A boolean value indicating the returned clientId is for signed-in status or signed-out status.
+ * @returns {Promise} A Promise that resolved the clientId, will return null if
+ * 1. the entire event is denied by config
+ * 2. the clientId is a denied field
+ */
+Base.prototype.clientId = function clientId(qualifiers) {
+ return this._eventHandler
+ .metricsData('', qualifiers)
+ .toJSON()
+ .then(function (eventFields) {
+ return eventFields ? eventFields.clientId : null;
+ });
+};
+
+/*
+ * src/event_fields/index.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2021 Apple Inc. All rights reserved.
+ *
+ */
+
+var EventFields = function (metricsKitInstance) {
+ this.base = new Base(metricsKitInstance);
+};
+
+/*
+ * src/metrics/clickstream_processor.js
+ * mt-metricskit-processor-clickstream
+ *
+ * Copyright © 2020 Apple Inc. All rights reserved.
+ *
+ */
+
+/**
+ * Supplies a JavaScript entrypoint to metrics functionality
+ * Since JavaScript is prototype-based and not class-based, and doesn't provide an "official" object model, this API is presented as a functional API, but still retains the ability to
+ * override and customize functionality via the "setDelegate()" method. In this way, it doesn't carry with it the spare baggage of exposing a bolt-on object model which may
+ * differ from a bolt-on (or homegrown) object model already existing in the app.
+ * @module src/metrics
+ * @param {Object} delegates - The below interface will be generated by Metrics delegates(mt-metricskit-delegates-*). Clients don't have to generate it by themselves
+ * For more details, please reference the respective document of mt-metricskit-delegates-*
+ * @constructor
+ */
+var ClickstreamProcessor = function ClickstreamProcessor(delegates) {
+ if (!reflect$1.isDefinedNonNull(delegates)) {
+ throw new Error('No delegate is provided to ClickstreamProcessor');
+ }
+
+ /**
+ ************************************ PRIVATE IVARS ************************************
+ */
+
+ // @private
+ this._initCalled = false;
+
+ // @private
+ this._delegatePackage = delegates;
+
+ /**
+ ************************************ PUBLIC METHODS/IVARS ************************************
+ */
+
+ /**
+ * Access the various "system" classes defined in ClickstreamProcessor.
+ * These classes are typically system/platform specific
+ * These classes can have their functionality replaced either en masse or piecemeal via the setDelegate() method.
+ * Available system classes are:
+ * environment
+ * eventRecorder
+ * logger
+ * @example: metrics.system.environment.setDelegate(myEnvironmentFunctions)
+ * @see setDelegate()
+ */
+ this.system = new System();
+
+ /**
+ * Access the various configuration values.
+ * These are basically pretty wrappers on config fields retrieved via the "configValue" method.
+ * Note: Must be created in the constructor in order for a client to set a debug source before fetching config with init()
+ * @example: metrics.config.isDisabled()
+ */
+ this.config = this._delegatePackage.config;
+
+ /**
+ * Sub-library providing access to all the various eventHandlers of ClickstreamProcessor.
+ */
+ this.eventHandlers = new EventHandlers(this);
+
+ /**
+ * Sub-library providing access to all the various eventFields of ClickstreamProcessor.
+ */
+ this.eventFields = new EventFields(this);
+
+ /**
+ * Sub-library providing various utility functions which are useful when interacting with ClickstreamProcessor
+ */
+ this.utils = new Utils(this);
+
+ this._constraints = null;
+};
+
+/**
+ * Initialize ClickstreamProcessor - will register, fetch config, set delegates, etc.
+ * NOTE: This API should not be called more than once. Subsequent calls will have no effect (no-op).
+ * NOTE 2: This function initializes metrics config values which are required for proper event generation.
+ * This initialization will be asynchronous unless a configSourcesFn is provided.
+ * Events that are generated before initialization have completed will use config defaults.
+ * If clients want to ensure that config is fetched before events are generated,
+ * they can do one of the following when calling clickstreamProcessor.init():
+ * (1) provide a configSourcesFn; in existing clients the config sources function is provided via the metrics.config.setDelegate() API
+ * (2) wait until the init() callback completes before generating and recording any events (Note that when using this method,
+ * any user actions that occured before the callback completes will have later eventTimes than when they actually occured)
+ *
+ * ADDITIONAL DESIGN NOTES:
+ * Promise/PubSub:
+ * These would require some polyfill or additional module support, which we do not want to bake into our framework.
+ * We will expect that clients will honor the init() contract by not calling it more than once, and we provide a completion callback.
+ * Deferring event generation:
+ * We could store a queue of events to generate once we have config. However, this is complex and error prone, and requires
+ * the eventRecorder to have special knowledge that this is happening and knowledge about how to properly generate those events
+ * (for example, the eventTime should reflect the time the deferred event was queued, not the time it was later generated).
+ * Using defaults:
+ * Most config values are common to the majority of apps and do not change very much, so in the interest of simplicity,
+ * we choose to fall back to default config values for any events generated before config resolves.
+ * The ingestion server will enrich clientId on events in a batch missing clientId.
+ * As an additional fallback, we store the most recently fetched config in localStorage for any future visits from the same browser.
+ *
+ * @returns {Promise} A Promise that resolved the MetricsKit initialization
+ */
+ClickstreamProcessor.prototype.init = function init() {
+ var initPromise = Promise.resolve();
+ if (!this._initCalled) {
+ this._initCalled = true;
+
+ // set any provided delegates
+ if (this._delegatePackage) {
+ reflect$1.setDelegates(this.eventHandlers, this._delegatePackage);
+ reflect$1.setDelegates(this.system, this._delegatePackage);
+ // TODO rdar://92551827 (Add test case for clickstream processor delegate initialization)
+ reflect$1.setDelegates(this.utils, this._delegatePackage);
+ }
+
+ // Init config
+ initializeConfig(this.config);
+ initPromise = this._delegatePackage.init();
+
+ this._constraints = new Constraints(this.config, {
+ environment: this.system.environment
+ });
+ }
+
+ return initPromise;
+};
+
+/**
+ * Release resources from MetricsKit
+ */
+ClickstreamProcessor.prototype.cleanup = function cleanup() {
+ if (this._delegatePackage && reflect$1.isFunction(this._delegatePackage.cleanup)) {
+ // cleanup delegate before cleanup config, in case the cleanup method of delegate uses the config
+ this._delegatePackage.cleanup();
+ }
+ cleanupConfig(this.config);
+ reflect$1.resetDelegates(this.eventHandlers);
+ reflect$1.resetDelegates(this.system);
+ reflect$1.resetDelegates(this.utils);
+ this.config = null;
+ this.system = null;
+ this.eventHandlers = null;
+ this.utils = null;
+ this._delegatePackage = null;
+ this._constraints = null;
+ this._initCalled = false;
+};
+
+export { ClickstreamProcessor };
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 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:
+ * Add "capacityXXX" fields to UserXP Figaro reporting.
+ * 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 either "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 either "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.
+ *
+ * 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; // 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*/) {
+ 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 };
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/actions/action-dispatcher.js b/shared/metrics-8/node_modules/@jet/engine/lib/actions/action-dispatcher.js
new file mode 100644
index 0000000..1280b06
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/actions/action-dispatcher.js
@@ -0,0 +1,64 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.ActionDispatcher = void 0;
+const optional_1 = require("@jet/environment/types/optional");
+class ActionDispatcher {
+ constructor(metricsPipeline) {
+ this.implementations = {};
+ this.metricsPipeline = metricsPipeline;
+ }
+ register(type, implementation) {
+ if (type in this.implementations) {
+ console.error(`An implementation is already registered for ${type}`);
+ }
+ this.implementations[type] = implementation;
+ }
+ async perform(action, metricsBehavior) {
+ if (!(action.$kind in this.implementations)) {
+ // 1. If there is no implementation registered for the type of action
+ // we were passed, we check for a chained dispatcher to forward to.
+ // If one is found, we forward this call without doing any work.
+ // If none is found, we give up.
+ if (optional_1.isSome(this.next)) {
+ return await this.next.perform(action, metricsBehavior);
+ }
+ else {
+ return "unsupported";
+ }
+ }
+ // 2. We have an implementation for the action we were given.
+ // We are responsible for processing metrics for that action.
+ this.processMetrics(action, metricsBehavior);
+ if (optional_1.isSome(this.next)) {
+ // 3a. If we have another dispatcher we are chained to, we forward to it
+ // if the implementation we have for the given action decides it cannot
+ // support performing it.
+ const outcome = await this.implementations[action.$kind](action);
+ if (outcome === "unsupported") {
+ return await this.next.perform(action, { behavior: "notProcessed" });
+ }
+ else {
+ return outcome;
+ }
+ }
+ else {
+ // 3b. We hand off control to the implementation we have for the given action type.
+ // If the implementation cannot perform the action, we give up.
+ return await this.implementations[action.$kind](action);
+ }
+ }
+ processMetrics(action, metricsBehavior) {
+ if (metricsBehavior.behavior === "notProcessed") {
+ return;
+ }
+ const actionMetrics = action.actionMetrics;
+ const context = {
+ customMetrics: actionMetrics.custom,
+ pageFields: metricsBehavior.context.pageFields,
+ };
+ action.actionMetrics.data.forEach((data) => {
+ this.metricsPipeline.process(data, context);
+ });
+ }
+}
+exports.ActionDispatcher = ActionDispatcher;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/actions/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/actions/index.js
new file mode 100644
index 0000000..303d3bb
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/actions/index.js
@@ -0,0 +1,13 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./action-dispatcher"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/index.js
new file mode 100644
index 0000000..c4bd837
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/index.js
@@ -0,0 +1,17 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./jet-bag"), exports);
+__exportStar(require("./jet-host"), exports);
+__exportStar(require("./jet-network-fetch"), exports);
+__exportStar(require("./localized-strings-bundle"), exports);
+__exportStar(require("./localized-strings-json-object"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-bag.js b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-bag.js
new file mode 100644
index 0000000..0ea378c
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-bag.js
@@ -0,0 +1,40 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.JetBag = void 0;
+class JetBag {
+ constructor(backing) {
+ this.backing = backing;
+ }
+ registerBagKeys() {
+ // do nothing.
+ }
+ string(key) {
+ const value = this.backing[key];
+ return typeof value === "string" || value === null ? value : undefined;
+ }
+ double(key) {
+ const value = this.backing[key];
+ return typeof value === "number" || value === null ? value : undefined;
+ }
+ integer(key) {
+ const value = this.backing[key];
+ return typeof value === "number" || value === null ? value : undefined;
+ }
+ boolean(key) {
+ const value = this.backing[key];
+ return typeof value === "boolean" || value === null ? value : undefined;
+ }
+ array(key) {
+ const value = this.backing[key];
+ return Array.isArray(value) || value === null ? value : undefined;
+ }
+ dictionary(key) {
+ const value = this.backing[key];
+ return typeof value === "object" ? value : undefined;
+ }
+ url(key) {
+ const value = this.backing[key];
+ return typeof value === "string" ? value : undefined;
+ }
+}
+exports.JetBag = JetBag;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-host.js b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-host.js
new file mode 100644
index 0000000..ed816b4
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-host.js
@@ -0,0 +1,19 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.JetHost = void 0;
+class JetHost {
+ constructor(options) {
+ this.osBuild = "unknown";
+ this.deviceModel = "unknown";
+ this.deviceModelFamily = "unknown";
+ this.devicePhysicalModel = "unknown";
+ this.deviceLocalizedModel = "unknown";
+ this.clientIdentifier = "unknown";
+ this.clientVersion = "unknown";
+ this.platform = options.platform;
+ }
+ isOSAtLeast() {
+ return false;
+ }
+}
+exports.JetHost = JetHost;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-network-fetch.js b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-network-fetch.js
new file mode 100644
index 0000000..8330737
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/jet-network-fetch.js
@@ -0,0 +1,39 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.JetNetworkFetch = void 0;
+const optional_1 = require("@jet/environment/types/optional");
+class JetNetworkFetch {
+ async fetch(request) {
+ var _a, _b, _c;
+ if (optional_1.isNothing(process === null || process === void 0 ? void 0 : process.env.MEDIA_API_TOKEN)) {
+ return await Promise.reject(new Error("process.env.MEDIA_API_TOKEN must be specified"));
+ }
+ const headers = {
+ ...((_a = request.headers) !== null && _a !== void 0 ? _a : {}),
+ authorization: `Bearer ${process === null || process === void 0 ? void 0 : process.env.MEDIA_API_TOKEN}`,
+ };
+ const response = await fetch(request.url, {
+ body: request.body,
+ method: (_b = request.method) !== null && _b !== void 0 ? _b : undefined,
+ cache: (_c = request.cache) !== null && _c !== void 0 ? _c : undefined,
+ headers: headers,
+ });
+ return {
+ ok: response.ok,
+ headers: Array.from(response.headers.keys()).reduce((previous, key) => {
+ const value = response.headers.get(key);
+ if (optional_1.isSome(value)) {
+ previous[key] = value;
+ }
+ return previous;
+ }, {}),
+ redirected: response.redirected,
+ status: response.status,
+ statusText: response.statusText,
+ url: response.url,
+ body: await response.text(),
+ metrics: [],
+ };
+ }
+}
+exports.JetNetworkFetch = JetNetworkFetch;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-bundle.js b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-bundle.js
new file mode 100644
index 0000000..0c4bf44
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-bundle.js
@@ -0,0 +1,68 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.LocalizedStringsBundle = void 0;
+const environment_1 = require("@jet/environment");
+const localized_strings_json_object_1 = require("./localized-strings-json-object");
+/**
+ * A localized string data source which loads strings from the application bundle.
+ *
+ * The bundle used by this data source can be a web app webpack bundle
+ * or a native app bundle bridged over to JS code.
+ */
+class LocalizedStringsBundle {
+ // MARK: - Initialization
+ /**
+ * Create localized strings bundle with all required attributes.
+ *
+ * @param bundle - The app bundle object.
+ */
+ constructor(bundle) {
+ this.bundle = bundle;
+ }
+ // MARK: - LocalizedStringsDataSource
+ async fetchStrings(language) {
+ var _a;
+ // Load the strings from bundle and cache them.
+ const localizations = this.bundle.localizationsProperty;
+ if (environment_1.isNothing(localizations)) {
+ throw new Error("Localized strings bundle index file is missing 'localizations' property");
+ }
+ let strings;
+ const format = (_a = localizations.format) !== null && _a !== void 0 ? _a : "json/inline" /* jsonInline */;
+ if (format === "json/inline" /* jsonInline */) {
+ const inlineLocalizations = localizations;
+ strings = inlineLocalizations[language];
+ }
+ else {
+ const externalLocalizations = localizations;
+ switch (externalLocalizations.format) {
+ case "json/multi-file" /* jsonMultiFile */:
+ {
+ // The path points to directory where JSON files are located.
+ // We don't even have to list a directory, just construct a final path.
+ // The path is also not an OS path but a bundle (e.g. JetPack) path.
+ // Bundle APIs always use "/" in the path, same as the paths used in the
+ // index.json (manifest) files.
+ const jsonPath = `${externalLocalizations.path}/${language}.json`;
+ strings = (await this.bundle.loadResource(jsonPath));
+ }
+ break;
+ case "json/single-file" /* jsonSingleFile */:
+ // The bundle contains single JSON file with all strings dictionary in it.
+ strings = (await this.bundle.loadResource(externalLocalizations.path))[language];
+ break;
+ case "loctable" /* loctable */:
+ throw new Error("Loctable format not supported in JS implementation");
+ case "js" /* js */:
+ throw new Error("Not yet implemented");
+ default:
+ throw new Error(`Unknown localization format: ${JSON.stringify(format)}`);
+ }
+ }
+ if (environment_1.isNothing(strings)) {
+ throw new Error(`Missing strings for ${language}`);
+ }
+ return new localized_strings_json_object_1.LocalizedStringsJSONObject(strings);
+ }
+}
+exports.LocalizedStringsBundle = LocalizedStringsBundle;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-json-object.js b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-json-object.js
new file mode 100644
index 0000000..eb7a9dd
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/dependencies/localized-strings-json-object.js
@@ -0,0 +1,21 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.LocalizedStringsJSONObject = void 0;
+/**
+ * A type providing access to underlying localized strings JSON object.
+ */
+class LocalizedStringsJSONObject {
+ /**
+ * Create localized strings JSON object.
+ *
+ * @param strings - A dictionary containing localized strings.
+ */
+ constructor(strings) {
+ this.strings = strings;
+ }
+ // MARK: - Localized Strings
+ string(key) {
+ return this.strings[key];
+ }
+}
+exports.LocalizedStringsJSONObject = LocalizedStringsJSONObject;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/index.js
new file mode 100644
index 0000000..624ce25
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/index.js
@@ -0,0 +1,15 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./actions"), exports);
+__exportStar(require("./dependencies"), exports);
+__exportStar(require("./metrics"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/index.js
new file mode 100644
index 0000000..e8d9f32
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/index.js
@@ -0,0 +1,16 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./metrics-fields-aggregator"), exports);
+__exportStar(require("./metrics-fields-builder"), exports);
+__exportStar(require("./metrics-fields-context"), exports);
+__exportStar(require("./metrics-fields-provider"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-aggregator.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-aggregator.js
new file mode 100644
index 0000000..b53d7a9
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-aggregator.js
@@ -0,0 +1,45 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.MetricsFieldsAggregator = void 0;
+const optional_1 = require("@jet/environment/types/optional");
+const page_metrics_fields_provider_1 = require("../field-providers/page-metrics-fields-provider");
+class MetricsFieldsAggregator {
+ constructor() {
+ this.optInProviders = new Map();
+ this.optOutProviders = new Map();
+ }
+ static makeDefaultAggregator() {
+ const aggregator = new MetricsFieldsAggregator();
+ aggregator.addOptInProvider(new page_metrics_fields_provider_1.PageMetricsFieldsProvider(), "pageFields");
+ return aggregator;
+ }
+ addOptInProvider(provider, request) {
+ this.optInProviders.set(request, provider);
+ }
+ addOptOutProvider(provider, request) {
+ this.optOutProviders.set(request, provider);
+ }
+ removeOptInProvider(request) {
+ this.optInProviders.delete(request);
+ }
+ removeOptOutProvider(request) {
+ this.optOutProviders.delete(request);
+ }
+ addMetricsFields(options) {
+ options.including.forEach((request) => {
+ const provider = this.optInProviders.get(request);
+ if (optional_1.isNothing(provider)) {
+ // No provider registered
+ return;
+ }
+ provider.addMetricsFields(options.builder, options.context);
+ });
+ this.optOutProviders.forEach((provider, request) => {
+ if (optional_1.isNothing(provider) || options.excluding.includes(request)) {
+ return;
+ }
+ provider.addMetricsFields(options.builder, options.context);
+ });
+ }
+}
+exports.MetricsFieldsAggregator = MetricsFieldsAggregator;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-builder.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-builder.js
new file mode 100644
index 0000000..d00e47b
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-builder.js
@@ -0,0 +1,15 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.SimpleMetricsFieldsBuilder = void 0;
+class SimpleMetricsFieldsBuilder {
+ constructor(baseFields) {
+ this.fields = baseFields;
+ }
+ addValue(value, field) {
+ this.fields[field] = value;
+ }
+ get allMetricsFields() {
+ return this.fields;
+ }
+}
+exports.SimpleMetricsFieldsBuilder = SimpleMetricsFieldsBuilder;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-context.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-context.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-context.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-provider.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-provider.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/aggregating/metrics-fields-provider.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/index.js
new file mode 100644
index 0000000..250f6dc
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/index.js
@@ -0,0 +1,13 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./page-metrics-fields-provider"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/page-metrics-fields-provider.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/page-metrics-fields-provider.js
new file mode 100644
index 0000000..67e0f53
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/field-providers/page-metrics-fields-provider.js
@@ -0,0 +1,19 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.PageMetricsFieldsProvider = void 0;
+const optional_1 = require("@jet/environment/types/optional");
+class PageMetricsFieldsProvider {
+ addMetricsFields(builder, context) {
+ const pageFields = context.pageFields;
+ if (optional_1.isNothing(pageFields)) {
+ // No page fields
+ return;
+ }
+ for (const field in pageFields) {
+ if (Object.prototype.hasOwnProperty.call(pageFields, field)) {
+ builder.addValue(pageFields[field], field);
+ }
+ }
+ }
+}
+exports.PageMetricsFieldsProvider = PageMetricsFieldsProvider;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/index.js
new file mode 100644
index 0000000..469a960
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/index.js
@@ -0,0 +1,18 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./aggregating"), exports);
+__exportStar(require("./field-providers"), exports);
+__exportStar(require("./linting"), exports);
+__exportStar(require("./presenters"), exports);
+__exportStar(require("./metrics-pipeline"), exports);
+__exportStar(require("./recording"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/index.js
new file mode 100644
index 0000000..7fb1fcf
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/index.js
@@ -0,0 +1,13 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./metrics-event-linter"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/metrics-event-linter.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/metrics-event-linter.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/linting/metrics-event-linter.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/metrics-pipeline.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/metrics-pipeline.js
new file mode 100644
index 0000000..ea32424
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/metrics-pipeline.js
@@ -0,0 +1,35 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.MetricsPipeline = exports.FlushBehavior = void 0;
+const metrics_fields_builder_1 = require("./aggregating/metrics-fields-builder");
+// eslint-disable-next-line @typescript-eslint/no-shadow
+var FlushBehavior;
+(function (FlushBehavior) {
+ FlushBehavior[FlushBehavior["automatic"] = 0] = "automatic";
+ FlushBehavior[FlushBehavior["never"] = 1] = "never";
+})(FlushBehavior = exports.FlushBehavior || (exports.FlushBehavior = {}));
+class MetricsPipeline {
+ constructor(options) {
+ var _a;
+ this.aggregator = options.aggregator;
+ this.linter = options.linter;
+ this.recorder = options.recorder;
+ this.flushBehavior = (_a = options.flushBehavior) !== null && _a !== void 0 ? _a : FlushBehavior.automatic;
+ }
+ async process(data, context) {
+ const builder = new metrics_fields_builder_1.SimpleMetricsFieldsBuilder(data.fields);
+ this.aggregator.addMetricsFields({
+ including: data.includingFields,
+ excluding: data.excludingFields,
+ builder: builder,
+ context: context,
+ });
+ const lintedEvent = await this.linter.processEvent(builder.allMetricsFields);
+ this.recorder.record(lintedEvent, data.topic);
+ if (data.shouldFlush && this.flushBehavior === FlushBehavior.automatic) {
+ this.recorder.flush();
+ }
+ return lintedEvent;
+ }
+}
+exports.MetricsPipeline = MetricsPipeline;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/index.js
new file mode 100644
index 0000000..56e55cf
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/index.js
@@ -0,0 +1,13 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./page-metrics-presenter"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/page-metrics-presenter.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/page-metrics-presenter.js
new file mode 100644
index 0000000..c04bbd6
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/presenters/page-metrics-presenter.js
@@ -0,0 +1,51 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.PageMetricsPresenter = void 0;
+const optional_1 = require("@jet/environment/types/optional");
+class PageMetricsPresenter {
+ constructor(metricsPipeline) {
+ this.metricsPipeline = metricsPipeline;
+ this.isViewAppeared = false;
+ }
+ set pageMetrics(pageMetrics) {
+ this.pageMetricsStore = pageMetrics;
+ if (optional_1.isSome(pageMetrics) && this.isViewAppeared) {
+ this.processInstructions("pageEnter");
+ }
+ }
+ get pageMetrics() {
+ return this.pageMetricsStore;
+ }
+ async processInstructions(invocationPoint) {
+ var _a, _b, _c;
+ if (optional_1.isNothing(this.pageMetrics)) {
+ return;
+ }
+ // istanbul ignore next
+ const invocationContext = {
+ customMetrics: (_a = this.baseContext) === null || _a === void 0 ? void 0 : _a.customMetrics,
+ pageFields: {
+ ...(_b = this.baseContext) === null || _b === void 0 ? void 0 : _b.pageFields,
+ ...(_c = this.pageMetrics) === null || _c === void 0 ? void 0 : _c.pageFields,
+ },
+ };
+ await Promise.all(this.pageMetrics.instructions.map((instruction) => {
+ const { invocationPoints } = instruction;
+ if (invocationPoints.length === 0 || !invocationPoints.includes(invocationPoint)) {
+ return;
+ }
+ return this.metricsPipeline.process(instruction.data, invocationContext);
+ }));
+ }
+ async didEnterPage() {
+ this.isViewAppeared = true;
+ if (optional_1.isSome(this.pageMetrics)) {
+ await this.processInstructions("pageEnter");
+ }
+ }
+ async didLeavePage() {
+ await this.processInstructions("pageExit");
+ this.isViewAppeared = false;
+ }
+}
+exports.PageMetricsPresenter = PageMetricsPresenter;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/index.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/index.js
new file mode 100644
index 0000000..dad3b1c
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/index.js
@@ -0,0 +1,14 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./logging-event-recorder"), exports);
+__exportStar(require("./metrics-event-recorder"), exports);
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/logging-event-recorder.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/logging-event-recorder.js
new file mode 100644
index 0000000..0e5ba43
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/logging-event-recorder.js
@@ -0,0 +1,13 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.LoggingEventRecorder = void 0;
+class LoggingEventRecorder {
+ record(event) {
+ console.log(`Record Event [${String(event.fields.eventType)}]`, event);
+ }
+ async flush() {
+ console.log("Flushing");
+ return 0;
+ }
+}
+exports.LoggingEventRecorder = LoggingEventRecorder;
diff --git a/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/metrics-event-recorder.js b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/metrics-event-recorder.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/lib/metrics/recording/metrics-event-recorder.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/index.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/index.js
new file mode 100644
index 0000000..3f77177
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/index.js
@@ -0,0 +1,19 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./models"), exports);
+__exportStar(require("./types/globals"), exports);
+__exportStar(require("./types/javascriptcore"), exports);
+__exportStar(require("./types/metrics"), exports);
+__exportStar(require("./types/models"), exports);
+__exportStar(require("./types/optional"), exports);
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/json/validation.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/json/validation.js
new file mode 100644
index 0000000..1351ad7
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/json/validation.js
@@ -0,0 +1,250 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.unexpectedNull = exports.catchingContext = exports.context = exports.recordValidationIncidents = exports.endContext = exports.getContextNames = exports.beginContext = exports.messageForRecoveryAction = exports.isValidatable = exports.unexpectedType = exports.extendedTypeof = void 0;
+const optional_1 = require("../types/optional");
+/**
+ * Returns a string containing the type of a given value.
+ * This function augments the built in `typeof` operator
+ * to return sensible values for arrays and null values.
+ *
+ * @privateRemarks
+ * This function is exported for testing.
+ *
+ * @param value - The value to find the type of.
+ * @returns A string containing the type of `value`.
+ */
+function extendedTypeof(value) {
+ if (Array.isArray(value)) {
+ return "array";
+ }
+ else if (value === null) {
+ return "null";
+ }
+ else {
+ return typeof value;
+ }
+}
+exports.extendedTypeof = extendedTypeof;
+/**
+ * Reports a non-fatal validation failure, logging a message to the console.
+ * @param recovery - The recovery action taken when the bad type was found.
+ * @param expected - The expected type of the value.
+ * @param actual - The actual value.
+ * @param pathString - A string containing the path to the value on the object which failed type validation.
+ */
+function unexpectedType(recovery, expected, actual, pathString) {
+ const actualType = extendedTypeof(actual);
+ const prettyPath = optional_1.isSome(pathString) && pathString.length > 0 ? pathString : "";
+ trackIncident({
+ type: "badType",
+ expected: expected,
+ // Our test assertions are matching the string interpolation of ${actual} value.
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ actual: `${actualType} (${actual})`,
+ objectPath: prettyPath,
+ contextNames: getContextNames(),
+ recoveryAction: recovery,
+ stack: new Error().stack,
+ });
+}
+exports.unexpectedType = unexpectedType;
+// endregion
+/**
+ * Determines if a given object conforms to the Validatable interface
+ * @param possibleValidatable - An object that might be considered validatable
+ *
+ * @returns `true` if it is an instance of Validatable, `false` if not
+ */
+function isValidatable(possibleValidatable) {
+ if (optional_1.isNothing(possibleValidatable)) {
+ return false;
+ }
+ // MAINTAINER'S NOTE: We must check for either the existence of a pre-existing incidents
+ // property *or* the ability to add one. Failure to do so will cause
+ // problems for clients that either a) use interfaces to define their
+ // view models; or b) return collections from their service routes.
+ return (Object.prototype.hasOwnProperty.call(possibleValidatable, "$incidents") ||
+ Object.isExtensible(possibleValidatable));
+}
+exports.isValidatable = isValidatable;
+/**
+ * Returns a developer-readable diagnostic message for a given recovery action.
+ * @param action - The recovery action to get the message for.
+ * @returns The message for `action`.
+ */
+function messageForRecoveryAction(action) {
+ switch (action) {
+ case "coercedValue":
+ return "Coerced format";
+ case "defaultValue":
+ return "Default value used";
+ case "ignoredValue":
+ return "Ignored value";
+ default:
+ return "Unknown";
+ }
+}
+exports.messageForRecoveryAction = messageForRecoveryAction;
+// region Contexts
+/**
+ * Shared validation context "stack".
+ *
+ * Because validation incidents propagate up the context stack,
+ * the representation used here is optimized for memory usage.
+ * A more literal representation of this would be a singly linked
+ * list describing a basic stack, but that will produce a large
+ * amount of unnecessary garbage and require copying `incidents`
+ * arrays backwards.
+ */
+const contextState = {
+ /// The names of each validation context on the stack.
+ nameStack: Array(),
+ /// All incidents reported so far. Cleared when the
+ /// context stack is emptied.
+ incidents: Array(),
+ // TODO: Removal of this is being tracked here:
+ // Intro Pricing: Un-suppress missing parent 'offers' error when server address missing key
+ /// The paths for incidents we wish to forgo tracking.
+ suppressedIncidentPaths: Array(),
+};
+/**
+ * Begin a new validation context with a given name,
+ * pushing it onto the validation context stack.
+ * @param name - The name for the validation context.
+ */
+function beginContext(name) {
+ contextState.nameStack.push(name);
+}
+exports.beginContext = beginContext;
+/**
+ * Traverses the validation context stack and collects all of the context names.
+ * @returns The names of all validation contexts on the stack, from oldest to newest.
+ */
+function getContextNames() {
+ if (contextState.nameStack.length === 0) {
+ return [""];
+ }
+ return contextState.nameStack.slice(0);
+}
+exports.getContextNames = getContextNames;
+/**
+ * Ends the current validation context
+ */
+function endContext() {
+ if (contextState.nameStack.length === 0) {
+ console.warn("endContext() called without active validation context, ignoring");
+ }
+ contextState.nameStack.pop();
+}
+exports.endContext = endContext;
+/**
+ * Records validation incidents back into an object that implements Validatable.
+ *
+ * Note: This method has a side-effect that the incident queue and name stack are cleared
+ * to prepare for the next thread's invocation.
+ *
+ * @param possibleValidatable - An object that may conform to Validatable, onto which we
+ * want to stash our validation incidents
+ */
+function recordValidationIncidents(possibleValidatable) {
+ if (isValidatable(possibleValidatable)) {
+ possibleValidatable.$incidents = contextState.incidents;
+ }
+ contextState.incidents = [];
+ contextState.nameStack = [];
+ contextState.suppressedIncidentPaths = [];
+}
+exports.recordValidationIncidents = recordValidationIncidents;
+/**
+ * Create a transient validation context, and call a function that will return a value.
+ *
+ * Prefer this function over manually calling begin/endContext,
+ * it is exception safe.
+ *
+ * @param name - The name of the context
+ * @param producer - A function that produces a result
+ * @returns The resulting type
+ */
+function context(name, producer, suppressingPath) {
+ let suppressingName = null;
+ if (optional_1.isSome(suppressingPath) && suppressingPath.length > 0) {
+ suppressingName = name;
+ contextState.suppressedIncidentPaths.push(suppressingPath);
+ }
+ let result = null;
+ try {
+ beginContext(name);
+ result = producer();
+ }
+ catch (e) {
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (!e.hasThrown) {
+ unexpectedType("defaultValue", "no exception", e.message);
+ e.hasThrown = true;
+ }
+ throw e;
+ }
+ finally {
+ if (name === suppressingName) {
+ contextState.suppressedIncidentPaths.pop();
+ }
+ endContext();
+ }
+ return result;
+}
+exports.context = context;
+/**
+ * Create a transient validation context, that catches errors and returns null
+ *
+ * @param name - The name of the context
+ * @param producer - A function that produces a result
+ * @param caught - An optional handler to provide a value when an error is caught
+ * @returns The resulting type
+ */
+function catchingContext(name, producer, caught) {
+ let result = null;
+ try {
+ result = context(name, producer);
+ }
+ catch (e) {
+ result = null;
+ if (optional_1.isSome(caught)) {
+ result = caught(e);
+ }
+ }
+ return result;
+}
+exports.catchingContext = catchingContext;
+/**
+ * Track an incident within the current validation context.
+ * @param incident - An incident object describing the problem.
+ */
+function trackIncident(incident) {
+ if (contextState.suppressedIncidentPaths.includes(incident.objectPath)) {
+ return;
+ }
+ contextState.incidents.push(incident);
+}
+// endregion
+// region Nullability
+/**
+ * Reports a non-fatal error indicating a value was unexpectedly null.
+ * @param recovery - The recovery action taken when the null value was found.
+ * @param expected - The expected type of the value.
+ * @param pathString - A string containing the path to the value on the object which was null.
+ */
+function unexpectedNull(recovery, expected, pathString) {
+ const prettyPath = optional_1.isSome(pathString) && pathString.length > 0 ? pathString : "";
+ trackIncident({
+ type: "nullValue",
+ expected: expected,
+ actual: "null",
+ objectPath: prettyPath,
+ contextNames: getContextNames(),
+ recoveryAction: recovery,
+ stack: new Error().stack,
+ });
+}
+exports.unexpectedNull = unexpectedNull;
+// endregion
+//# sourceMappingURL=validation.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/alert-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/alert-action.js
new file mode 100644
index 0000000..fcc6ea5
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/alert-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=alert-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/compound-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/compound-action.js
new file mode 100644
index 0000000..b3546e5
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/compound-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=compound-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/empty-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/empty-action.js
new file mode 100644
index 0000000..efb4d70
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/empty-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=empty-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/external-url-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/external-url-action.js
new file mode 100644
index 0000000..479a640
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/external-url-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=external-url-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-action.js
new file mode 100644
index 0000000..6f70d98
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=flow-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-back-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-back-action.js
new file mode 100644
index 0000000..75c66a2
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/flow-back-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=flow-back-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-action.js
new file mode 100644
index 0000000..e1fb6c3
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=http-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-template-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-template-action.js
new file mode 100644
index 0000000..6cb84d4
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/http-template-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=http-template-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/index.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/index.js
new file mode 100644
index 0000000..5e729a0
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/index.js
@@ -0,0 +1,22 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./alert-action"), exports);
+__exportStar(require("./compound-action"), exports);
+__exportStar(require("./empty-action"), exports);
+__exportStar(require("./external-url-action"), exports);
+__exportStar(require("./flow-action"), exports);
+__exportStar(require("./flow-back-action"), exports);
+__exportStar(require("./http-action"), exports);
+__exportStar(require("./http-template-action"), exports);
+__exportStar(require("./toast-action"), exports);
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/toast-action.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/toast-action.js
new file mode 100644
index 0000000..5d6a299
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/actions/toast-action.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=toast-action.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/artwork.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/artwork.js
new file mode 100644
index 0000000..f13a40c
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/artwork.js
@@ -0,0 +1,39 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeArtworkURLTemplate = void 0;
+const validation = require("../json/validation");
+const optional_1 = require("../types/optional");
+const urls_1 = require("../util/urls");
+/**
+ * Regex to parse artwork URL template string.
+ */
+const URL_TEMPLATE_PARSER = new RegExp("^({w}|[0-9]+(?:.[0-9]*)?)x({h}|[0-9]+(?:.[0-9]*)?)({c}|[a-z]{2}).({f}|[a-z]+)$");
+/**
+ * Create an instance of artwork URL template from string.
+ * @param fromString - String to create artwork URL template from.
+ * @returns A new artwork URL template or `null` if string
+ * does not represent a valid artwork URL template.
+ */
+function makeArtworkURLTemplate(fromString) {
+ // A valid URL that ends with '{w}x{h}{c}.{f}'
+ // with any of placeholders possibly resolved to an actual value.
+ const url = new urls_1.URL(fromString);
+ if (url.pathname === undefined) {
+ validation.context("makeArtworkURLTemplate", () => {
+ validation.unexpectedType("ignoredValue", "A valid URL string", fromString);
+ });
+ return null;
+ }
+ // Expecting 5 matches: whole string + width, height, crop code and format.
+ const lastPathComponent = fromString.substring(fromString.lastIndexOf("/") + 1);
+ const matches = URL_TEMPLATE_PARSER.exec(lastPathComponent);
+ if (optional_1.isNothing(matches) || matches.length !== 5) {
+ validation.context("makeArtworkURLTemplate", () => {
+ validation.unexpectedType("ignoredValue", "A valid artwork URL template ending with {w}x{h}{c}.{f} format", lastPathComponent);
+ });
+ return null;
+ }
+ return fromString;
+}
+exports.makeArtworkURLTemplate = makeArtworkURLTemplate;
+//# sourceMappingURL=artwork.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/button.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/button.js
new file mode 100644
index 0000000..036c19a
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/button.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=button.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/color.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/color.js
new file mode 100644
index 0000000..c68a1df
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/color.js
@@ -0,0 +1,131 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.areEqual = exports.luminanceFrom = exports.dynamicWith = exports.named = exports.rgbWith = exports.htmlWith = void 0;
+const optional_1 = require("../types/optional");
+// endregion
+// region Constructors
+/**
+ * Create new `HTMLColor` from hexadecimal string representation.
+ *
+ * @param hexString - Hexadecimal string representation.
+ */
+function htmlWith(hexString) {
+ if (optional_1.isNothing(hexString)) {
+ return null;
+ }
+ return {
+ $kind: "html",
+ value: hexString,
+ };
+}
+exports.htmlWith = htmlWith;
+/**
+ * Create new `RBGColor` with RGB components and opacity value.
+ *
+ * @param red - Red color value.
+ * @param green - Green color value.
+ * @param blue - Blue color value.
+ * @param alpha - Opacity value.
+ */
+function rgbWith(red, green, blue, alpha = 1.0) {
+ const newColor = {
+ $kind: "rgb",
+ red: red,
+ green: green,
+ blue: blue,
+ alpha: alpha,
+ };
+ return newColor;
+}
+exports.rgbWith = rgbWith;
+/**
+ * Create new named color using the color name.
+ *
+ * @param name - The name of the color.
+ */
+function named(name) {
+ const newColor = {
+ $kind: "named",
+ name: name,
+ };
+ return newColor;
+}
+exports.named = named;
+/**
+ * Create new dynamic color with light and dark color variants.
+ *
+ * @param lightColor - The light color variant.
+ * @param lightHighContrastColor - The light hight-contrast color variant.
+ * @param darkColor - The dark color variant.
+ * @param darkHighContrastColor - The dark hight-contrast color variant.
+ */
+function dynamicWith(lightColor, lightHighContrastColor, darkColor, darkHighContrastColor) {
+ const newColor = {
+ $kind: "dynamic",
+ lightColor: lightColor,
+ lightHighContrastColor: lightHighContrastColor,
+ darkColor: darkColor,
+ darkHighContrastColor: darkHighContrastColor,
+ };
+ return newColor;
+}
+exports.dynamicWith = dynamicWith;
+// endregion
+// region Properties
+/**
+ * Get the luminance of the color.
+ *
+ * @param rgbColor - The RGB color to get luminance for.
+ */
+function luminanceFrom(rgbColor) {
+ // Note: This is lifted from UIColor_Private
+ // Using RGB color components, calculates and returns (0.2126 * r) + (0.7152 * g) + (0.0722 * b).
+ return rgbColor.red * 0.2126 + rgbColor.green * 0.7152 + rgbColor.blue * 0.0722;
+}
+exports.luminanceFrom = luminanceFrom;
+// endregion
+// region Identity
+/**
+ * Compare two colors for equality.
+ *
+ * @param color1 - Left hand side color to compare.
+ * @param color2 - Right hand side color to compare.
+ * @returns A Boolean indicating whether the colors are equal.
+ */
+function areEqual(color1, color2) {
+ if (optional_1.isNothing(color1)) {
+ return optional_1.isNothing(color2);
+ }
+ else if (optional_1.isNothing(color2)) {
+ return optional_1.isNothing(color1);
+ }
+ const kind1 = color1.$kind;
+ const kind2 = color2.$kind;
+ if (kind1 === "named" && kind2 === "named") {
+ const namedColor1 = color1;
+ const namedColor2 = color2;
+ return namedColor1.name === namedColor2.name;
+ }
+ else if (kind1 === "rgb" && kind2 === "rgb") {
+ const rgbColor1 = color1;
+ const rgbColor2 = color2;
+ return (rgbColor1.red === rgbColor2.red &&
+ rgbColor1.green === rgbColor2.green &&
+ rgbColor1.blue === rgbColor2.blue &&
+ rgbColor1.alpha === rgbColor2.alpha);
+ }
+ else if (kind1 === "dynamic" && kind2 === "dynamic") {
+ const dynamicColor1 = color1;
+ const dynamicColor2 = color2;
+ return (areEqual(dynamicColor1.lightColor, dynamicColor2.lightColor) &&
+ areEqual(dynamicColor1.lightHighContrastColor, dynamicColor2.lightHighContrastColor) &&
+ areEqual(dynamicColor1.darkColor, dynamicColor2.darkColor) &&
+ areEqual(dynamicColor1.darkHighContrastColor, dynamicColor2.darkHighContrastColor));
+ }
+ else {
+ return false;
+ }
+}
+exports.areEqual = areEqual;
+// endregion
+//# sourceMappingURL=color.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/index.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/index.js
new file mode 100644
index 0000000..19009ad
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/index.js
@@ -0,0 +1,21 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./actions"), exports);
+__exportStar(require("./artwork"), exports);
+__exportStar(require("./button"), exports);
+__exportStar(require("./color"), exports);
+__exportStar(require("./menu"), exports);
+__exportStar(require("./paragraph"), exports);
+__exportStar(require("./programmed-text"), exports);
+__exportStar(require("./video"), exports);
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/menu.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/menu.js
new file mode 100644
index 0000000..200dc6b
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/menu.js
@@ -0,0 +1,8 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.menuSeparatorID = void 0;
+/**
+ * A standard identifier for including a separator in a menu.
+ */
+exports.menuSeparatorID = "com.apple.JetEngine.separator";
+//# sourceMappingURL=menu.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/paragraph.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/paragraph.js
new file mode 100644
index 0000000..3518ea7
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/paragraph.js
@@ -0,0 +1,4 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+// endregion
+//# sourceMappingURL=paragraph.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/programmed-text.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/programmed-text.js
new file mode 100644
index 0000000..18a8337
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/programmed-text.js
@@ -0,0 +1,5 @@
+"use strict";
+// region ProgrammedText
+Object.defineProperty(exports, "__esModule", { value: true });
+// endregion
+//# sourceMappingURL=programmed-text.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/video.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/video.js
new file mode 100644
index 0000000..0f0031f
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/models/video.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=video.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bag.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bag.js
new file mode 100644
index 0000000..ffe6106
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bag.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=bag.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bundle.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bundle.js
new file mode 100644
index 0000000..9a818e7
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/bundle.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=bundle.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cookie-provider.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cookie-provider.js
new file mode 100644
index 0000000..e681941
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cookie-provider.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=cookie-provider.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cryptography.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cryptography.js
new file mode 100644
index 0000000..de648d8
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/cryptography.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=cryptography.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/host.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/host.js
new file mode 100644
index 0000000..9dbd12d
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/host.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=host.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/index.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/index.js
new file mode 100644
index 0000000..028f856
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/index.js
@@ -0,0 +1,51 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+/* `preprocessor` and `testContent` are normally replaced by inline literals while bundling an app's JS.
+ *
+ * If these values have not been set we want to provide defaults however
+ * attempting to access them can trigger a ReferenceError as the
+ * variables are undefined (distinct from a defined variable being set to
+ * `undefined`).
+ *
+ * `typeof` checks can safely test undefined variables, note that these checks will become:
+ * `typeof { DEBUG_BUILD: true, ... }` when webpack's DefinePlugin is used in @jet/build's webpack task.
+ * When these variables have not been replaced we need to use `globalThis` to set them on the global scope
+ * in order to avoid ReferenceErrors attempting to access them.
+ */
+if (typeof preprocessor === "undefined") {
+ globalThis.preprocessor = {
+ PRODUCTION_BUILD: false,
+ CARRY_BUILD: false,
+ DEBUG_BUILD: false,
+ INTERNAL_BUILD: false,
+ };
+}
+if (typeof testContent === "undefined") {
+ globalThis.testContent = {
+ INCLUDE_TEST_CONTENT: false,
+ };
+}
+__exportStar(require("./bag"), exports);
+__exportStar(require("./bundle"), exports);
+__exportStar(require("./cookie-provider"), exports);
+__exportStar(require("./cryptography"), exports);
+__exportStar(require("./host"), exports);
+__exportStar(require("./jscookie"), exports);
+__exportStar(require("./net"), exports);
+__exportStar(require("./platform"), exports);
+__exportStar(require("./plist"), exports);
+__exportStar(require("./preprocessor"), exports);
+__exportStar(require("./random"), exports);
+__exportStar(require("./service"), exports);
+__exportStar(require("./types"), exports);
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/jscookie.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/jscookie.js
new file mode 100644
index 0000000..bfd5e29
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/jscookie.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=jscookie.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/net.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/net.js
new file mode 100644
index 0000000..6b810d4
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/net.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=net.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/platform.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/platform.js
new file mode 100644
index 0000000..eafaa33
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/platform.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=platform.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/plist.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/plist.js
new file mode 100644
index 0000000..29503e6
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/plist.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=plist.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/preprocessor.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/preprocessor.js
new file mode 100644
index 0000000..a04398d
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/preprocessor.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=preprocessor.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/random.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/random.js
new file mode 100644
index 0000000..3484776
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/random.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=random.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/service.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/service.js
new file mode 100644
index 0000000..a4b3c49
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/service.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=service.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/types.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/types.js
new file mode 100644
index 0000000..62c2e70
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/globals/types.js
@@ -0,0 +1,16 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.services = exports.random = exports.plist = exports.platform = exports.net = exports.localizer = exports.host = exports.cryptography = exports.cookieProvider = exports.bundle = exports.bag = void 0;
+const metatype_1 = require("../../util/metatype");
+exports.bag = metatype_1.makeMetatype("jet-engine:bag");
+exports.bundle = metatype_1.makeMetatype("jet-engine:bundle");
+exports.cookieProvider = metatype_1.makeMetatype("jet-engine:cookieProvider");
+exports.cryptography = metatype_1.makeMetatype("jet-engine:cryptography");
+exports.host = metatype_1.makeMetatype("jet-engine:host");
+exports.localizer = metatype_1.makeMetatype("jet-engine:localizer");
+exports.net = metatype_1.makeMetatype("jet-engine:net");
+exports.platform = metatype_1.makeMetatype("jet-engine:platform");
+exports.plist = metatype_1.makeMetatype("jet-engine:plist");
+exports.random = metatype_1.makeMetatype("jet-engine:random");
+exports.services = metatype_1.makeMetatype("jet-engine:services");
+//# sourceMappingURL=types.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/console.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/console.js
new file mode 100644
index 0000000..c4fb39a
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/console.js
@@ -0,0 +1,14 @@
+"use strict";
+/*
+ * Describes standard functionality available in JSContexts
+ *
+ * Types are defined here to allow us to match the behavior available in JSContext in the target OS
+ * which may not exactly match the definitions in standard TypeScript lib files, particularly on a
+ * pre-release OS.
+ *
+ * The living standard for the Console API is available at https://console.spec.whatwg.org
+ * The WebKit team has documented their interfaces at https://webkit.org/web-inspector/console-object-api/
+ * The equivalent interface in Node is https://nodejs.org/api/console.html
+ */
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=console.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/index.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/index.js
new file mode 100644
index 0000000..ee0a3fc
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/javascriptcore/index.js
@@ -0,0 +1,14 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+__exportStar(require("./console"), exports);
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/metrics.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/metrics.js
new file mode 100644
index 0000000..565e4b9
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/metrics.js
@@ -0,0 +1,57 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.notInstrumented = exports.PageInvocationPoint = exports.EMPTY_LINTED_METRICS_EVENT = void 0;
+/**
+ * An empty linted metrics event.
+ *
+ * The empty events should be skipped from recording
+ * by metrics event recorders.
+ */
+exports.EMPTY_LINTED_METRICS_EVENT = {
+ fields: {},
+ issues: [],
+};
+var PageInvocationPoint;
+(function (PageInvocationPoint) {
+ PageInvocationPoint["pageEnter"] = "pageEnter";
+ PageInvocationPoint["pageExit"] = "pageExit";
+ PageInvocationPoint["appExit"] = "appExit";
+ PageInvocationPoint["appEnter"] = "appEnter";
+ PageInvocationPoint["backButton"] = "backButton";
+})(PageInvocationPoint = exports.PageInvocationPoint || (exports.PageInvocationPoint = {}));
+/**
+ * Returns an empty metrics instance of the specified metrics type.
+ * @param metricsType - Type of the metrics data to return.
+ *
+ * @deprecated Do not use, all metrics events should be instrumented.
+ */
+function notInstrumented(metricsType) {
+ switch (metricsType) {
+ case 0 /* ActionMetrics */:
+ return {
+ data: [],
+ custom: {},
+ };
+ case 1 /* FetchTimingMetrics */:
+ return {};
+ case 2 /* PageMetrics */:
+ return {
+ instructions: [],
+ custom: {},
+ };
+ case 3 /* ImpressionMetrics */:
+ return {
+ id: {
+ id: "",
+ impressionIndex: NaN,
+ },
+ fields: {},
+ custom: {},
+ };
+ default:
+ return {};
+ }
+}
+exports.notInstrumented = notInstrumented;
+// endregion
+//# sourceMappingURL=metrics.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/models.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/models.js
new file mode 100644
index 0000000..b2dccd6
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/models.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=models.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/optional.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/optional.js
new file mode 100644
index 0000000..ea3aaeb
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/types/optional.js
@@ -0,0 +1,71 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.flatMapOptional = exports.mapOptional = exports.unsafeUnwrapOptional = exports.unwrapOptional = exports.isSome = exports.isNothing = exports.unsafeUninitialized = void 0;
+/**
+ * Bypass the protection provided by the `Optional` type
+ * and pretend to produce a value of `Some` while
+ * actually returning `Nothing`.
+ */
+function unsafeUninitialized() {
+ return undefined;
+}
+exports.unsafeUninitialized = unsafeUninitialized;
+/**
+ * Test whether an optional does not contain a value.
+ *
+ * @param value - An optional value to test.
+ */
+function isNothing(value) {
+ return value === undefined || value === null;
+}
+exports.isNothing = isNothing;
+/**
+ * Test whether an optional contains a value.
+ * @param value - An optional value to test.
+ */
+function isSome(value) {
+ return value !== undefined && value !== null;
+}
+exports.isSome = isSome;
+/**
+ * Unwrap the value contained in a given optional,
+ * throwing an error if there is no value.
+ *
+ * @param value - A value to unwrap.
+ */
+function unwrapOptional(value) {
+ if (isNothing(value)) {
+ throw new ReferenceError();
+ }
+ return value;
+}
+exports.unwrapOptional = unwrapOptional;
+/**
+ * Unwrap the value contained in a given optional
+ * without checking if the value exists.
+ *
+ * @param value - A value to unwrap.
+ */
+function unsafeUnwrapOptional(value) {
+ return value;
+}
+exports.unsafeUnwrapOptional = unsafeUnwrapOptional;
+function mapOptional(value, body) {
+ if (isSome(value)) {
+ return body(value);
+ }
+ else {
+ return value;
+ }
+}
+exports.mapOptional = mapOptional;
+function flatMapOptional(value, body) {
+ if (isSome(value)) {
+ return body(value);
+ }
+ else {
+ return value;
+ }
+}
+exports.flatMapOptional = flatMapOptional;
+//# sourceMappingURL=optional.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/metatype.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/metatype.js
new file mode 100644
index 0000000..372f58f
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/metatype.js
@@ -0,0 +1,10 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeMetatype = void 0;
+function makeMetatype(name) {
+ return {
+ name: name,
+ };
+}
+exports.makeMetatype = makeMetatype;
+//# sourceMappingURL=metatype.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/urls.js b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/urls.js
new file mode 100644
index 0000000..fd7ec3e
--- /dev/null
+++ b/shared/metrics-8/node_modules/@jet/engine/node_modules/@jet/environment/util/urls.js
@@ -0,0 +1,370 @@
+"use strict";
+// MARK: - Parsing Regular Expressions
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.URL = void 0;
+const optional_1 = require("../types/optional");
+const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i;
+const queryParamRegex = /([^=?&]+)=?([^&]*)/g;
+const componentOrder = ["hash", "query", "pathname", "host"];
+class URL {
+ constructor(url) {
+ var _a;
+ this.query = {};
+ if (optional_1.isNothing(url)) {
+ return;
+ }
+ // Split the protocol from the rest of the urls
+ let remainder = url;
+ const match = protocolRegex.exec(url);
+ if (optional_1.isSome(match)) {
+ // Pull out the protocol
+ let protocol = match[1];
+ if (protocol !== null && protocol !== undefined) {
+ protocol = protocol.split(":")[0];
+ }
+ this.protocol = protocol !== null && protocol !== void 0 ? protocol : undefined;
+ // Save the remainder
+ remainder = (_a = match[3]) !== null && _a !== void 0 ? _a : undefined;
+ }
+ // Then match each component in a specific order
+ let parse = { remainder: remainder, result: undefined };
+ for (const component of componentOrder) {
+ if (parse === undefined || parse.remainder === undefined) {
+ break;
+ }
+ switch (component) {
+ case "hash": {
+ parse = splitUrlComponent(parse.remainder, "#", "suffix");
+ this.hash = parse === null || parse === void 0 ? void 0 : parse.result;
+ break;
+ }
+ case "query": {
+ parse = splitUrlComponent(parse.remainder, "?", "suffix");
+ if ((parse === null || parse === void 0 ? void 0 : parse.result) !== undefined) {
+ this.query = URL.queryFromString(parse.result);
+ }
+ break;
+ }
+ case "pathname": {
+ parse = splitUrlComponent(parse.remainder, "/", "suffix");
+ if ((parse === null || parse === void 0 ? void 0 : parse.result) !== undefined) {
+ // Replace the initial /, since paths require it
+ this.pathname = "/" + parse.result;
+ }
+ break;
+ }
+ case "host": {
+ const authorityParse = splitUrlComponent(parse.remainder, "@", "prefix");
+ const userInfo = authorityParse === null || authorityParse === void 0 ? void 0 : authorityParse.result;
+ const hostPort = authorityParse === null || authorityParse === void 0 ? void 0 : authorityParse.remainder;
+ if (userInfo !== undefined) {
+ const userInfoSplit = userInfo.split(":");
+ this.username = decodeURIComponent(userInfoSplit[0]);
+ this.password = decodeURIComponent(userInfoSplit[1]);
+ }
+ if (hostPort !== undefined) {
+ const hostPortSplit = hostPort.split(":");
+ this.host = hostPortSplit[0];
+ this.port = hostPortSplit[1];
+ }
+ break;
+ }
+ default: {
+ throw new Error("Unhandled case!");
+ }
+ }
+ }
+ }
+ get(component) {
+ switch (component) {
+ // Exhaustive match to make sure TS property minifiers and other
+ // transformer plugins do not break this code.
+ case "protocol":
+ return this.protocol;
+ case "username":
+ return this.username;
+ case "password":
+ return this.password;
+ case "port":
+ return this.port;
+ case "pathname":
+ return this.pathname;
+ case "query":
+ return this.query;
+ case "hash":
+ return this.hash;
+ default:
+ // The fallback for component which is not a property of URL object.
+ return this[component];
+ }
+ }
+ set(component, value) {
+ if (value === undefined) {
+ return this;
+ }
+ if (component === "query") {
+ if (typeof value === "string") {
+ value = URL.queryFromString(value);
+ }
+ }
+ switch (component) {
+ // Exhaustive match to make sure TS property minifiers and other
+ // transformer plugins do not break this code.
+ case "protocol":
+ this.protocol = value;
+ break;
+ case "username":
+ this.username = value;
+ break;
+ case "password":
+ this.password = value;
+ break;
+ case "port":
+ this.port = value;
+ break;
+ case "pathname":
+ this.pathname = value;
+ break;
+ case "query":
+ this.query = value;
+ break;
+ case "hash":
+ this.hash = value;
+ break;
+ default:
+ // The fallback for component which is not a property of URL object.
+ this[component] = value;
+ break;
+ }
+ return this;
+ }
+ append(component, value) {
+ let existingValue = this.get(component);
+ let newValue;
+ if (component === "query") {
+ if (existingValue === undefined) {
+ existingValue = {};
+ }
+ if (typeof value === "string") {
+ value = URL.queryFromString(value);
+ }
+ if (typeof existingValue === "string") {
+ newValue = { existingValue, ...value };
+ }
+ else {
+ newValue = { ...existingValue, ...value };
+ }
+ }
+ else {
+ if (existingValue === undefined) {
+ existingValue = "";
+ }
+ let existingValueString = existingValue;
+ if (existingValueString === undefined) {
+ existingValueString = "";
+ }
+ let newValueString = existingValueString;
+ if (component === "pathname") {
+ const pathLength = existingValueString.length;
+ if (pathLength === 0 || existingValue[pathLength - 1] !== "/") {
+ newValueString += "/";
+ }
+ }
+ // The component is not "query" so we treat value as string.
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-plus-operands
+ newValueString += value;
+ newValue = newValueString;
+ }
+ return this.set(component, newValue);
+ }
+ param(key, value) {
+ if (key === null) {
+ return this;
+ }
+ if (this.query === undefined) {
+ this.query = {};
+ }
+ if (value === undefined) {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete this.query[key];
+ }
+ else {
+ this.query[key] = value;
+ }
+ return this;
+ }
+ removeParam(key) {
+ if (key === undefined || this.query === undefined) {
+ return this;
+ }
+ if (key in this.query) {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete this.query[key];
+ }
+ return this;
+ }
+ path(value) {
+ return this.append("pathname", value);
+ }
+ pathExtension() {
+ var _a, _b;
+ // Extract path extension if one exists
+ if (this.pathname === undefined) {
+ return undefined;
+ }
+ const lastFilenameComponents = (_b = (_a = this.pathname
+ .split("/")
+ .filter((item) => item.length > 0) // Remove any double or trailing slashes
+ .pop()) === null || _a === void 0 ? void 0 : _a.split(".")) !== null && _b !== void 0 ? _b : [];
+ if (lastFilenameComponents.filter(function (part) {
+ return part !== "";
+ }).length < 2 // Remove any empty parts (e.g. .ssh_config -> ["ssh_config"])
+ ) {
+ return undefined;
+ }
+ return lastFilenameComponents.pop();
+ }
+ /**
+ * Returns the path components of the URL
+ * @returns An array of non-empty path components from `urls`.
+ */
+ pathComponents() {
+ if (this.pathname === undefined) {
+ return [];
+ }
+ return this.pathname.split("/").filter((component) => component.length > 0);
+ }
+ /**
+ * Same as toString
+ *
+ * @returns A string representation of the URL
+ */
+ build() {
+ return this.toString();
+ }
+ /**
+ * Converts the URL to a string
+ *
+ * @returns A string representation of the URL
+ */
+ toString() {
+ let url = "";
+ if (this.protocol !== undefined) {
+ url += this.protocol + "://";
+ }
+ if (this.username !== undefined) {
+ url += encodeURIComponent(this.username);
+ if (this.password !== undefined) {
+ url += ":" + encodeURIComponent(this.password);
+ }
+ url += "@";
+ }
+ if (this.host !== undefined) {
+ url += this.host;
+ if (this.port !== undefined) {
+ url += ":" + this.port;
+ }
+ }
+ if (this.pathname !== undefined) {
+ url += this.pathname;
+ }
+ if (this.query !== undefined && Object.keys(this.query).length !== 0) {
+ url += "?" + URL.toQueryString(this.query);
+ }
+ if (this.hash !== undefined) {
+ url += "#" + this.hash;
+ }
+ return url;
+ }
+ // ----------------
+ // Static API
+ // ----------------
+ /**
+ * Converts a string into a query dictionary
+ * @param query - The string to parse
+ * @returns The query dictionary containing the key-value pairs in the query string
+ */
+ static queryFromString(query) {
+ const result = {};
+ let parseResult = queryParamRegex.exec(query);
+ while (parseResult !== null) {
+ const key = decodeURIComponent(parseResult[1]);
+ const value = decodeURIComponent(parseResult[2]);
+ result[key] = value;
+ parseResult = queryParamRegex.exec(query);
+ }
+ return result;
+ }
+ /**
+ * Converts a query dictionary into a query string
+ *
+ * @param query - The query dictionary
+ * @returns The string representation of the query dictionary
+ */
+ static toQueryString(query) {
+ let queryString = "";
+ let first = true;
+ for (const key of Object.keys(query)) {
+ if (!first) {
+ queryString += "&";
+ }
+ first = false;
+ queryString += encodeURIComponent(key);
+ const value = query[key];
+ if (value !== null && value.length > 0) {
+ queryString += "=" + encodeURIComponent(value);
+ }
+ }
+ return queryString;
+ }
+ /**
+ * Convenience method to instantiate a URL from a string
+ * @param url - The URL string to parse
+ * @returns The new URL object representing the URL
+ */
+ static from(url) {
+ return new URL(url);
+ }
+ /**
+ * Convenience method to instantiate a URL from numerous (optional) components
+ * @param protocol - The protocol type
+ * @param host - The host name
+ * @param path - The path
+ * @param query - The query
+ * @param hash - The hash
+ * @returns The new URL object representing the URL
+ */
+ static fromComponents(protocol, host, path, query, hash) {
+ const url = new URL();
+ url.protocol = protocol;
+ url.host = host;
+ url.pathname = path;
+ url.query = query !== null && query !== void 0 ? query : {};
+ url.hash = hash;
+ return url;
+ }
+}
+exports.URL = URL;
+// MARK: - Helpers
+function splitUrlComponent(input, marker, style) {
+ const index = input.indexOf(marker);
+ let result;
+ let remainder = input;
+ if (index !== -1) {
+ const prefix = input.slice(0, index);
+ const suffix = input.slice(index + marker.length, input.length);
+ if (style === "prefix") {
+ result = prefix;
+ remainder = suffix;
+ }
+ else {
+ result = suffix;
+ remainder = prefix;
+ }
+ }
+ return {
+ result: result,
+ remainder: remainder,
+ };
+}
+//# sourceMappingURL=urls.js.map
\ No newline at end of file
diff --git a/shared/metrics-8/src/constants.ts b/shared/metrics-8/src/constants.ts
new file mode 100644
index 0000000..6eff451
--- /dev/null
+++ b/shared/metrics-8/src/constants.ts
@@ -0,0 +1,19 @@
+/**
+ * A list of event types we use across all onyx apps for metrics.
+ */
+export const METRICS_EVENT_TYPES = [
+ // The following types come from the jet enum `MetricsEventType`
+ // https://github.pie.apple.com/app-store/jet-js/blob/505144151e875c1bcbacd898216127fbc14c1562/packages/environment/src/types/metrics.ts#L198-L205
+ // and the events could be handled by MetricsKit
+ // https://github.pie.apple.com/amp-ae/mt-metricskit/tree/dev/packages/processors/mt-metricskit-processor-clickstream/src/metrics/event_handlers
+ 'account', // For GDPR
+ 'click',
+ 'dialog',
+ 'enter',
+ 'exit',
+ 'impressions',
+ 'media',
+ 'page',
+ 'pageRender',
+ 'search',
+] as const;
diff --git a/shared/metrics-8/src/impression-provider.ts b/shared/metrics-8/src/impression-provider.ts
new file mode 100644
index 0000000..852d418
--- /dev/null
+++ b/shared/metrics-8/src/impression-provider.ts
@@ -0,0 +1,27 @@
+import type {
+ MetricsFieldsBuilder,
+ MetricsFieldsContext,
+ MetricsFieldsProvider,
+} from '@jet/engine';
+import { IMPRESSION_CONTEXT_NAME } from './impressions/constants';
+import type { Impressions } from './impressions';
+
+export class ImpressionFieldProvider implements MetricsFieldsProvider {
+ constructor(private readonly appContext: Map) {
+ this.appContext = appContext;
+ }
+
+ addMetricsFields(
+ builder: MetricsFieldsBuilder,
+ _metricsContext: MetricsFieldsContext,
+ ) {
+ const impressionInstance = this.appContext.get(
+ IMPRESSION_CONTEXT_NAME,
+ ) as Impressions;
+
+ if (impressionInstance?.settings?.captureType === 'jet') {
+ let impressions = impressionInstance.consumeImpressions();
+ builder.addValue(impressions, 'impressions');
+ }
+ }
+}
diff --git a/shared/metrics-8/src/impression-snapshot-provider.ts b/shared/metrics-8/src/impression-snapshot-provider.ts
new file mode 100644
index 0000000..c7261f9
--- /dev/null
+++ b/shared/metrics-8/src/impression-snapshot-provider.ts
@@ -0,0 +1,27 @@
+import type {
+ MetricsFieldsBuilder,
+ MetricsFieldsContext,
+ MetricsFieldsProvider,
+} from '@jet/engine';
+import { IMPRESSION_CONTEXT_NAME } from './impressions/constants';
+import type { Impressions } from './impressions';
+
+export class ImpressionSnapshotFieldProvider implements MetricsFieldsProvider {
+ constructor(private readonly appContext: Map) {
+ this.appContext = appContext;
+ }
+
+ addMetricsFields(
+ builder: MetricsFieldsBuilder,
+ _metricsContext: MetricsFieldsContext,
+ ) {
+ const impressionInstance = this.appContext.get(
+ IMPRESSION_CONTEXT_NAME,
+ ) as Impressions;
+
+ if (impressionInstance?.settings?.captureType === 'jet') {
+ let impressions = impressionInstance.captureSnapshotImpression();
+ builder.addValue(impressions, 'impressions');
+ }
+ }
+}
diff --git a/shared/metrics-8/src/impressions/constants.ts b/shared/metrics-8/src/impressions/constants.ts
new file mode 100644
index 0000000..0638f9e
--- /dev/null
+++ b/shared/metrics-8/src/impressions/constants.ts
@@ -0,0 +1 @@
+export const IMPRESSION_CONTEXT_NAME = 'metrics:impression' as const;
diff --git a/shared/metrics-8/src/impressions/index.ts b/shared/metrics-8/src/impressions/index.ts
new file mode 100644
index 0000000..b904feb
--- /dev/null
+++ b/shared/metrics-8/src/impressions/index.ts
@@ -0,0 +1,252 @@
+import type { Logger, LoggerFactory } from '@amp/web-apps-logger/src/types';
+import { IMPRESSION_CONTEXT_NAME } from './constants';
+import { createSvelteImpressionAction } from './utils/svelte/impressions-svelte-action';
+import type {
+ AppImpressionModel,
+ ImpressionSettings,
+ ImpressionsInstance,
+} from './types';
+import type {
+ ImpressionObserver,
+ newInstanceWithMetricsConfig,
+} from '@amp-metrics/mt-impressions-observer';
+import type { ClickstreamProcessor } from '@amp-metrics/mt-metricskit-processor-clickstream';
+
+/**
+ * Adapter class to handle interactions with
+ * metricsKit impression observer.
+ */
+export class Impressions implements ImpressionsInstance {
+ private readonly logger: Logger;
+ private impressionObserverInstance: ImpressionObserver | undefined;
+ private hasInitialized: boolean = false;
+ private impressionDataMap: Map = new Map();
+ private currentSnapshot: Record[] = [];
+ private readonly impressionSettings: ImpressionSettings | undefined;
+
+ constructor(
+ loggerFactory: LoggerFactory,
+ context: Map,
+ settings?: ImpressionSettings,
+ ) {
+ this.logger = loggerFactory.loggerFor(IMPRESSION_CONTEXT_NAME);
+ this.impressionSettings = settings;
+
+ context.set(IMPRESSION_CONTEXT_NAME, this);
+ }
+
+ async init(
+ makeImpressionObserver: typeof newInstanceWithMetricsConfig,
+ clickStreamInstance: ClickstreamProcessor,
+ ) {
+ if (this.hasInitialized) {
+ this.logger.warn(
+ 'Ignoring, Impressions.init() can only be called once',
+ );
+ return;
+ }
+
+ const options = { root: document, rootMargin: '0px' };
+ const config = clickStreamInstance.config;
+ const impressionObserver: ImpressionObserver =
+ await makeImpressionObserver(config, options);
+
+ impressionObserver.setDelegate({
+ extractImpressionInfo: (domNode: HTMLElement) => {
+ const dataMap = this.impressionDataMap;
+ const nodeMetricData = dataMap.get(domNode);
+ if (nodeMetricData) {
+ const impressionData = nodeMetricData.impressionMetrics;
+ impressionData.location =
+ clickStreamInstance.utils.eventFields.buildLocationStructure(
+ domNode,
+ (node: HTMLElement) => {
+ const metrics = dataMap.get(node);
+ if (metrics?.location) {
+ return metrics.location;
+ }
+ return;
+ },
+ );
+ return impressionData;
+ } else {
+ this.logger.warn('no impression data found for', domNode);
+ }
+ },
+ });
+
+ this.impressionObserverInstance = impressionObserver;
+ this.impressionDataMap.forEach((_value, node) => {
+ this.logger.debug('observing deffered node', node);
+ this.impressionObserverInstance?.observe(node);
+ });
+ this.hasInitialized = true;
+
+ this.logger.debug('impressions initialized');
+ }
+
+ get settings() {
+ return this.impressionSettings;
+ }
+
+ isEnabled(event: 'click' | 'exit' | 'impressions'): boolean {
+ if (this.impressionSettings?.captureType === 'jet') {
+ return (
+ this.impressionSettings?.metricsKitEvents?.includes(event) ??
+ false
+ );
+ }
+ return true;
+ }
+
+ consumeImpressions(): Record[] | undefined {
+ if (this.hasInitialized) {
+ this.logger.debug('consuming impression metrics');
+ return this.impressionObserverInstance?.consumeImpressions();
+ }
+ this.logger.warn('impressions not avaiable yet');
+ return;
+ }
+
+ captureSnapshotImpression(): Record[] | undefined {
+ const snapshot =
+ this.impressionObserverInstance?.snapshotImpressions() ?? [];
+
+ // if the current page already transitioned. fallback to the snapshot we captured before transition
+ if (snapshot.length === 0) {
+ return this.getSnapshotImpression();
+ }
+
+ return snapshot;
+ }
+
+ getSnapshotImpression(): Record[] | undefined {
+ if (this.hasInitialized) {
+ return this.currentSnapshot;
+ }
+ this.logger.warn('impressions not avaiable yet');
+ return;
+ }
+
+ setCurrentSnapshot(): void {
+ if (this.hasInitialized) {
+ this.logger.debug('capturing impression snapshot');
+ this.currentSnapshot =
+ this.impressionObserverInstance?.snapshotImpressions() ?? [];
+ } else {
+ this.logger.warn('impressions not avaiable yet');
+ }
+ }
+
+ get nodeList() {
+ const impressionClass = this;
+
+ return new Proxy(impressionClass.impressionDataMap, {
+ get(target, prop, receiver) {
+ const orginalFn = Reflect.get(target, prop, receiver);
+
+ // overriding 'set' to also be able to observe
+ if (prop === 'set') {
+ return (
+ node: HTMLElement,
+ value: Record,
+ ) => {
+ if (impressionClass.hasInitialized) {
+ impressionClass.logger.debug(
+ 'observing',
+ node,
+ value,
+ );
+
+ impressionClass.impressionObserverInstance?.observe(
+ node,
+ );
+ }
+
+ return orginalFn.bind(target)(node, value);
+ };
+ }
+
+ // overriding 'delete' to also be able to unobserve
+ if (prop === 'delete') {
+ return (node: HTMLElement) => {
+ if (impressionClass.hasInitialized) {
+ impressionClass.logger.debug('unobserve', node);
+ impressionClass.impressionObserverInstance?.unobserve(
+ node,
+ );
+ }
+
+ return orginalFn.bind(target)(node);
+ };
+ }
+
+ return orginalFn.bind(target);
+ },
+ set(target, prop, value) {
+ return Reflect.set(target, prop, value);
+ },
+ });
+ }
+}
+
+/**
+ * Server Noop for above
+ */
+class ServerNoopImpressions implements ImpressionsInstance {
+ readonly nodeList: WeakMap;
+ constructor() {
+ this.nodeList = new WeakMap();
+ }
+ setCurrentSnapshot(): void {}
+}
+
+/**
+ * Gets the current Impression instance from the Svelte context.
+ *
+ * @return The current instance of Impression
+ */
+export function generateBrowserImpressionsContextGetter(
+ getContext: (context: string) => unknown,
+): () => AppImpressionModel {
+ return function getImpressions(): AppImpressionModel {
+ const impressions = getContext(IMPRESSION_CONTEXT_NAME) as
+ | Impressions
+ | undefined;
+
+ if (!impressions) {
+ const noopImpressions = new ServerNoopImpressions();
+ return {
+ captureImpressions: (_node: any, _impressionsData: any) => {
+ return {
+ destroy() {},
+ };
+ },
+ impressions: noopImpressions,
+ };
+ }
+
+ return {
+ captureImpressions: createSvelteImpressionAction(impressions),
+ impressions,
+ };
+ };
+}
+
+/**
+ * Server No-op for generateImpressionsContextGetter
+ *
+ */
+export function generateServerImpressionsContextGetter(
+ _getContext: (context: string) => unknown,
+): () => AppImpressionModel {
+ const impressions = new ServerNoopImpressions();
+ return () => ({
+ captureImpressions: (_node: any, _impressionsData: any) => {
+ return {
+ destroy() {},
+ };
+ },
+ impressions,
+ });
+}
diff --git a/shared/metrics-8/src/index.ts b/shared/metrics-8/src/index.ts
new file mode 100644
index 0000000..59510b0
--- /dev/null
+++ b/shared/metrics-8/src/index.ts
@@ -0,0 +1,578 @@
+import type { Logger, LoggerFactory } from '@amp/web-apps-logger';
+import { getPWADisplayMode, PWADisplayMode } from '@amp/web-apps-utils/src';
+import type {
+ LintedMetricsEvent,
+ MetricsData,
+ MetricsFields,
+} from '@jet/environment/types/metrics';
+import type { PageMetrics } from '@jet/environment/types/metrics';
+
+import type { Opt } from '@jet/environment';
+
+import {
+ MetricsFieldsAggregator,
+ type MetricsFieldsContext,
+ type MetricsFieldsProvider,
+ MetricsPipeline,
+ PageMetricsPresenter,
+ type MetricsEventRecorder,
+} from '@jet/engine';
+
+import {
+ CompositeEventRecorder,
+ type FunnelKitConfig,
+ FunnelKitRecorder,
+ LoggingEventRecorder,
+ type MetricKitConfig,
+ MetricsKitRecorder,
+ VoidEventRecorder,
+} from './recorder';
+
+import type {
+ MetricsEnterEventType,
+ MetricsExitEventType,
+ SystemLoggerLevel,
+} from './types';
+
+import type {
+ EnvironmentDelegates,
+ WebDelegates as WebDelegatesInstance,
+} from '@amp-metrics/mt-metricskit-delegates-web';
+import type { ClickstreamProcessor as ClickstreamProcessorInstance } from '@amp-metrics/mt-metricskit-processor-clickstream';
+import { Impressions } from './impressions';
+import { buildMakeAjaxRequest } from './utils/metrics-dev-console/metrics-dev-network';
+import { ImpressionFieldProvider } from './impression-provider';
+import { ImpressionSnapshotFieldProvider } from './impression-snapshot-provider';
+import type { ImpressionSettings } from './impressions/types';
+
+const CONTEXT_NAME = 'metrics';
+
+export type MetricsProvider = {
+ provider: MetricsFieldsProvider;
+ request: string;
+};
+
+export interface MetricSettings {
+ shouldEnableImpressions?: () => boolean;
+ shouldEnableFunnelKit: () => boolean;
+ getConsumerId: () => Promise;
+ suppressMetricsKit?: boolean;
+ impressions?: ImpressionSettings;
+}
+
+interface InitializedMetrics {
+ clickstream: ClickstreamProcessorInstance;
+ webDelegate: WebDelegatesInstance;
+}
+
+interface Config {
+ baseFields: {
+ appName: string;
+ delegateApp: string;
+ appVersion: string;
+ resourceRevNum: string;
+ storageObject?: 'sessionStorage' | 'localStorage';
+ };
+ clickstream: MetricKitConfig;
+
+ /**
+ * `FunnelKit` configuration
+ *
+ * Can be `undefined` to disable the `FunnelKit` recorder entirely
+ */
+ funnel?: FunnelKitConfig;
+
+ initialURL?: string | null;
+}
+
+type ClickstreamProcessorClass = typeof ClickstreamProcessorInstance;
+type WebDelegatesClass = typeof WebDelegatesInstance;
+
+export class Metrics {
+ private readonly log: Logger;
+ private impressions: InstanceType | undefined;
+
+ // Properties asynchronously set in the `init` function
+ private ClickstreamProcessor!: ClickstreamProcessorClass;
+ private WebDelegates!: WebDelegatesClass;
+
+ private readonly metricsKitRecorder?: MetricsKitRecorder;
+ private readonly funnelKitRecorder?: FunnelKitRecorder;
+ private firstEnterRecorded: boolean = false;
+ private funnelKit: ClickstreamProcessorInstance | undefined;
+ private config: Config;
+
+ public readonly metricsPipeline: MetricsPipeline;
+ public currentPageMetrics: Opt;
+
+ static load(
+ loggerFactory: LoggerFactory,
+ context: Map,
+ processEvent: (fields: MetricsFields) => Promise,
+ config: Config,
+ listofMetricProviders: MetricsProvider[],
+ settings: MetricSettings,
+ ): Metrics {
+ const {
+ getConsumerId,
+ shouldEnableFunnelKit,
+ suppressMetricsKit = false,
+ } = settings;
+
+ const log = loggerFactory.loggerFor('Metrics');
+
+ // server
+ if (typeof window === 'undefined' || suppressMetricsKit) {
+ const recorder = new VoidEventRecorder();
+ const metricsPipeline = new MetricsPipeline({
+ aggregator: new MetricsFieldsAggregator(),
+ linter: {
+ async processEvent(
+ fields: MetricsFields,
+ ): Promise {
+ return { fields };
+ },
+ },
+ recorder,
+ });
+
+ return new Metrics(log, metricsPipeline, config);
+ }
+
+ config.initialURL = window.location.href;
+
+ const aggregator = setupAggregators(listofMetricProviders, context);
+
+ let impressions: InstanceType | undefined =
+ undefined;
+ if (settings.shouldEnableImpressions?.() ?? false) {
+ impressions = new Impressions(
+ loggerFactory,
+ context,
+ settings?.impressions,
+ );
+ }
+
+ const metricsKitRecorder = new MetricsKitRecorder(
+ loggerFactory,
+ config.clickstream,
+ impressions,
+ );
+
+ const recorders: MetricsEventRecorder[] = [
+ new LoggingEventRecorder(loggerFactory),
+ metricsKitRecorder,
+ ];
+
+ const funnelKitRecorder = config.funnel
+ ? new FunnelKitRecorder(loggerFactory, config.funnel, impressions)
+ : undefined;
+ if (funnelKitRecorder) {
+ recorders.push(funnelKitRecorder);
+ }
+
+ let recorder = new CompositeEventRecorder(recorders);
+
+ const metricsPipeline = new MetricsPipeline({
+ aggregator,
+ linter: {
+ processEvent: async (fields: MetricsFields) => {
+ const lintedEvent = await processEvent(fields);
+
+ // `dsId` is added by the LintMetricsEventIntentController in music-ui-js, but is not needed and erroneous for web
+ // https://github.pie.apple.com/music/music-ui-js/blob/50cbae83deccffad37e5b617394ea30b7e082660/src/metrics/LintMetricsEventIntentController.ts#L19-L22
+ if (lintedEvent.fields?.dsId) {
+ delete lintedEvent.fields.dsId;
+ }
+
+ // Consumer ID needs to be added at the time of processEvent because the ConsumerID is available after Sign In and not before sign In
+ // Using it through the delegates does not have ability to fetch it dynamically
+ const consumerId = await getConsumerId();
+ if (consumerId) {
+ lintedEvent.fields.consumerId = consumerId;
+ }
+
+ return lintedEvent;
+ },
+ },
+ recorder,
+ });
+
+ const metricsInstance = new Metrics(
+ log,
+ metricsPipeline,
+ config,
+ metricsKitRecorder,
+ funnelKitRecorder,
+ impressions,
+ );
+ metricsInstance.watchEnterAndExit();
+
+ (async () => {
+ try {
+ const metricsDependencies = [
+ import('@amp-metrics/mt-metricskit-processor-clickstream'),
+ import('@amp-metrics/mt-metricskit-delegates-web'),
+ impressions
+ ? import('@amp-metrics/mt-impressions-observer')
+ : undefined,
+ ] as const;
+
+ const [
+ { ClickstreamProcessor },
+ { WebDelegates },
+ impressionsDependency,
+ ] = await Promise.all(metricsDependencies);
+
+ metricsInstance.onDependenciesLoaded(
+ ClickstreamProcessor,
+ WebDelegates,
+ );
+
+ const { clickstream, webDelegate } = setupMtkit(
+ ClickstreamProcessor,
+ WebDelegates,
+ config,
+ );
+
+ if (impressions && impressionsDependency) {
+ const { newInstanceWithMetricsConfig } =
+ impressionsDependency;
+ impressions.init(newInstanceWithMetricsConfig, clickstream);
+ }
+
+ const eventRecorder = webDelegate.eventRecorder;
+ metricsKitRecorder.setupEventRecorder(
+ eventRecorder,
+ clickstream,
+ );
+
+ if (shouldEnableFunnelKit()) {
+ metricsInstance.enableFunnelKit();
+ }
+ log.info('Metricskit loaded');
+ } catch (e) {
+ log.warn('Metricskit failed to load', e);
+ }
+ })();
+
+ // Save Metrics Instance on Context before Returning
+ context.set(CONTEXT_NAME, metricsInstance);
+
+ return metricsInstance;
+ }
+
+ private constructor(
+ log: Logger,
+ metricsPipeline: MetricsPipeline,
+ config: Config,
+ metricsKitRecorder?: MetricsKitRecorder,
+ funnelKitRecorder?: FunnelKitRecorder,
+ impressions?: InstanceType,
+ ) {
+ this.log = log;
+ this.metricsPipeline = metricsPipeline;
+ this.metricsKitRecorder = metricsKitRecorder;
+ this.funnelKitRecorder = funnelKitRecorder;
+ this.config = config;
+ this.impressions = impressions;
+ }
+
+ /**
+ * Metrics code that should get called before a page changes.
+ */
+ willPageTransition(): void {
+ this.impressions?.setCurrentSnapshot();
+ }
+
+ async didEnterPage<
+ T extends { pageMetrics: PageMetrics; canonicalURL: string },
+ >(page: T | null): Promise {
+ if (this.currentPageMetrics) {
+ await this.currentPageMetrics.didLeavePage();
+ this.currentPageMetrics = null;
+ }
+
+ if (page?.pageMetrics) {
+ this.currentPageMetrics = new PageMetricsPresenter(
+ this.metricsPipeline,
+ );
+ this.currentPageMetrics.pageMetrics = page.pageMetrics;
+ await this.currentPageMetrics.didEnterPage();
+ } else {
+ this.log.warn('No pageMetrics', page);
+ }
+
+ if (!this.firstEnterRecorded) {
+ const event = document.referrer?.length > 0 ? 'link' : 'launch';
+ this.enter(event, { openUrl: page?.canonicalURL });
+ this.firstEnterRecorded = true;
+ }
+ }
+
+ async enter(type: MetricsEnterEventType, fields?: Opt) {
+ let openUrl: string = window.location.href;
+ let pwaDisplayMode: PWADisplayMode | null = null;
+
+ if (fields?.openUrl) {
+ openUrl = fields?.openUrl as string;
+ }
+
+ if (type === 'launch' && this.config.initialURL) {
+ openUrl = this.config.initialURL;
+ // Clearing the initial URL as we don't need this post launch event
+ this.config.initialURL = null;
+ pwaDisplayMode = getPWADisplayMode();
+ }
+
+ this.recordCustomEvent({
+ eventType: 'enter',
+ extRefUrl: document.referrer ?? '',
+ refUrl: document.referrer ?? '',
+ openUrl,
+ type,
+ // only add buildFlavor property if coming from the PWA (represented by 'standalone' in the manifest.json) or android app
+ ...(pwaDisplayMode === PWADisplayMode.STANDALONE ||
+ pwaDisplayMode === PWADisplayMode.TWA
+ ? { buildFlavor: pwaDisplayMode }
+ : {}),
+ });
+ }
+
+ async exit(type: MetricsExitEventType, _fields?: Opt) {
+ this.recordCustomEvent({
+ eventType: 'exit',
+ type,
+ });
+ }
+
+ async pageTransition() {
+ this.log.info('triggered metrics for page transition');
+ if (this.impressions) {
+ this.impressions.setCurrentSnapshot();
+ }
+ }
+
+ private watchEnterAndExit() {
+ document.addEventListener(
+ 'visibilitychange',
+ this.onVisibilityChange.bind(this),
+ );
+ }
+
+ async onVisibilityChange() {
+ if (document.visibilityState === 'visible') {
+ this.enter('taskSwitch');
+ } else {
+ this.exit('taskSwitch');
+ }
+ }
+
+ async processEvent(metricsFields: MetricsFields) {
+ const metricsData: MetricsData = {
+ excludingFields: [],
+ includingFields: [],
+ shouldFlush: false,
+ fields: metricsFields,
+ };
+ const context: MetricsFieldsContext = {};
+ await this.metricsPipeline.process(metricsData, context);
+ }
+
+ async recordCustomEvent(fields?: Opt) {
+ await this.processEvent({
+ ...this.currentPageMetrics?.pageMetrics?.pageFields,
+ ...fields,
+ });
+ }
+
+ /**
+ * Sets up FunnelKit for clickstream events
+ */
+ private setupFunnelKit(): void {
+ if (!this.config.funnel) {
+ this.log.warn(
+ 'Tried to set up `FunnelKit` but no config was provided',
+ );
+ return;
+ }
+
+ const { topic } = this.config.funnel;
+ const { clickstream, webDelegate } = setupStarkit(
+ this.ClickstreamProcessor,
+ this.WebDelegates,
+ this.config.funnel,
+ this.config.baseFields,
+ );
+ clickstream.config.setDebugSource(null);
+
+ // Disable PII fields and cookies for the funnel topic
+ webDelegate.eventRecorder.setProperties?.(topic, {
+ anonymous: true,
+ });
+
+ this.funnelKitRecorder?.setupEventRecorder(clickstream);
+ this.funnelKit = clickstream;
+ }
+
+ private onDependenciesLoaded(
+ ClickstreamProcessor: ClickstreamProcessorClass,
+ webDelegate: WebDelegatesClass,
+ ): void {
+ this.ClickstreamProcessor = ClickstreamProcessor;
+ this.WebDelegates = webDelegate;
+ }
+
+ disableMetrics(): void {
+ this.metricsKitRecorder?.disable();
+ }
+
+ enableMetrics(): void {
+ this.metricsKitRecorder?.enable();
+ }
+
+ enableFunnelKit(): void {
+ if (!this.funnelKit) {
+ this.setupFunnelKit();
+ }
+ this.funnelKitRecorder?.enableFunnelKit();
+ }
+
+ disableFunnelKit(): void {
+ this.funnelKitRecorder?.disableFunnelKit();
+ }
+}
+
+/**
+ * Shared setup for *kit, namely MetricsKit and FunnelKit
+ */
+function setupStarkit(
+ ClickstreamProcessor: ClickstreamProcessorClass,
+ WebDelegates: WebDelegatesClass,
+ setupConfig: FunnelKitConfig | MetricKitConfig,
+ config: Config['baseFields'],
+): InitializedMetrics {
+ const { topic } = setupConfig;
+ const webDelegate = new WebDelegates(topic);
+
+ if (import.meta.env.APP_SCOPE === 'internal') {
+ try {
+ // Temporary setup to get Network Dependency
+ const networkCopy = {
+ ...Object.getPrototypeOf(webDelegate.config.network),
+ };
+
+ const makeAjaxRequest = buildMakeAjaxRequest(networkCopy, topic);
+
+ webDelegate.setNetwork({
+ makeAjaxRequest,
+ });
+ } catch (e) {
+ console.warn('failed to setup flush logger');
+ }
+ }
+
+ const clickstream = new ClickstreamProcessor(webDelegate);
+
+ const systemLoggerLevel: SystemLoggerLevel = 'none';
+ clickstream.system.logger.setLevel(systemLoggerLevel);
+ clickstream.init();
+
+ setupMtkitDelegates(clickstream, setupConfig, config);
+ return { clickstream, webDelegate };
+}
+
+/**
+ * MetricsKit setup for main clickstream events
+ */
+function setupMtkit(
+ ClickstreamProcessor: ClickstreamProcessorClass,
+ webDelegates: WebDelegatesClass,
+ config: Config,
+): InitializedMetrics {
+ const mtkit = setupStarkit(
+ ClickstreamProcessor,
+ webDelegates,
+ config.clickstream,
+ config.baseFields,
+ );
+ return mtkit;
+}
+
+function setupMtkitDelegates(
+ mtkit: ClickstreamProcessorInstance,
+ setupConfig: FunnelKitConfig | MetricKitConfig,
+ config: Config['baseFields'],
+): void {
+ const { appName, delegateApp, appVersion, resourceRevNum, storageObject } =
+ config;
+ const additionalDelegates: EnvironmentDelegates = {
+ app: () => appName,
+ appVersion: () => appVersion,
+ delegateApp: () => delegateApp,
+ resourceRevNum: () => resourceRevNum,
+ };
+
+ if (storageObject === 'sessionStorage') {
+ additionalDelegates['localStorageObject'] = () => {
+ return sessionStorage;
+ };
+ }
+
+ mtkit.system.environment.setDelegate(additionalDelegates);
+
+ if (Array.isArray(setupConfig.constraintProfiles)) {
+ mtkit.config.setDelegate({
+ constraintProfiles: () => setupConfig.constraintProfiles,
+ });
+ }
+}
+
+function setupAggregators(
+ metricsFieldsProviders: MetricsProvider[],
+ context: Map,
+): MetricsFieldsAggregator {
+ const aggregator = MetricsFieldsAggregator.makeDefaultAggregator();
+
+ aggregator.addOptInProvider(
+ new ImpressionFieldProvider(context),
+ 'impressions',
+ );
+
+ aggregator.addOptInProvider(
+ new ImpressionSnapshotFieldProvider(context),
+ 'impressionsSnapshot',
+ );
+
+ metricsFieldsProviders.forEach((metricsFields) => {
+ aggregator.addOptOutProvider(
+ metricsFields.provider,
+ metricsFields.request,
+ );
+ });
+
+ return aggregator;
+}
+
+/**
+ * Gets the current Metrics instance from the Svelte context.
+ *
+ * @return metrics The current instance of Metrics
+ */
+
+export function generateMetricsContextGetter(
+ getContext: (context: string) => unknown,
+): () => Metrics {
+ return function getMetrics(): Metrics {
+ const metrics = getContext(CONTEXT_NAME) as Metrics | undefined;
+
+ if (!metrics) {
+ throw new Error('getMetrics called before Metrics.load');
+ }
+
+ return metrics;
+ };
+}
+
+export * from './impressions/index';
+export * from './impressions/utils/svelte/impressions-svelte-action';
diff --git a/shared/metrics-8/src/recorder/composite.ts b/shared/metrics-8/src/recorder/composite.ts
new file mode 100644
index 0000000..6302921
--- /dev/null
+++ b/shared/metrics-8/src/recorder/composite.ts
@@ -0,0 +1,20 @@
+import type { MetricsEventRecorder } from '@jet/engine';
+import type { LintedMetricsEvent } from '@jet/environment/types/metrics';
+import type { Opt } from '@jet/environment/types/optional';
+
+export class CompositeEventRecorder implements MetricsEventRecorder {
+ constructor(private readonly eventRecorders: MetricsEventRecorder[]) {}
+
+ record(event: LintedMetricsEvent, topic: Opt): void {
+ for (const eventRecorder of this.eventRecorders) {
+ eventRecorder.record(event, topic);
+ }
+ }
+
+ async flush(): Promise {
+ const flushed: number[] = await Promise.all(
+ this.eventRecorders.map((recorder) => recorder.flush()),
+ );
+ return Math.max(...flushed);
+ }
+}
diff --git a/shared/metrics-8/src/recorder/funnelkit.ts b/shared/metrics-8/src/recorder/funnelkit.ts
new file mode 100644
index 0000000..7f3fa84
--- /dev/null
+++ b/shared/metrics-8/src/recorder/funnelkit.ts
@@ -0,0 +1,237 @@
+import type { MetricsEventRecorder } from '@jet/engine';
+import type { LintedMetricsEvent } from '@jet/environment/types/metrics';
+import type { Opt } from '@jet/environment/types/optional';
+import type { Logger, LoggerFactory } from '@amp/web-apps-logger';
+import type { ClickstreamProcessor as ClickstreamProcessorInstance } from '@amp-metrics/mt-metricskit-processor-clickstream';
+import type { Impressions } from '../impressions';
+import { sendToMetricsDevConsole } from '../utils/metrics-dev-console/setup-metrics-dev';
+import { getEventFieldsWithTopic } from '../utils/get-event-field-topic';
+import { eventType } from '../utils/metrics-dev-console/constants';
+
+interface DeferredEvent {
+ event: LintedMetricsEvent;
+ topic: Opt;
+}
+
+export interface FunnelKitConfig {
+ constraintProfiles: string[];
+ topic: string;
+}
+
+/**
+ * These fields are considered PII and should be ignored by FunnelKit.
+ * `consumerId` is added via the `processEvent` based on when it is available (see jet/metrics/index.ts)
+ * However it should be ignored when sent to the FunnelKit topic.
+ */
+const IGNORED_FIELDS = ['consumerId'];
+
+export class FunnelKitRecorder implements MetricsEventRecorder {
+ private readonly log: Logger;
+ private funnelKit: ClickstreamProcessorInstance | undefined;
+ private funnelKitEnabled: boolean = false;
+ private recordedEventsCount: number;
+ private config: FunnelKitConfig;
+ private readonly impressions: InstanceType | undefined;
+
+ /**
+ * Queues events prior to the mt-event-queue recorder being available
+ */
+ private readonly deferredEvents: DeferredEvent[];
+
+ constructor(
+ loggerFactory: LoggerFactory,
+ config: FunnelKitConfig,
+ impressions: InstanceType | undefined,
+ ) {
+ this.log = loggerFactory.loggerFor('FunnelKitRecorder');
+ this.deferredEvents = [];
+ this.recordedEventsCount = 0;
+ this.config = config;
+ this.impressions = impressions;
+ }
+
+ async record(
+ event: LintedMetricsEvent,
+ eventTopic: Opt,
+ ): Promise {
+ let topic = eventTopic ?? this.config.topic;
+
+ // TV always uses the config topic
+ // TODO: rdar://151772731 (Align funnel metrics between Music + TV)
+ if (this.config.topic === 'xp_amp_tv_unidentified') {
+ topic = this.config.topic;
+ }
+
+ if (!this.funnelKitEnabled) {
+ this.log.info('FunnelKit not enabled', event, topic);
+ return;
+ }
+
+ if (this.funnelKit) {
+ const eventHandler = event.fields.eventType as string;
+ const { pageId, pageType, pageContext } = event.fields;
+ if (!eventHandler) {
+ this.log.warn('No `eventType` found on event', event, topic);
+ } else if (!this.impressions && eventHandler === 'impressions') {
+ this.log.info(
+ 'Supressing impression event. Impressions not enabled',
+ );
+ return;
+ }
+
+ // when the user leaves a page to report the accumulated impressions for that page
+ if (
+ (this.impressions?.isEnabled('exit') &&
+ eventHandler === 'exit') ||
+ (this.impressions?.isEnabled('click') &&
+ event.fields.actionType === 'navigate')
+ ) {
+ // create + capture impressions
+ const accumulatedImpressions =
+ this.impressions.consumeImpressions();
+ const metricsData = this.funnelKit?.eventHandlers[
+ 'impressions'
+ ]?.metricsData(pageId, pageType, pageContext, {
+ impressions: accumulatedImpressions,
+ });
+
+ metricsData
+ ?.recordEvent(topic)
+ .then((data) => {
+ this.log.info(
+ 'impressions event captured',
+ data,
+ topic,
+ );
+ sendToMetricsDevConsole(
+ data as { [key: string]: unknown },
+ topic,
+ );
+ })
+ .catch((e) => {
+ this.log.warn(
+ 'failed to capture impression metrics',
+ e,
+ topic,
+ );
+ });
+ }
+
+ let impressionsData: Record = {};
+ // snapshot impressions to include in click events
+ if (
+ (this.impressions?.isEnabled('click') &&
+ eventHandler === 'click') ||
+ (this.impressions?.isEnabled('impressions') &&
+ eventHandler === 'impressions')
+ ) {
+ const snapshotImpressions =
+ this.impressions.captureSnapshotImpression();
+ impressionsData = snapshotImpressions
+ ? {
+ impressions: snapshotImpressions,
+ }
+ : {};
+ }
+
+ const eventFields = getEventFieldsWithTopic(event, topic);
+ // Handle transaction events differently per Ember implementation
+ // https://github.pie.apple.com/amp-ui/ember-metrics/blob/7eb762601db5e37cb428d7a4e6f24e22d0529515/addon/services/metrics.js#L347-L349
+ const metricsDataArgs =
+ eventHandler === 'transaction'
+ ? [eventFields]
+ : [pageId, pageType, pageContext, eventFields];
+
+ try {
+ const baseFields = await this.funnelKit.eventHandlers[
+ eventHandler
+ ]
+ ?.metricsData(
+ // @ts-expect-error TypeScript doesn't handle spreading the argument array well
+ ...metricsDataArgs,
+ )
+ .toJSON();
+
+ const metricsData = {
+ ...baseFields,
+ ...eventFields,
+ ...impressionsData,
+ };
+ IGNORED_FIELDS.forEach(
+ (ignoredField) => delete metricsData[ignoredField],
+ );
+ this.log.info('FunnelKit event data', metricsData, topic);
+
+ try {
+ const data =
+ await this.funnelKit.system.eventRecorder.recordEvent(
+ topic,
+ metricsData,
+ );
+ sendToMetricsDevConsole(data, topic);
+ } catch (e) {
+ this.log.info(
+ 'FunnelKit failed to capture',
+ metricsData,
+ topic,
+ );
+ }
+
+ // on exit events we should flush all metrics
+ if (eventHandler === 'exit') {
+ this.funnelKit?.system.eventRecorder.flushUnreportedEvents?.(
+ true,
+ );
+
+ sendToMetricsDevConsole(
+ { metricsDevType: eventType.FLUSH, status: 'SUCCESS' },
+ topic,
+ );
+ }
+
+ this.recordedEventsCount++;
+ } catch (e) {
+ this.log.error('FunnelKit failed to capture metric', e, topic);
+ }
+ } else {
+ this.deferredEvents.push({ event, topic });
+ }
+ }
+
+ async flush(): Promise {
+ if (!this.funnelKitEnabled) {
+ return 0;
+ }
+
+ await this.funnelKit?.system.eventRecorder.flushUnreportedEvents(false);
+ const count = this.recordedEventsCount;
+ this.recordedEventsCount = 0;
+ return count;
+ }
+
+ setupEventRecorder(funnelKit: ClickstreamProcessorInstance): void {
+ this.funnelKit = funnelKit;
+ this.deferredEvents.forEach(({ event, topic }) =>
+ this.record(event, topic),
+ );
+ this.deferredEvents.length = 0;
+ }
+
+ enableFunnelKit(): void {
+ if (this.funnelKitEnabled) {
+ return;
+ }
+
+ this.log.info('Enabling FunnelKit');
+ this.funnelKitEnabled = true;
+ }
+
+ disableFunnelKit(): void {
+ if (!this.funnelKitEnabled) {
+ return;
+ }
+
+ this.log.info('Disabling FunnelKit');
+ this.funnelKitEnabled = false;
+ }
+}
diff --git a/shared/metrics-8/src/recorder/logging.ts b/shared/metrics-8/src/recorder/logging.ts
new file mode 100644
index 0000000..baa0373
--- /dev/null
+++ b/shared/metrics-8/src/recorder/logging.ts
@@ -0,0 +1,21 @@
+import type { MetricsEventRecorder } from '@jet/engine';
+import type { LintedMetricsEvent } from '@jet/environment/types/metrics';
+import type { Opt } from '@jet/environment/types/optional';
+import type { Logger, LoggerFactory } from '@amp/web-apps-logger';
+
+export class LoggingEventRecorder implements MetricsEventRecorder {
+ private readonly log: Logger;
+
+ constructor(loggerFactory: LoggerFactory) {
+ this.log = loggerFactory.loggerFor('LoggingEventRecorder');
+ }
+
+ record(event: LintedMetricsEvent, topic: Opt): void {
+ this.log.info('logged metrics event:', event, topic);
+ }
+
+ async flush(): Promise {
+ this.log.info('flushed metrics');
+ return 0;
+ }
+}
diff --git a/shared/metrics-8/src/recorder/metricskit.ts b/shared/metrics-8/src/recorder/metricskit.ts
new file mode 100644
index 0000000..9d724c9
--- /dev/null
+++ b/shared/metrics-8/src/recorder/metricskit.ts
@@ -0,0 +1,239 @@
+import type { MetricsEventRecorder } from '@jet/engine';
+import type { LintedMetricsEvent } from '@jet/environment/types/metrics';
+import type { Opt } from '@jet/environment/types/optional';
+import type { Logger, LoggerFactory } from '@amp/web-apps-logger';
+
+import { METRICS_EVENT_TYPES } from '../constants';
+
+import type { WebDelegates as WebDelegatesInstance } from '@amp-metrics/mt-metricskit-delegates-web';
+import type { ClickstreamProcessor as ClickstreamProcessorInstance } from '@amp-metrics/mt-metricskit-processor-clickstream';
+import type { Impressions } from '../impressions';
+import { sendToMetricsDevConsole } from '../utils/metrics-dev-console/setup-metrics-dev';
+import { getEventFieldsWithTopic } from '../utils/get-event-field-topic';
+import { eventType } from '../utils/metrics-dev-console/constants';
+
+interface DeferredEvent {
+ event: LintedMetricsEvent;
+ topic: Opt;
+}
+
+type EventRecorder = WebDelegatesInstance['eventRecorder'];
+
+type MetricEventType = (typeof METRICS_EVENT_TYPES)[number];
+
+export interface MetricKitConfig {
+ constraintProfiles: string[];
+ topic: string;
+}
+
+export class MetricsKitRecorder implements MetricsEventRecorder {
+ private readonly log: Logger;
+ private eventRecorder: EventRecorder | undefined;
+ private mtkit: ClickstreamProcessorInstance | undefined;
+ private recordedEventsCount: number;
+ private config: MetricKitConfig;
+ private readonly impressions: InstanceType | undefined;
+ private enabled: boolean = true;
+
+ /**
+ * Queues events prior to the mt-event-queue recorder being available
+ */
+ private readonly deferredEvents: DeferredEvent[];
+
+ constructor(
+ loggerFactory: LoggerFactory,
+ config: MetricKitConfig,
+ impressions: InstanceType | undefined,
+ ) {
+ this.log = loggerFactory.loggerFor('MetricsKitRecorder');
+ this.deferredEvents = [];
+ this.recordedEventsCount = 0;
+ this.config = config;
+ this.impressions = impressions;
+ }
+
+ record(event: LintedMetricsEvent, topic: Opt): void {
+ topic = topic ?? this.config.topic;
+ if (this.isDisabled()) {
+ this.log.info(
+ `topic ${this.config.topic} is disabled following event not captured:`,
+ event,
+ );
+ return;
+ }
+
+ if (this.eventRecorder) {
+ const eventHandler = event.fields.eventType as MetricEventType;
+ const { pageId, pageType, pageContext } = event.fields;
+ if (!eventHandler) {
+ this.log.warn('No `eventType` found on event', event, topic);
+ return;
+ } else if (!METRICS_EVENT_TYPES.includes(eventHandler)) {
+ this.log.warn(
+ 'Invalid `eventType` found on event',
+ event,
+ topic,
+ );
+ return;
+ } else if (!this.impressions && eventHandler === 'impressions') {
+ this.log.info(
+ 'Supressing impression event. Impressions not enabled',
+ );
+ return;
+ }
+
+ // when the user leaves a page to report the accumulated impressions for that page
+ if (
+ (this.impressions?.isEnabled('exit') &&
+ eventHandler === 'exit') ||
+ (this.impressions?.isEnabled('click') &&
+ event.fields.actionType === 'navigate')
+ ) {
+ // create + capture impressions
+ const accumulatedImpressions =
+ this.impressions.consumeImpressions();
+
+ const metricsData = this.mtkit?.eventHandlers[
+ 'impressions'
+ ]?.metricsData(pageId, pageType, pageContext, {
+ impressions: accumulatedImpressions,
+ });
+
+ metricsData
+ ?.recordEvent(topic)
+ .then((data) => {
+ this.log.info(
+ 'impressions event captured',
+ data,
+ topic,
+ );
+ sendToMetricsDevConsole(
+ data as { [key: string]: unknown },
+ topic ?? '',
+ );
+ })
+ .catch((e) => {
+ this.log.warn(
+ 'failed to capture impression metrics',
+ e,
+ topic,
+ );
+ });
+ }
+
+ let impressionsData = {};
+ // snapshot impressions to include in click events
+ if (
+ (this.impressions?.isEnabled('click') &&
+ eventHandler === 'click') ||
+ (this.impressions?.isEnabled('impressions') &&
+ eventHandler === 'impressions')
+ ) {
+ const snapshotImpressions =
+ this.impressions.captureSnapshotImpression();
+ impressionsData = {
+ impressions: snapshotImpressions,
+ };
+ }
+
+ const eventFields = getEventFieldsWithTopic(event, topic);
+ // click events are the only ones with different method signature
+ // https://github.pie.apple.com/amp-metrics/mt-metricskit/blob/7.3.5/src/metrics/event_handlers/click.js#L133
+ const metricsDataArgs =
+ eventHandler === 'click' // TODO rdar://102438307 (JMOTW Clickstream – Pass targetElement to click events)
+ ? [
+ pageId,
+ pageType,
+ pageContext,
+ null,
+ { ...eventFields, ...impressionsData },
+ ]
+ : [pageId, pageType, pageContext, eventFields];
+
+ if (eventHandler === 'impressions') {
+ metricsDataArgs.push(impressionsData);
+ }
+
+ let metricsData = this.mtkit?.eventHandlers[
+ eventHandler
+ ]?.metricsData(
+ // @ts-expect-error TypeScript doesn't handle spreading the argument array well
+ ...metricsDataArgs,
+ );
+
+ metricsData
+ ?.recordEvent(topic)
+ .then((data) => {
+ this.log.info('MetricsKit event data', data, topic);
+ sendToMetricsDevConsole(
+ data as { [key: string]: unknown },
+ topic ?? '',
+ );
+ })
+ .catch((e) => {
+ this.log.error(
+ 'MetricsKit failed to capture metric',
+ e,
+ topic,
+ );
+ });
+
+ this.recordedEventsCount++;
+
+ // on exit events we should flush all metrics
+ if (eventHandler === 'exit') {
+ this.eventRecorder?.flushUnreportedEvents?.(true);
+ sendToMetricsDevConsole(
+ { metricsDevType: eventType.FLUSH, status: 'SUCCESS' },
+ topic,
+ );
+ }
+ } else {
+ this.deferredEvents.push({ event, topic });
+ }
+ }
+
+ async flush(): Promise {
+ await this.eventRecorder?.flushUnreportedEvents?.(false);
+ const count = this.recordedEventsCount;
+ this.recordedEventsCount = 0;
+ return count;
+ }
+
+ setupEventRecorder(
+ eventRecorder: EventRecorder,
+ mtkit: ClickstreamProcessorInstance,
+ ): void {
+ this.eventRecorder = eventRecorder;
+ this.mtkit = mtkit;
+ this.deferredEvents.forEach(({ event, topic }) =>
+ this.record(event, topic),
+ );
+ this.deferredEvents.length = 0;
+ }
+
+ isDisabled(): boolean {
+ return !this.enabled;
+ }
+
+ enable(): void {
+ if (this.enabled) {
+ this.log.info(
+ `Clickstream topic ${this.config.topic} already enabled`,
+ );
+ return;
+ }
+
+ this.log.info(`Enabling clickstream topic ${this.config.topic}`);
+ this.enabled = true;
+ }
+
+ disable(): void {
+ if (this.isDisabled()) {
+ return;
+ }
+
+ this.log.info(`Disabling clickstream topic ${this.config.topic}`);
+ this.enabled = false;
+ }
+}
diff --git a/shared/metrics-8/src/recorder/void.ts b/shared/metrics-8/src/recorder/void.ts
new file mode 100644
index 0000000..475c759
--- /dev/null
+++ b/shared/metrics-8/src/recorder/void.ts
@@ -0,0 +1,17 @@
+import type { MetricsEventRecorder } from '@jet/engine';
+import type { LintedMetricsEvent } from '@jet/environment/types/metrics';
+import type { Opt } from '@jet/environment/types/optional';
+
+export class VoidEventRecorder implements MetricsEventRecorder {
+ private recorded: number = 0;
+
+ record(_event: LintedMetricsEvent, _topic: Opt): void {
+ this.recorded++;
+ }
+
+ async flush(): Promise {
+ const { recorded } = this;
+ this.recorded = 0;
+ return recorded;
+ }
+}
diff --git a/shared/metrics-8/src/utils/get-event-field-topic.ts b/shared/metrics-8/src/utils/get-event-field-topic.ts
new file mode 100644
index 0000000..96bb125
--- /dev/null
+++ b/shared/metrics-8/src/utils/get-event-field-topic.ts
@@ -0,0 +1,11 @@
+import type { LintedMetricsEvent } from '@jet/environment';
+import type { MetricsFields } from '~/types';
+
+export function getEventFieldsWithTopic(
+ event: LintedMetricsEvent,
+ topic: string,
+) {
+ return 'topic' in event.fields
+ ? event.fields
+ : ({ ...event.fields, topic } as MetricsFields);
+}
diff --git a/shared/metrics-8/src/utils/metrics-dev-console/constants.ts b/shared/metrics-8/src/utils/metrics-dev-console/constants.ts
new file mode 100644
index 0000000..7193da8
--- /dev/null
+++ b/shared/metrics-8/src/utils/metrics-dev-console/constants.ts
@@ -0,0 +1,7 @@
+/**
+ * Event type constants for metrics development console
+ */
+export const eventType = {
+ RECORD: 'record',
+ FLUSH: 'flush',
+} as const;
diff --git a/shared/metrics-8/src/utils/metrics-dev-console/setup-metrics-dev.ts b/shared/metrics-8/src/utils/metrics-dev-console/setup-metrics-dev.ts
new file mode 100644
index 0000000..fb7def6
--- /dev/null
+++ b/shared/metrics-8/src/utils/metrics-dev-console/setup-metrics-dev.ts
@@ -0,0 +1,55 @@
+import { isFlushEvent, makeFlushEvent } from './events/flush-event';
+import { makeRecordEvent } from './events/record-event';
+import type { MetricsOptions, FlushEvent, MetricsObject } from './type';
+
+/**
+ * Updates the metrics console by dispatching appropriate events
+ */
+const updateMetricsConsole = (
+ topic: string,
+ metricsData: MetricsOptions | FlushEvent,
+): void => {
+ let event = null;
+ const { metricsDevType, ...data } = metricsData ?? ({} as MetricsObject);
+
+ if (isFlushEvent(metricsData)) {
+ event = makeFlushEvent(metricsData, topic);
+ } else if (metricsData) {
+ event = makeRecordEvent(data, topic);
+ }
+
+ if (event) {
+ try {
+ window.dispatchEvent(event);
+ } catch (e) {
+ console.error('metric console failed', e);
+ }
+ }
+};
+
+const isMetricsDevConsoleEnabled = () => {
+ return (
+ typeof window !== 'undefined' &&
+ window.localStorage?.getItem('metrics-dev') === 'true'
+ );
+};
+
+/**
+ * Sends metrics data to the development console if enabled
+ * @param metricsData - The metrics data to send
+ * @param topic - The topic/category for the metrics
+ */
+export const sendToMetricsDevConsole = (
+ metricsData: MetricsOptions,
+ topic: string,
+): void => {
+ if (import.meta.env.APP_SCOPE === 'internal') {
+ if (isMetricsDevConsoleEnabled()) {
+ try {
+ updateMetricsConsole(topic, metricsData);
+ } catch (error) {
+ console.warn('Failed to send metrics to dev console:', error);
+ }
+ }
+ }
+};
--
cgit v1.2.3